@supermousejs/core 2.4.2 → 2.4.3

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @supermousejs/core
2
2
 
3
+ ## 2.4.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 16a387f: Updated README with npm-sensible quick start section
8
+
3
9
  ## 2.4.2
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -5,330 +5,154 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
6
6
  ![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40supermousejs%2Fcore?label=minzip)
7
7
 
8
- <div align="center">
9
- <img src="https://raw.githubusercontent.com/Whitestar14/supermouse-js/main/docs/public/favicon.svg" width="180" height="180">
10
- </div>
8
+ Supermouse is a headless, physics‑based cursor engine for the web written in TypeScript. It provides pointer tracking, state, physics, and a plugin lifecycle. You install plugins (or write your own) to render custom cursors and effects. The core is zero‑dependency and TypeScript‑first.
11
9
 
12
- Supermouse.js is a physics-based custom cursor engine written in TypeScript for the web that allows you to install composable cursors and cursor effects, or write them yourself, in your code. It is highly performant, written in TypeScript from scratch, has zero dependencies, and uses a plugin system so the core remains lean.
13
-
14
- [Documentation](https://supermouse.js.org) | [Examples](https://supermouse.js.org/docs/guide/cookbook) | [License](#license)
10
+ **Documentation:** [supermouse.js.org](https://supermouse.js.org)
15
11
 
16
12
  ## Installation
17
13
 
18
14
  ```bash
19
15
  pnpm add @supermousejs/core
20
-
21
16
  npm install @supermousejs/core
22
17
  ```
23
18
 
24
- Install only the plugins you want:
19
+ Install plugins you need:
25
20
 
26
21
  ```bash
27
22
  pnpm add @supermousejs/dot @supermousejs/ring
28
-
29
23
  npm install @supermousejs/dot @supermousejs/ring
30
24
  ```
31
25
 
32
- ## Usage
33
-
34
- Supermouse itself does not render a cursor, as it is not a component system. Nor does it render a default stub.
35
-
36
- The core is essentially a headless system that provides pointer tracking, state, physics, and a plugin lifecycle. Plugins automatically use the internal Supermouse engine to render to the DOM, but you can write a renderer yourself like in the example below:
26
+ ## Quick Start
37
27
 
38
28
  ```ts
39
29
  import { Supermouse } from "@supermousejs/core";
30
+ import { Dot } from "@supermousejs/dot";
40
31
 
41
32
  const mouse = new Supermouse({
42
- smoothness: 0.15
43
- });
44
-
45
- const dot = document.createElement("div");
46
-
47
- Object.assign(dot.style, {
48
- width: "8px",
49
- height: "8px",
50
- borderRadius: "50%",
51
- background: "red",
52
- position: "absolute",
53
- transform: "translate(-50%, -50%)",
54
- pointerEvents: "none"
33
+ plugins: [Dot({ size: 8 })]
55
34
  });
56
-
57
- mouse.container.appendChild(dot);
58
-
59
- function render() {
60
- const { smooth } = mouse.state;
61
-
62
- dot.style.transform = `translate3d(${smooth.x}px, ${smooth.y}px, 0) translate(-50%, -50%)`;
63
-
64
- requestAnimationFrame(render);
65
- }
66
-
67
- render();
68
35
  ```
69
36
 
70
- It is best to avoid writing this way though, as defining another `requestAnimationFrame` besides Supermouse's internal one will leave you with two rAF loops. Use Supermouse's plugin interface instead:
37
+ Or use the plugin interface directly:
71
38
 
72
39
  ```ts
73
40
  import { Supermouse } from "@supermousejs/core";
74
41
 
75
42
  const dot = document.createElement("div");
76
-
77
- Object.assign(dot.style, {
78
- width: "8px",
79
- height: "8px",
80
- borderRadius: "50%",
81
- background: "red",
82
- position: "absolute",
83
- transform: "translate(-50%, -50%)",
84
- pointerEvents: "none"
85
- });
43
+ // ... style dot ...
86
44
 
87
45
  const redDot = {
88
46
  name: "red-dot",
89
- install(mouse) {
90
- mouse.container.appendChild(dot);
47
+ install(app) {
48
+ app.stage.appendChild(dot);
91
49
  },
92
- update(mouse) {
93
- const { smooth } = mouse.state;
94
-
95
- dot.style.transform = `translate3d(${smooth.x}px, ${smooth.y}px, 0) translate(-50%, -50%)`;
50
+ update(app) {
51
+ const { smooth } = app.state;
52
+ dot.style.transform = `translate3d(${smooth.x}px, ${smooth.y}px, 0)`;
96
53
  },
97
54
  destroy() {
98
55
  dot.remove();
99
56
  }
100
57
  };
101
58
 
102
- const mouse = new Supermouse({
103
- plugins: [redDot]
104
- });
105
- ```
106
-
107
- Now your dot has all the capabilities of Supermouse (the native cursor automatically hidden, the input normalized, hover detection and accessibility baked in, and the red dot part of the plugin lifecycle). However, if your goal is a simple red dot on your webpage, Supermouse might be overkill, even though the core is designed to be lightweight. For simple effects, plain HTML/CSS and JavaScript may suffice.
108
-
109
- Supermouse's advantages become apparent when you need to handle increasingly complex effects with granularity. For example, adding a `Ring` to the cursor can be done declaratively:
110
-
111
- ```ts
112
- import { Dot } from "@supermousejs/dot";
113
- import { Ring } from "@supermousejs/ring";
114
-
115
- const app = new Supermouse({
116
- plugins: [Dot({ size: 8 }), Ring({ size: 24 })]
117
- });
59
+ const mouse = new Supermouse({ plugins: [redDot] });
118
60
  ```
119
61
 
120
- You can also chain plugin instantiation imperatively with `.use`:
121
-
122
- ```ts
123
- app.use(Dot({ size: 8 }))
124
-
125
- if (someEffect) {
126
- app.use(Effect1({...})).use(Effect2({...}))
127
- }
128
- ```
129
-
130
- Read more about plugins and how to write them [here](https://github.com/Whitestar14/supermouse-js/blob/main/PLUGINS.md).
131
-
132
62
  ## Options
133
63
 
134
- | Option | Default | Description |
135
- | --------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
136
- | `smoothness` | `0.15` | Lower values for a smoother follow |
137
- | `hoverSelectors` | `["a", "button", "input", "textarea", "[data-hover]", "[data-cursor]"]` | Selectors that set `state.isHover` to `true`. |
138
- | `enableTouch` | `false` | Whether touch events move the cursor. |
139
- | `autoDisableOnMobile` | `true` | Disables the custom cursor on devices with a coarse pointer unless `enableTouch` is `true`. |
140
- | `cursor` | `"auto"` | Cursor mode: `'auto'`, `'custom'`, `'native'`, or `'both'`. Replaces the old `hideCursor` and `ignoreOnNative`. |
141
- | `hideOnLeave` | `true` | Hides the cursor when the pointer leaves the browser window. |
142
- | `container` | `document.body` | The area where the instance is active. |
143
- | `zIndex` | `9999` | The stack order of the cursor stage. Increase this if overlays cover the cursor. |
144
- | `dataPrefix` | `"supermouse"` | A prefix for `data-*` attributes. Prevents conflicts between instances. |
145
- | `rules` | — | A map of selectors to interaction data. The core adds this data to `state.interaction` on hover. |
146
- | `plugins` | — | Plugins to install when you create the instance. |
147
- | `autoStart` | `true` | Set to `false` to prevent automatic start. Call `.start()` when ready. |
148
-
149
- ### Cursor Modes
150
-
151
- Prior to v2.4, earlier versions used two separate options, `hideCursor` and `ignoreOnNative`, to control when the native cursor should be hidden or shown. In v2.4, these have been combined into a single **`cursor`** option that decides both behaviors at once.
152
-
153
- ```ts
154
- const app = new Supermouse({
155
- cursor: "auto"
156
- });
157
- ```
158
-
159
- There are four modes:
160
-
161
- - **`"auto"`** (default) – Supermouse looks at whatever is under the pointer and makes a reasonable choice. Over buttons, links, inputs, and other native controls, the native OS cursor appears; everywhere else, your custom cursor takes over. This is the best starting point and for most projects, it's what you're going to use without extra configuration.
162
-
163
- - **`"custom"`** – The custom cursor is always shown, and the native cursor is always hidden. This is the mode you'd use when you want complete visual control and are confident your custom cursor handles all interactions gracefully.
164
-
165
- - **`"native"`** – The native cursor is always visible, and the custom cursor stays hidden. Useful when you temporarily want to give full control back to the OS.
166
-
167
- - **`"both"`** – Both the native cursor and the custom cursor are visible at the same time. This is particularly useful for effects that surround the native pointer such as a ring that highlights the native arrow, or a trail that follows the real cursor.
168
-
169
- You can switch modes at any time with:
170
-
171
- ```ts
172
- mouse.setCursor("both");
173
- ```
64
+ | Option | Default | Description |
65
+ | --------------------- | ----------------------------------------------------------------------- | --------------------------------------------- |
66
+ | `smoothness` | `0.15` | Lower = smoother cursor follow |
67
+ | `hoverSelectors` | `["a", "button", "input", "textarea", "[data-hover]", "[data-cursor]"]` | Selectors that set `state.isHover` |
68
+ | `enableTouch` | `false` | Allow touch events to move cursor |
69
+ | `autoDisableOnMobile` | `true` | Disable on coarse‑pointer devices |
70
+ | `cursor` | `"auto"` | `"auto"`, `"custom"`, `"native"`, or `"both"` |
71
+ | `hideOnLeave` | `true` | Hide when pointer leaves window |
72
+ | `container` | `document.body` | Scoping container |
73
+ | `zIndex` | `9999` | Stage stacking order |
74
+ | `dataPrefix` | `"supermouse"` | Prefix for data attributes |
75
+ | `rules` | — | Map of selectors to interaction data |
76
+ | `plugins` | — | Plugins to install at creation |
77
+ | `autoStart` | `true` | Set `false` and call `.start()` manually |
174
78
 
175
- If you’re coming from an older version and relied on those options, the following table might help you translate:
79
+ ## Cursor Modes
176
80
 
177
- | Old behaviour | New `cursor` value |
178
- | -------------------------------------------- | ---------------------------------------------------------- |
179
- | `hideCursor: true`, `ignoreOnNative: "auto"` | `"auto"` |
180
- | `hideCursor: true`, `ignoreOnNative: null` | `"custom"` |
181
- | `hideCursor: false` | `"both"` or `"native"` depending on the rest of your setup |
81
+ - `"auto"` – native cursor on interactive elements, custom elsewhere.
82
+ - `"custom"` – always custom, hide native.
83
+ - `"native"` – always native, hide custom.
84
+ - `"both"` – show both native and custom together.
182
85
 
183
- The rest of the documentation uses `cursor` consistently. If you have questions about a specific migration case, the [options documentation](https://supermouse.js.org/advanced/api) covers the details.
86
+ Change at runtime with `mouse.setCursor("both")`.
184
87
 
185
- ### Containers
88
+ ## Containers & Multiple Instances
186
89
 
187
- By default, Supermouse appends the cursor stage to `document.body` and uses global event listeners. You can scope an instance to a specific container:
90
+ Scope an instance to a container:
188
91
 
189
92
  ```ts
190
- const modal = document.getElementById("my-modal");
191
-
192
- const app = new Supermouse({
193
- container: modal,
194
- cursor: "custom"
195
- });
196
- ```
197
-
198
- This allows multiple independent instances on the same page. CSS and cursor‑hiding rules are scoped to avoid leakage. For example:
199
-
200
- ```ts
201
- // Main page cursor
202
- const app1 = new Supermouse({
203
- container: document.body,
204
- cursor: "custom"
205
- });
206
- app1.use(Dot({ color: "red" }));
207
-
208
- // Modal cursor
209
- const app2 = new Supermouse({
210
- container: document.getElementById("modal"),
211
- cursor: "both"
212
- });
213
- app2.use(Dot({ color: "blue" }));
93
+ const modal = document.getElementById("modal");
94
+ const mouse = new Supermouse({ container: modal, cursor: "custom" });
214
95
  ```
215
96
 
216
- ### Forcing Native Cursor on an Element
217
-
218
- When `cursor` is `"auto"`, Supermouse automatically detects when to show the native OS cursor. To force native cursor on an element and its descendants, add the attribute `data-supermouse-ignore`:
97
+ Multiple instances can coexist; CSS rules are scoped automatically.
219
98
 
220
- ```html
221
- <div data-supermouse-ignore>This area will show the native cursor</div>
222
- ```
99
+ ## Native Cursor Fallback
223
100
 
224
- This only works when `cursor` is `"auto"`.
225
-
226
- > **Note:** Custom cursors often break usability on native controls (inputs, text selection, drag handles). The `"auto"` cursor mode briefly yields control back to the OS cursor for these interactions, ensuring accessibility.
227
-
228
- ### Data Attributes
229
-
230
- Some plugins can be reactive to HTML data attributes. For example, `Dot` watches for `data-supermouse-color`:
101
+ In `"auto"` mode, add `data-supermouse-ignore` to force the native cursor on an element and its descendants.
231
102
 
232
103
  ```html
233
- <div data-supermouse-color="#fff000">...</div>
234
- ```
235
-
236
- The `dataPrefix` option can customize this:
237
-
238
- ```ts
239
- const app = new Supermouse({ dataPrefix: "sm" });
104
+ <div data-supermouse-ignore>Native cursor here</div>
240
105
  ```
241
106
 
242
- ```html
243
- <div data-sm-stick>Sticky Zone</div>
244
- ```
107
+ ## Data Attributes
245
108
 
246
- `data-supermouse-ignore` becomes `data-sm-ignore`.
109
+ Plugins can react to `data-*` attributes, e.g. `data-supermouse-color`. The prefix is configurable via `dataPrefix`.
247
110
 
248
- ### Rules
111
+ ## Rules
249
112
 
250
- Use `rules` to define interaction data without writing many data attributes:
113
+ Define interaction data without writing many attributes:
251
114
 
252
115
  ```ts
253
- const app = new Supermouse({
116
+ const mouse = new Supermouse({
254
117
  rules: {
255
- ".primary-action": {
256
- magnetic: true,
257
- distance: 200,
258
- color: "red",
259
- text: "Click!",
260
- scale: 1.5
261
- }
118
+ ".primary-action": { magnetic: true, color: "red" }
262
119
  }
263
120
  });
264
121
  ```
265
122
 
266
- When both rules and data attributes apply to the same element, the HTML `data-supermouse-*` attribute wins for that property.
123
+ HTML `data-*` attributes override rule values per property.
267
124
 
268
125
  ## API
269
126
 
270
127
  ```ts
271
- const mouse = new Supermouse(options?);
272
-
273
- // Mouse state read inside plugins
274
- mouse.state
275
- // All options with defaults applied
276
- mouse.options
277
- // The DOM element the plugins render into
278
- mouse.stage
279
-
280
- // Whether the instance is currently processing input
281
- mouse.isEnabled
282
-
283
- // Start processing input and re-apply cursor state
284
- mouse.enable()
285
- // Stop processing input, restore native cursor, reset state
286
- mouse.disable()
287
- // Pause input and hide the stage without destroying the instance
288
- mouse.suspend()
289
- // Resume input and show the stage
290
- mouse.resume()
291
-
292
- // Set cursor mode: "auto" | "custom" | "native" | "both"
293
- mouse.setCursor(mode)
294
-
295
- // Install a plugin
296
- mouse.use(plugin)
297
- // Return a plugin by name
298
- mouse.getPlugin(name)
299
- // Enable a disabled plugin
300
- mouse.enablePlugin(name)
301
- // Disable a plugin
302
- mouse.disablePlugin(name)
303
- // Toggle a plugin's enabled state
304
- mouse.togglePlugin(name)
305
-
306
- // Add a selector at runtime. Whenever the cursor hovers over this selector, Supermouse detects it and hides the native cursor for it
307
- mouse.registerHoverTarget(selector)
308
-
309
- // Start the animation loop if stopped
310
- mouse.start()
311
- // Advance one frame manually
312
- mouse.step(time)
313
-
314
- // Destroy Supermouse and plugin instances
315
- mouse.destroy()
316
- ```
128
+ mouse.state; // current MouseState
129
+ mouse.options; // resolved options
130
+ mouse.stage; // DOM element plugins render into
131
+ mouse.isEnabled; // boolean
317
132
 
318
- ## Browser support
133
+ mouse.enable(); // start input, hide native cursor
134
+ mouse.disable(); // stop input, show native cursor, reset
135
+ mouse.suspend(); // pause non‑destructively
136
+ mouse.resume(); // resume from suspend
319
137
 
320
- Supermouse.js supports all modern browsers.
138
+ mouse.setCursor(mode); // "auto" | "custom" | "native" | "both"
321
139
 
322
- ## Contributing
140
+ mouse.use(plugin); // install plugin
141
+ mouse.getPlugin(name); // retrieve plugin
142
+ mouse.enablePlugin(name); // enable plugin
143
+ mouse.disablePlugin(name); // disable plugin
144
+ mouse.togglePlugin(name); // toggle plugin
323
145
 
324
- Any bug fixes, performance / docs improvements are welcome. Before adding new effects or features to core, please read [how to contribute](https://github.com/Whitestar14/supermouse-js/blob/main/CONTRIBUTING.md).
146
+ mouse.registerHoverTarget(sel); // add hover selector at runtime
147
+ mouse.start(); // start animation loop
148
+ mouse.step(time); // advance one frame manually
149
+ mouse.destroy(); // destroy instance and plugins
150
+ ```
325
151
 
326
- ## Credits
152
+ ## Browser Support
327
153
 
328
- This project was heavily inspired by [Pointer.js](https://github.com/mageowl/pointer.js) and the cursor effects gallery, [Curzr](https://github.com/fuzionix/curzr).
154
+ All modern browsers.
329
155
 
330
156
  ## License
331
157
 
332
158
  MIT
333
-
334
- Built and maintained by [Whitestar14](https://github.com/Whitestar14)
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- const c = "2.4.2";
1
+ const c = "2.4.3";
2
2
  function p(o, t, e) {
3
3
  return o + (t - o) * e;
4
4
  }
package/dist/index.umd.js CHANGED
@@ -1,4 +1,4 @@
1
- (function(r,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(r=typeof globalThis<"u"?globalThis:r||self,l(r.SupermouseCore={}))})(this,(function(r){"use strict";const l="2.4.2";function v(a,t,e){return a+(t-a)*e}function c(a,t,e,s){return v(a,t,1-Math.exp(-e*s))}const u={x:-100,y:-100},b=new Set(["input","textarea","select"]),y=new Set(["default","auto","pointer","none","inherit","grab","grabbing"]),d=["a","button","input","textarea","[data-hover]","[data-cursor]"];class p{constructor(t,e,s,i){this.state=t,this.options=e,this.getHoverSelector=s,this.onEnableChange=i,this.dataPrefix=this.options.dataPrefix??"supermouse",this.normalizedDataPrefix=this.dataPrefix.toLowerCase(),this.ignoreAttribute=`data-${this.dataPrefix}-ignore`,this.ruleEntries=this.options.rules?Object.entries(this.options.rules):[],this.checkDeviceCapability(),this.checkMotionPreference(),this.setupContainerRectTracking(),this.bindEvents()}state;options;getHoverSelector;onEnableChange;mediaQueryList;motionQuery;dataPrefix;normalizedDataPrefix;ignoreAttribute;abortController=new AbortController;nativeTarget=null;hasSeenPointer=!1;isEnabled=!0;containerRect=null;resizeObserver;matchedRules=[];lastParsedTarget=null;ruleEntries;currentTarget=null;checkDeviceCapability(){this.options.autoDisableOnMobile&&(this.mediaQueryList=window.matchMedia("(pointer: fine)"),this.updateEnabledState(this.mediaQueryList.matches),this.mediaQueryList.addEventListener("change",t=>this.updateEnabledState(t.matches),{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)}setupContainerRectTracking(){const t=this.options.container;if(!t||t===document.body)return;const e=()=>{this.containerRect=t.getBoundingClientRect()};e(),window.addEventListener("resize",e,{signal:this.abortController.signal}),window.addEventListener("scroll",e,{passive:!0,signal:this.abortController.signal}),typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(e),this.resizeObserver.observe(t))}parseDOMInteraction(t){if(t!==this.lastParsedTarget){this.lastParsedTarget=t,this.matchedRules=[];for(const[i,n]of this.ruleEntries)this.matchesSelector(t,i)&&this.matchedRules.push({selector:i,rules:n})}const e={};for(const{rules:i}of this.matchedRules)try{const n=typeof i=="function"?i(t):i;if(!n||typeof n!="object")continue;for(const[o,h]of Object.entries(n))e[o]=typeof h=="function"?h(t):h}catch(n){console.error("[Supermouse] Rule threw during evaluation:",n)}const s=this.normalizedDataPrefix;for(const i in t.dataset){if(!i.toLowerCase().startsWith(s))continue;const n=i.slice(s.length);if(!n)continue;const o=t.dataset[i];e[n[0].toLowerCase()+n.slice(1)]=o===""?!0:o}this.state.interaction=e}matchesSelector(t,e){try{if(!/\s/.test(e.trim()))return t.matches(e)}catch{}const s=e.trim().split(/\s+/);if(s.length<2)return!1;const i=s.pop(),n=s.join(" ");if(!i||!n)return!1;try{return t.matches(i)&&!!t.closest(n)}catch{return!1}}isOutsideContainer(t){const{container:e}=this.options;return!!e&&e!==document.body&&!e.contains(t)}resolveComputedCursor(t){return window.getComputedStyle(t).cursor}handleMove=t=>{if(this.options.autoDisableOnMobile&&t.pointerType==="touch"&&!this.options.enableTouch)return;let e=t.clientX,s=t.clientY;const i=this.options.container;i&&this.containerRect&&i!==document.body&&(e-=this.containerRect.left,s-=this.containerRect.top),this.state.pointer.x=e,this.state.pointer.y=s,this.hasSeenPointer=!0,this.isEnabled&&(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(this.isOutsideContainer(e))return;if(this.state.cursorMode==="auto"&&e.closest(`[${this.ignoreAttribute}]`)){this.state.isHover=!1,this.state.hoverTarget=null,this.state.interaction={},this.currentTarget=null,this.lastParsedTarget=null,this.matchedRules=[],this.state.isNative=!0,this.nativeTarget=e;return}if(this.state.isNative=!1,this.nativeTarget=null,this.currentTarget=e,this.parseDOMInteraction(e),this.state.cursorMode!=="auto"){const o=e.closest(this.getHoverSelector());o&&(this.state.isHover=!0,this.state.hoverTarget=o);return}const s=e.closest(this.getHoverSelector());s&&(this.state.isHover=!0,this.state.hoverTarget=s);const i=b.has(e.localName)||e.isContentEditable,n=!y.has(this.resolveComputedCursor(e));(i||n)&&(this.state.isNative=!0,this.nativeTarget=e)};handleMouseOut=t=>{if(!this.isEnabled)return;const e=t.target,s=t.relatedTarget;this.isOutsideContainer(e)||((e===this.state.hoverTarget||e.contains(this.state.hoverTarget))&&(!s||!this.state.hoverTarget?.contains(s))&&(this.state.isHover=!1,this.state.hoverTarget=null),this.nativeTarget&&(e===this.nativeTarget||e.contains(this.nativeTarget))&&(!s||!this.nativeTarget.contains(s))&&(this.state.isNative=!1,this.nativeTarget=null),e===this.currentTarget&&(this.currentTarget=null,this.lastParsedTarget=null,this.matchedRules=[]))};handleDocumentMouseOut=t=>{this.isEnabled&&t.relatedTarget===null&&this.options.hideOnLeave&&this.handleWindowLeave()};handleWindowLeave=()=>{this.options.hideOnLeave&&(this.state.hasReceivedInput=!1,this.state.pointer={...u})};clearHover(){this.state.isHover=!1,this.state.hoverTarget=null,this.state.isNative=!1,this.nativeTarget=null,this.state.interaction={},this.currentTarget=null,this.lastParsedTarget=null,this.matchedRules=[]}getCurrentTarget(){return this.currentTarget}bindEvents(){const{signal:t}=this.abortController;window.addEventListener("pointermove",this.handleMove,{passive:!0,signal:t}),window.addEventListener("pointerdown",this.handleDown,{passive:!0,signal:t}),window.addEventListener("pointerup",this.handleUp,{signal:t});const s=!this.options.container||this.options.container===document.body?document:this.options.container;s.addEventListener("mouseover",this.handleMouseOver,{signal:t}),s.addEventListener("mouseout",this.handleMouseOut,{signal:t}),document.addEventListener("mouseout",this.handleDocumentMouseOut,{signal:t})}destroy(){this.abortController.abort(),this.resizeObserver?.disconnect()}}let C=0;class g{constructor(t=document.body,e=9999){if(this.container=t,this.zIndex=e,!t||!(t instanceof HTMLElement))throw new Error(`[Supermouse] Invalid container: ${t}. Must be an HTMLElement.`);t.isConnected||console.warn("[Supermouse] container is not attached to the document — stage sizing/positioning will be wrong until it is.");const s=C++;this.id=`supermouse-style-${s}`,this.scopeClass=`supermouse-scope-${s}`,this.hideClass=`supermouse-hide-${s}`;const i=t===document.body;if(this.element=document.createElement("div"),Object.assign(this.element.style,{position:i?"fixed":"absolute",inset:"0px",pointerEvents:"none",zIndex:String(this.zIndex),opacity:"1",transition:"opacity 0.15s ease"}),!i){const n=window.getComputedStyle(t);this.originalContainerPosition=n.position,n.position==="static"&&(t.style.position="relative")}this.originalContainerCursor=t.style.cursor,t.appendChild(this.element),this.styleTag=document.createElement("style"),this.styleTag.id=this.id,document.head.appendChild(this.styleTag),this.container.classList.add("supermouse-scope",this.scopeClass),this.updateCursorCSS()}container;zIndex;element;styleTag;id;scopeClass;hideClass;currentCursorState=null;originalContainerPosition="";originalContainerCursor="";selectors=new Set(["a","button","input","textarea","select",'[role="button"]',"[tabindex]"]);addSelectors(t){let e=!1;for(const s of t)s.split(",").forEach(i=>{const n=i.trim();n&&!this.selectors.has(n)&&(this.selectors.add(n),e=!0)});e&&this.updateCursorCSS()}addSelector(t){this.addSelectors([t])}setVisibility(t){this.element.style.opacity=t?"1":"0"}setNativeCursor(t){t!==this.currentCursorState&&(this.currentCursorState=t,this.container.classList.toggle(this.hideClass,t==="none"),this.container.style.cursor=t==="none"?"none":this.originalContainerCursor)}updateCursorCSS(){const t=Array.from(this.selectors);if(t.length===0){this.styleTag.innerText="";return}const e=`:not(.${this.scopeClass} .supermouse-scope):not(.${this.scopeClass} .supermouse-scope *)`,s=h=>`.${this.scopeClass}.${this.hideClass} ${h}${e} { cursor: none !important; }`,i=t.map(s).join(`
1
+ (function(r,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(r=typeof globalThis<"u"?globalThis:r||self,l(r.SupermouseCore={}))})(this,(function(r){"use strict";const l="2.4.3";function v(a,t,e){return a+(t-a)*e}function c(a,t,e,s){return v(a,t,1-Math.exp(-e*s))}const u={x:-100,y:-100},b=new Set(["input","textarea","select"]),y=new Set(["default","auto","pointer","none","inherit","grab","grabbing"]),d=["a","button","input","textarea","[data-hover]","[data-cursor]"];class p{constructor(t,e,s,i){this.state=t,this.options=e,this.getHoverSelector=s,this.onEnableChange=i,this.dataPrefix=this.options.dataPrefix??"supermouse",this.normalizedDataPrefix=this.dataPrefix.toLowerCase(),this.ignoreAttribute=`data-${this.dataPrefix}-ignore`,this.ruleEntries=this.options.rules?Object.entries(this.options.rules):[],this.checkDeviceCapability(),this.checkMotionPreference(),this.setupContainerRectTracking(),this.bindEvents()}state;options;getHoverSelector;onEnableChange;mediaQueryList;motionQuery;dataPrefix;normalizedDataPrefix;ignoreAttribute;abortController=new AbortController;nativeTarget=null;hasSeenPointer=!1;isEnabled=!0;containerRect=null;resizeObserver;matchedRules=[];lastParsedTarget=null;ruleEntries;currentTarget=null;checkDeviceCapability(){this.options.autoDisableOnMobile&&(this.mediaQueryList=window.matchMedia("(pointer: fine)"),this.updateEnabledState(this.mediaQueryList.matches),this.mediaQueryList.addEventListener("change",t=>this.updateEnabledState(t.matches),{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)}setupContainerRectTracking(){const t=this.options.container;if(!t||t===document.body)return;const e=()=>{this.containerRect=t.getBoundingClientRect()};e(),window.addEventListener("resize",e,{signal:this.abortController.signal}),window.addEventListener("scroll",e,{passive:!0,signal:this.abortController.signal}),typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(e),this.resizeObserver.observe(t))}parseDOMInteraction(t){if(t!==this.lastParsedTarget){this.lastParsedTarget=t,this.matchedRules=[];for(const[i,n]of this.ruleEntries)this.matchesSelector(t,i)&&this.matchedRules.push({selector:i,rules:n})}const e={};for(const{rules:i}of this.matchedRules)try{const n=typeof i=="function"?i(t):i;if(!n||typeof n!="object")continue;for(const[o,h]of Object.entries(n))e[o]=typeof h=="function"?h(t):h}catch(n){console.error("[Supermouse] Rule threw during evaluation:",n)}const s=this.normalizedDataPrefix;for(const i in t.dataset){if(!i.toLowerCase().startsWith(s))continue;const n=i.slice(s.length);if(!n)continue;const o=t.dataset[i];e[n[0].toLowerCase()+n.slice(1)]=o===""?!0:o}this.state.interaction=e}matchesSelector(t,e){try{if(!/\s/.test(e.trim()))return t.matches(e)}catch{}const s=e.trim().split(/\s+/);if(s.length<2)return!1;const i=s.pop(),n=s.join(" ");if(!i||!n)return!1;try{return t.matches(i)&&!!t.closest(n)}catch{return!1}}isOutsideContainer(t){const{container:e}=this.options;return!!e&&e!==document.body&&!e.contains(t)}resolveComputedCursor(t){return window.getComputedStyle(t).cursor}handleMove=t=>{if(this.options.autoDisableOnMobile&&t.pointerType==="touch"&&!this.options.enableTouch)return;let e=t.clientX,s=t.clientY;const i=this.options.container;i&&this.containerRect&&i!==document.body&&(e-=this.containerRect.left,s-=this.containerRect.top),this.state.pointer.x=e,this.state.pointer.y=s,this.hasSeenPointer=!0,this.isEnabled&&(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(this.isOutsideContainer(e))return;if(this.state.cursorMode==="auto"&&e.closest(`[${this.ignoreAttribute}]`)){this.state.isHover=!1,this.state.hoverTarget=null,this.state.interaction={},this.currentTarget=null,this.lastParsedTarget=null,this.matchedRules=[],this.state.isNative=!0,this.nativeTarget=e;return}if(this.state.isNative=!1,this.nativeTarget=null,this.currentTarget=e,this.parseDOMInteraction(e),this.state.cursorMode!=="auto"){const o=e.closest(this.getHoverSelector());o&&(this.state.isHover=!0,this.state.hoverTarget=o);return}const s=e.closest(this.getHoverSelector());s&&(this.state.isHover=!0,this.state.hoverTarget=s);const i=b.has(e.localName)||e.isContentEditable,n=!y.has(this.resolveComputedCursor(e));(i||n)&&(this.state.isNative=!0,this.nativeTarget=e)};handleMouseOut=t=>{if(!this.isEnabled)return;const e=t.target,s=t.relatedTarget;this.isOutsideContainer(e)||((e===this.state.hoverTarget||e.contains(this.state.hoverTarget))&&(!s||!this.state.hoverTarget?.contains(s))&&(this.state.isHover=!1,this.state.hoverTarget=null),this.nativeTarget&&(e===this.nativeTarget||e.contains(this.nativeTarget))&&(!s||!this.nativeTarget.contains(s))&&(this.state.isNative=!1,this.nativeTarget=null),e===this.currentTarget&&(this.currentTarget=null,this.lastParsedTarget=null,this.matchedRules=[]))};handleDocumentMouseOut=t=>{this.isEnabled&&t.relatedTarget===null&&this.options.hideOnLeave&&this.handleWindowLeave()};handleWindowLeave=()=>{this.options.hideOnLeave&&(this.state.hasReceivedInput=!1,this.state.pointer={...u})};clearHover(){this.state.isHover=!1,this.state.hoverTarget=null,this.state.isNative=!1,this.nativeTarget=null,this.state.interaction={},this.currentTarget=null,this.lastParsedTarget=null,this.matchedRules=[]}getCurrentTarget(){return this.currentTarget}bindEvents(){const{signal:t}=this.abortController;window.addEventListener("pointermove",this.handleMove,{passive:!0,signal:t}),window.addEventListener("pointerdown",this.handleDown,{passive:!0,signal:t}),window.addEventListener("pointerup",this.handleUp,{signal:t});const s=!this.options.container||this.options.container===document.body?document:this.options.container;s.addEventListener("mouseover",this.handleMouseOver,{signal:t}),s.addEventListener("mouseout",this.handleMouseOut,{signal:t}),document.addEventListener("mouseout",this.handleDocumentMouseOut,{signal:t})}destroy(){this.abortController.abort(),this.resizeObserver?.disconnect()}}let C=0;class g{constructor(t=document.body,e=9999){if(this.container=t,this.zIndex=e,!t||!(t instanceof HTMLElement))throw new Error(`[Supermouse] Invalid container: ${t}. Must be an HTMLElement.`);t.isConnected||console.warn("[Supermouse] container is not attached to the document — stage sizing/positioning will be wrong until it is.");const s=C++;this.id=`supermouse-style-${s}`,this.scopeClass=`supermouse-scope-${s}`,this.hideClass=`supermouse-hide-${s}`;const i=t===document.body;if(this.element=document.createElement("div"),Object.assign(this.element.style,{position:i?"fixed":"absolute",inset:"0px",pointerEvents:"none",zIndex:String(this.zIndex),opacity:"1",transition:"opacity 0.15s ease"}),!i){const n=window.getComputedStyle(t);this.originalContainerPosition=n.position,n.position==="static"&&(t.style.position="relative")}this.originalContainerCursor=t.style.cursor,t.appendChild(this.element),this.styleTag=document.createElement("style"),this.styleTag.id=this.id,document.head.appendChild(this.styleTag),this.container.classList.add("supermouse-scope",this.scopeClass),this.updateCursorCSS()}container;zIndex;element;styleTag;id;scopeClass;hideClass;currentCursorState=null;originalContainerPosition="";originalContainerCursor="";selectors=new Set(["a","button","input","textarea","select",'[role="button"]',"[tabindex]"]);addSelectors(t){let e=!1;for(const s of t)s.split(",").forEach(i=>{const n=i.trim();n&&!this.selectors.has(n)&&(this.selectors.add(n),e=!0)});e&&this.updateCursorCSS()}addSelector(t){this.addSelectors([t])}setVisibility(t){this.element.style.opacity=t?"1":"0"}setNativeCursor(t){t!==this.currentCursorState&&(this.currentCursorState=t,this.container.classList.toggle(this.hideClass,t==="none"),this.container.style.cursor=t==="none"?"none":this.originalContainerCursor)}updateCursorCSS(){const t=Array.from(this.selectors);if(t.length===0){this.styleTag.innerText="";return}const e=`:not(.${this.scopeClass} .supermouse-scope):not(.${this.scopeClass} .supermouse-scope *)`,s=h=>`.${this.scopeClass}.${this.hideClass} ${h}${e} { cursor: none !important; }`,i=t.map(s).join(`
2
2
  `),n=`.${this.scopeClass}.${this.hideClass} *${e} { cursor: none !important; }`,o=`.${this.scopeClass}.${this.hideClass} { cursor: none !important; }`;this.styleTag.innerText=`
3
3
  ${o}
4
4
  ${n}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supermousejs/core",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "files": [
5
5
  "dist",
6
6
  "README.md",