@supermousejs/core 2.0.5 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -3,8 +3,8 @@ class c {
3
3
  constructor(t = document.body, e) {
4
4
  if (this.container = t, this.hideNativeCursor = e, !t || !(t instanceof HTMLElement))
5
5
  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}`;
6
+ const i = d++;
7
+ this.id = `supermouse-style-${i}`, this.scopeClass = `supermouse-scope-${i}`;
8
8
  const n = t === document.body;
9
9
  this.element = document.createElement("div"), Object.assign(this.element.style, {
10
10
  position: n ? "fixed" : "absolute",
@@ -18,15 +18,14 @@ class c {
18
18
  transition: "opacity 0.15s ease"
19
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");
20
20
  }
21
- /** The container element appended to the document. Plugins must append here. */
21
+ container;
22
+ hideNativeCursor;
23
+ /** The container element appended to the document. */
22
24
  element;
23
25
  styleTag;
24
26
  id;
25
27
  scopeClass;
26
- // Cache to prevent redundant DOM updates
27
28
  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
29
  selectors = /* @__PURE__ */ new Set([
31
30
  "a",
32
31
  "button",
@@ -37,16 +36,13 @@ class c {
37
36
  "[tabindex]"
38
37
  ]);
39
38
  /**
40
- * Adds a new CSS selector to the "Hide Native Cursor" list.
39
+ * Adds a new CSS selector to the Hide Native Cursor list.
41
40
  * Called by `Supermouse` (and subsequently plugins) during install to ensure
42
41
  * the native cursor is hidden on their specific interactive targets.
43
42
  */
44
43
  addSelector(t) {
45
44
  this.selectors.add(t), this.hideNativeCursor && this.updateCursorCSS();
46
45
  }
47
- /**
48
- * Controls the opacity of the entire stage (all custom cursor elements).
49
- */
50
46
  setVisibility(t) {
51
47
  this.element.style.opacity = t ? "1" : "0";
52
48
  }
@@ -63,7 +59,7 @@ class c {
63
59
  this.styleTag.innerText = "";
64
60
  return;
65
61
  }
66
- const e = t.map((s) => `.${this.scopeClass} ${s}`).join(", ");
62
+ const e = t.map((i) => `.${this.scopeClass} ${i}`).join(", ");
67
63
  this.styleTag.innerText = `
68
64
  ${e} {
69
65
  cursor: none !important;
@@ -75,24 +71,23 @@ class c {
75
71
  }
76
72
  }
77
73
  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();
74
+ constructor(t, e, i, n) {
75
+ this.state = t, this.options = e, this.getHoverSelector = i, this.onEnableChange = n, this.checkDeviceCapability(), this.checkMotionPreference(), this.bindEvents();
80
76
  }
77
+ state;
78
+ options;
79
+ getHoverSelector;
80
+ onEnableChange;
81
81
  mediaQueryList;
82
82
  mediaQueryHandler;
83
83
  motionQuery;
84
84
  /**
85
- * Master switch for input processing.
85
+ * Master switch for input processing.
86
86
  * Toggled by `Supermouse.enable()`/`disable()` or automatically by device capability checks.
87
87
  */
88
88
  isEnabled = !0;
89
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).
90
+ * Automatically disables the custom cursor on devices without fine pointer control.
96
91
  * Relies on `matchMedia('(pointer: fine)')`.
97
92
  */
98
93
  checkDeviceCapability() {
@@ -105,108 +100,95 @@ class p {
105
100
  * If true, the core physics engine will switch to instant snapping (high damping) to avoid motion sickness.
106
101
  */
107
102
  checkMotionPreference() {
108
- this.motionQuery = window.matchMedia("(prefer-reduced-motion: reduce)"), this.state.reducedMotion = this.motionQuery.matches, this.motionQuery.addEventListener("change", (t) => {
103
+ this.motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)"), this.state.reducedMotion = this.motionQuery.matches, this.motionQuery.addEventListener("change", (t) => {
109
104
  this.state.reducedMotion = t.matches;
110
105
  });
111
106
  }
112
107
  updateEnabledState(t) {
113
108
  this.isEnabled = t, this.onEnableChange(t);
114
109
  }
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
110
  parseDOMInteraction(t) {
126
111
  if (this.options.resolveInteraction) {
127
112
  this.state.interaction = this.options.resolveInteraction(t);
128
113
  return;
129
114
  }
130
- if (this.interactionCache.has(t)) {
131
- this.state.interaction = this.interactionCache.get(t);
132
- return;
133
- }
134
115
  const e = {};
135
116
  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)
117
+ for (const [n, s] of Object.entries(this.options.rules))
118
+ t.matches(n) && Object.assign(e, s);
119
+ const i = t.dataset;
120
+ for (const n in i)
140
121
  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;
122
+ const s = n.slice(10);
123
+ if (s) {
124
+ const r = s.charAt(0).toLowerCase() + s.slice(1), a = i[n];
125
+ a !== void 0 && (e[r] = a === "" ? !0 : a);
145
126
  }
146
127
  }
