@supermousejs/core 2.1.0 → 2.2.0
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/CHANGELOG.md +57 -51
- package/dist/index.d.ts +10 -3
- package/dist/index.mjs +133 -125
- package/dist/index.umd.js +2 -2
- package/package.json +1 -1
- package/src/Supermouse.ts +412 -43
- package/tsconfig.json +9 -9
- package/src/systems/Input.ts +0 -231
- package/src/systems/Stage.ts +0 -126
- package/src/systems/index.ts +0 -2
- package/src/utils/math.ts +0 -11
package/src/Supermouse.ts
CHANGED
|
@@ -1,9 +1,381 @@
|
|
|
1
1
|
declare const __VERSION__: string;
|
|
2
2
|
|
|
3
3
|
import type { MouseState, SupermouseOptions, SupermousePlugin } from "./types";
|
|
4
|
-
import { Stage, Input } from "./systems";
|
|
5
|
-
import { damp, angle } from "./utils/math";
|
|
6
4
|
|
|
5
|
+
function lerp(start: number, end: number, factor: number): number {
|
|
6
|
+
return start + (end - start) * factor;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function damp(a: number, b: number, lambda: number, dt: number): number {
|
|
10
|
+
return lerp(a, b, 1 - Math.exp(-lambda * dt));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function angle(x: number, y: number): number {
|
|
14
|
+
return Math.atan2(y, x) * (180 / Math.PI);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Input.ts
|
|
19
|
+
*
|
|
20
|
+
* This class listens to browser events and mutates the shared `MouseState` object.
|
|
21
|
+
*
|
|
22
|
+
* @internal This is an internal system class instantiated by `Supermouse`.
|
|
23
|
+
*/
|
|
24
|
+
export class Input {
|
|
25
|
+
private mediaQueryList?: MediaQueryList;
|
|
26
|
+
private mediaQueryHandler?: (e: MediaQueryListEvent) => void;
|
|
27
|
+
private motionQuery?: MediaQueryList;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Master switch for input processing.
|
|
31
|
+
* Toggled by `Supermouse.enable()`/`disable()` or automatically by device capability checks.
|
|
32
|
+
*/
|
|
33
|
+
public isEnabled: boolean = true;
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
private state: MouseState,
|
|
37
|
+
private options: SupermouseOptions,
|
|
38
|
+
private getHoverSelector: () => string,
|
|
39
|
+
private onEnableChange: (enabled: boolean) => void
|
|
40
|
+
) {
|
|
41
|
+
this.checkDeviceCapability();
|
|
42
|
+
this.checkMotionPreference();
|
|
43
|
+
this.bindEvents();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private abortController = new AbortController();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Automatically disables the custom cursor on devices without fine pointer control.
|
|
50
|
+
*/
|
|
51
|
+
private checkDeviceCapability(): void {
|
|
52
|
+
if (!this.options.autoDisableOnMobile) return;
|
|
53
|
+
|
|
54
|
+
this.mediaQueryList = window.matchMedia("(pointer: fine)");
|
|
55
|
+
this.updateEnabledState(this.mediaQueryList.matches);
|
|
56
|
+
|
|
57
|
+
this.mediaQueryHandler = (e: MediaQueryListEvent) => {
|
|
58
|
+
this.updateEnabledState(e.matches);
|
|
59
|
+
};
|
|
60
|
+
this.mediaQueryList.addEventListener("change", this.mediaQueryHandler, {
|
|
61
|
+
signal: this.abortController.signal
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Checks for `prefers-reduced-motion`.
|
|
67
|
+
* If true, the core physics engine will switch to instant snapping (high damping) to avoid motion sickness.
|
|
68
|
+
*/
|
|
69
|
+
private checkMotionPreference(): void {
|
|
70
|
+
this.motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
71
|
+
this.state.reducedMotion = this.motionQuery.matches;
|
|
72
|
+
|
|
73
|
+
this.motionQuery.addEventListener(
|
|
74
|
+
"change",
|
|
75
|
+
(e) => {
|
|
76
|
+
this.state.reducedMotion = e.matches;
|
|
77
|
+
},
|
|
78
|
+
{ signal: this.abortController.signal }
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private updateEnabledState(enabled: boolean): void {
|
|
83
|
+
this.isEnabled = enabled;
|
|
84
|
+
this.onEnableChange(enabled);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private parseDOMInteraction(element: HTMLElement): void {
|
|
88
|
+
if (this.options.resolveInteraction) {
|
|
89
|
+
this.state.interaction = this.options.resolveInteraction(element);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const data: Record<string, string | boolean> = {};
|
|
94
|
+
|
|
95
|
+
if (this.options.rules) {
|
|
96
|
+
for (const [selector, rules] of Object.entries(this.options.rules)) {
|
|
97
|
+
if (element.matches(selector)) {
|
|
98
|
+
Object.assign(data, rules);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const dataset = element.dataset;
|
|
104
|
+
for (const key in dataset) {
|
|
105
|
+
if (key.startsWith("supermouse")) {
|
|
106
|
+
const prop = key.slice(10);
|
|
107
|
+
if (prop) {
|
|
108
|
+
const cleanKey = prop.charAt(0).toLowerCase() + prop.slice(1);
|
|
109
|
+
const val = dataset[key];
|
|
110
|
+
if (val !== undefined) {
|
|
111
|
+
data[cleanKey] = val === "" ? true : val;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.state.interaction = data;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private handleMove(e: PointerEvent): void {
|
|
121
|
+
if (!this.isEnabled) return;
|
|
122
|
+
|
|
123
|
+
if (this.options.autoDisableOnMobile && e.pointerType === "touch") return;
|
|
124
|
+
|
|
125
|
+
let x = e.clientX;
|
|
126
|
+
let y = e.clientY;
|
|
127
|
+
|
|
128
|
+
if (this.options.container && this.options.container !== document.body) {
|
|
129
|
+
const rect = this.options.container.getBoundingClientRect();
|
|
130
|
+
x -= rect.left;
|
|
131
|
+
y -= rect.top;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.state.pointer.x = x;
|
|
135
|
+
this.state.pointer.y = y;
|
|
136
|
+
|
|
137
|
+
if (!this.state.hasReceivedInput) {
|
|
138
|
+
this.state.hasReceivedInput = true;
|
|
139
|
+
this.state.target.x = this.state.smooth.x = x;
|
|
140
|
+
this.state.target.y = this.state.smooth.y = y;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private handleDown(): void {
|
|
145
|
+
if (this.isEnabled) this.state.isDown = true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private handleUp(): void {
|
|
149
|
+
if (this.isEnabled) this.state.isDown = false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private handleMouseOver(e: MouseEvent): void {
|
|
153
|
+
if (!this.isEnabled) return;
|
|
154
|
+
const target = e.target as HTMLElement;
|
|
155
|
+
|
|
156
|
+
if (target.closest("[data-supermouse-ignore]")) {
|
|
157
|
+
this.state.isNative = true;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const selector = this.getHoverSelector();
|
|
162
|
+
const hoverable = target.closest(selector);
|
|
163
|
+
|
|
164
|
+
if (hoverable) {
|
|
165
|
+
this.state.isHover = true;
|
|
166
|
+
this.state.hoverTarget = hoverable as HTMLElement;
|
|
167
|
+
this.parseDOMInteraction(this.state.hoverTarget);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const strategy = this.options.ignoreOnNative;
|
|
171
|
+
|
|
172
|
+
if (strategy) {
|
|
173
|
+
const checkTags = strategy === true || strategy === "auto" || strategy === "tag";
|
|
174
|
+
const checkCSS = strategy === true || strategy === "auto" || strategy === "css";
|
|
175
|
+
let isNative = false;
|
|
176
|
+
|
|
177
|
+
if (checkTags) {
|
|
178
|
+
const tag = target.localName;
|
|
179
|
+
if (tag === "input" || tag === "textarea" || tag === "select" || target.isContentEditable) {
|
|
180
|
+
isNative = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!isNative && checkCSS) {
|
|
185
|
+
const style = window.getComputedStyle(target).cursor;
|
|
186
|
+
const supermouseAllowed = ["default", "auto", "pointer", "none", "inherit"];
|
|
187
|
+
if (!supermouseAllowed.includes(style)) {
|
|
188
|
+
isNative = true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (isNative) {
|
|
193
|
+
this.state.isNative = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private handleMouseOut(e: MouseEvent): void {
|
|
199
|
+
if (!this.isEnabled) return;
|
|
200
|
+
const target = e.target as HTMLElement;
|
|
201
|
+
|
|
202
|
+
if (target === this.state.hoverTarget || target.contains(this.state.hoverTarget)) {
|
|
203
|
+
if (!e.relatedTarget || !this.state.hoverTarget?.contains(e.relatedTarget as Node)) {
|
|
204
|
+
this.state.isHover = false;
|
|
205
|
+
this.state.hoverTarget = null;
|
|
206
|
+
this.state.interaction = {};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (this.state.isNative) {
|
|
211
|
+
this.state.isNative = false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private handleWindowLeave(): void {
|
|
216
|
+
if (this.options.hideOnLeave) {
|
|
217
|
+
this.state.hasReceivedInput = false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public clearHover(): void {
|
|
222
|
+
this.state.isHover = false;
|
|
223
|
+
this.state.hoverTarget = null;
|
|
224
|
+
this.state.isNative = false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private bindEvents(): void {
|
|
228
|
+
const { signal } = this.abortController;
|
|
229
|
+
window.addEventListener("pointermove", this.handleMove.bind(this), { passive: true, signal });
|
|
230
|
+
window.addEventListener("pointerdown", this.handleDown.bind(this), { passive: true, signal });
|
|
231
|
+
window.addEventListener("pointerup", this.handleUp.bind(this), { signal });
|
|
232
|
+
|
|
233
|
+
document.addEventListener("mouseover", this.handleMouseOver.bind(this), { signal });
|
|
234
|
+
document.addEventListener("mouseout", this.handleMouseOut.bind(this), { signal });
|
|
235
|
+
document.addEventListener("mouseleave", this.handleWindowLeave.bind(this), { signal });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
public destroy(): void {
|
|
239
|
+
this.abortController.abort();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let stageCount = 0;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Stage.ts
|
|
247
|
+
*
|
|
248
|
+
* This class manages the DOM container for the custom cursor and handles native cursor visibility.
|
|
249
|
+
* It is instantiated by the `Supermouse` class and is not intended for direct use by plugins.
|
|
250
|
+
*
|
|
251
|
+
* @internal
|
|
252
|
+
*/
|
|
253
|
+
export class Stage {
|
|
254
|
+
/** The container element appended to the document. */
|
|
255
|
+
public readonly element: HTMLDivElement;
|
|
256
|
+
private styleTag: HTMLStyleElement;
|
|
257
|
+
private id: string;
|
|
258
|
+
private scopeClass: string;
|
|
259
|
+
|
|
260
|
+
private currentCursorState: "none" | "auto" | "" | null = null;
|
|
261
|
+
|
|
262
|
+
private selectors: Set<string> = new Set([
|
|
263
|
+
"a",
|
|
264
|
+
"button",
|
|
265
|
+
"input",
|
|
266
|
+
"textarea",
|
|
267
|
+
"select",
|
|
268
|
+
'[role="button"]',
|
|
269
|
+
"[tabindex]"
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
constructor(
|
|
273
|
+
private container: HTMLElement = document.body,
|
|
274
|
+
private hideNativeCursor: boolean
|
|
275
|
+
) {
|
|
276
|
+
if (!container || !(container instanceof HTMLElement)) {
|
|
277
|
+
throw new Error(`[Supermouse] Invalid container: ${container}. Must be an HTMLElement.`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const instanceId = stageCount++;
|
|
281
|
+
this.id = `supermouse-style-${instanceId}`;
|
|
282
|
+
this.scopeClass = `supermouse-scope-${instanceId}`;
|
|
283
|
+
|
|
284
|
+
const isBody = container === document.body;
|
|
285
|
+
|
|
286
|
+
this.element = document.createElement("div");
|
|
287
|
+
Object.assign(this.element.style, {
|
|
288
|
+
position: isBody ? "fixed" : "absolute",
|
|
289
|
+
top: "0",
|
|
290
|
+
left: "0",
|
|
291
|
+
width: "100%",
|
|
292
|
+
height: "100%",
|
|
293
|
+
pointerEvents: "none",
|
|
294
|
+
zIndex: "9999",
|
|
295
|
+
opacity: "1",
|
|
296
|
+
transition: "opacity 0.15s ease"
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (!isBody) {
|
|
300
|
+
const computed = window.getComputedStyle(container);
|
|
301
|
+
if (computed.position === "static") {
|
|
302
|
+
container.style.position = "relative";
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
container.appendChild(this.element);
|
|
307
|
+
|
|
308
|
+
this.styleTag = document.createElement("style");
|
|
309
|
+
this.styleTag.id = this.id;
|
|
310
|
+
document.head.appendChild(this.styleTag);
|
|
311
|
+
|
|
312
|
+
this.container.classList.add(this.scopeClass);
|
|
313
|
+
|
|
314
|
+
if (this.hideNativeCursor) {
|
|
315
|
+
this.setNativeCursor("none");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Adds a new CSS selector to the `selectors` set.
|
|
321
|
+
* Called by `Supermouse` and subsequently plugins during install to ensure
|
|
322
|
+
* the native cursor is hidden on their specific interactive targets.
|
|
323
|
+
*/
|
|
324
|
+
public addSelector(selector: string): void {
|
|
325
|
+
this.selectors.add(selector);
|
|
326
|
+
if (this.hideNativeCursor) {
|
|
327
|
+
this.updateCursorCSS();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
public setVisibility(visible: boolean): void {
|
|
332
|
+
this.element.style.opacity = visible ? "1" : "0";
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Toggles the visibility of the native cursor via CSS injection.
|
|
337
|
+
* @param type 'none' to hide, 'auto' to show.
|
|
338
|
+
*/
|
|
339
|
+
public setNativeCursor(type: "none" | "auto" | ""): void {
|
|
340
|
+
if (!this.hideNativeCursor && type === "none") return;
|
|
341
|
+
|
|
342
|
+
if (type === this.currentCursorState) return;
|
|
343
|
+
this.currentCursorState = type;
|
|
344
|
+
|
|
345
|
+
if (type === "none") {
|
|
346
|
+
this.container.style.cursor = "none";
|
|
347
|
+
this.updateCursorCSS();
|
|
348
|
+
} else {
|
|
349
|
+
this.container.style.cursor = "";
|
|
350
|
+
this.styleTag.innerText = "";
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private updateCursorCSS(): void {
|
|
355
|
+
const rawSelectors = Array.from(this.selectors);
|
|
356
|
+
if (rawSelectors.length === 0) {
|
|
357
|
+
this.styleTag.innerText = "";
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const scopedSelectors = rawSelectors.map((s) => `.${this.scopeClass} ${s}`).join(", ");
|
|
362
|
+
|
|
363
|
+
this.styleTag.innerText = `
|
|
364
|
+
${scopedSelectors} {
|
|
365
|
+
cursor: none !important;
|
|
366
|
+
}
|
|
367
|
+
`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
public destroy(): void {
|
|
371
|
+
this.element.remove();
|
|
372
|
+
this.styleTag.remove();
|
|
373
|
+
this.container.style.cursor = "";
|
|
374
|
+
this.container.classList.remove(this.scopeClass);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const OFFSCREEN = { x: -100, y: -100 } as const;
|
|
7
379
|
export const DEFAULT_HOVER_SELECTORS = [
|
|
8
380
|
"a",
|
|
9
381
|
"button",
|
|
@@ -14,10 +386,12 @@ export const DEFAULT_HOVER_SELECTORS = [
|
|
|
14
386
|
];
|
|
15
387
|
|
|
16
388
|
/**
|
|
17
|
-
* Runtime Loop
|
|
389
|
+
* Supermouse Runtime Loop
|
|
18
390
|
*
|
|
19
|
-
* This class orchestrates the application state, manages the animation loop
|
|
20
|
-
* and coordinates data flow between the
|
|
391
|
+
* This class orchestrates the application state, manages the animation loop,
|
|
392
|
+
* and coordinates data flow between the internal systems, and the plugins.
|
|
393
|
+
*
|
|
394
|
+
* @default
|
|
21
395
|
*/
|
|
22
396
|
export class Supermouse {
|
|
23
397
|
public static readonly version: string = __VERSION__;
|
|
@@ -108,7 +482,7 @@ export class Supermouse {
|
|
|
108
482
|
/**
|
|
109
483
|
* Retrieves a registered plugin instance by its unique name.
|
|
110
484
|
*/
|
|
111
|
-
public getPlugin(name: string) {
|
|
485
|
+
public getPlugin(name: string): SupermousePlugin | undefined {
|
|
112
486
|
return this.plugins.find((p) => p.name === name);
|
|
113
487
|
}
|
|
114
488
|
|
|
@@ -123,7 +497,7 @@ export class Supermouse {
|
|
|
123
497
|
* Enables a specific plugin by name.
|
|
124
498
|
* Triggers the `onEnable` lifecycle hook of the plugin.
|
|
125
499
|
*/
|
|
126
|
-
public enablePlugin(name: string) {
|
|
500
|
+
public enablePlugin(name: string): void {
|
|
127
501
|
const plugin = this.getPlugin(name);
|
|
128
502
|
if (plugin && plugin.isEnabled === false) {
|
|
129
503
|
plugin.isEnabled = true;
|
|
@@ -135,7 +509,7 @@ export class Supermouse {
|
|
|
135
509
|
* Disables a specific plugin by name.
|
|
136
510
|
* Triggers the `onDisable` lifecycle hook.
|
|
137
511
|
*/
|
|
138
|
-
public disablePlugin(name: string) {
|
|
512
|
+
public disablePlugin(name: string): void {
|
|
139
513
|
const plugin = this.getPlugin(name);
|
|
140
514
|
if (plugin && plugin.isEnabled !== false) {
|
|
141
515
|
plugin.isEnabled = false;
|
|
@@ -146,7 +520,7 @@ export class Supermouse {
|
|
|
146
520
|
/**
|
|
147
521
|
* Toggles the enabled state of a plugin.
|
|
148
522
|
*/
|
|
149
|
-
public togglePlugin(name: string) {
|
|
523
|
+
public togglePlugin(name: string): void {
|
|
150
524
|
const plugin = this.getPlugin(name);
|
|
151
525
|
if (plugin) {
|
|
152
526
|
if (plugin.isEnabled === false) this.enablePlugin(name);
|
|
@@ -154,7 +528,7 @@ export class Supermouse {
|
|
|
154
528
|
}
|
|
155
529
|
}
|
|
156
530
|
|
|
157
|
-
public registerHoverTarget(selector: string) {
|
|
531
|
+
public registerHoverTarget(selector: string): void {
|
|
158
532
|
if (!this.hoverSelectors.has(selector)) {
|
|
159
533
|
this.hoverSelectors.add(selector);
|
|
160
534
|
this.stage.addSelector(selector);
|
|
@@ -173,21 +547,21 @@ export class Supermouse {
|
|
|
173
547
|
*
|
|
174
548
|
* @param type 'auto' (Show Native), 'none' (Hide Native), or null (Resume Auto-detection)
|
|
175
549
|
*/
|
|
176
|
-
public setCursor(type: "auto" | "none" | null) {
|
|
550
|
+
public setCursor(type: "auto" | "none" | null): void {
|
|
177
551
|
this.state.forcedCursor = type;
|
|
178
552
|
}
|
|
179
553
|
|
|
180
|
-
private init() {
|
|
554
|
+
private init(): void {
|
|
181
555
|
if (this.options.autoStart) {
|
|
182
556
|
this.startLoop();
|
|
183
557
|
}
|
|
184
558
|
}
|
|
185
559
|
|
|
186
|
-
public enable() {
|
|
560
|
+
public enable(): void {
|
|
187
561
|
this.input.isEnabled = true;
|
|
188
562
|
this.stage.setNativeCursor("none");
|
|
189
563
|
}
|
|
190
|
-
public disable() {
|
|
564
|
+
public disable(): void {
|
|
191
565
|
this.input.isEnabled = false;
|
|
192
566
|
this.stage.setNativeCursor("auto");
|
|
193
567
|
this.resetPosition();
|
|
@@ -198,7 +572,7 @@ export class Supermouse {
|
|
|
198
572
|
*
|
|
199
573
|
* @param plugin - The plugin object to install.
|
|
200
574
|
*/
|
|
201
|
-
public use(plugin: SupermousePlugin) {
|
|
575
|
+
public use(plugin: SupermousePlugin): this {
|
|
202
576
|
if (this.plugins.find((p) => p.name === plugin.name)) {
|
|
203
577
|
console.warn(`[Supermouse] Plugin "${plugin.name}" already installed.`);
|
|
204
578
|
return this;
|
|
@@ -215,19 +589,22 @@ export class Supermouse {
|
|
|
215
589
|
return this;
|
|
216
590
|
}
|
|
217
591
|
|
|
218
|
-
private
|
|
219
|
-
|
|
220
|
-
this.state.
|
|
221
|
-
this.state.
|
|
222
|
-
this.state.smooth = { ...off };
|
|
592
|
+
private resetCoords(): void {
|
|
593
|
+
this.state.pointer = { ...OFFSCREEN };
|
|
594
|
+
this.state.target = { ...OFFSCREEN };
|
|
595
|
+
this.state.smooth = { ...OFFSCREEN };
|
|
223
596
|
this.state.velocity = { x: 0, y: 0 };
|
|
224
597
|
this.state.angle = 0;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private resetPosition(): void {
|
|
601
|
+
this.resetCoords();
|
|
225
602
|
this.state.hasReceivedInput = false;
|
|
226
603
|
this.state.shape = null;
|
|
227
604
|
this.state.interaction = {};
|
|
228
605
|
}
|
|
229
606
|
|
|
230
|
-
private startLoop() {
|
|
607
|
+
private startLoop(): void {
|
|
231
608
|
if (this.isRunning) return;
|
|
232
609
|
this.isRunning = true;
|
|
233
610
|
this.lastTime = performance.now();
|
|
@@ -239,11 +616,11 @@ export class Supermouse {
|
|
|
239
616
|
*
|
|
240
617
|
* @param time Current timestamp in milliseconds.
|
|
241
618
|
*/
|
|
242
|
-
public step(time: number) {
|
|
619
|
+
public step(time: number): void {
|
|
243
620
|
this.tick(time);
|
|
244
621
|
}
|
|
245
622
|
|
|
246
|
-
private runPluginSafe(plugin: SupermousePlugin, deltaTime: number) {
|
|
623
|
+
private runPluginSafe(plugin: SupermousePlugin, deltaTime: number): void {
|
|
247
624
|
if (plugin.isEnabled === false) return;
|
|
248
625
|
try {
|
|
249
626
|
plugin.update?.(this, deltaTime);
|
|
@@ -252,14 +629,16 @@ export class Supermouse {
|
|
|
252
629
|
plugin.isEnabled = false;
|
|
253
630
|
try {
|
|
254
631
|
plugin.onDisable?.(this);
|
|
255
|
-
} catch (err) {
|
|
632
|
+
} catch (err) {
|
|
633
|
+
console.error(`[Supermouse] Failed to execute onDisable for plugin '${plugin.name}'.`, err);
|
|
634
|
+
}
|
|
256
635
|
}
|
|
257
636
|
}
|
|
258
637
|
|
|
259
638
|
/**
|
|
260
639
|
* Runs on every animation frame.
|
|
261
640
|
*/
|
|
262
|
-
private tick = (time: number) => {
|
|
641
|
+
private tick = (time: number): void => {
|
|
263
642
|
const dtMs = time - this.lastTime;
|
|
264
643
|
const dt = Math.min(dtMs / 1000, 0.1);
|
|
265
644
|
this.lastTime = time;
|
|
@@ -274,25 +653,27 @@ export class Supermouse {
|
|
|
274
653
|
|
|
275
654
|
if (this.input.isEnabled && this.options.hideCursor) {
|
|
276
655
|
let targetState: "none" | "auto" = "auto";
|
|
277
|
-
|
|
278
656
|
if (this.state.forcedCursor !== null) {
|
|
279
657
|
targetState = this.state.forcedCursor;
|
|
280
658
|
} else {
|
|
281
659
|
const showNative = this.state.isNative || !this.state.hasReceivedInput;
|
|
282
660
|
targetState = showNative ? "auto" : "none";
|
|
283
661
|
}
|
|
284
|
-
|
|
285
662
|
this.stage.setNativeCursor(targetState);
|
|
286
663
|
}
|
|
287
664
|
|
|
288
665
|
if (this.input.isEnabled) {
|
|
289
666
|
this.state.target.x = this.state.pointer.x;
|
|
290
667
|
this.state.target.y = this.state.pointer.y;
|
|
668
|
+
} else {
|
|
669
|
+
this.resetCoords();
|
|
670
|
+
}
|
|
291
671
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
672
|
+
for (let i = 0; i < this.plugins.length; i++) {
|
|
673
|
+
this.runPluginSafe(this.plugins[i], dtMs);
|
|
674
|
+
}
|
|
295
675
|
|
|
676
|
+
if (this.input.isEnabled) {
|
|
296
677
|
const factor = this.state.reducedMotion ? 1000 : (1 / this.options.smoothness!) * 2;
|
|
297
678
|
|
|
298
679
|
this.state.smooth.x = damp(this.state.smooth.x, this.state.target.x, factor, dt);
|
|
@@ -300,24 +681,12 @@ export class Supermouse {
|
|
|
300
681
|
|
|
301
682
|
const vx = this.state.target.x - this.state.smooth.x;
|
|
302
683
|
const vy = this.state.target.y - this.state.smooth.y;
|
|
303
|
-
|
|
304
684
|
this.state.velocity.x = vx;
|
|
305
685
|
this.state.velocity.y = vy;
|
|
306
686
|
|
|
307
687
|
if (Math.abs(vx) > 0.1 || Math.abs(vy) > 0.1) {
|
|
308
688
|
this.state.angle = angle(vx, vy);
|
|
309
689
|
}
|
|
310
|
-
} else {
|
|
311
|
-
this.state.smooth.x = -100;
|
|
312
|
-
this.state.smooth.y = -100;
|
|
313
|
-
this.state.pointer.x = -100;
|
|
314
|
-
this.state.pointer.y = -100;
|
|
315
|
-
this.state.velocity.x = 0;
|
|
316
|
-
this.state.velocity.y = 0;
|
|
317
|
-
|
|
318
|
-
for (let i = 0; i < this.plugins.length; i++) {
|
|
319
|
-
this.runPluginSafe(this.plugins[i], dtMs);
|
|
320
|
-
}
|
|
321
690
|
}
|
|
322
691
|
|
|
323
692
|
if (this.options.autoStart && this.isRunning) {
|
|
@@ -328,7 +697,7 @@ export class Supermouse {
|
|
|
328
697
|
/**
|
|
329
698
|
* Destroys the instance.
|
|
330
699
|
*/
|
|
331
|
-
public destroy() {
|
|
700
|
+
public destroy(): void {
|
|
332
701
|
this.isRunning = false;
|
|
333
702
|
cancelAnimationFrame(this.rafId);
|
|
334
703
|
this.input.destroy();
|
package/tsconfig.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.composite-lib.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "src",
|
|
5
|
-
"outDir": "dist"
|
|
6
|
-
},
|
|
7
|
-
"include": ["src"],
|
|
8
|
-
"references": []
|
|
9
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.composite-lib.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "dist"
|
|
6
|
+
},
|
|
7
|
+
"include": ["src"],
|
|
8
|
+
"references": []
|
|
9
|
+
}
|