@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/dist/index.mjs CHANGED
@@ -1,13 +1,143 @@
1
- let d = 0;
2
- class c {
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 s = d++;
7
- this.id = `supermouse-style-${s}`, this.scopeClass = `supermouse-scope-${s}`;
8
- const n = t === document.body;
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: n ? "fixed" : "absolute",
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
- }), n || 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");
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
- /** The container element appended to the document. Plugins must append here. */
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 "Hide Native Cursor" list.
41
- * Called by `Supermouse` (and subsequently plugins) during install to ensure
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((s) => `.${this.scopeClass} ${s}`).join(", ");
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
- class p {
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
- /** The current version of Supermouse.js */
224
- static version = "2.0.5";
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(g), this.stage = new c(this.options.container, !!this.options.hideCursor), this.hoverSelectors.forEach((e) => this.stage.addSelector(e)), this.input = new p(
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
- * Useful for drag-and-drop operations, modals, or special UI states.
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, s) => (e.priority || 0) - (s.priority || 0)), t.install?.(this), this);
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
- const t = { x: -100, y: -100 };
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
- * Useful when integrating with external game loops (e.g., Three.js, PixiJS) where
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 (s) {
412
- console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`, s), t.isEnabled = !1, t.onDisable?.(this);
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, s = Math.min(e / 1e3, 0.1);
421
- this.lastTime = t;
422
- const n = this.input.isEnabled && !this.state.isNative && this.state.hasReceivedInput;
423
- if (this.stage.setVisibility(n), this.input.isEnabled && this.options.hideCursor) {
424
- let i = "auto";
425
- this.state.forcedCursor !== null ? i = this.state.forcedCursor : i = this.state.isNative || !this.state.hasReceivedInput ? "auto" : "none", this.stage.setNativeCursor(i);
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
- this.state.target.x = this.state.pointer.x, this.state.target.y = this.state.pointer.y;
429
- for (let a = 0; a < this.plugins.length; a++)
430
- this.runPluginSafe(this.plugins[a], e);
431
- const i = this.options.smoothness, l = this.state.reducedMotion ? 1e3 : 1 / i * 2;
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
- g as DEFAULT_HOVER_SELECTORS,
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(u,d){typeof exports=="object"&&typeof module<"u"?d(exports):typeof define=="function"&&define.amd?define(["exports"],d):(u=typeof globalThis<"u"?globalThis:u||self,d(u.SupermouseCore={}))})(this,(function(u){"use strict";let d=0;class f{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 s=d++;this.id=`supermouse-style-${s}`,this.scopeClass=`supermouse-scope-${s}`;const n=t===document.body;this.element=document.createElement("div"),Object.assign(this.element.style,{position:n?"fixed":"absolute",top:"0",left:"0",width:"100%",height:"100%",pointerEvents:"none",zIndex:"9999",opacity:"1",transition:"opacity 0.15s ease"}),n||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")}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(s=>`.${this.scopeClass} ${s}`).join(", ");this.styleTag.innerText=`
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)}}class v{constructor(t,e,s,n){this.state=t,this.options=e,this.getHoverSelector=s,this.onEnableChange=n,this.checkDeviceCapability(),this.checkMotionPreference(),this.bindEvents()}mediaQueryList;mediaQueryHandler;motionQuery;isEnabled=!0;interactionCache=new WeakMap;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))}checkMotionPreference(){this.motionQuery=window.matchMedia("(prefer-reduced-motion: reduce)"),this.state.reducedMotion=this.motionQuery.matches,this.motionQuery.addEventListener("change",t=>{this.state.reducedMotion=t.matches})}updateEnabledState(t){this.isEnabled=t,this.onEnableChange(t)}parseDOMInteraction(t){if(this.options.resolveInteraction){this.state.interaction=this.options.resolveInteraction(t);return}if(this.interactionCache.has(t)){this.state.interaction=this.interactionCache.get(t);return}const e={};if(this.options.rules)for(const[n,i]of Object.entries(this.options.rules))t.matches(n)&&Object.assign(e,i);const s=t.dataset;for(const n in s)if(n.startsWith("supermouse")){const i=n.slice(10);if(i){const l=i.charAt(0).toLowerCase()+i.slice(1),r=s[n];e[l]=r===""?!0:r}}this.interactionCache.set(t,e),this.state.interaction=e}handleMove=t=>{if(!this.isEnabled||this.options.autoDisableOnMobile&&t.pointerType==="touch")return;let e=t.clientX,s=t.clientY;if(this.options.container&&this.options.container!==document.body){const n=this.options.container.getBoundingClientRect();e-=n.left,s-=n.top}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)};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 s=this.getHoverSelector(),n=e.closest(s);n&&(this.state.isHover=!0,this.state.hoverTarget=n,this.parseDOMInteraction(this.state.hoverTarget));const i=this.options.ignoreOnNative;if(i){const l=i===!0||i==="auto"||i==="tag",r=i===!0||i==="auto"||i==="css";let h=!1;if(l){const a=e.localName;(a==="input"||a==="textarea"||a==="select"||e.isContentEditable)&&(h=!0)}if(!h&&r){const a=window.getComputedStyle(e).cursor;["default","auto","pointer","none","inherit"].includes(a)||(h=!0)}h&&(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)};bindEvents(){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)}destroy(){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)}}function m(o,t,e){return o+(t-o)*e}function c(o,t,e,s){return m(o,t,1-Math.exp(-e*s))}function g(o,t){return Math.atan2(t,o)*(180/Math.PI)}const p=["a","button","input","textarea","[data-hover]","[data-cursor]"];class y{static version="2.0.5";version="2.0.5";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(p),this.stage=new f(this.options.container,!!this.options.hideCursor),this.hoverSelectors.forEach(e=>this.stage.addSelector(e)),this.input=new v(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,s)=>(e.priority||0)-(s.priority||0)),t.install?.(this),this)}resetPosition(){const t={x:-100,y:-100};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={}}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(s){console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`,s),t.isEnabled=!1,t.onDisable?.(this)}}tick=t=>{const e=t-this.lastTime,s=Math.min(e/1e3,.1);this.lastTime=t;const n=this.input.isEnabled&&!this.state.isNative&&this.state.hasReceivedInput;if(this.stage.setVisibility(n),this.input.isEnabled&&this.options.hideCursor){let i="auto";this.state.forcedCursor!==null?i=this.state.forcedCursor:i=this.state.isNative||!this.state.hasReceivedInput?"auto":"none",this.stage.setNativeCursor(i)}if(this.input.isEnabled){this.state.target.x=this.state.pointer.x,this.state.target.y=this.state.pointer.y;for(let a=0;a<this.plugins.length;a++)this.runPluginSafe(this.plugins[a],e);const i=this.options.smoothness,l=this.state.reducedMotion?1e3:1/i*2;this.state.smooth.x=c(this.state.smooth.x,this.state.target.x,l,s),this.state.smooth.y=c(this.state.smooth.y,this.state.target.y,l,s);const r=this.state.target.x-this.state.smooth.x,h=this.state.target.y-this.state.smooth.y;this.state.velocity.x=r,this.state.velocity.y=h,(Math.abs(r)>.1||Math.abs(h)>.1)&&(this.state.angle=g(r,h))}else{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;for(let i=0;i<this.plugins.length;i++)this.runPluginSafe(this.plugins[i],e)}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=[]}}u.DEFAULT_HOVER_SELECTORS=p,u.Supermouse=y,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
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"})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supermousejs/core",
3
- "version": "2.0.5",
3
+ "version": "2.2.0",
4
4
  "description": "The core physics engine and state manager for Supermouse.",
5
5
  "main": "dist/index.umd.js",
6
6
  "module": "dist/index.mjs",