147
- this.interactionCache.set(t, e), this.state.interaction = e;
128
+ this.state.interaction = e;
148
129
  }
149
- // --- Handlers ---
150
- // Unified Pointer Event Handler
151
- handleMove = (t) => {
130
+ handleMove(t) {
152
131
  if (!this.isEnabled || this.options.autoDisableOnMobile && t.pointerType === "touch") return;
153
- let e = t.clientX, s = t.clientY;
132
+ let e = t.clientX, i = t.clientY;
154
133
  if (this.options.container && this.options.container !== document.body) {
155
134
  const n = this.options.container.getBoundingClientRect();
156
- e -= n.left, s -= n.top;
135
+ e -= n.left, i -= n.top;
157
136
  }
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 = () => {
137
+ 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);
138
+ }
139
+ handleDown() {
161
140
  this.isEnabled && (this.state.isDown = !0);
162
- };
163
- handleUp = () => {
141
+ }
142
+ handleUp() {
164
143
  this.isEnabled && (this.state.isDown = !1);
165
- };
166
- handleMouseOver = (t) => {
144
+ }
145
+ handleMouseOver(t) {
167
146
  if (!this.isEnabled) return;
168
147
  const e = t.target;
169
148
  if (e.closest("[data-supermouse-ignore]")) {
170
149
  this.state.isNative = !0;
171
150
  return;
172
151
  }
173
- const s = this.getHoverSelector(), n = e.closest(s);
152
+ const i = this.getHoverSelector(), n = e.closest(i);
174
153
  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";
154
+ const s = this.options.ignoreOnNative;
155
+ if (s) {
156
+ const r = s === !0 || s === "auto" || s === "tag", a = s === !0 || s === "auto" || s === "css";
178
157
  let h = !1;
179
- if (l) {
180
- const a = e.localName;
181
- (a === "input" || a === "textarea" || a === "select" || e.isContentEditable) && (h = !0);
158
+ if (r) {
159
+ const l = e.localName;
160
+ (l === "input" || l === "textarea" || l === "select" || e.isContentEditable) && (h = !0);
182
161
  }
183
- if (!h && r) {
184
- const a = window.getComputedStyle(e).cursor;
185
- ["default", "auto", "pointer", "none", "inherit"].includes(a) || (h = !0);
162
+ if (!h && a) {
163
+ const l = window.getComputedStyle(e).cursor;
164
+ ["default", "auto", "pointer", "none", "inherit"].includes(l) || (h = !0);
186
165
  }
187
166
  h && (this.state.isNative = !0);
188
167
  }
189
- };
190
- handleMouseOut = (t) => {
168
+ }
169
+ handleMouseOut(t) {
191
170
  if (!this.isEnabled) return;
192
171
  const e = t.target;
193
172
  (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 = () => {
173
+ }
174
+ handleWindowLeave() {
196
175
  this.options.hideOnLeave && (this.state.hasReceivedInput = !1);
197
- };
176
+ }
177
+ clearHover() {
178
+ this.state.isHover = !1, this.state.hoverTarget = null, this.state.isNative = !1;
179
+ }
198
180
  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);
181
+ window.addEventListener("pointermove", this.handleMove.bind(this), { passive: !0 }), window.addEventListener("pointerdown", this.handleDown.bind(this), { passive: !0 }), window.addEventListener("pointerup", this.handleUp.bind(this)), document.addEventListener("mouseover", this.handleMouseOver.bind(this)), document.addEventListener("mouseout", this.handleMouseOut.bind(this)), document.addEventListener("mouseleave", this.handleWindowLeave.bind(this));
200
182
  }
201
183
  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);
184
+ this.mediaQueryList && this.mediaQueryHandler && this.mediaQueryList.removeEventListener("change", this.mediaQueryHandler), this.motionQuery && (this.motionQuery.onchange = null), 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
185
  }
204
186
  }
205
187
  function v(o, t, e) {
206
188
  return o + (t - o) * e;
207
189
  }
