@supermousejs/core 2.0.4 → 2.1.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 +51 -31
- package/LICENSE.md +21 -21
- package/README.md +30 -31
- package/dist/index.d.ts +8 -55
- package/dist/index.mjs +85 -146
- package/dist/index.umd.js +2 -2
- package/package.json +11 -1
- package/src/Supermouse.ts +339 -405
- package/src/index.ts +2 -2
- package/src/systems/Input.ts +231 -262
- package/src/systems/Stage.ts +126 -156
- package/src/systems/index.ts +2 -2
- package/src/types.ts +168 -169
- package/src/utils/math.ts +11 -20
- package/tsconfig.json +7 -14
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +32 -32
package/src/Supermouse.ts
CHANGED
|
@@ -1,405 +1,339 @@
|
|
|
1
|
-
declare const __VERSION__: string;
|
|
2
|
-
|
|
3
|
-
import { MouseState, SupermouseOptions, SupermousePlugin } from
|
|
4
|
-
import { Stage, Input } from
|
|
5
|
-
import { damp, angle } from
|
|
6
|
-
|
|
7
|
-
export const DEFAULT_HOVER_SELECTORS = [
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
*
|
|
166
|
-
*/
|
|
167
|
-
public
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
private
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
this.
|
|
266
|
-
|
|
267
|
-
this.state.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
// 4. Sync Target
|
|
341
|
-
// By default, target = raw pointer. Logic plugins may override this in the next step.
|
|
342
|
-
this.state.target.x = this.state.pointer.x;
|
|
343
|
-
this.state.target.y = this.state.pointer.y;
|
|
344
|
-
|
|
345
|
-
// 5. Run Plugins
|
|
346
|
-
// This iterates through the priority-sorted list.
|
|
347
|
-
// - Logic Plugins (Magnetic) run first and modify this.state.target
|
|
348
|
-
// - Visual Plugins (Dot, Ring) run last and read this.state.smooth
|
|
349
|
-
for (let i = 0; i < this.plugins.length; i++) {
|
|
350
|
-
this.runPluginSafe(this.plugins[i], dtMs);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// 6. Physics Integration (Damping)
|
|
354
|
-
// Convert abstract smoothness (0-1) to damping factor (1-50 approx)
|
|
355
|
-
const userSmooth = this.options.smoothness!;
|
|
356
|
-
// Map 0.15 (floaty) -> ~10, 0.5 (snappy) -> ~25
|
|
357
|
-
const dampFactor = this.state.reducedMotion ? 1000 : (1 / userSmooth) * 2;
|
|
358
|
-
|
|
359
|
-
this.state.smooth.x = damp(this.state.smooth.x, this.state.target.x, dampFactor, dt);
|
|
360
|
-
this.state.smooth.y = damp(this.state.smooth.y, this.state.target.y, dampFactor, dt);
|
|
361
|
-
|
|
362
|
-
const vx = this.state.target.x - this.state.smooth.x;
|
|
363
|
-
const vy = this.state.target.y - this.state.smooth.y;
|
|
364
|
-
|
|
365
|
-
this.state.velocity.x = vx;
|
|
366
|
-
this.state.velocity.y = vy;
|
|
367
|
-
|
|
368
|
-
// Only update angle if moving significantly (prevents jitter at rest)
|
|
369
|
-
if (Math.abs(vx) > 0.1 || Math.abs(vy) > 0.1) {
|
|
370
|
-
this.state.angle = angle(vx, vy);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
} else {
|
|
374
|
-
// Inactive State: Force positions off-screen
|
|
375
|
-
this.state.smooth.x = -100;
|
|
376
|
-
this.state.smooth.y = -100;
|
|
377
|
-
this.state.pointer.x = -100;
|
|
378
|
-
this.state.pointer.y = -100;
|
|
379
|
-
this.state.velocity.x = 0;
|
|
380
|
-
this.state.velocity.y = 0;
|
|
381
|
-
|
|
382
|
-
// Still run plugins (e.g. for exit animations)
|
|
383
|
-
for (let i = 0; i < this.plugins.length; i++) {
|
|
384
|
-
this.runPluginSafe(this.plugins[i], dtMs);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (this.options.autoStart && this.isRunning) {
|
|
389
|
-
this.rafId = requestAnimationFrame(this.tick);
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Destroys the instance.
|
|
395
|
-
* Stops the loop, removes all DOM elements, removes all event listeners, and calls destroy on all plugins.
|
|
396
|
-
*/
|
|
397
|
-
public destroy() {
|
|
398
|
-
this.isRunning = false;
|
|
399
|
-
cancelAnimationFrame(this.rafId);
|
|
400
|
-
this.input.destroy();
|
|
401
|
-
this.stage.destroy();
|
|
402
|
-
this.plugins.forEach(p => p.destroy?.(this));
|
|
403
|
-
this.plugins = [];
|
|
404
|
-
}
|
|
405
|
-
}
|
|
1
|
+
declare const __VERSION__: string;
|
|
2
|
+
|
|
3
|
+
import type { MouseState, SupermouseOptions, SupermousePlugin } from "./types";
|
|
4
|
+
import { Stage, Input } from "./systems";
|
|
5
|
+
import { damp, angle } from "./utils/math";
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_HOVER_SELECTORS = [
|
|
8
|
+
"a",
|
|
9
|
+
"button",
|
|
10
|
+
"input",
|
|
11
|
+
"textarea",
|
|
12
|
+
"[data-hover]",
|
|
13
|
+
"[data-cursor]"
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runtime Loop of Supermouse.
|
|
18
|
+
*
|
|
19
|
+
* This class orchestrates the application state, manages the animation loop (`requestAnimationFrame`),
|
|
20
|
+
* and coordinates data flow between the Input system, the Stage system, and the Plugins.
|
|
21
|
+
*/
|
|
22
|
+
export class Supermouse {
|
|
23
|
+
public static readonly version: string = __VERSION__;
|
|
24
|
+
public readonly version: string = __VERSION__;
|
|
25
|
+
|
|
26
|
+
state: MouseState;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Configuration options.
|
|
30
|
+
*/
|
|
31
|
+
options: SupermouseOptions;
|
|
32
|
+
|
|
33
|
+
private plugins: SupermousePlugin[] = [];
|
|
34
|
+
private stage: Stage;
|
|
35
|
+
private input: Input;
|
|
36
|
+
|
|
37
|
+
private rafId: number = 0;
|
|
38
|
+
private lastTime: number = 0;
|
|
39
|
+
private isRunning: boolean = false;
|
|
40
|
+
|
|
41
|
+
private hoverSelectors: Set<string>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new Supermouse instance.
|
|
45
|
+
*
|
|
46
|
+
* @param options - Global configuration options.
|
|
47
|
+
* @throws Will throw if running in a non-browser environment (window/document undefined).
|
|
48
|
+
*/
|
|
49
|
+
constructor(options: SupermouseOptions = {}) {
|
|
50
|
+
this.options = {
|
|
51
|
+
smoothness: 0.15,
|
|
52
|
+
enableTouch: false,
|
|
53
|
+
autoDisableOnMobile: true,
|
|
54
|
+
ignoreOnNative: "auto",
|
|
55
|
+
hideCursor: true,
|
|
56
|
+
hideOnLeave: true,
|
|
57
|
+
autoStart: true,
|
|
58
|
+
container: document.body,
|
|
59
|
+
...options
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (!this.options.container) {
|
|
63
|
+
this.options.container = document.body;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.state = {
|
|
67
|
+
pointer: { x: -100, y: -100 },
|
|
68
|
+
target: { x: -100, y: -100 },
|
|
69
|
+
smooth: { x: -100, y: -100 },
|
|
70
|
+
velocity: { x: 0, y: 0 },
|
|
71
|
+
angle: 0,
|
|
72
|
+
isDown: false,
|
|
73
|
+
isHover: false,
|
|
74
|
+
isNative: false,
|
|
75
|
+
forcedCursor: null,
|
|
76
|
+
hoverTarget: null,
|
|
77
|
+
reducedMotion: false,
|
|
78
|
+
hasReceivedInput: false,
|
|
79
|
+
shape: null,
|
|
80
|
+
interaction: {}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (this.options.hoverSelectors) {
|
|
84
|
+
this.hoverSelectors = new Set(this.options.hoverSelectors);
|
|
85
|
+
} else {
|
|
86
|
+
this.hoverSelectors = new Set(DEFAULT_HOVER_SELECTORS);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.stage = new Stage(this.options.container, !!this.options.hideCursor);
|
|
90
|
+
this.hoverSelectors.forEach((s) => this.stage.addSelector(s));
|
|
91
|
+
|
|
92
|
+
this.input = new Input(
|
|
93
|
+
this.state,
|
|
94
|
+
this.options,
|
|
95
|
+
() => Array.from(this.hoverSelectors).join(", "),
|
|
96
|
+
(enabled) => {
|
|
97
|
+
if (!enabled) this.resetPosition();
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (this.options.plugins) {
|
|
102
|
+
this.options.plugins.forEach((p) => this.use(p));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.init();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Retrieves a registered plugin instance by its unique name.
|
|
110
|
+
*/
|
|
111
|
+
public getPlugin(name: string) {
|
|
112
|
+
return this.plugins.find((p) => p.name === name);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Returns whether the cursor system is currently enabled (processing input).
|
|
117
|
+
*/
|
|
118
|
+
public get isEnabled(): boolean {
|
|
119
|
+
return this.input.isEnabled;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Enables a specific plugin by name.
|
|
124
|
+
* Triggers the `onEnable` lifecycle hook of the plugin.
|
|
125
|
+
*/
|
|
126
|
+
public enablePlugin(name: string) {
|
|
127
|
+
const plugin = this.getPlugin(name);
|
|
128
|
+
if (plugin && plugin.isEnabled === false) {
|
|
129
|
+
plugin.isEnabled = true;
|
|
130
|
+
plugin.onEnable?.(this);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Disables a specific plugin by name.
|
|
136
|
+
* Triggers the `onDisable` lifecycle hook.
|
|
137
|
+
*/
|
|
138
|
+
public disablePlugin(name: string) {
|
|
139
|
+
const plugin = this.getPlugin(name);
|
|
140
|
+
if (plugin && plugin.isEnabled !== false) {
|
|
141
|
+
plugin.isEnabled = false;
|
|
142
|
+
plugin.onDisable?.(this);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Toggles the enabled state of a plugin.
|
|
148
|
+
*/
|
|
149
|
+
public togglePlugin(name: string) {
|
|
150
|
+
const plugin = this.getPlugin(name);
|
|
151
|
+
if (plugin) {
|
|
152
|
+
if (plugin.isEnabled === false) this.enablePlugin(name);
|
|
153
|
+
else this.disablePlugin(name);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public registerHoverTarget(selector: string) {
|
|
158
|
+
if (!this.hoverSelectors.has(selector)) {
|
|
159
|
+
this.hoverSelectors.add(selector);
|
|
160
|
+
this.stage.addSelector(selector);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The fixed container element where plugins should append their DOM nodes.
|
|
166
|
+
*/
|
|
167
|
+
public get container(): HTMLDivElement {
|
|
168
|
+
return this.stage.element;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Manually override the native cursor visibility.
|
|
173
|
+
*
|
|
174
|
+
* @param type 'auto' (Show Native), 'none' (Hide Native), or null (Resume Auto-detection)
|
|
175
|
+
*/
|
|
176
|
+
public setCursor(type: "auto" | "none" | null) {
|
|
177
|
+
this.state.forcedCursor = type;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private init() {
|
|
181
|
+
if (this.options.autoStart) {
|
|
182
|
+
this.startLoop();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public enable() {
|
|
187
|
+
this.input.isEnabled = true;
|
|
188
|
+
this.stage.setNativeCursor("none");
|
|
189
|
+
}
|
|
190
|
+
public disable() {
|
|
191
|
+
this.input.isEnabled = false;
|
|
192
|
+
this.stage.setNativeCursor("auto");
|
|
193
|
+
this.resetPosition();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Registers a new plugin.
|
|
198
|
+
*
|
|
199
|
+
* @param plugin - The plugin object to install.
|
|
200
|
+
*/
|
|
201
|
+
public use(plugin: SupermousePlugin) {
|
|
202
|
+
if (this.plugins.find((p) => p.name === plugin.name)) {
|
|
203
|
+
console.warn(`[Supermouse] Plugin "${plugin.name}" already installed.`);
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (plugin.isEnabled === undefined) {
|
|
208
|
+
plugin.isEnabled = true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.plugins.push(plugin);
|
|
212
|
+
this.plugins.sort((a, b) => (a.priority || 0) - (b.priority || 0));
|
|
213
|
+
|
|
214
|
+
plugin.install?.(this);
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private resetPosition() {
|
|
219
|
+
const off = { x: -100, y: -100 };
|
|
220
|
+
this.state.pointer = { ...off };
|
|
221
|
+
this.state.target = { ...off };
|
|
222
|
+
this.state.smooth = { ...off };
|
|
223
|
+
this.state.velocity = { x: 0, y: 0 };
|
|
224
|
+
this.state.angle = 0;
|
|
225
|
+
this.state.hasReceivedInput = false;
|
|
226
|
+
this.state.shape = null;
|
|
227
|
+
this.state.interaction = {};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private startLoop() {
|
|
231
|
+
if (this.isRunning) return;
|
|
232
|
+
this.isRunning = true;
|
|
233
|
+
this.lastTime = performance.now();
|
|
234
|
+
this.tick(this.lastTime);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Manually steps the animation loop.
|
|
239
|
+
*
|
|
240
|
+
* @param time Current timestamp in milliseconds.
|
|
241
|
+
*/
|
|
242
|
+
public step(time: number) {
|
|
243
|
+
this.tick(time);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private runPluginSafe(plugin: SupermousePlugin, deltaTime: number) {
|
|
247
|
+
if (plugin.isEnabled === false) return;
|
|
248
|
+
try {
|
|
249
|
+
plugin.update?.(this, deltaTime);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
console.error(`[Supermouse] Plugin '${plugin.name}' crashed and has been disabled.`, e);
|
|
252
|
+
plugin.isEnabled = false;
|
|
253
|
+
try {
|
|
254
|
+
plugin.onDisable?.(this);
|
|
255
|
+
} catch (err) {}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Runs on every animation frame.
|
|
261
|
+
*/
|
|
262
|
+
private tick = (time: number) => {
|
|
263
|
+
const dtMs = time - this.lastTime;
|
|
264
|
+
const dt = Math.min(dtMs / 1000, 0.1);
|
|
265
|
+
this.lastTime = time;
|
|
266
|
+
|
|
267
|
+
if (this.state.hoverTarget && !this.state.hoverTarget.isConnected) {
|
|
268
|
+
this.input.clearHover();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const shouldShowStage =
|
|
272
|
+
this.input.isEnabled && !this.state.isNative && this.state.hasReceivedInput;
|
|
273
|
+
this.stage.setVisibility(shouldShowStage);
|
|
274
|
+
|
|
275
|
+
if (this.input.isEnabled && this.options.hideCursor) {
|
|
276
|
+
let targetState: "none" | "auto" = "auto";
|
|
277
|
+
|
|
278
|
+
if (this.state.forcedCursor !== null) {
|
|
279
|
+
targetState = this.state.forcedCursor;
|
|
280
|
+
} else {
|
|
281
|
+
const showNative = this.state.isNative || !this.state.hasReceivedInput;
|
|
282
|
+
targetState = showNative ? "auto" : "none";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.stage.setNativeCursor(targetState);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (this.input.isEnabled) {
|
|
289
|
+
this.state.target.x = this.state.pointer.x;
|
|
290
|
+
this.state.target.y = this.state.pointer.y;
|
|
291
|
+
|
|
292
|
+
for (let i = 0; i < this.plugins.length; i++) {
|
|
293
|
+
this.runPluginSafe(this.plugins[i], dtMs);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const factor = this.state.reducedMotion ? 1000 : (1 / this.options.smoothness!) * 2;
|
|
297
|
+
|
|
298
|
+
this.state.smooth.x = damp(this.state.smooth.x, this.state.target.x, factor, dt);
|
|
299
|
+
this.state.smooth.y = damp(this.state.smooth.y, this.state.target.y, factor, dt);
|
|
300
|
+
|
|
301
|
+
const vx = this.state.target.x - this.state.smooth.x;
|
|
302
|
+
const vy = this.state.target.y - this.state.smooth.y;
|
|
303
|
+
|
|
304
|
+
this.state.velocity.x = vx;
|
|
305
|
+
this.state.velocity.y = vy;
|
|
306
|
+
|
|
307
|
+
if (Math.abs(vx) > 0.1 || Math.abs(vy) > 0.1) {
|
|
308
|
+
this.state.angle = angle(vx, vy);
|
|
309
|
+
}
|
|
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
|
+
}
|
|
322
|
+
|
|
323
|
+
if (this.options.autoStart && this.isRunning) {
|
|
324
|
+
this.rafId = requestAnimationFrame(this.tick);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Destroys the instance.
|
|
330
|
+
*/
|
|
331
|
+
public destroy() {
|
|
332
|
+
this.isRunning = false;
|
|
333
|
+
cancelAnimationFrame(this.rafId);
|
|
334
|
+
this.input.destroy();
|
|
335
|
+
this.stage.destroy();
|
|
336
|
+
this.plugins.forEach((p) => p.destroy?.(this));
|
|
337
|
+
this.plugins = [];
|
|
338
|
+
}
|
|
339
|
+
}
|