@supermousejs/core 2.0.5 → 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 +20 -0
- package/LICENSE.md +21 -21
- package/README.md +30 -31
- package/dist/index.d.ts +16 -56
- package/dist/index.mjs +181 -234
- package/dist/index.umd.js +2 -2
- package/package.json +1 -1
- package/src/Supermouse.ts +708 -405
- package/src/index.ts +2 -2
- package/src/types.ts +168 -168
- package/tsconfig.json +9 -16
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +32 -32
- package/src/systems/Input.ts +0 -262
- package/src/systems/Stage.ts +0 -155
- package/src/systems/index.ts +0 -2
- package/src/utils/math.ts +0 -20
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,143 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
function c(n, t, e) {
|
|
2
|
+
return n + (t - n) * e;
|
|
3
|
+
}
|
|
4
|
+
function d(n, t, e, i) {
|
|
5
|
+
return c(n, t, 1 - Math.exp(-e * i));
|
|
6
|
+
}
|
|
7
|
+
function p(n, t) {
|
|
8
|
+
return Math.atan2(t, n) * (180 / Math.PI);
|
|
9
|
+
}
|
|
10
|
+
class f {
|
|
11
|
+
constructor(t, e, i, o) {
|
|
12
|
+
this.state = t, this.options = e, this.getHoverSelector = i, this.onEnableChange = o, this.checkDeviceCapability(), this.checkMotionPreference(), this.bindEvents();
|
|
13
|
+
}
|
|
14
|
+
state;
|
|
15
|
+
options;
|
|
16
|
+
getHoverSelector;
|
|
17
|
+
onEnableChange;
|
|
18
|
+
mediaQueryList;
|
|
19
|
+
mediaQueryHandler;
|
|
20
|
+
motionQuery;
|
|
21
|
+
/**
|
|
22
|
+
* Master switch for input processing.
|
|
23
|
+
* Toggled by `Supermouse.enable()`/`disable()` or automatically by device capability checks.
|
|
24
|
+
*/
|
|
25
|
+
isEnabled = !0;
|
|
26
|
+
abortController = new AbortController();
|
|
27
|
+
/**
|
|
28
|
+
* Automatically disables the custom cursor on devices without fine pointer control.
|
|
29
|
+
*/
|
|
30
|
+
checkDeviceCapability() {
|
|
31
|
+
this.options.autoDisableOnMobile && (this.mediaQueryList = window.matchMedia("(pointer: fine)"), this.updateEnabledState(this.mediaQueryList.matches), this.mediaQueryHandler = (t) => {
|
|
32
|
+
this.updateEnabledState(t.matches);
|
|
33
|
+
}, this.mediaQueryList.addEventListener("change", this.mediaQueryHandler, {
|
|
34
|
+
signal: this.abortController.signal
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Checks for `prefers-reduced-motion`.
|
|
39
|
+
* If true, the core physics engine will switch to instant snapping (high damping) to avoid motion sickness.
|
|
40
|
+
*/
|
|
41
|
+
checkMotionPreference() {
|
|
42
|
+
this.motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)"), this.state.reducedMotion = this.motionQuery.matches, this.motionQuery.addEventListener(
|
|
43
|
+
"change",
|
|
44
|
+
(t) => {
|
|
45
|
+
this.state.reducedMotion = t.matches;
|
|
46
|
+
},
|
|
47
|
+
{ signal: this.abortController.signal }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
updateEnabledState(t) {
|
|
51
|
+
this.isEnabled = t, this.onEnableChange(t);
|
|
52
|
+
}
|
|
53
|
+
parseDOMInteraction(t) {
|
|
54
|
+
if (this.options.resolveInteraction) {
|
|
55
|
+
this.state.interaction = this.options.resolveInteraction(t);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const e = {};
|
|
59
|
+
if (this.options.rules)
|
|
60
|
+
for (const [o, s] of Object.entries(this.options.rules))
|
|
61
|
+
t.matches(o) && Object.assign(e, s);
|
|
62
|
+
const i = t.dataset;
|
|
63
|
+
for (const o in i)
|
|
64
|
+
if (o.startsWith("supermouse")) {
|
|
65
|
+
const s = o.slice(10);
|
|
66
|
+
if (s) {
|
|
67
|
+
const r = s.charAt(0).toLowerCase() + s.slice(1), a = i[o];
|
|
68
|
+
a !== void 0 && (e[r] = a === "" ? !0 : a);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this.state.interaction = e;
|
|
72
|
+
}
|
|
73
|
+
handleMove(t) {
|
|
74
|
+
if (!this.isEnabled || this.options.autoDisableOnMobile && t.pointerType === "touch") return;
|
|
75
|
+
let e = t.clientX, i = t.clientY;
|
|
76
|
+
if (this.options.container && this.options.container !== document.body) {
|
|
77
|
+
const o = this.options.container.getBoundingClientRect();
|
|
78
|
+
e -= o.left, i -= o.top;
|
|
79
|
+
}
|
|
80
|
+
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
|
+
handleDown() {
|
|
83
|
+
this.isEnabled && (this.state.isDown = !0);
|
|
84
|
+
}
|
|
85
|
+
handleUp() {
|
|
86
|
+
this.isEnabled && (this.state.isDown = !1);
|
|
87
|
+
}
|
|
88
|
+
handleMouseOver(t) {
|
|
89
|
+
if (!this.isEnabled) return;
|
|
90
|
+
const e = t.target;
|
|
91
|
+
if (e.closest("[data-supermouse-ignore]")) {
|
|
92
|
+
this.state.isNative = !0;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const i = this.getHoverSelector(), o = e.closest(i);
|
|
96
|
+
o && (this.state.isHover = !0, this.state.hoverTarget = o, this.parseDOMInteraction(this.state.hoverTarget));
|
|
97
|
+
const s = this.options.ignoreOnNative;
|
|
98
|
+
if (s) {
|
|
99
|
+
const r = s === !0 || s === "auto" || s === "tag", a = s === !0 || s === "auto" || s === "css";
|
|
100
|
+
let l = !1;
|
|
101
|
+
if (r) {
|
|
102
|
+
const h = e.localName;
|
|
103
|
+
(h === "input" || h === "textarea" || h === "select" || e.isContentEditable) && (l = !0);
|
|
104
|
+
}
|
|
105
|
+
if (!l && a) {
|
|
106
|
+
const h = window.getComputedStyle(e).cursor;
|
|
107
|
+
["default", "auto", "pointer", "none", "inherit"].includes(h) || (l = !0);
|
|
108
|
+
}
|
|
109
|
+
l && (this.state.isNative = !0);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
handleMouseOut(t) {
|
|
113
|
+
if (!this.isEnabled) return;
|
|
114
|
+
const e = t.target;
|
|
115
|
+
(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
|
+
handleWindowLeave() {
|
|
118
|
+
this.options.hideOnLeave && (this.state.hasReceivedInput = !1);
|
|
119
|
+
}
|
|
120
|
+
clearHover() {
|
|
121
|
+
this.state.isHover = !1, this.state.hoverTarget = null, this.state.isNative = !1;
|
|
122
|
+
}
|
|
123
|
+
bindEvents() {
|
|
124
|
+
const { signal: t } = this.abortController;
|
|
125
|
+
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 });
|
|
126
|
+
}
|
|
127
|
+
destroy() {
|
|
128
|
+
this.abortController.abort();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
let g = 0;
|
|
132
|
+
class v {
|
|
3
133
|
constructor(t = document.body, e) {
|
|
4
134
|
if (this.container = t, this.hideNativeCursor = e, !t || !(t instanceof HTMLElement))
|
|
5
135
|
throw new Error(`[Supermouse] Invalid container: ${t}. Must be an HTMLElement.`);
|
|
6
|
-
const
|
|
7
|
-
this.id = `supermouse-style-${
|
|
8
|
-
const
|
|
136
|
+
const i = g++;
|
|
137
|
+
this.id = `supermouse-style-${i}`, this.scopeClass = `supermouse-scope-${i}`;
|
|
138
|
+
const o = t === document.body;
|
|
9
139
|
this.element = document.createElement("div"), Object.assign(this.element.style, {
|
|
10
|
-
position:
|
|
140
|
+
position: o ? "fixed" : "absolute",
|
|
11
141
|
top: "0",
|
|
12
142
|
left: "0",
|
|
13
143
|
width: "100%",
|
|
@@ -16,17 +146,16 @@ class c {
|
|
|
16
146
|
zIndex: "9999",
|
|
17
147
|
opacity: "1",
|
|
18
148
|
transition: "opacity 0.15s ease"
|
|
19
|
-
}),
|
|
149
|
+
}), o || window.getComputedStyle(t).position === "static" && (t.style.position = "relative"), 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");
|
|
20
150
|
}
|
|
21
|
-
|
|
151
|
+
container;
|
|
152
|
+
hideNativeCursor;
|
|
153
|
+
/** The container element appended to the document. */
|
|
22
154
|
element;
|
|
23
155
|
styleTag;
|
|
24
156
|
id;
|
|
25
157
|
scopeClass;
|
|
26
|
-
// Cache to prevent redundant DOM updates
|
|
27
158
|
currentCursorState = null;
|
|
28
|
-
// Defaults for CSS hiding. We must override user-agent styles on these elements
|
|
29
|
-
// to prevent the native cursor from popping through.
|
|
30
159
|
selectors = /* @__PURE__ */ new Set([
|
|
31
160
|
"a",
|
|
32
161
|
"button",
|
|
@@ -37,16 +166,13 @@ class c {
|
|
|
37
166
|
"[tabindex]"
|
|
38
167
|
]);
|
|
39
168
|
/**
|
|
40
|
-
* Adds a new CSS selector to the
|
|
41
|
-
* Called by `Supermouse`
|
|
169
|
+
* Adds a new CSS selector to the `selectors` set.
|
|
170
|
+
* Called by `Supermouse` and subsequently plugins during install to ensure
|
|
42
171
|
* the native cursor is hidden on their specific interactive targets.
|
|
43
172
|
*/
|
|
44
173
|
addSelector(t) {
|
|
45
174
|
this.selectors.add(t), this.hideNativeCursor && this.updateCursorCSS();
|
|
46
175
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Controls the opacity of the entire stage (all custom cursor elements).
|
|
49
|
-
*/
|
|
50
176
|
setVisibility(t) {
|
|
51
177
|
this.element.style.opacity = t ? "1" : "0";
|
|
52
178
|
}
|
|
@@ -63,7 +189,7 @@ class c {
|
|
|
63
189
|
this.styleTag.innerText = "";
|
|
64
190
|
return;
|
|
65
191
|
}
|
|
66
|
-
const e = t.map((
|
|
192
|
+
const e = t.map((i) => `.${this.scopeClass} ${i}`).join(", ");
|
|
67
193
|
this.styleTag.innerText = `
|
|
68
194
|
${e} {
|
|
69
195
|
cursor: none !important;
|
|
@@ -74,144 +200,7 @@ class c {
|
|
|
74
200
|
this.element.remove(), this.styleTag.remove(), this.container.style.cursor = "", this.container.classList.remove(this.scopeClass);
|
|
75
201
|
}
|
|
76
202
|
}
|
|
77
|
-
|
|
78
|
-
constructor(t, e, s, n) {
|
|
79
|
-
this.state = t, this.options = e, this.getHoverSelector = s, this.onEnableChange = n, this.checkDeviceCapability(), this.checkMotionPreference(), this.bindEvents();
|
|
80
|
-
}
|
|
81
|
-
mediaQueryList;
|
|
82
|
-
mediaQueryHandler;
|
|
83
|
-
motionQuery;
|
|
84
|
-
/**
|
|
85
|
-
* Master switch for input processing.
|
|
86
|
-
* Toggled by `Supermouse.enable()`/`disable()` or automatically by device capability checks.
|
|
87
|
-
*/
|
|
88
|
-
isEnabled = !0;
|
|
89
|
-
/**
|
|
90
|
-
* Performance Optimization:
|
|
91
|
-
* We cache the resolved InteractionState for every element we encounter in a WeakMap.
|
|
92
|
-
*/
|
|
93
|
-
interactionCache = /* @__PURE__ */ new WeakMap();
|
|
94
|
-
/**
|
|
95
|
-
* Automatically disables the custom cursor on devices without fine pointer control (e.g. phones/tablets).
|
|
96
|
-
* Relies on `matchMedia('(pointer: fine)')`.
|
|
97
|
-
*/
|
|
98
|
-
checkDeviceCapability() {
|
|
99
|
-
this.options.autoDisableOnMobile && (this.mediaQueryList = window.matchMedia("(pointer: fine)"), this.updateEnabledState(this.mediaQueryList.matches), this.mediaQueryHandler = (t) => {
|
|
100
|
-
this.updateEnabledState(t.matches);
|
|
101
|
-
}, this.mediaQueryList.addEventListener("change", this.mediaQueryHandler));
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Checks for `prefers-reduced-motion`.
|
|
105
|
-
* If true, the core physics engine will switch to instant snapping (high damping) to avoid motion sickness.
|
|
106
|
-
*/
|
|
107
|
-
checkMotionPreference() {
|
|
108
|
-
this.motionQuery = window.matchMedia("(prefer-reduced-motion: reduce)"), this.state.reducedMotion = this.motionQuery.matches, this.motionQuery.addEventListener("change", (t) => {
|
|
109
|
-
this.state.reducedMotion = t.matches;
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
updateEnabledState(t) {
|
|
113
|
-
this.isEnabled = t, this.onEnableChange(t);
|
|
114
|
-
}
|
|
115
|
-
// --- Interaction Parsing ---
|
|
116
|
-
/**
|
|
117
|
-
* Scrapes the DOM element for metadata to populate `state.interaction`.
|
|
118
|
-
*
|
|
119
|
-
* **Strategy:**
|
|
120
|
-
* 1. Check WeakMap cache.
|
|
121
|
-
* 2. Apply config-based `rules`.
|
|
122
|
-
* 3. Scrape `dataset` (supermouse*).
|
|
123
|
-
* 4. Cache result.
|
|
124
|
-
*/
|
|
125
|
-
parseDOMInteraction(t) {
|
|
126
|
-
if (this.options.resolveInteraction) {
|
|
127
|
-
this.state.interaction = this.options.resolveInteraction(t);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
if (this.interactionCache.has(t)) {
|
|
131
|
-
this.state.interaction = this.interactionCache.get(t);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const e = {};
|
|
135
|
-
if (this.options.rules)
|
|
136
|
-
for (const [n, i] of Object.entries(this.options.rules))
|
|
137
|
-
t.matches(n) && Object.assign(e, i);
|
|
138
|
-
const s = t.dataset;
|
|
139
|
-
for (const n in s)
|
|
140
|
-
if (n.startsWith("supermouse")) {
|
|
141
|
-
const i = n.slice(10);
|
|
142
|
-
if (i) {
|
|
143
|
-
const l = i.charAt(0).toLowerCase() + i.slice(1), r = s[n];
|
|
144
|
-
e[l] = r === "" ? !0 : r;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
this.interactionCache.set(t, e), this.state.interaction = e;
|
|
148
|
-
}
|
|
149
|
-
// --- Handlers ---
|
|
150
|
-
// Unified Pointer Event Handler
|
|
151
|
-
handleMove = (t) => {
|
|
152
|
-
if (!this.isEnabled || this.options.autoDisableOnMobile && t.pointerType === "touch") return;
|
|
153
|
-
let e = t.clientX, s = t.clientY;
|
|
154
|
-
if (this.options.container && this.options.container !== document.body) {
|
|
155
|
-
const n = this.options.container.getBoundingClientRect();
|
|
156
|
-
e -= n.left, s -= n.top;
|
|
157
|
-
}
|
|
158
|
-
this.state.pointer.x = e, this.state.pointer.y = s, this.state.hasReceivedInput || (this.state.hasReceivedInput = !0, this.state.target.x = this.state.smooth.x = e, this.state.target.y = this.state.smooth.y = s);
|
|
159
|
-
};
|
|
160
|
-
handleDown = () => {
|
|
161
|
-
this.isEnabled && (this.state.isDown = !0);
|
|
162
|
-
};
|
|
163
|
-
handleUp = () => {
|
|
164
|
-
this.isEnabled && (this.state.isDown = !1);
|
|
165
|
-
};
|
|
166
|
-
handleMouseOver = (t) => {
|
|
167
|
-
if (!this.isEnabled) return;
|
|
168
|
-
const e = t.target;
|
|
169
|
-
if (e.closest("[data-supermouse-ignore]")) {
|
|
170
|
-
this.state.isNative = !0;
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const s = this.getHoverSelector(), n = e.closest(s);
|
|
174
|
-
n && (this.state.isHover = !0, this.state.hoverTarget = n, this.parseDOMInteraction(this.state.hoverTarget));
|
|
175
|
-
const i = this.options.ignoreOnNative;
|
|
176
|
-
if (i) {
|
|
177
|
-
const l = i === !0 || i === "auto" || i === "tag", r = i === !0 || i === "auto" || i === "css";
|
|
178
|
-
let h = !1;
|
|
179
|
-
if (l) {
|
|
180
|
-
const a = e.localName;
|
|
181
|
-
(a === "input" || a === "textarea" || a === "select" || e.isContentEditable) && (h = !0);
|
|
182
|
-
}
|
|
183
|
-
if (!h && r) {
|
|
184
|
-
const a = window.getComputedStyle(e).cursor;
|
|
185
|
-
["default", "auto", "pointer", "none", "inherit"].includes(a) || (h = !0);
|
|
186
|
-
}
|
|
187
|
-
h && (this.state.isNative = !0);
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
handleMouseOut = (t) => {
|
|
191
|
-
if (!this.isEnabled) return;
|
|
192
|
-
const e = t.target;
|
|
193
|
-
(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);
|
|
194
|
-
};
|
|
195
|
-
handleWindowLeave = () => {
|
|
196
|
-
this.options.hideOnLeave && (this.state.hasReceivedInput = !1);
|
|
197
|
-
};
|
|
198
|
-
bindEvents() {
|
|
199
|
-
window.addEventListener("pointermove", this.handleMove, { passive: !0 }), window.addEventListener("pointerdown", this.handleDown, { passive: !0 }), window.addEventListener("pointerup", this.handleUp), document.addEventListener("mouseover", this.handleMouseOver), document.addEventListener("mouseout", this.handleMouseOut), document.addEventListener("mouseleave", this.handleWindowLeave);
|
|
200
|
-
}
|
|
201
|
-
destroy() {
|
|
202
|
-
this.mediaQueryList && this.mediaQueryHandler && this.mediaQueryList.removeEventListener("change", this.mediaQueryHandler), window.removeEventListener("pointermove", this.handleMove), window.removeEventListener("pointerdown", this.handleDown), window.removeEventListener("pointerup", this.handleUp), document.removeEventListener("mouseover", this.handleMouseOver), document.removeEventListener("mouseout", this.handleMouseOut), document.removeEventListener("mouseleave", this.handleWindowLeave);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
function v(o, t, e) {
|
|
206
|
-
return o + (t - o) * e;
|
|
207
|
-
}
|
|
208
|
-
function u(o, t, e, s) {
|
|
209
|
-
return v(o, t, 1 - Math.exp(-e * s));
|
|
210
|
-
}
|
|
211
|
-
function f(o, t) {
|
|
212
|
-
return Math.atan2(t, o) * (180 / Math.PI);
|
|
213
|
-
}
|
|
214
|
-
const g = [
|
|
203
|
+
const u = { x: -100, y: -100 }, m = [
|
|
215
204
|
"a",
|
|
216
205
|
"button",
|
|
217
206
|
"input",
|
|
@@ -220,34 +209,15 @@ const g = [
|
|
|
220
209
|
"[data-cursor]"
|
|
221
210
|
];
|
|
222
211
|
class y {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
version = "2.0.5";
|
|
226
|
-
/**
|
|
227
|
-
* The Single Source of Truth.
|
|
228
|
-
*
|
|
229
|
-
* This object is shared by reference. `Input` writes to it; `Supermouse` physics reads/writes to it;
|
|
230
|
-
* Plugins read/write to it.
|
|
231
|
-
*/
|
|
212
|
+
static version = "2.2.0";
|
|
213
|
+
version = "2.2.0";
|
|
232
214
|
state;
|
|
233
215
|
/**
|
|
234
216
|
* Configuration options.
|
|
235
217
|
*/
|
|
236
218
|
options;
|
|
237
|
-
/**
|
|
238
|
-
* Registry of active plugins.
|
|
239
|
-
* @internal Use `use()`, `enablePlugin()`, or `disablePlugin()` to interact with this.
|
|
240
|
-
*/
|
|
241
219
|
plugins = [];
|
|
242
|
-
/**
|
|
243
|
-
* The Stage System responsible for the DOM container and CSS injection.
|
|
244
|
-
* @internal
|
|
245
|
-
*/
|
|
246
220
|
stage;
|
|
247
|
-
/**
|
|
248
|
-
* The Input System responsible for event listeners.
|
|
249
|
-
* @internal
|
|
250
|
-
*/
|
|
251
221
|
input;
|
|
252
222
|
rafId = 0;
|
|
253
223
|
lastTime = 0;
|
|
@@ -255,7 +225,7 @@ class y {
|
|
|
255
225
|
hoverSelectors;
|
|
256
226
|
/**
|
|
257
227
|
* Creates a new Supermouse instance.
|
|
258
|
-
*
|
|
228
|
+
*
|
|
259
229
|
* @param options - Global configuration options.
|
|
260
230
|
* @throws Will throw if running in a non-browser environment (window/document undefined).
|
|
261
231
|
*/
|
|
@@ -285,7 +255,7 @@ class y {
|
|
|
285
255
|
hasReceivedInput: !1,
|
|
286
256
|
shape: null,
|
|
287
257
|
interaction: {}
|
|
288
|
-
}, this.options.hoverSelectors ? this.hoverSelectors = new Set(this.options.hoverSelectors) : this.hoverSelectors = new Set(
|
|
258
|
+
}, this.options.hoverSelectors ? this.hoverSelectors = new Set(this.options.hoverSelectors) : this.hoverSelectors = new Set(m), this.stage = new v(this.options.container, !!this.options.hideCursor), this.hoverSelectors.forEach((e) => this.stage.addSelector(e)), this.input = new f(
|
|
289
259
|
this.state,
|
|
290
260
|
this.options,
|
|
291
261
|
() => Array.from(this.hoverSelectors).join(", "),
|
|
@@ -329,20 +299,10 @@ class y {
|
|
|
329
299
|
const e = this.getPlugin(t);
|
|
330
300
|
e && (e.isEnabled === !1 ? this.enablePlugin(t) : this.disablePlugin(t));
|
|
331
301
|
}
|
|
332
|
-
/**
|
|
333
|
-
* Registers a CSS selector as an "Interactive Target".
|
|
334
|
-
*
|
|
335
|
-
* When the mouse hovers over an element matching this selector:
|
|
336
|
-
* 1. `state.isHover` becomes `true`.
|
|
337
|
-
* 2. `state.hoverTarget` is set to the element.
|
|
338
|
-
* 3. The `Stage` system injects CSS to hide the native cursor for this element (if `hideCursor: true`).
|
|
339
|
-
*
|
|
340
|
-
* @param selector - A valid CSS selector string (e.g., `.my-button`, `[data-trigger]`).
|
|
341
|
-
*/
|
|
342
302
|
registerHoverTarget(t) {
|
|
343
303
|
this.hoverSelectors.has(t) || (this.hoverSelectors.add(t), this.stage.addSelector(t));
|
|
344
304
|
}
|
|
345
|
-
/**
|
|
305
|
+
/**
|
|
346
306
|
* The fixed container element where plugins should append their DOM nodes.
|
|
347
307
|
*/
|
|
348
308
|
get container() {
|
|
@@ -350,8 +310,7 @@ class y {
|
|
|
350
310
|
}
|
|
351
311
|
/**
|
|
352
312
|
* Manually override the native cursor visibility.
|
|
353
|
-
*
|
|
354
|
-
*
|
|
313
|
+
*
|
|
355
314
|
* @param type 'auto' (Show Native), 'none' (Hide Native), or null (Resume Auto-detection)
|
|
356
315
|
*/
|
|
357
316
|
setCursor(t) {
|
|
@@ -360,45 +319,32 @@ class y {
|
|
|
360
319
|
init() {
|
|
361
320
|
this.options.autoStart && this.startLoop();
|
|
362
321
|
}
|
|
363
|
-
/**
|
|
364
|
-
* Starts the update loop and enables input listeners.
|
|
365
|
-
* Hides the native cursor if configured.
|
|
366
|
-
*/
|
|
367
322
|
enable() {
|
|
368
323
|
this.input.isEnabled = !0, this.stage.setNativeCursor("none");
|
|
369
324
|
}
|
|
370
|
-
/**
|
|
371
|
-
* Stops the update loop, disables listeners, and restores the native cursor.
|
|
372
|
-
* Resets internal state positions to off-screen.
|
|
373
|
-
*/
|
|
374
325
|
disable() {
|
|
375
326
|
this.input.isEnabled = !1, this.stage.setNativeCursor("auto"), this.resetPosition();
|
|
376
327
|
}
|
|
377
328
|
/**
|
|
378
329
|
* Registers a new plugin.
|
|
379
|
-
*
|
|
380
|
-
* @remarks
|
|
381
|
-
* Plugins are sorted by `priority` immediately after registration.
|
|
382
|
-
* - **Negative Priority (< 0)**: Logic plugins (run before physics).
|
|
383
|
-
* - **Positive Priority (>= 0)**: Visual plugins (run after physics).
|
|
384
|
-
*
|
|
330
|
+
*
|
|
385
331
|
* @param plugin - The plugin object to install.
|
|
386
332
|
*/
|
|
387
333
|
use(t) {
|
|
388
|
-
return this.plugins.find((e) => e.name === t.name) ? (console.warn(`[Supermouse] Plugin "${t.name}" already installed.`), this) : (t.isEnabled === void 0 && (t.isEnabled = !0), this.plugins.push(t), this.plugins.sort((e,
|
|
334
|
+
return this.plugins.find((e) => e.name === t.name) ? (console.warn(`[Supermouse] Plugin "${t.name}" already installed.`), this) : (t.isEnabled === void 0 && (t.isEnabled = !0), this.plugins.push(t), this.plugins.sort((e, i) => (e.priority || 0) - (i.priority || 0)), t.install?.(this), this);
|
|
335
|
+
}
|
|
336
|
+
resetCoords() {
|
|
337
|
+
this.state.pointer = { ...u }, this.state.target = { ...u }, this.state.smooth = { ...u }, this.state.velocity = { x: 0, y: 0 }, this.state.angle = 0;
|
|
389
338
|
}
|
|
390
339
|
resetPosition() {
|
|
391
|
-
|
|
392
|
-
this.state.pointer = { ...t }, this.state.target = { ...t }, this.state.smooth = { ...t }, this.state.velocity = { x: 0, y: 0 }, this.state.angle = 0, this.state.hasReceivedInput = !1, this.state.shape = null, this.state.interaction = {};
|
|
340
|
+
this.resetCoords(), this.state.hasReceivedInput = !1, this.state.shape = null, this.state.interaction = {};
|
|
393
341
|
}
|
|
394
342
|
startLoop() {
|
|
395
343
|
this.isRunning || (this.isRunning = !0, this.lastTime = performance.now(), this.tick(this.lastTime));
|
|
396
344
|
}
|
|
397
345
|
/**
|
|
398
346
|
* Manually steps the animation loop.
|
|
399
|
-
*
|
|
400
|
-
* you want to disable the internal RAF and drive `Supermouse` from your own ticker.
|
|
401
|
-
*
|
|
347
|
+
*
|
|
402
348
|
* @param time Current timestamp in milliseconds.
|
|
403
349
|
*/
|
|
404
350
|
step(t) {
|
|
@@ -408,46 +354,47 @@ class y {
|
|
|
408
354
|
if (t.isEnabled !== !1)
|
|
409
355
|
try {
|
|
410
356
|
t.update?.(this, e);
|
|
411
|
-
} catch (
|
|
412
|
-
console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`,
|
|
357
|
+
} catch (i) {
|
|
358
|
+
console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`, i), t.isEnabled = !1;
|
|
359
|
+
try {
|
|
360
|
+
t.onDisable?.(this);
|
|
361
|
+
} catch (o) {
|
|
362
|
+
console.error(`[Supermouse] Failed to execute onDisable for plugin '${t.name}'.`, o);
|
|
363
|
+
}
|
|
413
364
|
}
|
|
414
365
|
}
|
|
415
366
|
/**
|
|
416
|
-
* The Heartbeat.
|
|
417
367
|
* Runs on every animation frame.
|
|
418
368
|
*/
|
|
419
369
|
tick = (t) => {
|
|
420
|
-
const e = t - this.lastTime,
|
|
421
|
-
this.lastTime = t;
|
|
422
|
-
const
|
|
423
|
-
if (this.stage.setVisibility(
|
|
424
|
-
let
|
|
425
|
-
this.state.forcedCursor !== null ?
|
|
370
|
+
const e = t - this.lastTime, i = Math.min(e / 1e3, 0.1);
|
|
371
|
+
this.lastTime = t, this.state.hoverTarget && !this.state.hoverTarget.isConnected && this.input.clearHover();
|
|
372
|
+
const o = this.input.isEnabled && !this.state.isNative && this.state.hasReceivedInput;
|
|
373
|
+
if (this.stage.setVisibility(o), this.input.isEnabled && this.options.hideCursor) {
|
|
374
|
+
let s = "auto";
|
|
375
|
+
this.state.forcedCursor !== null ? s = this.state.forcedCursor : s = this.state.isNative || !this.state.hasReceivedInput ? "auto" : "none", this.stage.setNativeCursor(s);
|
|
426
376
|
}
|
|
377
|
+
this.input.isEnabled ? (this.state.target.x = this.state.pointer.x, this.state.target.y = this.state.pointer.y) : this.resetCoords();
|
|
378
|
+
for (let s = 0; s < this.plugins.length; s++)
|
|
379
|
+
this.runPluginSafe(this.plugins[s], e);
|
|
427
380
|
if (this.input.isEnabled) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
this.state.smooth.x = u(this.state.smooth.x, this.state.target.x, l, s), this.state.smooth.y = u(this.state.smooth.y, this.state.target.y, l, s);
|
|
433
|
-
const r = this.state.target.x - this.state.smooth.x, h = this.state.target.y - this.state.smooth.y;
|
|
434
|
-
this.state.velocity.x = r, this.state.velocity.y = h, (Math.abs(r) > 0.1 || Math.abs(h) > 0.1) && (this.state.angle = f(r, h));
|
|
435
|
-
} else {
|
|
436
|
-
this.state.smooth.x = -100, this.state.smooth.y = -100, this.state.pointer.x = -100, this.state.pointer.y = -100, this.state.velocity.x = 0, this.state.velocity.y = 0;
|
|
437
|
-
for (let i = 0; i < this.plugins.length; i++)
|
|
438
|
-
this.runPluginSafe(this.plugins[i], e);
|
|
381
|
+
const s = this.state.reducedMotion ? 1e3 : 1 / this.options.smoothness * 2;
|
|
382
|
+
this.state.smooth.x = d(this.state.smooth.x, this.state.target.x, s, i), this.state.smooth.y = d(this.state.smooth.y, this.state.target.y, s, i);
|
|
383
|
+
const r = this.state.target.x - this.state.smooth.x, a = this.state.target.y - this.state.smooth.y;
|
|
384
|
+
this.state.velocity.x = r, this.state.velocity.y = a, (Math.abs(r) > 0.1 || Math.abs(a) > 0.1) && (this.state.angle = p(r, a));
|
|
439
385
|
}
|
|
440
386
|
this.options.autoStart && this.isRunning && (this.rafId = requestAnimationFrame(this.tick));
|
|
441
387
|
};
|
|
442
|
-
/**
|
|
388
|
+
/**
|
|
443
389
|
* Destroys the instance.
|
|
444
|
-
* Stops the loop, removes all DOM elements, removes all event listeners, and calls destroy on all plugins.
|
|
445
390
|
*/
|
|
446
391
|
destroy() {
|
|
447
392
|
this.isRunning = !1, cancelAnimationFrame(this.rafId), this.input.destroy(), this.stage.destroy(), this.plugins.forEach((t) => t.destroy?.(this)), this.plugins = [];
|
|
448
393
|
}
|
|
449
394
|
}
|
|
450
395
|
export {
|
|
451
|
-
|
|
396
|
+
m as DEFAULT_HOVER_SELECTORS,
|
|
397
|
+
f as Input,
|
|
398
|
+
v as Stage,
|
|
452
399
|
y as Supermouse
|
|
453
400
|
};
|
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(n,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(n=typeof globalThis<"u"?globalThis:n||self,l(n.SupermouseCore={}))})(this,(function(n){"use strict";function l(a,t,e){return a+(t-a)*e}function p(a,t,e,i){return l(a,t,1-Math.exp(-e*i))}function m(a,t){return Math.atan2(t,a)*(180/Math.PI)}class f{constructor(t,e,i,o){this.state=t,this.options=e,this.getHoverSelector=i,this.onEnableChange=o,this.checkDeviceCapability(),this.checkMotionPreference(),this.bindEvents()}state;options;getHoverSelector;onEnableChange;mediaQueryList;mediaQueryHandler;motionQuery;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[o,s]of Object.entries(this.options.rules))t.matches(o)&&Object.assign(e,s);const i=t.dataset;for(const o in i)if(o.startsWith("supermouse")){const s=o.slice(10);if(s){const h=s.charAt(0).toLowerCase()+s.slice(1),r=i[o];r!==void 0&&(e[h]=r===""?!0:r)}}this.state.interaction=e}handleMove(t){if(!this.isEnabled||this.options.autoDisableOnMobile&&t.pointerType==="touch")return;let e=t.clientX,i=t.clientY;if(this.options.container&&this.options.container!==document.body){const o=this.options.container.getBoundingClientRect();e-=o.left,i-=o.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("[data-supermouse-ignore]")){this.state.isNative=!0;return}const i=this.getHoverSelector(),o=e.closest(i);o&&(this.state.isHover=!0,this.state.hoverTarget=o,this.parseDOMInteraction(this.state.hoverTarget));const s=this.options.ignoreOnNative;if(s){const h=s===!0||s==="auto"||s==="tag",r=s===!0||s==="auto"||s==="css";let d=!1;if(h){const u=e.localName;(u==="input"||u==="textarea"||u==="select"||e.isContentEditable)&&(d=!0)}if(!d&&r){const u=window.getComputedStyle(e).cursor;["default","auto","pointer","none","inherit"].includes(u)||(d=!0)}d&&(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)}clearHover(){this.state.isHover=!1,this.state.hoverTarget=null,this.state.isNative=!1}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 o=t===document.body;this.element=document.createElement("div"),Object.assign(this.element.style,{position:o?"fixed":"absolute",top:"0",left:"0",width:"100%",height:"100%",pointerEvents:"none",zIndex:"9999",opacity:"1",transition:"opacity 0.15s ease"}),o||window.getComputedStyle(t).position==="static"&&(t.style.position="relative"),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;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="",this.container.classList.remove(this.scopeClass)}}
|
|
5
|
+
`}destroy(){this.element.remove(),this.styleTag.remove(),this.container.style.cursor="",this.container.classList.remove(this.scopeClass)}}const c={x:-100,y:-100},v=["a","button","input","textarea","[data-hover]","[data-cursor]"];class y{static version="2.2.0";version="2.2.0";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,...t},this.options.container||(this.options.container=document.body),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.resetPosition()}),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}setCursor(t){this.state.forcedCursor=t}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.resetPosition()}use(t){return this.plugins.find(e=>e.name===t.name)?(console.warn(`[Supermouse] Plugin "${t.name}" already installed.`),this):(t.isEnabled===void 0&&(t.isEnabled=!0),this.plugins.push(t),this.plugins.sort((e,i)=>(e.priority||0)-(i.priority||0)),t.install?.(this),this)}resetCoords(){this.state.pointer={...c},this.state.target={...c},this.state.smooth={...c},this.state.velocity={x:0,y:0},this.state.angle=0}resetPosition(){this.resetCoords(),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))}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),t.isEnabled=!1;try{t.onDisable?.(this)}catch(o){console.error(`[Supermouse] Failed to execute onDisable for plugin '${t.name}'.`,o)}}}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 o=this.input.isEnabled&&!this.state.isNative&&this.state.hasReceivedInput;if(this.stage.setVisibility(o),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.target.x=this.state.pointer.x,this.state.target.y=this.state.pointer.y):this.resetCoords();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);const h=this.state.target.x-this.state.smooth.x,r=this.state.target.y-this.state.smooth.y;this.state.velocity.x=h,this.state.velocity.y=r,(Math.abs(h)>.1||Math.abs(r)>.1)&&(this.state.angle=m(h,r))}this.options.autoStart&&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=[]}}n.DEFAULT_HOVER_SELECTORS=v,n.Input=f,n.Stage=g,n.Supermouse=y,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
|