208
- function u(o, t, e, s) {
209
- return v(o, t, 1 - Math.exp(-e * s));
190
+ function u(o, t, e, i) {
191
+ return v(o, t, 1 - Math.exp(-e * i));
210
192
  }
211
193
  function f(o, t) {
212
194
  return Math.atan2(t, o) * (180 / Math.PI);
@@ -220,34 +202,15 @@ const g = [
220
202
  "[data-cursor]"
221
203
  ];
222
204
  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
- */
205
+ static version = "2.1.0";
206
+ version = "2.1.0";
232
207
  state;
233
208
  /**
234
209
  * Configuration options.
235
210
  */
236
211
  options;
237
- /**
238
- * Registry of active plugins.
239
- * @internal Use `use()`, `enablePlugin()`, or `disablePlugin()` to interact with this.
240
- */
241
212
  plugins = [];
242
- /**
243
- * The Stage System responsible for the DOM container and CSS injection.
244
- * @internal
245
- */
246
213
  stage;
247
- /**
248
- * The Input System responsible for event listeners.
249
- * @internal
250
- */
251
214
  input;
252
215
  rafId = 0;
253
216
  lastTime = 0;
@@ -255,7 +218,7 @@ class y {
255
218
  hoverSelectors;
256
219
  /**
257
220
  * Creates a new Supermouse instance.
258
- *
221
+ *
259
222
  * @param options - Global configuration options.
260
223
  * @throws Will throw if running in a non-browser environment (window/document undefined).
261
224
  */
@@ -329,20 +292,10 @@ class y {
329
292
  const e = this.getPlugin(t);
330
293
  e && (e.isEnabled === !1 ? this.enablePlugin(t) : this.disablePlugin(t));
331
294
  }
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
295
  registerHoverTarget(t) {
343
296
  this.hoverSelectors.has(t) || (this.hoverSelectors.add(t), this.stage.addSelector(t));
344
297
  }
345
- /**
298
+ /**
346
299
  * The fixed container element where plugins should append their DOM nodes.
347
300
  */
348
301
  get container() {
@@ -350,8 +303,7 @@ class y {
350
303
  }
351
304
  /**
352
305
  * Manually override the native cursor visibility.
353
- * Useful for drag-and-drop operations, modals, or special UI states.
354
- *
306
+ *
355
307
  * @param type 'auto' (Show Native), 'none' (Hide Native), or null (Resume Auto-detection)
356
308
  */
357
309
  setCursor(t) {
@@ -360,32 +312,19 @@ class y {
360
312
  init() {
361
313
  this.options.autoStart && this.startLoop();
362
314
  }
363
- /**
364
- * Starts the update loop and enables input listeners.
365
- * Hides the native cursor if configured.
366
- */
367
315
  enable() {
368
316
  this.input.isEnabled = !0, this.stage.setNativeCursor("none");
369
317
  }
370
- /**
371
- * Stops the update loop, disables listeners, and restores the native cursor.
372
- * Resets internal state positions to off-screen.
373
- */
374
318
  disable() {
375
319
  this.input.isEnabled = !1, this.stage.setNativeCursor("auto"), this.resetPosition();
376
320
  }
377
321
  /**
378
322
  * 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
- *
323
+ *
385
324
  * @param plugin - The plugin object to install.
386
325
  */
387
326
  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);
327
+ 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);
389
328
  }
390
329
  resetPosition() {
391
330
  const t = { x: -100, y: -100 };
@@ -396,9 +335,7 @@ class y {
396
335
  }
397
336
  /**
398
337
  * 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
- *
338
+ *
402
339
  * @param time Current timestamp in milliseconds.
403
340
  */
404
341
  step(t) {
@@ -408,40 +345,42 @@ class y {
408
345
  if (t.isEnabled !== !1)
409
346
  try {
410
347
  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);
348
+ } catch (i) {
349
+ console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`, i), t.isEnabled = !1;
350
+ try {
351
+ t.onDisable?.(this);
352
+ } catch {
353
+ }
413
354
  }
414
355
  }
415
356
  /**
416
- * The Heartbeat.
417
357
  * Runs on every animation frame.
418
358
  */
419
359
  tick = (t) => {
420
- const e = t - this.lastTime, s = Math.min(e / 1e3, 0.1);
421
- this.lastTime = t;
360
+ const e = t - this.lastTime, i = Math.min(e / 1e3, 0.1);
361
+ this.lastTime = t, this.state.hoverTarget && !this.state.hoverTarget.isConnected && this.input.clearHover();
422
362
  const n = this.input.isEnabled && !this.state.isNative && this.state.hasReceivedInput;
423
363
  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);
364
+ let s = "auto";
365
+ this.state.forcedCursor !== null ? s = this.state.forcedCursor : s = this.state.isNative || !this.state.hasReceivedInput ? "auto" : "none", this.stage.setNativeCursor(s);
426
366
  }
427
367
  if (this.input.isEnabled) {
428
368
  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));
369
+ for (let h = 0; h < this.plugins.length; h++)
370
+ this.runPluginSafe(this.plugins[h], e);
371
+ const s = this.state.reducedMotion ? 1e3 : 1 / this.options.smoothness * 2;
372
+ this.state.smooth.x = u(this.state.smooth.x, this.state.target.x, s, i), this.state.smooth.y = u(this.state.smooth.y, this.state.target.y, s, i);
373
+ const r = this.state.target.x - this.state.smooth.x, a = this.state.target.y - this.state.smooth.y;
374
+ this.state.velocity.x = r, this.state.velocity.y = a, (Math.abs(r) > 0.1 || Math.abs(a) > 0.1) && (this.state.angle = f(r, a));
435
375
  } else {
436
376
  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);
377
+ for (let s = 0; s < this.plugins.length; s++)
378
+ this.runPluginSafe(this.plugins[s], e);
439
379
  }
440
380
  this.options.autoStart && this.isRunning && (this.rafId = requestAnimationFrame(this.tick));
441
381
  };
442
- /**
382
+ /**
443
383
  * Destroys the instance.
444
- * Stops the loop, removes all DOM elements, removes all event listeners, and calls destroy on all plugins.
445
384
  */
446
385
  destroy() {
447
386
  this.isRunning = !1, cancelAnimationFrame(this.rafId), this.input.destroy(), this.stage.destroy(), this.plugins.forEach((t) => t.destroy?.(this)), this.plugins = [];
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(l,u){typeof exports=="object"&&typeof module<"u"?u(exports):typeof define=="function"&&define.amd?define(["exports"],u):(l=typeof globalThis<"u"?globalThis:l||self,u(l.SupermouseCore={}))})(this,(function(l){"use strict";let u=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 i=u++;this.id=`supermouse-style-${i}`,this.scopeClass=`supermouse-scope-${i}`;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")}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)}}class v{constructor(t,e,i,n){this.state=t,this.options=e,this.getHoverSelector=i,this.onEnableChange=n,this.checkDeviceCapability(),this.checkMotionPreference(),this.bindEvents()}state;options;getHoverSelector;onEnableChange;mediaQueryList;mediaQueryHandler;motionQuery;isEnabled=!0;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("(prefers-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}const e={};if(this.options.rules)for(const[n,s]of Object.entries(this.options.rules))t.matches(n)&&Object.assign(e,s);const i=t.dataset;for(const n in i)if(n.startsWith("supermouse")){const s=n.slice(10);if(s){const r=s.charAt(0).toLowerCase()+s.slice(1),a=i[n];a!==void 0&&(e[r]=a===""?!0:a)}}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 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("[data-supermouse-ignore]")){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){const r=s===!0||s==="auto"||s==="tag",a=s===!0||s==="auto"||s==="css";let h=!1;if(r){const d=e.localName;(d==="input"||d==="textarea"||d==="select"||e.isContentEditable)&&(h=!0)}if(!h&&a){const d=window.getComputedStyle(e).cursor;["default","auto","pointer","none","inherit"].includes(d)||(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)}clearHover(){this.state.isHover=!1,this.state.hoverTarget=null,this.state.isNative=!1}bindEvents(){window.addEventListener("pointermove",this.handleMove.bind(this),{passive:!0}),window.addEventListener("pointerdown",this.handleDown.bind(this),{passive:!0}),window.addEventListener("pointerup",this.handleUp.bind(this)),document.addEventListener("mouseover",this.handleMouseOver.bind(this)),document.addEventListener("mouseout",this.handleMouseOut.bind(this)),document.addEventListener("mouseleave",this.handleWindowLeave.bind(this))}destroy(){this.mediaQueryList&&this.mediaQueryHandler&&this.mediaQueryList.removeEventListener("change",this.mediaQueryHandler),this.motionQuery&&(this.motionQuery.onchange=null),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,i){return m(o,t,1-Math.exp(-e*i))}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.1.0";version="2.1.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(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,i)=>(e.priority||0)-(i.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(i){console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`,i),t.isEnabled=!1;try{t.onDisable?.(this)}catch{}}}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)}if(this.input.isEnabled){this.state.target.x=this.state.pointer.x,this.state.target.y=this.state.pointer.y;for(let h=0;h<this.plugins.length;h++)this.runPluginSafe(this.plugins[h],e);const s=this.state.reducedMotion?1e3:1/this.options.smoothness*2;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);const r=this.state.target.x-this.state.smooth.x,a=this.state.target.y-this.state.smooth.y;this.state.velocity.x=r,this.state.velocity.y=a,(Math.abs(r)>.1||Math.abs(a)>.1)&&(this.state.angle=g(r,a))}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 s=0;s<this.plugins.length;s++)this.runPluginSafe(this.plugins[s],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=[]}}l.DEFAULT_HOVER_SELECTORS=p,l.Supermouse=y,Object.defineProperty(l,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.1.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",