@supermousejs/core 2.2.0 → 2.3.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 +11 -0
- package/dist/index.d.ts +36 -17
- package/dist/index.mjs +91 -72
- package/dist/index.umd.js +2 -2
- package/package.json +1 -1
- package/src/Supermouse.ts +731 -708
- package/src/__tests__/setup.ts +13 -0
- package/src/__tests__/supermouse.test.ts +19 -0
- package/src/types.ts +27 -12
- package/vitest.config.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @supermousejs/core
|
|
2
2
|
|
|
3
|
+
## 2.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8dc8e06: cleanup and robustness pass: better case-insensitive data-\* handling, safer plugin lifecycle behavior, more reliable cursor restoration, and a more consistent reset/update flow
|
|
8
|
+
- 600de13: added `dataPrefix` option for customizable data attribute handling, added a `start()` public method to manually start the raf loop and enhanced supermouse's resistance to bugs
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- b72e264: Renamed `setCursor` to `setNativeCursor` for simplicity and removed redundant checks on `ignoreOnNative`
|
|
13
|
+
|
|
3
14
|
## 2.2.0
|
|
4
15
|
|
|
5
16
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -28,21 +28,21 @@ export declare interface MousePosition {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export declare interface MouseState {
|
|
31
|
-
/** The raw position
|
|
31
|
+
/** The raw position from the latest pointer event, before smoothing is applied. */
|
|
32
32
|
pointer: MousePosition;
|
|
33
|
-
/** The
|
|
33
|
+
/** The current goal position that the core loop is driving toward. */
|
|
34
34
|
target: MousePosition;
|
|
35
35
|
/** The smoothed/interpolated position used for rendering. */
|
|
36
36
|
smooth: MousePosition;
|
|
37
|
-
/** The current
|
|
37
|
+
/** The current movement vector derived from the smoothed state. */
|
|
38
38
|
velocity: MousePosition;
|
|
39
|
-
/** The
|
|
39
|
+
/** The current movement angle in degrees, derived from velocity. */
|
|
40
40
|
angle: number;
|
|
41
41
|
/** Whether the pointer is currently pressed down. */
|
|
42
42
|
isDown: boolean;
|
|
43
43
|
/** Whether the pointer is currently hovering over a registered interactive element. */
|
|
44
44
|
isHover: boolean;
|
|
45
|
-
/** Whether the
|
|
45
|
+
/** Whether the runtime has temporarily restored the native cursor due to native-input heuristics. */
|
|
46
46
|
isNative: boolean;
|
|
47
47
|
/**
|
|
48
48
|
* If set, this overrides all auto-detection logic.
|
|
@@ -131,11 +131,11 @@ export declare class Supermouse {
|
|
|
131
131
|
*/
|
|
132
132
|
get container(): HTMLDivElement;
|
|
133
133
|
/**
|
|
134
|
-
*
|
|
134
|
+
* Sets the native cursor visibility.
|
|
135
135
|
*
|
|
136
|
-
* @param
|
|
136
|
+
* @param mode
|
|
137
137
|
*/
|
|
138
|
-
|
|
138
|
+
setNativeCursor(mode: "hide" | "show" | "auto"): void;
|
|
139
139
|
private init;
|
|
140
140
|
enable(): void;
|
|
141
141
|
disable(): void;
|
|
@@ -145,9 +145,13 @@ export declare class Supermouse {
|
|
|
145
145
|
* @param plugin - The plugin object to install.
|
|
146
146
|
*/
|
|
147
147
|
use(plugin: SupermousePlugin): this;
|
|
148
|
-
private
|
|
149
|
-
private resetPosition;
|
|
148
|
+
private reset;
|
|
150
149
|
private startLoop;
|
|
150
|
+
/**
|
|
151
|
+
* Starts the animation loop. This is automatically called if `autoStart` is true.
|
|
152
|
+
* Plugins can call this method to resume the loop if it has been stopped.
|
|
153
|
+
*/
|
|
154
|
+
start(): void;
|
|
151
155
|
/**
|
|
152
156
|
* Manually steps the animation loop.
|
|
153
157
|
*
|
|
@@ -191,20 +195,24 @@ export declare interface SupermouseOptions {
|
|
|
191
195
|
autoDisableOnMobile?: boolean;
|
|
192
196
|
/**
|
|
193
197
|
* Strategy for detecting when to fallback to the native cursor.
|
|
194
|
-
* - `
|
|
198
|
+
* - `'auto'`: Checks both HTML tags and CSS cursor styles (Accurate but slower).
|
|
195
199
|
* - `'tag'`: Checks only semantic tags like <input>, <textarea> (Fastest, prevents layout thrashing).
|
|
196
200
|
* - `'css'`: Checks only computed CSS cursor styles (Slow, triggers reflow).
|
|
197
|
-
* - `
|
|
201
|
+
* - `null`: Never fallback to native cursor.
|
|
202
|
+
*
|
|
198
203
|
* @default 'auto'
|
|
199
204
|
*/
|
|
200
|
-
ignoreOnNative?:
|
|
205
|
+
ignoreOnNative?: NativeIgnoreStrategy | null;
|
|
201
206
|
/**
|
|
202
|
-
* Whether to hide the native cursor via
|
|
207
|
+
* Whether to hide the native cursor via scoped CSS injection on the container.
|
|
208
|
+
* When enabled, the stage toggles `cursor: none` on the configured container and
|
|
209
|
+
* on registered hover targets.
|
|
203
210
|
* @default true
|
|
204
211
|
*/
|
|
205
212
|
hideCursor?: boolean;
|
|
206
213
|
/**
|
|
207
|
-
* Whether to hide the custom cursor when the
|
|
214
|
+
* Whether to hide the custom cursor when the pointer leaves the browser viewport.
|
|
215
|
+
* When enabled the runtime clears the cursor back to an off-screen position to avoid stale hover state.
|
|
208
216
|
* @default true
|
|
209
217
|
*/
|
|
210
218
|
hideOnLeave?: boolean;
|
|
@@ -224,14 +232,24 @@ export declare interface SupermouseOptions {
|
|
|
224
232
|
autoStart?: boolean;
|
|
225
233
|
/**
|
|
226
234
|
* Semantic rules mapping CSS selectors to interaction state.
|
|
235
|
+
* Rules are evaluated against hovered elements and merged into `state.interaction`.
|
|
236
|
+
* Matching data attributes on the same element are also read and can override or enrich the final object.
|
|
227
237
|
* @example { 'button': { icon: 'pointer' } }
|
|
228
238
|
*/
|
|
229
239
|
rules?: Record<string, InteractionState>;
|
|
230
240
|
/**
|
|
231
241
|
* Custom strategy to resolve interaction state from a hovered element.
|
|
232
|
-
*
|
|
242
|
+
* When provided, this callback bypasses the default `rules` + `data-[prefix]-*` scraping and
|
|
243
|
+
* returns the interaction payload directly for the current hover target.
|
|
233
244
|
*/
|
|
234
245
|
resolveInteraction?: (target: HTMLElement) => InteractionState;
|
|
246
|
+
/**
|
|
247
|
+
* The prefix used for data attributes to store hover metadata.
|
|
248
|
+
* For example, if dataPrefix is "supermouse", then the attribute would be "data-supermouse-*".
|
|
249
|
+
* This allows for multiple instances of Supermouse to coexist without conflicting data attributes.
|
|
250
|
+
* @default "supermouse"
|
|
251
|
+
*/
|
|
252
|
+
dataPrefix?: string;
|
|
235
253
|
}
|
|
236
254
|
|
|
237
255
|
/**
|
|
@@ -246,7 +264,7 @@ export declare interface SupermousePlugin {
|
|
|
246
264
|
isEnabled?: boolean;
|
|
247
265
|
/** Called when `app.use()` is executed. */
|
|
248
266
|
install?: (instance: Supermouse) => void;
|
|
249
|
-
/** Called on every animation frame. */
|
|
267
|
+
/** Called on every animation frame with the frame delta time in milliseconds. */
|
|
250
268
|
update?: (instance: Supermouse, deltaTime: number) => void;
|
|
251
269
|
/** Called when the plugin is removed or the app is destroyed. */
|
|
252
270
|
destroy?: (instance: Supermouse) => void;
|
|
@@ -258,6 +276,7 @@ export declare interface SupermousePlugin {
|
|
|
258
276
|
|
|
259
277
|
/**
|
|
260
278
|
* Allows a property to be a static value or a function that returns the value based on state.
|
|
279
|
+
* This is primarily useful for plugin option definitions that should react to the current runtime state.
|
|
261
280
|
*/
|
|
262
281
|
export declare type ValueOrGetter<T> = T | ((state: MouseState) => T);
|
|
263
282
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
const d = "2.3.0";
|
|
2
|
+
function p(n, t, e) {
|
|
2
3
|
return n + (t - n) * e;
|
|
3
4
|
}
|
|
4
|
-
function
|
|
5
|
-
return
|
|
6
|
-
}
|
|
7
|
-
function p(n, t) {
|
|
8
|
-
return Math.atan2(t, n) * (180 / Math.PI);
|
|
5
|
+
function c(n, t, e, i) {
|
|
6
|
+
return p(n, t, 1 - Math.exp(-e * i));
|
|
9
7
|
}
|
|
10
8
|
class f {
|
|
11
|
-
constructor(t, e, i,
|
|
12
|
-
this.state = t, this.options = e, this.getHoverSelector = i, this.onEnableChange =
|
|
9
|
+
constructor(t, e, i, a) {
|
|
10
|
+
this.state = t, this.options = e, this.getHoverSelector = i, this.onEnableChange = a, this.dataPrefix = this.options.dataPrefix ?? "supermouse", this.normalizedDataPrefix = this.dataPrefix.toLowerCase(), this.ignoreAttribute = `data-${this.dataPrefix}-ignore`, this.checkDeviceCapability(), this.checkMotionPreference(), this.bindEvents();
|
|
13
11
|
}
|
|
14
12
|
state;
|
|
15
13
|
options;
|
|
@@ -18,6 +16,9 @@ class f {
|
|
|
18
16
|
mediaQueryList;
|
|
19
17
|
mediaQueryHandler;
|
|
20
18
|
motionQuery;
|
|
19
|
+
dataPrefix;
|
|
20
|
+
normalizedDataPrefix;
|
|
21
|
+
ignoreAttribute;
|
|
21
22
|
/**
|
|
22
23
|
* Master switch for input processing.
|
|
23
24
|
* Toggled by `Supermouse.enable()`/`disable()` or automatically by device capability checks.
|
|
@@ -52,30 +53,30 @@ class f {
|
|
|
52
53
|
}
|
|
53
54
|
parseDOMInteraction(t) {
|
|
54
55
|
if (this.options.resolveInteraction) {
|
|
55
|
-
this.state.interaction = this.options.resolveInteraction(t);
|
|
56
|
+
this.state.interaction = this.options.resolveInteraction(t) || {};
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
const e = {};
|
|
59
60
|
if (this.options.rules)
|
|
60
|
-
for (const [
|
|
61
|
-
t.matches(
|
|
62
|
-
const i =
|
|
63
|
-
for (const
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
61
|
+
for (const [s, o] of Object.entries(this.options.rules))
|
|
62
|
+
t.matches(s) && Object.assign(e, o);
|
|
63
|
+
const i = this.normalizedDataPrefix, a = this.dataPrefix.length;
|
|
64
|
+
for (const s in t.dataset) {
|
|
65
|
+
if (!s.toLowerCase().startsWith(i)) continue;
|
|
66
|
+
const o = s.slice(a);
|
|
67
|
+
if (!o) continue;
|
|
68
|
+
const r = t.dataset[s];
|
|
69
|
+
e[o[0].toLowerCase() + o.slice(1)] = r === "" ? !0 : r;
|
|
70
|
+
}
|
|
71
71
|
this.state.interaction = e;
|
|
72
72
|
}
|
|
73
73
|
handleMove(t) {
|
|
74
|
-
if (!this.isEnabled || this.options.autoDisableOnMobile && t.pointerType === "touch")
|
|
74
|
+
if (!this.isEnabled || this.options.autoDisableOnMobile && t.pointerType === "touch" && !this.options.enableTouch)
|
|
75
|
+
return;
|
|
75
76
|
let e = t.clientX, i = t.clientY;
|
|
76
77
|
if (this.options.container && this.options.container !== document.body) {
|
|
77
|
-
const
|
|
78
|
-
e -=
|
|
78
|
+
const a = this.options.container.getBoundingClientRect();
|
|
79
|
+
e -= a.left, i -= a.top;
|
|
79
80
|
}
|
|
80
81
|
this.state.pointer.x = e, this.state.pointer.y = i, this.state.hasReceivedInput || (this.state.hasReceivedInput = !0, this.state.target.x = this.state.smooth.x = e, this.state.target.y = this.state.smooth.y = i);
|
|
81
82
|
}
|
|
@@ -88,25 +89,25 @@ class f {
|
|
|
88
89
|
handleMouseOver(t) {
|
|
89
90
|
if (!this.isEnabled) return;
|
|
90
91
|
const e = t.target;
|
|
91
|
-
if (e.closest(
|
|
92
|
+
if (e.closest(`[${this.ignoreAttribute}]`)) {
|
|
92
93
|
this.state.isNative = !0;
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
95
|
-
const i = this.getHoverSelector(),
|
|
96
|
-
|
|
96
|
+
const i = this.getHoverSelector(), a = e.closest(i);
|
|
97
|
+
a && (this.state.isHover = !0, this.state.hoverTarget = a, this.parseDOMInteraction(this.state.hoverTarget));
|
|
97
98
|
const s = this.options.ignoreOnNative;
|
|
98
|
-
if (s) {
|
|
99
|
-
const
|
|
100
|
-
let
|
|
101
|
-
if (
|
|
99
|
+
if (s && s !== null) {
|
|
100
|
+
const o = s === "auto" || s === "tag", r = s === "auto" || s === "css";
|
|
101
|
+
let u = !1;
|
|
102
|
+
if (o) {
|
|
102
103
|
const h = e.localName;
|
|
103
|
-
(h === "input" || h === "textarea" || h === "select" || e.isContentEditable) && (
|
|
104
|
+
(h === "input" || h === "textarea" || h === "select" || e.isContentEditable) && (u = !0);
|
|
104
105
|
}
|
|
105
|
-
if (!
|
|
106
|
+
if (!u && r) {
|
|
106
107
|
const h = window.getComputedStyle(e).cursor;
|
|
107
|
-
["default", "auto", "pointer", "none", "inherit"].includes(h) || (
|
|
108
|
+
["default", "auto", "pointer", "none", "inherit"].includes(h) || (u = !0);
|
|
108
109
|
}
|
|
109
|
-
|
|
110
|
+
u && (this.state.isNative = !0);
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
handleMouseOut(t) {
|
|
@@ -115,10 +116,10 @@ class f {
|
|
|
115
116
|
(e === this.state.hoverTarget || e.contains(this.state.hoverTarget)) && (!t.relatedTarget || !this.state.hoverTarget?.contains(t.relatedTarget)) && (this.state.isHover = !1, this.state.hoverTarget = null, this.state.interaction = {}), this.state.isNative && (this.state.isNative = !1);
|
|
116
117
|
}
|
|
117
118
|
handleWindowLeave() {
|
|
118
|
-
this.options.hideOnLeave && (this.state.hasReceivedInput = !1);
|
|
119
|
+
this.options.hideOnLeave && (this.state.hasReceivedInput = !1, this.state.pointer = { ...l });
|
|
119
120
|
}
|
|
120
121
|
clearHover() {
|
|
121
|
-
this.state.isHover = !1, this.state.hoverTarget = null, this.state.isNative = !1;
|
|
122
|
+
this.state.isHover = !1, this.state.hoverTarget = null, this.state.isNative = !1, this.state.interaction = {};
|
|
122
123
|
}
|
|
123
124
|
bindEvents() {
|
|
124
125
|
const { signal: t } = this.abortController;
|
|
@@ -135,18 +136,19 @@ class v {
|
|
|
135
136
|
throw new Error(`[Supermouse] Invalid container: ${t}. Must be an HTMLElement.`);
|
|
136
137
|
const i = g++;
|
|
137
138
|
this.id = `supermouse-style-${i}`, this.scopeClass = `supermouse-scope-${i}`;
|
|
138
|
-
const
|
|
139
|
-
this.element = document.createElement("div"), Object.assign(this.element.style, {
|
|
140
|
-
position:
|
|
141
|
-
|
|
142
|
-
left: "0",
|
|
143
|
-
width: "100%",
|
|
144
|
-
height: "100%",
|
|
139
|
+
const a = t === document.body;
|
|
140
|
+
if (this.element = document.createElement("div"), Object.assign(this.element.style, {
|
|
141
|
+
position: a ? "fixed" : "absolute",
|
|
142
|
+
inset: "0px",
|
|
145
143
|
pointerEvents: "none",
|
|
146
144
|
zIndex: "9999",
|
|
147
145
|
opacity: "1",
|
|
148
146
|
transition: "opacity 0.15s ease"
|
|
149
|
-
}),
|
|
147
|
+
}), !a) {
|
|
148
|
+
const s = window.getComputedStyle(t);
|
|
149
|
+
this.originalContainerPosition = s.position, s.position === "static" && (t.style.position = "relative");
|
|
150
|
+
}
|
|
151
|
+
this.originalContainerCursor = t.style.cursor, t.appendChild(this.element), this.styleTag = document.createElement("style"), this.styleTag.id = this.id, document.head.appendChild(this.styleTag), this.container.classList.add(this.scopeClass), this.hideNativeCursor && this.setNativeCursor("none");
|
|
150
152
|
}
|
|
151
153
|
container;
|
|
152
154
|
hideNativeCursor;
|
|
@@ -156,6 +158,8 @@ class v {
|
|
|
156
158
|
id;
|
|
157
159
|
scopeClass;
|
|
158
160
|
currentCursorState = null;
|
|
161
|
+
originalContainerPosition = "";
|
|
162
|
+
originalContainerCursor = "";
|
|
159
163
|
selectors = /* @__PURE__ */ new Set([
|
|
160
164
|
"a",
|
|
161
165
|
"button",
|
|
@@ -197,10 +201,10 @@ class v {
|
|
|
197
201
|
`;
|
|
198
202
|
}
|
|
199
203
|
destroy() {
|
|
200
|
-
this.element.remove(), this.styleTag.remove(), this.container.style.cursor =
|
|
204
|
+
this.element.remove(), this.styleTag.remove(), this.container.style.cursor = this.originalContainerCursor, this.container.classList.remove(this.scopeClass), this.container !== document.body && this.originalContainerPosition === "static" && (this.container.style.position = "");
|
|
201
205
|
}
|
|
202
206
|
}
|
|
203
|
-
const
|
|
207
|
+
const l = { x: -100, y: -100 }, m = [
|
|
204
208
|
"a",
|
|
205
209
|
"button",
|
|
206
210
|
"input",
|
|
@@ -209,8 +213,8 @@ const u = { x: -100, y: -100 }, m = [
|
|
|
209
213
|
"[data-cursor]"
|
|
210
214
|
];
|
|
211
215
|
class y {
|
|
212
|
-
static version =
|
|
213
|
-
version =
|
|
216
|
+
static version = d;
|
|
217
|
+
version = d;
|
|
214
218
|
state;
|
|
215
219
|
/**
|
|
216
220
|
* Configuration options.
|
|
@@ -239,8 +243,9 @@ class y {
|
|
|
239
243
|
hideOnLeave: !0,
|
|
240
244
|
autoStart: !0,
|
|
241
245
|
container: document.body,
|
|
246
|
+
dataPrefix: "supermouse",
|
|
242
247
|
...t
|
|
243
|
-
}, this.
|
|
248
|
+
}, this.state = {
|
|
244
249
|
pointer: { x: -100, y: -100 },
|
|
245
250
|
target: { x: -100, y: -100 },
|
|
246
251
|
smooth: { x: -100, y: -100 },
|
|
@@ -260,7 +265,7 @@ class y {
|
|
|
260
265
|
this.options,
|
|
261
266
|
() => Array.from(this.hoverSelectors).join(", "),
|
|
262
267
|
(e) => {
|
|
263
|
-
e || this.
|
|
268
|
+
e || this.reset(!0);
|
|
264
269
|
}
|
|
265
270
|
), this.options.plugins && this.options.plugins.forEach((e) => this.use(e)), this.init();
|
|
266
271
|
}
|
|
@@ -309,12 +314,12 @@ class y {
|
|
|
309
314
|
return this.stage.element;
|
|
310
315
|
}
|
|
311
316
|
/**
|
|
312
|
-
*
|
|
317
|
+
* Sets the native cursor visibility.
|
|
313
318
|
*
|
|
314
|
-
* @param
|
|
319
|
+
* @param mode
|
|
315
320
|
*/
|
|
316
|
-
|
|
317
|
-
this.state.forcedCursor = t;
|
|
321
|
+
setNativeCursor(t) {
|
|
322
|
+
this.state.forcedCursor = t === "auto" ? null : t === "hide" ? "none" : "auto";
|
|
318
323
|
}
|
|
319
324
|
init() {
|
|
320
325
|
this.options.autoStart && this.startLoop();
|
|
@@ -323,7 +328,7 @@ class y {
|
|
|
323
328
|
this.input.isEnabled = !0, this.stage.setNativeCursor("none");
|
|
324
329
|
}
|
|
325
330
|
disable() {
|
|
326
|
-
this.input.isEnabled = !1, this.stage.setNativeCursor("auto"), this.
|
|
331
|
+
this.input.isEnabled = !1, this.stage.setNativeCursor("auto"), this.reset(!0);
|
|
327
332
|
}
|
|
328
333
|
/**
|
|
329
334
|
* Registers a new plugin.
|
|
@@ -331,17 +336,29 @@ class y {
|
|
|
331
336
|
* @param plugin - The plugin object to install.
|
|
332
337
|
*/
|
|
333
338
|
use(t) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
339
|
+
if (this.plugins.some((i) => i.name === t.name))
|
|
340
|
+
return console.warn(`[Supermouse] Plugin "${t.name}" already installed.`), this;
|
|
341
|
+
t.isEnabled === void 0 && (t.isEnabled = !0);
|
|
342
|
+
try {
|
|
343
|
+
t.install?.(this);
|
|
344
|
+
} catch (i) {
|
|
345
|
+
return console.error(`[Supermouse] Failed to install plugin '${t.name}'.`, i), this;
|
|
346
|
+
}
|
|
347
|
+
return this.plugins.push(t), this.plugins.sort((i, a) => (i.priority || 0) - (a.priority || 0)), this;
|
|
338
348
|
}
|
|
339
|
-
|
|
340
|
-
this.
|
|
349
|
+
reset(t = !1) {
|
|
350
|
+
this.state.pointer = { ...l }, this.state.target = { ...l }, this.state.smooth = { ...l }, this.state.velocity = { x: 0, y: 0 }, this.state.angle = 0, t && (this.state.hasReceivedInput = !1, this.state.shape = null, this.state.interaction = {});
|
|
341
351
|
}
|
|
342
352
|
startLoop() {
|
|
343
353
|
this.isRunning || (this.isRunning = !0, this.lastTime = performance.now(), this.tick(this.lastTime));
|
|
344
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Starts the animation loop. This is automatically called if `autoStart` is true.
|
|
357
|
+
* Plugins can call this method to resume the loop if it has been stopped.
|
|
358
|
+
*/
|
|
359
|
+
start() {
|
|
360
|
+
this.startLoop();
|
|
361
|
+
}
|
|
345
362
|
/**
|
|
346
363
|
* Manually steps the animation loop.
|
|
347
364
|
*
|
|
@@ -355,11 +372,13 @@ class y {
|
|
|
355
372
|
try {
|
|
356
373
|
t.update?.(this, e);
|
|
357
374
|
} catch (i) {
|
|
358
|
-
console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`, i)
|
|
375
|
+
console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`, i);
|
|
376
|
+
const a = this.plugins.indexOf(t);
|
|
377
|
+
a > -1 && this.plugins.splice(a, 1), t.isEnabled = !1;
|
|
359
378
|
try {
|
|
360
|
-
t.onDisable?.(this);
|
|
361
|
-
} catch (
|
|
362
|
-
console.error(`[Supermouse] Failed to
|
|
379
|
+
t.destroy?.(this), t.onDisable?.(this);
|
|
380
|
+
} catch (s) {
|
|
381
|
+
console.error(`[Supermouse] Failed to cleanup crashed plugin '${t.name}'.`, s);
|
|
363
382
|
}
|
|
364
383
|
}
|
|
365
384
|
}
|
|
@@ -369,21 +388,21 @@ class y {
|
|
|
369
388
|
tick = (t) => {
|
|
370
389
|
const e = t - this.lastTime, i = Math.min(e / 1e3, 0.1);
|
|
371
390
|
this.lastTime = t, this.state.hoverTarget && !this.state.hoverTarget.isConnected && this.input.clearHover();
|
|
372
|
-
const
|
|
373
|
-
if (this.stage.setVisibility(
|
|
391
|
+
const a = this.input.isEnabled && !this.state.isNative && this.state.hasReceivedInput;
|
|
392
|
+
if (this.stage.setVisibility(a), this.input.isEnabled && this.options.hideCursor) {
|
|
374
393
|
let s = "auto";
|
|
375
394
|
this.state.forcedCursor !== null ? s = this.state.forcedCursor : s = this.state.isNative || !this.state.hasReceivedInput ? "auto" : "none", this.stage.setNativeCursor(s);
|
|
376
395
|
}
|
|
377
|
-
this.input.isEnabled ? (this.state.target.x = this.state.pointer.x, this.state.target.y = this.state.pointer.y) : this.
|
|
396
|
+
this.input.isEnabled && this.state.hasReceivedInput ? (this.state.target.x = this.state.pointer.x, this.state.target.y = this.state.pointer.y) : this.state.target = { ...l };
|
|
378
397
|
for (let s = 0; s < this.plugins.length; s++)
|
|
379
398
|
this.runPluginSafe(this.plugins[s], e);
|
|
380
399
|
if (this.input.isEnabled) {
|
|
381
400
|
const s = this.state.reducedMotion ? 1e3 : 1 / this.options.smoothness * 2;
|
|
382
|
-
this.state.smooth.x =
|
|
383
|
-
const
|
|
384
|
-
|
|
401
|
+
this.state.smooth.x = c(this.state.smooth.x, this.state.target.x, s, i), this.state.smooth.y = c(this.state.smooth.y, this.state.target.y, s, i), this.state.velocity.x = this.state.target.x - this.state.smooth.x, this.state.velocity.y = this.state.target.y - this.state.smooth.y;
|
|
402
|
+
const { x: o, y: r } = this.state.velocity;
|
|
403
|
+
(Math.abs(o) > 0.1 || Math.abs(r) > 0.1) && (this.state.angle = Math.atan2(r, o) * (180 / Math.PI));
|
|
385
404
|
}
|
|
386
|
-
this.
|
|
405
|
+
this.isRunning && (this.rafId = requestAnimationFrame(this.tick));
|
|
387
406
|
};
|
|
388
407
|
/**
|
|
389
408
|
* Destroys the instance.
|
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(a,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(a=typeof globalThis<"u"?globalThis:a||self,l(a.SupermouseCore={}))})(this,(function(a){"use strict";const l="2.3.0";function m(r,t,e){return r+(t-r)*e}function p(r,t,e,i){return m(r,t,1-Math.exp(-e*i))}class f{constructor(t,e,i,n){this.state=t,this.options=e,this.getHoverSelector=i,this.onEnableChange=n,this.dataPrefix=this.options.dataPrefix??"supermouse",this.normalizedDataPrefix=this.dataPrefix.toLowerCase(),this.ignoreAttribute=`data-${this.dataPrefix}-ignore`,this.checkDeviceCapability(),this.checkMotionPreference(),this.bindEvents()}state;options;getHoverSelector;onEnableChange;mediaQueryList;mediaQueryHandler;motionQuery;dataPrefix;normalizedDataPrefix;ignoreAttribute;isEnabled=!0;abortController=new AbortController;checkDeviceCapability(){this.options.autoDisableOnMobile&&(this.mediaQueryList=window.matchMedia("(pointer: fine)"),this.updateEnabledState(this.mediaQueryList.matches),this.mediaQueryHandler=t=>{this.updateEnabledState(t.matches)},this.mediaQueryList.addEventListener("change",this.mediaQueryHandler,{signal:this.abortController.signal}))}checkMotionPreference(){this.motionQuery=window.matchMedia("(prefers-reduced-motion: reduce)"),this.state.reducedMotion=this.motionQuery.matches,this.motionQuery.addEventListener("change",t=>{this.state.reducedMotion=t.matches},{signal:this.abortController.signal})}updateEnabledState(t){this.isEnabled=t,this.onEnableChange(t)}parseDOMInteraction(t){if(this.options.resolveInteraction){this.state.interaction=this.options.resolveInteraction(t)||{};return}const e={};if(this.options.rules)for(const[s,o]of Object.entries(this.options.rules))t.matches(s)&&Object.assign(e,o);const i=this.normalizedDataPrefix,n=this.dataPrefix.length;for(const s in t.dataset){if(!s.toLowerCase().startsWith(i))continue;const o=s.slice(n);if(!o)continue;const h=t.dataset[s];e[o[0].toLowerCase()+o.slice(1)]=h===""?!0:h}this.state.interaction=e}handleMove(t){if(!this.isEnabled||this.options.autoDisableOnMobile&&t.pointerType==="touch"&&!this.options.enableTouch)return;let e=t.clientX,i=t.clientY;if(this.options.container&&this.options.container!==document.body){const n=this.options.container.getBoundingClientRect();e-=n.left,i-=n.top}this.state.pointer.x=e,this.state.pointer.y=i,this.state.hasReceivedInput||(this.state.hasReceivedInput=!0,this.state.target.x=this.state.smooth.x=e,this.state.target.y=this.state.smooth.y=i)}handleDown(){this.isEnabled&&(this.state.isDown=!0)}handleUp(){this.isEnabled&&(this.state.isDown=!1)}handleMouseOver(t){if(!this.isEnabled)return;const e=t.target;if(e.closest(`[${this.ignoreAttribute}]`)){this.state.isNative=!0;return}const i=this.getHoverSelector(),n=e.closest(i);n&&(this.state.isHover=!0,this.state.hoverTarget=n,this.parseDOMInteraction(this.state.hoverTarget));const s=this.options.ignoreOnNative;if(s&&s!==null){const o=s==="auto"||s==="tag",h=s==="auto"||s==="css";let c=!1;if(o){const d=e.localName;(d==="input"||d==="textarea"||d==="select"||e.isContentEditable)&&(c=!0)}if(!c&&h){const d=window.getComputedStyle(e).cursor;["default","auto","pointer","none","inherit"].includes(d)||(c=!0)}c&&(this.state.isNative=!0)}}handleMouseOut(t){if(!this.isEnabled)return;const e=t.target;(e===this.state.hoverTarget||e.contains(this.state.hoverTarget))&&(!t.relatedTarget||!this.state.hoverTarget?.contains(t.relatedTarget))&&(this.state.isHover=!1,this.state.hoverTarget=null,this.state.interaction={}),this.state.isNative&&(this.state.isNative=!1)}handleWindowLeave(){this.options.hideOnLeave&&(this.state.hasReceivedInput=!1,this.state.pointer={...u})}clearHover(){this.state.isHover=!1,this.state.hoverTarget=null,this.state.isNative=!1,this.state.interaction={}}bindEvents(){const{signal:t}=this.abortController;window.addEventListener("pointermove",this.handleMove.bind(this),{passive:!0,signal:t}),window.addEventListener("pointerdown",this.handleDown.bind(this),{passive:!0,signal:t}),window.addEventListener("pointerup",this.handleUp.bind(this),{signal:t}),document.addEventListener("mouseover",this.handleMouseOver.bind(this),{signal:t}),document.addEventListener("mouseout",this.handleMouseOut.bind(this),{signal:t}),document.addEventListener("mouseleave",this.handleWindowLeave.bind(this),{signal:t})}destroy(){this.abortController.abort()}}let b=0;class g{constructor(t=document.body,e){if(this.container=t,this.hideNativeCursor=e,!t||!(t instanceof HTMLElement))throw new Error(`[Supermouse] Invalid container: ${t}. Must be an HTMLElement.`);const i=b++;this.id=`supermouse-style-${i}`,this.scopeClass=`supermouse-scope-${i}`;const n=t===document.body;if(this.element=document.createElement("div"),Object.assign(this.element.style,{position:n?"fixed":"absolute",inset:"0px",pointerEvents:"none",zIndex:"9999",opacity:"1",transition:"opacity 0.15s ease"}),!n){const s=window.getComputedStyle(t);this.originalContainerPosition=s.position,s.position==="static"&&(t.style.position="relative")}this.originalContainerCursor=t.style.cursor,t.appendChild(this.element),this.styleTag=document.createElement("style"),this.styleTag.id=this.id,document.head.appendChild(this.styleTag),this.container.classList.add(this.scopeClass),this.hideNativeCursor&&this.setNativeCursor("none")}container;hideNativeCursor;element;styleTag;id;scopeClass;currentCursorState=null;originalContainerPosition="";originalContainerCursor="";selectors=new Set(["a","button","input","textarea","select",'[role="button"]',"[tabindex]"]);addSelector(t){this.selectors.add(t),this.hideNativeCursor&&this.updateCursorCSS()}setVisibility(t){this.element.style.opacity=t?"1":"0"}setNativeCursor(t){!this.hideNativeCursor&&t==="none"||t!==this.currentCursorState&&(this.currentCursorState=t,t==="none"?(this.container.style.cursor="none",this.updateCursorCSS()):(this.container.style.cursor="",this.styleTag.innerText=""))}updateCursorCSS(){const t=Array.from(this.selectors);if(t.length===0){this.styleTag.innerText="";return}const e=t.map(i=>`.${this.scopeClass} ${i}`).join(", ");this.styleTag.innerText=`
|
|
2
2
|
${e} {
|
|
3
3
|
cursor: none !important;
|
|
4
4
|
}
|
|
5
|
-
`}destroy(){this.element.remove(),this.styleTag.remove(),this.container.style.cursor=
|
|
5
|
+
`}destroy(){this.element.remove(),this.styleTag.remove(),this.container.style.cursor=this.originalContainerCursor,this.container.classList.remove(this.scopeClass),this.container!==document.body&&this.originalContainerPosition==="static"&&(this.container.style.position="")}}const u={x:-100,y:-100},v=["a","button","input","textarea","[data-hover]","[data-cursor]"];class y{static version=l;version=l;state;options;plugins=[];stage;input;rafId=0;lastTime=0;isRunning=!1;hoverSelectors;constructor(t={}){this.options={smoothness:.15,enableTouch:!1,autoDisableOnMobile:!0,ignoreOnNative:"auto",hideCursor:!0,hideOnLeave:!0,autoStart:!0,container:document.body,dataPrefix:"supermouse",...t},this.state={pointer:{x:-100,y:-100},target:{x:-100,y:-100},smooth:{x:-100,y:-100},velocity:{x:0,y:0},angle:0,isDown:!1,isHover:!1,isNative:!1,forcedCursor:null,hoverTarget:null,reducedMotion:!1,hasReceivedInput:!1,shape:null,interaction:{}},this.options.hoverSelectors?this.hoverSelectors=new Set(this.options.hoverSelectors):this.hoverSelectors=new Set(v),this.stage=new g(this.options.container,!!this.options.hideCursor),this.hoverSelectors.forEach(e=>this.stage.addSelector(e)),this.input=new f(this.state,this.options,()=>Array.from(this.hoverSelectors).join(", "),e=>{e||this.reset(!0)}),this.options.plugins&&this.options.plugins.forEach(e=>this.use(e)),this.init()}getPlugin(t){return this.plugins.find(e=>e.name===t)}get isEnabled(){return this.input.isEnabled}enablePlugin(t){const e=this.getPlugin(t);e&&e.isEnabled===!1&&(e.isEnabled=!0,e.onEnable?.(this))}disablePlugin(t){const e=this.getPlugin(t);e&&e.isEnabled!==!1&&(e.isEnabled=!1,e.onDisable?.(this))}togglePlugin(t){const e=this.getPlugin(t);e&&(e.isEnabled===!1?this.enablePlugin(t):this.disablePlugin(t))}registerHoverTarget(t){this.hoverSelectors.has(t)||(this.hoverSelectors.add(t),this.stage.addSelector(t))}get container(){return this.stage.element}setNativeCursor(t){this.state.forcedCursor=t==="auto"?null:t==="hide"?"none":"auto"}init(){this.options.autoStart&&this.startLoop()}enable(){this.input.isEnabled=!0,this.stage.setNativeCursor("none")}disable(){this.input.isEnabled=!1,this.stage.setNativeCursor("auto"),this.reset(!0)}use(t){if(this.plugins.some(i=>i.name===t.name))return console.warn(`[Supermouse] Plugin "${t.name}" already installed.`),this;t.isEnabled===void 0&&(t.isEnabled=!0);try{t.install?.(this)}catch(i){return console.error(`[Supermouse] Failed to install plugin '${t.name}'.`,i),this}return this.plugins.push(t),this.plugins.sort((i,n)=>(i.priority||0)-(n.priority||0)),this}reset(t=!1){this.state.pointer={...u},this.state.target={...u},this.state.smooth={...u},this.state.velocity={x:0,y:0},this.state.angle=0,t&&(this.state.hasReceivedInput=!1,this.state.shape=null,this.state.interaction={})}startLoop(){this.isRunning||(this.isRunning=!0,this.lastTime=performance.now(),this.tick(this.lastTime))}start(){this.startLoop()}step(t){this.tick(t)}runPluginSafe(t,e){if(t.isEnabled!==!1)try{t.update?.(this,e)}catch(i){console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`,i);const n=this.plugins.indexOf(t);n>-1&&this.plugins.splice(n,1),t.isEnabled=!1;try{t.destroy?.(this),t.onDisable?.(this)}catch(s){console.error(`[Supermouse] Failed to cleanup crashed plugin '${t.name}'.`,s)}}}tick=t=>{const e=t-this.lastTime,i=Math.min(e/1e3,.1);this.lastTime=t,this.state.hoverTarget&&!this.state.hoverTarget.isConnected&&this.input.clearHover();const n=this.input.isEnabled&&!this.state.isNative&&this.state.hasReceivedInput;if(this.stage.setVisibility(n),this.input.isEnabled&&this.options.hideCursor){let s="auto";this.state.forcedCursor!==null?s=this.state.forcedCursor:s=this.state.isNative||!this.state.hasReceivedInput?"auto":"none",this.stage.setNativeCursor(s)}this.input.isEnabled&&this.state.hasReceivedInput?(this.state.target.x=this.state.pointer.x,this.state.target.y=this.state.pointer.y):this.state.target={...u};for(let s=0;s<this.plugins.length;s++)this.runPluginSafe(this.plugins[s],e);if(this.input.isEnabled){const s=this.state.reducedMotion?1e3:1/this.options.smoothness*2;this.state.smooth.x=p(this.state.smooth.x,this.state.target.x,s,i),this.state.smooth.y=p(this.state.smooth.y,this.state.target.y,s,i),this.state.velocity.x=this.state.target.x-this.state.smooth.x,this.state.velocity.y=this.state.target.y-this.state.smooth.y;const{x:o,y:h}=this.state.velocity;(Math.abs(o)>.1||Math.abs(h)>.1)&&(this.state.angle=Math.atan2(h,o)*(180/Math.PI))}this.isRunning&&(this.rafId=requestAnimationFrame(this.tick))};destroy(){this.isRunning=!1,cancelAnimationFrame(this.rafId),this.input.destroy(),this.stage.destroy(),this.plugins.forEach(t=>t.destroy?.(this)),this.plugins=[]}}a.DEFAULT_HOVER_SELECTORS=v,a.Input=f,a.Stage=g,a.Supermouse=y,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
|