@supermousejs/core 2.5.0-beta.4 → 2.5.0-beta.5

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,32 @@
1
1
  # @supermousejs/core
2
2
 
3
+ ## 2.5.0-beta.5
4
+
5
+ ### Patch Changes
6
+
7
+ - d82c62d: ---
8
+
9
+ Beta.5 — cursor policy flattening, suppression simplification, and API renames.
10
+
11
+ ## Changed — breaking
12
+ - `cursorPolicy` option renamed to `nativeCursorSelectors` and flattened to a plain `string[]`. Migration: `cursorPolicy: { native: [".x"] }` → `nativeCursorSelectors: [".x"]`. The `hide` key is gone; cursor suppression is unconditional and no longer configurable.
13
+ - `CursorPolicy` and `CursorPolicyInput` types removed from the public export surface. Code importing them fails at compile time. The only remaining shape is `string[]`.
14
+ - `DEFAULT_CURSOR_POLICY` renamed to `DEFAULT_NATIVE_CURSOR_SELECTORS`. Value reduced to `["select", "[contenteditable]"]`.
15
+ - `registerHoverTarget(selector)` renamed to `addHoverSelectors(selectors)`. Same behavior — comma-separated selectors are added to the current scope's shared hover set. The new name matches `hoverSelectors` and reflects that it mutates a set rather than registering a single element.
16
+ - `@supermousejs/utils`: `VisualConfig.selector` renamed to `hoverSelector`. Update `definePlugin({ ..., selector: "x" })` to `definePlugin({ ..., hoverSelector: "x" })`.
17
+
18
+ ## Changed — behavior
19
+ - `input` and `textarea` removed from the default native cursor list. Text inputs and textareas are unaffected — their UA cursor is `text`, which the probe reads and treats as a fallback trigger. Button-like inputs (`<input type="button">`, `<input type="submit">`) and checkboxes/radios change: their UA cursor is `default`, which the probe classifies as non-fallback, so the custom cursor now shows over them. This matches the pre-existing treatment of `<button>`.
20
+ - Cursor suppression is now a single wildcard rule per scope instead of a wildcard plus per-selector rules. Behavior is unchanged — verified in Chromium and Firefox against `<a>`, `<button>`, `<input>` (all types), `<textarea>`, `<select>`, `[contenteditable]`, `[role="button"]`, `[tabindex]`, `<label>`, and a `<canvas>` with site CSS. Range slider thumb pseudo-elements retain their explicit rules (`*` does not match pseudo-elements).
21
+
22
+ ## Fixed
23
+ - `handleMouseOut` no longer leaves `state.isHover` and `state.isNative` stale when the pointer leaves from a descendant of the tracked element. Moving from `<span>` inside `<button>` directly outside the button previously failed to clear `hoverTarget` — the containment check was inverted.
24
+
25
+ ## Internal
26
+ - `policy.ts` removed from `@supermousejs/core`. The default moved to `constants.ts`; `normalizePolicy` dissolved into `??` fallbacks at the call sites.
27
+ - `Scope.hideSelectors` field removed.
28
+ - Size budget re-tightened to 6.2 kB gzip / 5.5 kB brotli.
29
+
3
30
  ## 2.5.0-beta.4
4
31
 
5
32
  ### Patch Changes
package/README.md CHANGED
@@ -63,23 +63,23 @@ const mouse = new Supermouse({ plugins: [redDot] });
63
63
 
64
64
  ## Options
65
65
 
66
- | Option | Default | Description |
67
- | ----------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------- |
68
- | `smoothness` | `0.15` | Lower = smoother cursor follow |
69
- | `hoverSelectors` | `["a", "button", "input", "textarea", "[data-hover]", "[data-cursor]"]` | Selectors that set `state.isHover` |
70
- | `enableTouch` | `false` | Allow touch events to move the cursor |
71
- | `autoDisableOnMobile` | `true` | Disable on coarse-pointer devices |
72
- | `cursor` | `"auto"` | `"auto"`, `"custom"`, `"native"`, or `"both"` |
73
- | `cursorPolicy` | `DEFAULT_CURSOR_POLICY` | Rules for native-cursor fallback and CSS suppression |
74
- | `inheritDataAttributes` | `true` | Cascade `data-*` attributes and `rules` from ancestors |
75
- | `hideOnLeave` | `true` | Hide when the pointer leaves the window |
76
- | `container` | `document.body` | Primary scope's container |
77
- | `scopes` | — | Additional scopes registered at construction |
78
- | `zIndex` | `9999` | Stage stacking order |
79
- | `dataPrefix` | `"supermouse"` | Prefix for data attributes |
80
- | `rules` | — | Map of selectors to interaction data |
81
- | `plugins` | — | Plugins to install at creation |
82
- | `autoStart` | `true` | Set `false` and call `.start()` manually |
66
+ | Option | Default | Description |
67
+ | ----------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------ |
68
+ | `smoothness` | `0.15` | Lower = smoother cursor follow |
69
+ | `hoverSelectors` | `["a", "button", "input", "textarea", "[data-hover]", "[data-cursor]"]` | Selectors that set `state.isHover` |
70
+ | `enableTouch` | `false` | Allow touch events to move the cursor |
71
+ | `autoDisableOnMobile` | `true` | Disable on coarse-pointer devices |
72
+ | `cursor` | `"auto"` | `"auto"`, `"custom"`, `"native"`, or `"both"` |
73
+ | `cursorPolicy` | `DEFAULT_CURSOR_POLICY` | Rules for native-cursor fallback and CSS suppression |
74
+ | `inheritDataAttributes` | `true` | Cascade `data-*` attributes and `rules` from ancestors |
75
+ | `hideOnLeave` | `true` | Hide when the pointer leaves the window |
76
+ | `container` | `document.body` | Primary scope's container |
77
+ | `scopes` | — | Additional scopes registered at construction |
78
+ | `zIndex` | `9999` | Stage stacking order |
79
+ | `dataPrefix` | `"supermouse"` | Prefix for data attributes |
80
+ | `rules` | — | Map of selectors to interaction data |
81
+ | `plugins` | — | Plugins to install at creation |
82
+ | `autoStart` | `true` | Set `false` and call `.start()` manually |
83
83
 
84
84
  ## Cursor Modes
85
85
 
@@ -108,10 +108,7 @@ import { Supermouse, DEFAULT_CURSOR_POLICY } from "@supermousejs/core";
108
108
 
109
109
  const mouse = new Supermouse({
110
110
  cursorPolicy: {
111
- rules: [
112
- ...DEFAULT_CURSOR_POLICY.rules,
113
- { selector: "[data-native]", native: true }
114
- ]
111
+ rules: [...DEFAULT_CURSOR_POLICY.rules, { selector: "[data-native]", native: true }]
115
112
  }
116
113
  });
117
114
  ```
@@ -183,32 +180,32 @@ Rules match ancestors as well as the hovered element. HTML `data-*` attributes o
183
180
  ## API
184
181
 
185
182
  ```ts
186
- mouse.state; // current MouseState
187
- mouse.options; // resolved options
188
- mouse.stage; // DOM element plugins render into
189
- mouse.container; // container element (active scope)
190
- mouse.isEnabled; // input processing flag
191
- mouse.isRunning; // animation loop flag
192
-
193
- mouse.enable(); // start input, apply cursor state
194
- mouse.disable(); // stop input, restore native cursor
183
+ mouse.state; // current MouseState
184
+ mouse.options; // resolved options
185
+ mouse.stage; // DOM element plugins render into
186
+ mouse.container; // container element (active scope)
187
+ mouse.isEnabled; // input processing flag
188
+ mouse.isRunning; // animation loop flag
189
+
190
+ mouse.enable(); // start input, apply cursor state
191
+ mouse.disable(); // stop input, restore native cursor
195
192
  mouse.disable({ reset: true }); // also reset physics and hover state
196
- mouse.reset(); // reset state only
193
+ mouse.reset(); // reset state only
197
194
 
198
- mouse.setCursor(mode); // "auto" | "custom" | "native" | "both"
195
+ mouse.setCursor(mode); // "auto" | "custom" | "native" | "both"
199
196
 
200
- mouse.addScope(config); // -> { name, container, setCursor, remove }
197
+ mouse.addScope(config); // -> { name, container, setCursor, remove }
201
198
 
202
- mouse.use(plugin); // install plugin
203
- mouse.getPlugin(name); // retrieve plugin
204
- mouse.enablePlugin(name); // enable plugin
205
- mouse.disablePlugin(name); // disable plugin
206
- mouse.togglePlugin(name); // toggle plugin
199
+ mouse.use(plugin); // install plugin
200
+ mouse.getPlugin(name); // retrieve plugin
201
+ mouse.enablePlugin(name); // enable plugin
202
+ mouse.disablePlugin(name); // disable plugin
203
+ mouse.togglePlugin(name); // toggle plugin
207
204
 
208
- mouse.registerHoverTarget(sel); // add hover selector at runtime
209
- mouse.start(); // start animation loop
210
- mouse.step(time); // advance one frame manually
211
- mouse.destroy(); // destroy instance and plugins
205
+ mouse.addHoverSelector(sel); // add hover selector at runtime
206
+ mouse.start(); // start animation loop
207
+ mouse.step(time); // advance one frame manually
208
+ mouse.destroy(); // destroy instance and plugins
212
209
  ```
213
210
 
214
211
  ## Browser Support
@@ -217,4 +214,4 @@ All modern browsers.
217
214
 
218
215
  ## License
219
216
 
220
- MIT
217
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,35 +1,23 @@
1
1
  /** Overall cursor mode. */
2
2
  export declare type CursorMode = "auto" | "custom" | "native" | "both";
3
3
 
4
+ export declare const DEFAULT_HOVER_SELECTORS: string[];
5
+
4
6
  /**
5
- * A cursor policy maps two independent concerns to flat selector lists.
7
+ * Selectors whose elements yield to the OS cursor in `"auto"` mode.
6
8
  *
7
- * - `native`: selectors whose elements yield to the OS cursor when the
8
- * cursor mode is `"auto"`. Hovering them temporarily shows the native
9
- * cursor and hides the custom one.
9
+ * The engine emits one wildcard rule per scope that suppresses the native
10
+ * cursor over every element (with `!important`, exclusions, and the probe
11
+ * attribute opt-out). This list is the exception: elements matching these
12
+ * selectors override the wildcard and let the OS cursor through.
10
13
  *
11
- * - `hide`: selectors that receive `cursor: none !important` when the
12
- * custom cursor is active. Needed for elements whose UA stylesheet sets
13
- * a cursor (`a { cursor: pointer }`) that would otherwise override the
14
- * inherited suppression.
14
+ * `input` and `textarea` are not listed. Their UA cursor is `text`, which
15
+ * is not in `SUPERMOUSE_CURSORS`, so the probe already treats them as
16
+ * fallback candidates. `select` and `[contenteditable]` have UA cursors
17
+ * (`default`, `auto`) that the probe classifies as non-fallback, so they
18
+ * need explicit entries.
15
19
  */
16
- export declare interface CursorPolicy {
17
- native: string[];
18
- hide: string[];
19
- }
20
-
21
- /**
22
- * Shorthand accepted at the public API boundary. `{ native?: [...], hide?: [...] }`
23
- * normalizes to the full `CursorPolicy` shape with missing keys defaulting to `[]`.
24
- */
25
- export declare type CursorPolicyInput = CursorPolicy | {
26
- native?: string[];
27
- hide?: string[];
28
- };
29
-
30
- export declare const DEFAULT_CURSOR_POLICY: CursorPolicy;
31
-
32
- export declare const DEFAULT_HOVER_SELECTORS: string[];
20
+ export declare const DEFAULT_NATIVE_CURSOR_SELECTORS: string[];
33
21
 
34
22
  /**
35
23
  * Interaction state consumed by plugins.
@@ -126,8 +114,11 @@ export declare interface ScopeConfig {
126
114
  cursor?: CursorMode;
127
115
  /** Hover selectors for this scope. Inherits from top-level if omitted. */
128
116
  hoverSelectors?: string[];
129
- /** Custom cursor policy for this scope. Inherits from top-level if omitted. */
130
- cursorPolicy?: CursorPolicyInput;
117
+ /**
118
+ * Selectors whose elements yield to the OS cursor in `"auto"` mode.
119
+ * Defaults to `DEFAULT_NATIVE_CURSOR_SELECTORS`.
120
+ */
121
+ nativeCursorSelectors?: string[];
131
122
  /** Plugins installed when this scope activates. */
132
123
  plugins?: SupermousePlugin[];
133
124
  /**
@@ -313,12 +304,21 @@ export declare class Supermouse {
313
304
  private bindVisibilityHandling;
314
305
  private startLoop;
315
306
  /**
316
- * Adds one or more hover selectors to the current scope. Intended for
317
- * raw-object plugins during `install`. Plugins written with `definePlugin`
318
- * should use the `selector` option instead, and consumers setting up a
319
- * scope should prefer the `hoverSelectors` option at construction.
307
+ * Adds one or more hover selectors to the current scope's shared set.
308
+ *
309
+ * During a plugin's `install()`, writes to that plugin's owning scope.
310
+ * At runtime, writes to the currently active scope. All plugins in the
311
+ * scope share one selector set: `state.hoverTarget` is the innermost
312
+ * element matching any registered selector, so two plugins that
313
+ * register different granularities (e.g. `a` vs `p`) receive the same
314
+ * `hoverTarget` — the innermost match.
315
+ *
316
+ * Prefer the `hoverSelectors` option at construction for static cases,
317
+ * or a visual plugin's `hoverSelector` field for plugins that need
318
+ * their own contribution. Use this method for dynamic registration —
319
+ * elements that mount after construction.
320
320
  */
321
- registerHoverTarget(selector: string): void;
321
+ addHoverSelectors(selectors: string): void;
322
322
  }
323
323
 
324
324
  export declare type SupermouseInstance = Supermouse;
@@ -354,10 +354,8 @@ export declare interface SupermouseOptions {
354
354
  * @default "auto"
355
355
  */
356
356
  cursor?: CursorMode;
357
- /**
358
- * Custom cursor policy. Defaults to `DEFAULT_CURSOR_POLICY` from `./policy`.
359
- */
360
- cursorPolicy?: CursorPolicyInput;
357
+ /** Native cursor selectors for this scope. Inherits from top-level if omitted. */
358
+ nativeCursorSelectors?: string[];
361
359
  /**
362
360
  * Additional scopes registered at construction. The primary scope is
363
361
  * created from the top-level `container` / `hoverSelectors` / `plugins`
package/dist/index.mjs CHANGED
@@ -1,25 +1,5 @@
1
- const S = {
2
- native: ["input", "textarea", "select", "[contenteditable]"],
3
- hide: [
4
- "input",
5
- "textarea",
6
- "select",
7
- "[contenteditable]",
8
- "a",
9
- "button",
10
- '[role="button"]',
11
- "[tabindex]",
12
- "label"
13
- ]
14
- };
15
- function b(n) {
16
- return n ? {
17
- native: n.native ?? [],
18
- hide: n.hide ?? []
19
- } : S;
20
- }
21
- let y = 0;
22
- class C {
1
+ let S = 0;
2
+ class b {
23
3
  element;
24
4
  scopeClass;
25
5
  hideClass;
@@ -37,7 +17,7 @@ class C {
37
17
  !t.isConnected && !s && console.warn(
38
18
  "[Supermouse] container is not attached to the document — stage sizing/positioning will be wrong until it is."
39
19
  );
40
- const i = y++;
20
+ const i = S++;
41
21
  this.scopeClass = `supermouse-scope-${i}`, this.hideClass = `supermouse-hide-${i}`, this.element = document.createElement("div"), Object.assign(this.element.style, {
42
22
  position: "absolute",
43
23
  inset: "0px",
@@ -92,13 +72,11 @@ class C {
92
72
  this._detach();
93
73
  }
94
74
  }
95
- class P {
75
+ class y {
96
76
  constructor(t, e) {
97
- this.config = t, this.name = t.name, this.cursorMode = t.cursor ?? e.cursor, this.hoverSelectors = new Set(t.hoverSelectors ?? e.hoverSelectors), this.inheritDataAttributes = t.inheritDataAttributes ?? e.inheritDataAttributes, this.ruleEntries = t.rules ? Object.entries(t.rules) : e.ruleEntries;
98
- const s = t.cursorPolicy ? b(t.cursorPolicy) : e.cursorPolicy;
99
- this.nativeSelectors = s.native, this.hideSelectors = s.hide;
100
- let i;
101
- typeof t.container == "string" ? (this.containerSelector = t.container, this._bound = !1, i = document.createElement("div"), i.setAttribute("data-supermouse-placeholder", "")) : (this.containerSelector = null, this._bound = !0, i = t.container), this.stage = new C(i, t.zIndex ?? e.zIndex);
77
+ this.config = t, this.name = t.name, this.cursorMode = t.cursor ?? e.cursor, this.hoverSelectors = new Set(t.hoverSelectors ?? e.hoverSelectors), this.inheritDataAttributes = t.inheritDataAttributes ?? e.inheritDataAttributes, this.ruleEntries = t.rules ? Object.entries(t.rules) : e.ruleEntries, this.nativeSelectors = t.nativeCursorSelectors ?? e.nativeCursorSelectors;
78
+ let s;
79
+ typeof t.container == "string" ? (this.containerSelector = t.container, this._bound = !1, s = document.createElement("div"), s.setAttribute("data-supermouse-placeholder", "")) : (this.containerSelector = null, this._bound = !0, s = t.container), this.stage = new b(s, t.zIndex ?? e.zIndex);
102
80
  }
103
81
  config;
104
82
  stage;
@@ -111,7 +89,6 @@ class P {
111
89
  hoverSelectors;
112
90
  plugins = [];
113
91
  nativeSelectors;
114
- hideSelectors;
115
92
  inheritDataAttributes;
116
93
  ruleEntries;
117
94
  name;
@@ -159,20 +136,18 @@ class P {
159
136
  return this.nativeSelectors.join(", ");
160
137
  }
161
138
  buildRules() {
162
- const t = this.stage.getRulePrefix(), e = this.stage.getExclusion(), s = ":not([data-sm-probe])", i = [
139
+ const t = this.stage.getRulePrefix(), e = this.stage.getExclusion(), s = ":not([data-sm-probe])";
140
+ return [
163
141
  `${t}${e}${s} { cursor: none !important; }`,
164
- `${t} *${e}${s} { cursor: none !important; }`
165
- ];
166
- for (const h of this.hideSelectors)
167
- i.push(`${t} ${h}${e}${s} { cursor: none !important; }`);
168
- return i.push(
169
- `${t} input[type="range"]${e}::-webkit-slider-thumb { cursor: none !important; }`
170
- ), i.push(
142
+ `${t} *${e}${s} { cursor: none !important; }`,
143
+ `${t} *${e}${s} { cursor: none !important; }`,
144
+ // Pseudo-elements are not matched by `*`; they need explicit rules.
145
+ `${t} input[type="range"]${e}::-webkit-slider-thumb { cursor: none !important; }`,
171
146
  `${t} input[type="range"]${e}::-moz-range-thumb { cursor: none !important; }`
172
- ), i;
147
+ ];
173
148
  }
174
149
  }
175
- const d = { x: -100, y: -100 }, E = /* @__PURE__ */ new Set([
150
+ const d = { x: -100, y: -100 }, C = /* @__PURE__ */ new Set([
176
151
  "default",
177
152
  "auto",
178
153
  "pointer",
@@ -180,7 +155,7 @@ const d = { x: -100, y: -100 }, E = /* @__PURE__ */ new Set([
180
155
  "inherit",
181
156
  "grab",
182
157
  "grabbing"
183
- ]), _ = [
158
+ ]), P = ["select", "[contenteditable]"], E = [
184
159
  "a",
185
160
  "button",
186
161
  "input",
@@ -188,9 +163,9 @@ const d = { x: -100, y: -100 }, E = /* @__PURE__ */ new Set([
188
163
  "[data-hover]",
189
164
  "[data-cursor]"
190
165
  ];
191
- class w {
192
- constructor(t, e, s, i, h, r) {
193
- this.state = t, this.options = e, this.onEnableChange = s, this.onActiveScopeChange = i, this.resolveScope = h, this.onHoverSettled = r, this.dataPrefix = e.dataPrefix ?? "supermouse", this.normalizedDataPrefix = this.dataPrefix.toLowerCase(), this.ignoreAttribute = `data-${this.dataPrefix}-ignore`, this.checkDeviceCapability(), this.checkMotionPreference(), this.bindEvents();
166
+ class _ {
167
+ constructor(t, e, s, i, h, o) {
168
+ this.state = t, this.options = e, this.onEnableChange = s, this.onActiveScopeChange = i, this.resolveScope = h, this.onHoverSettled = o, this.dataPrefix = e.dataPrefix ?? "supermouse", this.normalizedDataPrefix = this.dataPrefix.toLowerCase(), this.ignoreAttribute = `data-${this.dataPrefix}-ignore`, this.checkDeviceCapability(), this.checkMotionPreference(), this.bindEvents();
194
169
  }
195
170
  state;
196
171
  options;
@@ -248,40 +223,40 @@ class w {
248
223
  const e = this.activeScope.container, s = this.activeScope.inheritDataAttributes, i = this.normalizedDataPrefix, h = this.activeScope.ruleEntries;
249
224
  if (t !== this.lastParsedTarget) {
250
225
  this.lastParsedTarget = t, this.cachedChain = [];
251
- let o = t;
252
- for (; o; ) {
226
+ let n = t;
227
+ for (; n; ) {
253
228
  const p = [];
254
- for (const [a, l] of h)
255
- this.matchesSelector(o, a) && p.push({ selector: a, rules: l });
256
- if (this.cachedChain.push({ element: o, matchedRules: p }), !s || o === e) break;
257
- const c = o.parentElement;
229
+ for (const [r, l] of h)
230
+ this.matchesSelector(n, r) && p.push({ selector: r, rules: l });
231
+ if (this.cachedChain.push({ element: n, matchedRules: p }), !s || n === e) break;
232
+ const c = n.parentElement;
258
233
  if (!c || !e.contains(c)) break;
259
- o = c;
234
+ n = c;
260
235
  }
261
236
  }
262
- const r = {};
263
- for (const { element: o, matchedRules: p } of this.cachedChain) {
237
+ const o = {};
238
+ for (const { element: n, matchedRules: p } of this.cachedChain) {
264
239
  const c = {};
265
- for (const { rules: a } of p)
240
+ for (const { rules: r } of p)
266
241
  try {
267
- const l = typeof a == "function" ? a(o) : a;
242
+ const l = typeof r == "function" ? r(n) : r;
268
243
  if (!l || typeof l != "object") continue;
269
244
  for (const [v, u] of Object.entries(l))
270
- c[v] = typeof u == "function" ? u(o) : u;
245
+ c[v] = typeof u == "function" ? u(n) : u;
271
246
  } catch (l) {
272
247
  console.error("[Supermouse] Rule threw during evaluation:", l);
273
248
  }
274
- for (const a in o.dataset) {
275
- if (!a.toLowerCase().startsWith(i)) continue;
276
- const l = a.slice(i.length);
249
+ for (const r in n.dataset) {
250
+ if (!r.toLowerCase().startsWith(i)) continue;
251
+ const l = r.slice(i.length);
277
252
  if (!l) continue;
278
- const v = l[0].toLowerCase() + l.slice(1), u = o.dataset[a];
253
+ const v = l[0].toLowerCase() + l.slice(1), u = n.dataset[r];
279
254
  c[v] = u == null || u === "" ? !0 : u;
280
255
  }
281
- for (const [a, l] of Object.entries(c))
282
- a in r || (r[a] = l);
256
+ for (const [r, l] of Object.entries(c))
257
+ r in o || (o[r] = l);
283
258
  }
284
- this.state.interaction = r;
259
+ this.state.interaction = o;
285
260
  }
286
261
  matchesSelector(t, e) {
287
262
  try {
@@ -335,13 +310,13 @@ class w {
335
310
  this.state.isNative = !0, this.nativeTarget = t;
336
311
  return;
337
312
  }
338
- (t.isContentEditable || !E.has(e)) && (this.state.isNative = !0, this.nativeTarget = t);
313
+ (t.isContentEditable || !C.has(e)) && (this.state.isNative = !0, this.nativeTarget = t);
339
314
  }
340
315
  }
341
316
  handleMouseOut = (t) => {
342
317
  if (!this.isEnabled) return;
343
318
  const e = t.target, s = t.relatedTarget;
344
- (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.state.pointerTarget = null, this.state.authoredCursor = null, this.lastParsedTarget = null, this.cachedChain = []), !s && this.options.hideOnLeave && (this.state.hasReceivedInput = !1, this.state.pointer = { ...d });
319
+ (e === this.state.hoverTarget || this.state.hoverTarget?.contains(e)) && (!s || !this.state.hoverTarget?.contains(s)) && (this.state.isHover = !1, this.state.hoverTarget = null), this.nativeTarget && (e === this.nativeTarget || this.nativeTarget.contains(e)) && (!s || !this.nativeTarget.contains(s)) && (this.state.isNative = !1, this.nativeTarget = null), e === this.currentTarget && (this.currentTarget = null, this.state.pointerTarget = null, this.state.authoredCursor = null, this.lastParsedTarget = null, this.cachedChain = []), !s && this.options.hideOnLeave && (this.state.hasReceivedInput = !1, this.state.pointer = { ...d });
345
320
  };
346
321
  clearHover() {
347
322
  this.state.isHover = !1, this.state.hoverTarget = null, this.state.isNative = !1, this.nativeTarget = null, this.state.interaction = {}, this.currentTarget = null, this.state.pointerTarget = null, this.state.authoredCursor = null, this.lastParsedTarget = null, this.cachedChain = [];
@@ -357,27 +332,27 @@ class w {
357
332
  this.abortController.abort(), this.cachedChain = [];
358
333
  }
359
334
  }
360
- let x = 0;
335
+ let w = 0;
361
336
  const f = /* @__PURE__ */ new Map();
362
- function T() {
363
- return x++;
337
+ function x() {
338
+ return w++;
364
339
  }
365
- function M(n, t) {
366
- let e = f.get(n);
367
- e || (e = document.createElement("style"), e.id = `supermouse-styles-${n}`, document.head.appendChild(e), f.set(n, e)), e.textContent = t.join(`
340
+ function T(a, t) {
341
+ let e = f.get(a);
342
+ e || (e = document.createElement("style"), e.id = `supermouse-styles-${a}`, document.head.appendChild(e), f.set(a, e)), e.textContent = t.join(`
368
343
  `);
369
344
  }
370
- function A(n) {
371
- f.get(n)?.remove(), f.delete(n);
345
+ function M(a) {
346
+ f.get(a)?.remove(), f.delete(a);
372
347
  }
373
- const g = "2.5.0-beta.4";
374
- function D(n, t, e) {
375
- return n + (t - n) * e;
348
+ const g = "2.5.0-beta.5";
349
+ function A(a, t, e) {
350
+ return a + (t - a) * e;
376
351
  }
377
- function m(n, t, e, s) {
378
- return D(n, t, 1 - Math.exp(-e * s));
352
+ function m(a, t, e, s) {
353
+ return A(a, t, 1 - Math.exp(-e * s));
379
354
  }
380
- class I {
355
+ class D {
381
356
  static version = g;
382
357
  version = g;
383
358
  state;
@@ -406,7 +381,7 @@ class I {
406
381
  zIndex: 9999,
407
382
  inheritDataAttributes: !0,
408
383
  ...t
409
- }, this.styleOwner = T(), this.state = {
384
+ }, this.styleOwner = x(), this.state = {
410
385
  pointer: { ...d },
411
386
  target: { ...d },
412
387
  smooth: { ...d },
@@ -430,12 +405,12 @@ class I {
430
405
  container: this.options.container,
431
406
  cursor: this.options.cursor,
432
407
  hoverSelectors: this.options.hoverSelectors,
433
- cursorPolicy: this.options.cursorPolicy,
408
+ nativeCursorSelectors: this.options.nativeCursorSelectors,
434
409
  plugins: this.options.plugins,
435
410
  rules: this.options.rules,
436
411
  zIndex: this.options.zIndex
437
412
  });
438
- if (this.input = new w(
413
+ if (this.input = new _(
439
414
  this.state,
440
415
  this.options,
441
416
  (s) => {
@@ -554,7 +529,7 @@ class I {
554
529
  }
555
530
  t.stage.destroy();
556
531
  }
557
- this._scopes = [], this.scopeByName.clear(), this.scopeHandles.clear(), this._activeScope = null, this.state.scope = null, A(this.styleOwner);
532
+ this._scopes = [], this.scopeByName.clear(), this.scopeHandles.clear(), this._activeScope = null, this.state.scope = null, M(this.styleOwner);
558
533
  }
559
534
  // ─── Internal ───
560
535
  _running = !1;
@@ -569,10 +544,10 @@ class I {
569
544
  break;
570
545
  }
571
546
  }
572
- const e = new P(t, {
547
+ const e = new y(t, {
573
548
  cursor: this.options.cursor,
574
- hoverSelectors: this.options.hoverSelectors ?? _,
575
- cursorPolicy: this.options.cursorPolicy ? b(this.options.cursorPolicy) : S,
549
+ hoverSelectors: this.options.hoverSelectors ?? E,
550
+ nativeCursorSelectors: this.options.nativeCursorSelectors ?? P,
576
551
  zIndex: this.options.zIndex,
577
552
  inheritDataAttributes: this.options.inheritDataAttributes,
578
553
  ruleEntries: this.options.rules ? Object.entries(this.options.rules) : []
@@ -705,20 +680,20 @@ class I {
705
680
  this._installingScope = null, t.plugins.push(e), t.plugins.sort((s, i) => (s.priority ?? 0) - (i.priority ?? 0)), (t !== this._activeScope || !t.active) && e.element && (e.element.style.display = "none");
706
681
  }
707
682
  removePluginFromScope(t, e) {
708
- const s = t.plugins.findIndex((o) => o.name === e);
683
+ const s = t.plugins.findIndex((n) => n.name === e);
709
684
  if (s === -1) return;
710
685
  const i = t.plugins[s];
711
686
  t.plugins.splice(s, 1);
712
687
  const h = () => {
713
688
  try {
714
689
  i.onDisable?.(this), i.destroy?.(this);
715
- } catch (o) {
716
- console.error(`[Supermouse] Plugin '${e}' cleanup threw:`, o);
690
+ } catch (n) {
691
+ console.error(`[Supermouse] Plugin '${e}' cleanup threw:`, n);
717
692
  }
718
693
  i.element?.remove();
719
- }, r = i.onBeforeDisable?.(this);
720
- r && typeof r.then == "function" ? Promise.resolve(r).then(h).catch((o) => {
721
- console.error(`[Supermouse] Plugin '${e}' onBeforeDisable threw:`, o), h();
694
+ }, o = i.onBeforeDisable?.(this);
695
+ o && typeof o.then == "function" ? Promise.resolve(o).then(h).catch((n) => {
696
+ console.error(`[Supermouse] Plugin '${e}' onBeforeDisable threw:`, n), h();
722
697
  }) : h();
723
698
  }
724
699
  handleActiveScopeChange(t) {
@@ -764,7 +739,7 @@ class I {
764
739
  rebuildStylesheet() {
765
740
  const t = [":where(.supermouse-scope .supermouse-scope) { cursor: auto }"];
766
741
  for (const e of this._scopes) t.push(...e.buildRules());
767
- M(this.styleOwner, t);
742
+ T(this.styleOwner, t);
768
743
  }
769
744
  runPluginSafe(t, e) {
770
745
  if (t.isEnabled !== !1)
@@ -811,14 +786,14 @@ class I {
811
786
  const h = this._activeScope;
812
787
  if (h) {
813
788
  h.stage.setVisibility(this.resolveStageVisibility()), this.input.isEnabled && h.stage.setNativeCursor(this.resolveCursorState());
814
- for (let r = 0; r < h.plugins.length; r++)
815
- this.runPluginSafe(h.plugins[r], e);
789
+ for (let o = 0; o < h.plugins.length; o++)
790
+ this.runPluginSafe(h.plugins[o], e);
816
791
  }
817
792
  if (this.cleanupCrashedPlugins(), this.input.isEnabled) {
818
- const r = this.state.reducedMotion ? 1e3 : 1 / this.options.smoothness * 2, o = this.state.smooth.x, p = this.state.smooth.y;
819
- this.state.smooth.x = m(this.state.smooth.x, this.state.target.x, r, s), this.state.smooth.y = m(this.state.smooth.y, this.state.target.y, r, s), this.state.displacement.x = this.state.target.x - this.state.smooth.x, this.state.displacement.y = this.state.target.y - this.state.smooth.y, s > 0 ? (this.state.velocity.x = (this.state.smooth.x - o) / s, this.state.velocity.y = (this.state.smooth.y - p) / s) : (this.state.velocity.x = 0, this.state.velocity.y = 0);
820
- const { x: c, y: a } = this.state.velocity;
821
- (Math.abs(c) > 0.1 || Math.abs(a) > 0.1) && (this.state.angle = Math.atan2(a, c) * (180 / Math.PI));
793
+ const o = this.state.reducedMotion ? 1e3 : 1 / this.options.smoothness * 2, n = this.state.smooth.x, p = this.state.smooth.y;
794
+ this.state.smooth.x = m(this.state.smooth.x, this.state.target.x, o, s), this.state.smooth.y = m(this.state.smooth.y, this.state.target.y, o, s), this.state.displacement.x = this.state.target.x - this.state.smooth.x, this.state.displacement.y = this.state.target.y - this.state.smooth.y, s > 0 ? (this.state.velocity.x = (this.state.smooth.x - n) / s, this.state.velocity.y = (this.state.smooth.y - p) / s) : (this.state.velocity.x = 0, this.state.velocity.y = 0);
795
+ const { x: c, y: r } = this.state.velocity;
796
+ (Math.abs(c) > 0.1 || Math.abs(r) > 0.1) && (this.state.angle = Math.atan2(r, c) * (180 / Math.PI));
822
797
  }
823
798
  }
824
799
  tick = (t) => {
@@ -837,12 +812,21 @@ class I {
837
812
  this._running || (this._running = !0, !document.hidden && (this.lastTime = performance.now(), this.rafId = requestAnimationFrame(this.tick)));
838
813
  }
839
814
  /**
840
- * Adds one or more hover selectors to the current scope. Intended for
841
- * raw-object plugins during `install`. Plugins written with `definePlugin`
842
- * should use the `selector` option instead, and consumers setting up a
843
- * scope should prefer the `hoverSelectors` option at construction.
815
+ * Adds one or more hover selectors to the current scope's shared set.
816
+ *
817
+ * During a plugin's `install()`, writes to that plugin's owning scope.
818
+ * At runtime, writes to the currently active scope. All plugins in the
819
+ * scope share one selector set: `state.hoverTarget` is the innermost
820
+ * element matching any registered selector, so two plugins that
821
+ * register different granularities (e.g. `a` vs `p`) receive the same
822
+ * `hoverTarget` — the innermost match.
823
+ *
824
+ * Prefer the `hoverSelectors` option at construction for static cases,
825
+ * or a visual plugin's `hoverSelector` field for plugins that need
826
+ * their own contribution. Use this method for dynamic registration —
827
+ * elements that mount after construction.
844
828
  */
845
- registerHoverTarget(t) {
829
+ addHoverSelectors(t) {
846
830
  const e = this._installingScope ?? this._activeScope ?? this._scopes[0];
847
831
  if (e)
848
832
  for (const s of t.split(",")) {
@@ -852,7 +836,7 @@ class I {
852
836
  }
853
837
  }
854
838
  export {
855
- S as DEFAULT_CURSOR_POLICY,
856
- _ as DEFAULT_HOVER_SELECTORS,
857
- I as Supermouse
839
+ E as DEFAULT_HOVER_SELECTORS,
840
+ P as DEFAULT_NATIVE_CURSOR_SELECTORS,
841
+ D as Supermouse
858
842
  };
package/dist/index.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- (function(u,p){typeof exports=="object"&&typeof module<"u"?p(exports):typeof define=="function"&&define.amd?define(["exports"],p):(u=typeof globalThis<"u"?globalThis:u||self,p(u.SupermouseCore={}))})(this,(function(u){"use strict";const p={native:["input","textarea","select","[contenteditable]"],hide:["input","textarea","select","[contenteditable]","a","button",'[role="button"]',"[tabindex]","label"]};function S(n){return n?{native:n.native??[],hide:n.hide??[]}:p}let P=0;class E{element;scopeClass;hideClass;container;currentCursorState=null;originalContainerPosition="";originalContainerCursor="";_visible=!0;constructor(t,e){if(!t||!(t instanceof HTMLElement))throw new Error(`[Supermouse] Invalid container: ${t}. Must be an HTMLElement.`);const s=t.hasAttribute("data-supermouse-placeholder");!t.isConnected&&!s&&console.warn("[Supermouse] container is not attached to the document — stage sizing/positioning will be wrong until it is.");const i=P++;this.scopeClass=`supermouse-scope-${i}`,this.hideClass=`supermouse-hide-${i}`,this.element=document.createElement("div"),Object.assign(this.element.style,{position:"absolute",inset:"0px",pointerEvents:"none",zIndex:String(e),opacity:"1",transition:"opacity 0.15s ease"}),this.container=t,this._attach(t)}_attach(t){const e=t===document.body;if(this.element.style.position=e?"fixed":"absolute",e)this.originalContainerPosition="";else{const s=window.getComputedStyle(t);this.originalContainerPosition=s.position,s.position==="static"&&(t.style.position="relative")}this.originalContainerCursor=t.style.cursor,t.appendChild(this.element),t.classList.add("supermouse-scope",this.scopeClass)}_detach(){const t=this.container;t.classList.remove("supermouse-scope",this.scopeClass,this.hideClass),t!==document.body&&this.originalContainerPosition==="static"&&(t.style.position=""),t.style.cursor=this.originalContainerCursor,this.element.remove()}attach(t){if(this.container===t)return;const e=this.currentCursorState;this._detach(),this.container=t,this._attach(t),this.currentCursorState=null,e&&this.setNativeCursor(e)}get containerElement(){return this.container}getRulePrefix(){return`.${this.scopeClass}.${this.hideClass}`}getExclusion(){return`:not(.${this.scopeClass} .supermouse-scope):not(.${this.scopeClass} .supermouse-scope *)`}setVisibility(t){t!==this._visible&&(this._visible=t,this.element.style.opacity=t?"1":"0")}setNativeCursor(t){t!==this.currentCursorState&&(this.currentCursorState=t,this.container.classList.toggle(this.hideClass,t==="none"))}destroy(){this._detach()}}class _{constructor(t,e){this.config=t,this.name=t.name,this.cursorMode=t.cursor??e.cursor,this.hoverSelectors=new Set(t.hoverSelectors??e.hoverSelectors),this.inheritDataAttributes=t.inheritDataAttributes??e.inheritDataAttributes,this.ruleEntries=t.rules?Object.entries(t.rules):e.ruleEntries;const s=t.cursorPolicy?S(t.cursorPolicy):e.cursorPolicy;this.nativeSelectors=s.native,this.hideSelectors=s.hide;let i;typeof t.container=="string"?(this.containerSelector=t.container,this._bound=!1,i=document.createElement("div"),i.setAttribute("data-supermouse-placeholder","")):(this.containerSelector=null,this._bound=!0,i=t.container),this.stage=new E(i,t.zIndex??e.zIndex)}config;stage;containerSelector;hoverSelectors;plugins=[];nativeSelectors;hideSelectors;inheritDataAttributes;ruleEntries;name;cursorMode;active=!0;_bound;get container(){return this.stage.containerElement}get resolved(){return this._bound}match(t){if(this.containerSelector===null)return this.stage.containerElement===t;try{return t.matches(this.containerSelector)}catch{return!1}}bind(t){this.containerSelector!==null&&(this._bound&&this.stage.containerElement===t||(this.stage.attach(t),this._bound=!0))}contains(t){return this._bound&&this.stage.containerElement.contains(t)}get hoverSelectorString(){return Array.from(this.hoverSelectors).join(", ")}get nativeSelectorString(){return this.nativeSelectors.join(", ")}buildRules(){const t=this.stage.getRulePrefix(),e=this.stage.getExclusion(),s=":not([data-sm-probe])",i=[`${t}${e}${s} { cursor: none !important; }`,`${t} *${e}${s} { cursor: none !important; }`];for(const h of this.hideSelectors)i.push(`${t} ${h}${e}${s} { cursor: none !important; }`);return i.push(`${t} input[type="range"]${e}::-webkit-slider-thumb { cursor: none !important; }`),i.push(`${t} input[type="range"]${e}::-moz-range-thumb { cursor: none !important; }`),i}}const f={x:-100,y:-100},w=new Set(["default","auto","pointer","none","inherit","grab","grabbing"]),b=["a","button","input","textarea","[data-hover]","[data-cursor]"];class x{constructor(t,e,s,i,h,r){this.state=t,this.options=e,this.onEnableChange=s,this.onActiveScopeChange=i,this.resolveScope=h,this.onHoverSettled=r,this.dataPrefix=e.dataPrefix??"supermouse",this.normalizedDataPrefix=this.dataPrefix.toLowerCase(),this.ignoreAttribute=`data-${this.dataPrefix}-ignore`,this.checkDeviceCapability(),this.checkMotionPreference(),this.bindEvents()}state;options;onEnableChange;onActiveScopeChange;resolveScope;onHoverSettled;abortController=new AbortController;mediaQueryList;motionQuery;dataPrefix;normalizedDataPrefix;ignoreAttribute;activeScope=null;nativeTarget=null;currentTarget=null;lastParsedTarget=null;hasSeenPointer=!1;isEnabled=!0;viewportX=0;viewportY=0;cachedChain=[];setActiveScope(t){t!==this.activeScope&&(this.activeScope=t,this.lastParsedTarget=null,this.cachedChain=[],this.hasSeenPointer&&(this.applyPointerToState(),this.state.target.x=this.state.smooth.x=this.state.pointer.x,this.state.target.y=this.state.smooth.y=this.state.pointer.y),this.onActiveScopeChange(t))}applyPointerToState(){const t=this.activeScope?.container??document.body;if(t===document.body){this.state.pointer.x=this.viewportX,this.state.pointer.y=this.viewportY;return}const e=t.getBoundingClientRect();this.state.pointer.x=this.viewportX-e.left,this.state.pointer.y=this.viewportY-e.top}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)}parseDOMInteraction(t){if(!this.activeScope)return;const e=this.activeScope.container,s=this.activeScope.inheritDataAttributes,i=this.normalizedDataPrefix,h=this.activeScope.ruleEntries;if(t!==this.lastParsedTarget){this.lastParsedTarget=t,this.cachedChain=[];let o=t;for(;o;){const v=[];for(const[a,l]of h)this.matchesSelector(o,a)&&v.push({selector:a,rules:l});if(this.cachedChain.push({element:o,matchedRules:v}),!s||o===e)break;const c=o.parentElement;if(!c||!e.contains(c))break;o=c}}const r={};for(const{element:o,matchedRules:v}of this.cachedChain){const c={};for(const{rules:a}of v)try{const l=typeof a=="function"?a(o):a;if(!l||typeof l!="object")continue;for(const[m,d]of Object.entries(l))c[m]=typeof d=="function"?d(o):d}catch(l){console.error("[Supermouse] Rule threw during evaluation:",l)}for(const a in o.dataset){if(!a.toLowerCase().startsWith(i))continue;const l=a.slice(i.length);if(!l)continue;const m=l[0].toLowerCase()+l.slice(1),d=o.dataset[a];c[m]=d==null||d===""?!0:d}for(const[a,l]of Object.entries(c))a in r||(r[a]=l)}this.state.interaction=r}matchesSelector(t,e){try{return t.matches(e)}catch{return!1}}resolveAuthoredCursor(t){t.setAttribute("data-sm-probe","");try{return window.getComputedStyle(t).cursor}finally{t.removeAttribute("data-sm-probe")}}handleMove=t=>{this.options.autoDisableOnMobile&&t.pointerType==="touch"&&!this.options.enableTouch||(this.viewportX=t.clientX,this.viewportY=t.clientY,this.hasSeenPointer=!0,this.applyPointerToState(),this.isEnabled&&(this.state.hasReceivedInput||(this.state.hasReceivedInput=!0,this.state.target.x=this.state.smooth.x=this.state.pointer.x,this.state.target.y=this.state.smooth.y=this.state.pointer.y)))};handleDown=()=>{this.isEnabled&&(this.state.isDown=!0)};handleUp=()=>{this.isEnabled&&(this.state.isDown=!1)};handleMouseOver=t=>{if(!this.isEnabled)return;const e=t.target,s=this.resolveScope(e);s&&s!==this.activeScope&&this.setActiveScope(s),this.activeScope&&(this.settleHoverState(e),this.onHoverSettled())};isPointerInside(t){if(!this.hasSeenPointer)return!1;const e=t.getBoundingClientRect();return this.viewportX>=e.left&&this.viewportX<=e.right&&this.viewportY>=e.top&&this.viewportY<=e.bottom}settleHoverState(t){if(this.state.cursorMode==="auto"&&t.closest(`[${this.ignoreAttribute}]`)){this.clearHover(),this.state.isNative=!0,this.nativeTarget=t;return}this.state.isNative=!1,this.nativeTarget=null,this.currentTarget=t,this.state.pointerTarget=t;const e=this.resolveAuthoredCursor(t);this.state.authoredCursor=e,this.parseDOMInteraction(t);const s=t.closest(this.activeScope.hoverSelectorString);if(s&&(this.state.isHover=!0,this.state.hoverTarget=s),this.state.cursorMode==="auto"){if(this.matchesSelector(t,this.activeScope.nativeSelectorString)){this.state.isNative=!0,this.nativeTarget=t;return}(t.isContentEditable||!w.has(e))&&(this.state.isNative=!0,this.nativeTarget=t)}}handleMouseOut=t=>{if(!this.isEnabled)return;const e=t.target,s=t.relatedTarget;(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.state.pointerTarget=null,this.state.authoredCursor=null,this.lastParsedTarget=null,this.cachedChain=[]),!s&&this.options.hideOnLeave&&(this.state.hasReceivedInput=!1,this.state.pointer={...f})};clearHover(){this.state.isHover=!1,this.state.hoverTarget=null,this.state.isNative=!1,this.nativeTarget=null,this.state.interaction={},this.currentTarget=null,this.state.pointerTarget=null,this.state.authoredCursor=null,this.lastParsedTarget=null,this.cachedChain=[]}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}),document.addEventListener("mouseover",this.handleMouseOver,{signal:t}),document.addEventListener("mouseout",this.handleMouseOut,{signal:t})}destroy(){this.abortController.abort(),this.cachedChain=[]}}let T=0;const g=new Map;function M(){return T++}function A(n,t){let e=g.get(n);e||(e=document.createElement("style"),e.id=`supermouse-styles-${n}`,document.head.appendChild(e),g.set(n,e)),e.textContent=t.join(`
2
- `)}function D(n){g.get(n)?.remove(),g.delete(n)}const y="2.5.0-beta.4";function I(n,t,e){return n+(t-n)*e}function C(n,t,e,s){return I(n,t,1-Math.exp(-e*s))}class O{static version=y;version=y;state;options;_scopes=[];_activeScope=null;_installingScope=null;scopeByName=new Map;scopeHandles=new Map;input;styleOwner;rafId=0;lastTime=0;visibilityAbortController=new AbortController;crashedPlugins=[];constructor(t={}){this.options={smoothness:.15,enableTouch:!1,autoDisableOnMobile:!0,cursor:"auto",hideOnLeave:!0,autoStart:!0,container:document.body,dataPrefix:"supermouse",zIndex:9999,inheritDataAttributes:!0,...t},this.styleOwner=M(),this.state={pointer:{...f},target:{...f},smooth:{...f},velocity:{x:0,y:0},displacement:{x:0,y:0},angle:0,isDown:!1,isHover:!1,isNative:!1,cursorMode:this.options.cursor,pointerTarget:null,hoverTarget:null,authoredCursor:null,reducedMotion:!1,hasReceivedInput:!1,shape:null,interaction:{},scope:null};const e=this.createScope({container:this.options.container,cursor:this.options.cursor,hoverSelectors:this.options.hoverSelectors,cursorPolicy:this.options.cursorPolicy,plugins:this.options.plugins,rules:this.options.rules,zIndex:this.options.zIndex});if(this.input=new x(this.state,this.options,s=>{s||this.reset()},s=>this.handleActiveScopeChange(s),s=>this.resolveScopeForNode(s),()=>this.applyCursorNow()),this.input.setActiveScope(e),this.installPlugins(e),this.options.scopes)for(const s of this.options.scopes)this.addScope(s);this.rebuildStylesheet(),this.bindVisibilityHandling(),this.options.autoStart&&this.startLoop()}get container(){return(this._activeScope??this._scopes[0]).container}get stage(){return(this._installingScope??this._activeScope??this._scopes[0]).stage.element}get isEnabled(){return this.input.isEnabled}get isRunning(){return this._running}use(t){return this.installPlugin(this._scopes[0],t),this}getPlugin(t){for(const e of this._scopes){const s=e.plugins.find(i=>i.name===t);if(s)return s}}getScope(t){const e=this.scopeByName.get(t);return e?this.scopeHandles.get(e):void 0}enablePlugin(t){const e=this.getPlugin(t);if(!e||e.isEnabled!==!1)return;e.isEnabled=!0;const s=this.findScopeForPlugin(e);s&&s===this._activeScope&&s.active&&this.scopeActivatePlugin(e)}disablePlugin(t){const e=this.getPlugin(t);if(!e||e.isEnabled===!1)return;const s=this.findScopeForPlugin(e);s!==null&&s===this._activeScope&&s.active?this.deactivatePlugin(e):e.isEnabled=!1}togglePlugin(t){const e=this.getPlugin(t);e&&(e.isEnabled===!1?this.enablePlugin(t):this.disablePlugin(t))}setCursor(t){this._activeScope&&this.setScopeCursor(this._activeScope,t)}addScope(t){const e=this.createScope(t);return this.installPlugins(e),this.rebuildStylesheet(),this.scopeHandles.get(e)}enable(){this.input.isEnabled=!0,this.input.hasSeenPointer&&(this.state.target.x=this.state.smooth.x=this.state.pointer.x,this.state.target.y=this.state.smooth.y=this.state.pointer.y,this.resetMotion(),this.state.hasReceivedInput=!0),this._activeScope&&this._activeScope.stage.setNativeCursor(this.resolveCursorState())}disable(t){this.input.isEnabled=!1,this._activeScope&&(this._activeScope.stage.setNativeCursor("auto"),this._activeScope.stage.setVisibility(!1)),t?.reset&&this.reset()}reset(){this.state.target={...f},this.state.smooth={...f},this.resetMotion(),this.state.angle=0,this.state.hasReceivedInput=!1,this.state.shape=null,this.state.interaction={}}start(){this.startLoop()}step(t){this.update(t)}destroy(){this._running=!1,cancelAnimationFrame(this.rafId),this.visibilityAbortController.abort(),this.input.destroy();for(const t of this._scopes){for(const e of t.plugins)try{e.destroy?.(this)}catch(s){console.error(s)}t.stage.destroy()}this._scopes=[],this.scopeByName.clear(),this.scopeHandles.clear(),this._activeScope=null,this.state.scope=null,D(this.styleOwner)}_running=!1;createScope(t){if(typeof t.container!="string"){const s=t.container;for(const i of this._scopes)if(i.containerSelector===null&&i.container===s){console.warn("[Supermouse] A scope is already registered for this container. The previous scope will no longer activate.");break}}const e=new _(t,{cursor:this.options.cursor,hoverSelectors:this.options.hoverSelectors??b,cursorPolicy:this.options.cursorPolicy?S(this.options.cursorPolicy):p,zIndex:this.options.zIndex,inheritDataAttributes:this.options.inheritDataAttributes,ruleEntries:this.options.rules?Object.entries(this.options.rules):[]});return this._scopes.push(e),e.name&&this.scopeByName.set(e.name,e),this.scopeHandles.set(e,this.buildScopeHandle(e)),e}buildScopeHandle(t){const e={get name(){return t.name},get container(){return t.resolved?t.container:null},get active(){return t.active},get resolved(){return t.resolved},destroy:()=>this.destroyScope(t),setCursor:s=>this.setScopeCursor(t,s),deactivate:()=>this.deactivateScope(t),activate:()=>this.activateScope(t),use:s=>(this.installPlugin(t,s),e),removePlugin:s=>this.removePluginFromScope(t,s),getPlugin:s=>t.plugins.find(i=>i.name===s)};return e}destroyScope(t){const e=this._scopes.indexOf(t);if(e!==-1){for(const s of t.plugins)try{s.destroy?.(this)}catch(i){console.error(i)}if(t.plugins.length=0,t.stage.destroy(),this._scopes.splice(e,1),t.name&&this.scopeByName.delete(t.name),this.scopeHandles.delete(t),this._activeScope===t){const s=this.resolveScopeForNode(t.container.parentElement)??this._scopes.find(i=>i.active)??null;this.input.setActiveScope(s),this.input.clearHover(),this.applyCursorNow()}this.rebuildStylesheet()}}setScopeCursor(t,e){t.cursorMode=e,t===this._activeScope&&(this.state.cursorMode=e,this.applyCursorNow())}deactivateScope(t){if(t.active&&(t.active=!1,this._activeScope===t)){const e=this.resolveScopeForNode(t.container.parentElement)??this._scopes.find(s=>s.active)??null;this.input.setActiveScope(e),this.input.clearHover(),this.applyCursorNow()}}activateScope(t){if(t.active=!0,this._activeScope!==t&&this.input.hasSeenPointer){if(t.containerSelector!==null&&!t.resolved){const e=document.querySelectorAll(t.containerSelector);for(const s of e)if(this.input.isPointerInside(s)){t.bind(s);break}}t.resolved&&this.input.isPointerInside(t.container)&&(this.input.setActiveScope(t),this.applyCursorNow())}}resolveScopeForNode(t){for(let e=t;e;e=e.parentElement)for(const s of this._scopes)if(s.active&&s.match(e))return s.bind(e),s;return null}applyCursorNow(){const t=this._activeScope;t&&(t.stage.setVisibility(this.resolveStageVisibility()),this.input.isEnabled&&t.stage.setNativeCursor(this.resolveCursorState()))}findScopeForPlugin(t){for(const e of this._scopes)if(e.plugins.includes(t))return e;return null}installPlugins(t){if(t.config.plugins)for(const e of t.config.plugins)this.installPlugin(t,e)}installPlugin(t,e){if(t.plugins.some(s=>s.name===e.name)){console.warn(`[Supermouse] Plugin "${e.name}" already installed.`);return}e.isEnabled??=!0,this._installingScope=t;try{e.install?.(this)}catch(s){console.error(`[Supermouse] Failed to install plugin '${e.name}'.`,s),this._installingScope=null;return}this._installingScope=null,t.plugins.push(e),t.plugins.sort((s,i)=>(s.priority??0)-(i.priority??0)),(t!==this._activeScope||!t.active)&&e.element&&(e.element.style.display="none")}removePluginFromScope(t,e){const s=t.plugins.findIndex(o=>o.name===e);if(s===-1)return;const i=t.plugins[s];t.plugins.splice(s,1);const h=()=>{try{i.onDisable?.(this),i.destroy?.(this)}catch(o){console.error(`[Supermouse] Plugin '${e}' cleanup threw:`,o)}i.element?.remove()},r=i.onBeforeDisable?.(this);r&&typeof r.then=="function"?Promise.resolve(r).then(h).catch(o=>{console.error(`[Supermouse] Plugin '${e}' onBeforeDisable threw:`,o),h()}):h()}handleActiveScopeChange(t){const e=this._activeScope;if(e!==t){if(e)for(const s of e.plugins)s.isEnabled!==!1&&this.scopeDeactivatePlugin(s);if(this._activeScope=t,t){this.state.cursorMode=t.cursorMode,this.state.scope={name:t.name,container:t.container};for(const s of t.plugins)s.isEnabled!==!1&&this.scopeActivatePlugin(s)}else this.state.scope=null}}deactivatePlugin(t){if(t.isEnabled===!1)return;t.isEnabled=!1;const e=()=>{t.element&&(t.element.style.display="none"),t.onDisable?.(this)},s=t.onBeforeDisable?.(this);s&&typeof s.then=="function"?Promise.resolve(s).then(e).catch(i=>{console.error(`[Supermouse] Plugin '${t.name}' onBeforeDisable threw:`,i),e()}):e()}scopeDeactivatePlugin(t){const e=()=>{t.element&&(t.element.style.display="none"),t.onDisable?.(this)},s=t.onBeforeDisable?.(this);s&&typeof s.then=="function"?Promise.resolve(s).then(e).catch(i=>{console.error(`[Supermouse] Plugin '${t.name}' onBeforeDisable threw:`,i),e()}):e()}scopeActivatePlugin(t){t.element&&(t.element.style.display=""),t.onEnable?.(this)}rebuildStylesheet(){const t=[":where(.supermouse-scope .supermouse-scope) { cursor: auto }"];for(const e of this._scopes)t.push(...e.buildRules());A(this.styleOwner,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,this.crashedPlugins.push(t)}}cleanupCrashedPlugins(){if(this.crashedPlugins.length!==0){for(const t of this.crashedPlugins){for(const e of this._scopes){const s=e.plugins.indexOf(t);if(s!==-1){e.plugins.splice(s,1);break}}try{t.onDisable?.(this),t.destroy?.(this)}catch(e){console.error(`[Supermouse] Failed to cleanup crashed plugin '${t.name}'.`,e)}t.element?.remove()}this.crashedPlugins=[]}}resolveStageVisibility(){return this.state.cursorMode==="native"?!1:this.state.cursorMode==="custom"||this.state.cursorMode==="both"?this.input.isEnabled&&this.state.hasReceivedInput:this.input.isEnabled&&!this.state.isNative&&this.state.hasReceivedInput}resolveCursorState(){return!this.input.isEnabled||this.state.cursorMode==="native"||this.state.cursorMode==="both"?"auto":this.state.cursorMode==="custom"?"none":this.state.isNative||!this.state.hasReceivedInput?"auto":"none"}resetMotion(){this.state.velocity={x:0,y:0},this.state.displacement={x:0,y:0}}update(t){const e=t-this.lastTime,s=Math.min(e/1e3,.1);this.lastTime=t;const i=this.input.getCurrentTarget();i&&!i.isConnected?this.input.clearHover():i&&this.input.parseDOMInteraction(i),this.input.isEnabled&&this.state.hasReceivedInput&&(this.state.target.x=this.state.pointer.x,this.state.target.y=this.state.pointer.y);const h=this._activeScope;if(h){h.stage.setVisibility(this.resolveStageVisibility()),this.input.isEnabled&&h.stage.setNativeCursor(this.resolveCursorState());for(let r=0;r<h.plugins.length;r++)this.runPluginSafe(h.plugins[r],e)}if(this.cleanupCrashedPlugins(),this.input.isEnabled){const r=this.state.reducedMotion?1e3:1/this.options.smoothness*2,o=this.state.smooth.x,v=this.state.smooth.y;this.state.smooth.x=C(this.state.smooth.x,this.state.target.x,r,s),this.state.smooth.y=C(this.state.smooth.y,this.state.target.y,r,s),this.state.displacement.x=this.state.target.x-this.state.smooth.x,this.state.displacement.y=this.state.target.y-this.state.smooth.y,s>0?(this.state.velocity.x=(this.state.smooth.x-o)/s,this.state.velocity.y=(this.state.smooth.y-v)/s):(this.state.velocity.x=0,this.state.velocity.y=0);const{x:c,y:a}=this.state.velocity;(Math.abs(c)>.1||Math.abs(a)>.1)&&(this.state.angle=Math.atan2(a,c)*(180/Math.PI))}}tick=t=>{this.update(t),this._running&&(this.rafId=requestAnimationFrame(this.tick))};bindVisibilityHandling(){document.addEventListener("visibilitychange",()=>{this._running&&(document.hidden?cancelAnimationFrame(this.rafId):(this.lastTime=performance.now(),this.rafId=requestAnimationFrame(this.tick)))},{signal:this.visibilityAbortController.signal})}startLoop(){this._running||(this._running=!0,!document.hidden&&(this.lastTime=performance.now(),this.rafId=requestAnimationFrame(this.tick)))}registerHoverTarget(t){const e=this._installingScope??this._activeScope??this._scopes[0];if(e)for(const s of t.split(",")){const i=s.trim();i&&e.hoverSelectors.add(i)}}}u.DEFAULT_CURSOR_POLICY=p,u.DEFAULT_HOVER_SELECTORS=b,u.Supermouse=O,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(u,v){typeof exports=="object"&&typeof module<"u"?v(exports):typeof define=="function"&&define.amd?define(["exports"],v):(u=typeof globalThis<"u"?globalThis:u||self,v(u.SupermouseCore={}))})(this,(function(u){"use strict";let v=0;class E{element;scopeClass;hideClass;container;currentCursorState=null;originalContainerPosition="";originalContainerCursor="";_visible=!0;constructor(t,e){if(!t||!(t instanceof HTMLElement))throw new Error(`[Supermouse] Invalid container: ${t}. Must be an HTMLElement.`);const s=t.hasAttribute("data-supermouse-placeholder");!t.isConnected&&!s&&console.warn("[Supermouse] container is not attached to the document — stage sizing/positioning will be wrong until it is.");const i=v++;this.scopeClass=`supermouse-scope-${i}`,this.hideClass=`supermouse-hide-${i}`,this.element=document.createElement("div"),Object.assign(this.element.style,{position:"absolute",inset:"0px",pointerEvents:"none",zIndex:String(e),opacity:"1",transition:"opacity 0.15s ease"}),this.container=t,this._attach(t)}_attach(t){const e=t===document.body;if(this.element.style.position=e?"fixed":"absolute",e)this.originalContainerPosition="";else{const s=window.getComputedStyle(t);this.originalContainerPosition=s.position,s.position==="static"&&(t.style.position="relative")}this.originalContainerCursor=t.style.cursor,t.appendChild(this.element),t.classList.add("supermouse-scope",this.scopeClass)}_detach(){const t=this.container;t.classList.remove("supermouse-scope",this.scopeClass,this.hideClass),t!==document.body&&this.originalContainerPosition==="static"&&(t.style.position=""),t.style.cursor=this.originalContainerCursor,this.element.remove()}attach(t){if(this.container===t)return;const e=this.currentCursorState;this._detach(),this.container=t,this._attach(t),this.currentCursorState=null,e&&this.setNativeCursor(e)}get containerElement(){return this.container}getRulePrefix(){return`.${this.scopeClass}.${this.hideClass}`}getExclusion(){return`:not(.${this.scopeClass} .supermouse-scope):not(.${this.scopeClass} .supermouse-scope *)`}setVisibility(t){t!==this._visible&&(this._visible=t,this.element.style.opacity=t?"1":"0")}setNativeCursor(t){t!==this.currentCursorState&&(this.currentCursorState=t,this.container.classList.toggle(this.hideClass,t==="none"))}destroy(){this._detach()}}class P{constructor(t,e){this.config=t,this.name=t.name,this.cursorMode=t.cursor??e.cursor,this.hoverSelectors=new Set(t.hoverSelectors??e.hoverSelectors),this.inheritDataAttributes=t.inheritDataAttributes??e.inheritDataAttributes,this.ruleEntries=t.rules?Object.entries(t.rules):e.ruleEntries,this.nativeSelectors=t.nativeCursorSelectors??e.nativeCursorSelectors;let s;typeof t.container=="string"?(this.containerSelector=t.container,this._bound=!1,s=document.createElement("div"),s.setAttribute("data-supermouse-placeholder","")):(this.containerSelector=null,this._bound=!0,s=t.container),this.stage=new E(s,t.zIndex??e.zIndex)}config;stage;containerSelector;hoverSelectors;plugins=[];nativeSelectors;inheritDataAttributes;ruleEntries;name;cursorMode;active=!0;_bound;get container(){return this.stage.containerElement}get resolved(){return this._bound}match(t){if(this.containerSelector===null)return this.stage.containerElement===t;try{return t.matches(this.containerSelector)}catch{return!1}}bind(t){this.containerSelector!==null&&(this._bound&&this.stage.containerElement===t||(this.stage.attach(t),this._bound=!0))}contains(t){return this._bound&&this.stage.containerElement.contains(t)}get hoverSelectorString(){return Array.from(this.hoverSelectors).join(", ")}get nativeSelectorString(){return this.nativeSelectors.join(", ")}buildRules(){const t=this.stage.getRulePrefix(),e=this.stage.getExclusion(),s=":not([data-sm-probe])";return[`${t}${e}${s} { cursor: none !important; }`,`${t} *${e}${s} { cursor: none !important; }`,`${t} *${e}${s} { cursor: none !important; }`,`${t} input[type="range"]${e}::-webkit-slider-thumb { cursor: none !important; }`,`${t} input[type="range"]${e}::-moz-range-thumb { cursor: none !important; }`]}}const d={x:-100,y:-100},_=new Set(["default","auto","pointer","none","inherit","grab","grabbing"]),S=["select","[contenteditable]"],b=["a","button","input","textarea","[data-hover]","[data-cursor]"];class w{constructor(t,e,s,i,h,o){this.state=t,this.options=e,this.onEnableChange=s,this.onActiveScopeChange=i,this.resolveScope=h,this.onHoverSettled=o,this.dataPrefix=e.dataPrefix??"supermouse",this.normalizedDataPrefix=this.dataPrefix.toLowerCase(),this.ignoreAttribute=`data-${this.dataPrefix}-ignore`,this.checkDeviceCapability(),this.checkMotionPreference(),this.bindEvents()}state;options;onEnableChange;onActiveScopeChange;resolveScope;onHoverSettled;abortController=new AbortController;mediaQueryList;motionQuery;dataPrefix;normalizedDataPrefix;ignoreAttribute;activeScope=null;nativeTarget=null;currentTarget=null;lastParsedTarget=null;hasSeenPointer=!1;isEnabled=!0;viewportX=0;viewportY=0;cachedChain=[];setActiveScope(t){t!==this.activeScope&&(this.activeScope=t,this.lastParsedTarget=null,this.cachedChain=[],this.hasSeenPointer&&(this.applyPointerToState(),this.state.target.x=this.state.smooth.x=this.state.pointer.x,this.state.target.y=this.state.smooth.y=this.state.pointer.y),this.onActiveScopeChange(t))}applyPointerToState(){const t=this.activeScope?.container??document.body;if(t===document.body){this.state.pointer.x=this.viewportX,this.state.pointer.y=this.viewportY;return}const e=t.getBoundingClientRect();this.state.pointer.x=this.viewportX-e.left,this.state.pointer.y=this.viewportY-e.top}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)}parseDOMInteraction(t){if(!this.activeScope)return;const e=this.activeScope.container,s=this.activeScope.inheritDataAttributes,i=this.normalizedDataPrefix,h=this.activeScope.ruleEntries;if(t!==this.lastParsedTarget){this.lastParsedTarget=t,this.cachedChain=[];let n=t;for(;n;){const f=[];for(const[r,l]of h)this.matchesSelector(n,r)&&f.push({selector:r,rules:l});if(this.cachedChain.push({element:n,matchedRules:f}),!s||n===e)break;const c=n.parentElement;if(!c||!e.contains(c))break;n=c}}const o={};for(const{element:n,matchedRules:f}of this.cachedChain){const c={};for(const{rules:r}of f)try{const l=typeof r=="function"?r(n):r;if(!l||typeof l!="object")continue;for(const[m,p]of Object.entries(l))c[m]=typeof p=="function"?p(n):p}catch(l){console.error("[Supermouse] Rule threw during evaluation:",l)}for(const r in n.dataset){if(!r.toLowerCase().startsWith(i))continue;const l=r.slice(i.length);if(!l)continue;const m=l[0].toLowerCase()+l.slice(1),p=n.dataset[r];c[m]=p==null||p===""?!0:p}for(const[r,l]of Object.entries(c))r in o||(o[r]=l)}this.state.interaction=o}matchesSelector(t,e){try{return t.matches(e)}catch{return!1}}resolveAuthoredCursor(t){t.setAttribute("data-sm-probe","");try{return window.getComputedStyle(t).cursor}finally{t.removeAttribute("data-sm-probe")}}handleMove=t=>{this.options.autoDisableOnMobile&&t.pointerType==="touch"&&!this.options.enableTouch||(this.viewportX=t.clientX,this.viewportY=t.clientY,this.hasSeenPointer=!0,this.applyPointerToState(),this.isEnabled&&(this.state.hasReceivedInput||(this.state.hasReceivedInput=!0,this.state.target.x=this.state.smooth.x=this.state.pointer.x,this.state.target.y=this.state.smooth.y=this.state.pointer.y)))};handleDown=()=>{this.isEnabled&&(this.state.isDown=!0)};handleUp=()=>{this.isEnabled&&(this.state.isDown=!1)};handleMouseOver=t=>{if(!this.isEnabled)return;const e=t.target,s=this.resolveScope(e);s&&s!==this.activeScope&&this.setActiveScope(s),this.activeScope&&(this.settleHoverState(e),this.onHoverSettled())};isPointerInside(t){if(!this.hasSeenPointer)return!1;const e=t.getBoundingClientRect();return this.viewportX>=e.left&&this.viewportX<=e.right&&this.viewportY>=e.top&&this.viewportY<=e.bottom}settleHoverState(t){if(this.state.cursorMode==="auto"&&t.closest(`[${this.ignoreAttribute}]`)){this.clearHover(),this.state.isNative=!0,this.nativeTarget=t;return}this.state.isNative=!1,this.nativeTarget=null,this.currentTarget=t,this.state.pointerTarget=t;const e=this.resolveAuthoredCursor(t);this.state.authoredCursor=e,this.parseDOMInteraction(t);const s=t.closest(this.activeScope.hoverSelectorString);if(s&&(this.state.isHover=!0,this.state.hoverTarget=s),this.state.cursorMode==="auto"){if(this.matchesSelector(t,this.activeScope.nativeSelectorString)){this.state.isNative=!0,this.nativeTarget=t;return}(t.isContentEditable||!_.has(e))&&(this.state.isNative=!0,this.nativeTarget=t)}}handleMouseOut=t=>{if(!this.isEnabled)return;const e=t.target,s=t.relatedTarget;(e===this.state.hoverTarget||this.state.hoverTarget?.contains(e))&&(!s||!this.state.hoverTarget?.contains(s))&&(this.state.isHover=!1,this.state.hoverTarget=null),this.nativeTarget&&(e===this.nativeTarget||this.nativeTarget.contains(e))&&(!s||!this.nativeTarget.contains(s))&&(this.state.isNative=!1,this.nativeTarget=null),e===this.currentTarget&&(this.currentTarget=null,this.state.pointerTarget=null,this.state.authoredCursor=null,this.lastParsedTarget=null,this.cachedChain=[]),!s&&this.options.hideOnLeave&&(this.state.hasReceivedInput=!1,this.state.pointer={...d})};clearHover(){this.state.isHover=!1,this.state.hoverTarget=null,this.state.isNative=!1,this.nativeTarget=null,this.state.interaction={},this.currentTarget=null,this.state.pointerTarget=null,this.state.authoredCursor=null,this.lastParsedTarget=null,this.cachedChain=[]}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}),document.addEventListener("mouseover",this.handleMouseOver,{signal:t}),document.addEventListener("mouseout",this.handleMouseOut,{signal:t})}destroy(){this.abortController.abort(),this.cachedChain=[]}}let x=0;const g=new Map;function T(){return x++}function M(a,t){let e=g.get(a);e||(e=document.createElement("style"),e.id=`supermouse-styles-${a}`,document.head.appendChild(e),g.set(a,e)),e.textContent=t.join(`
2
+ `)}function A(a){g.get(a)?.remove(),g.delete(a)}const y="2.5.0-beta.5";function D(a,t,e){return a+(t-a)*e}function C(a,t,e,s){return D(a,t,1-Math.exp(-e*s))}class I{static version=y;version=y;state;options;_scopes=[];_activeScope=null;_installingScope=null;scopeByName=new Map;scopeHandles=new Map;input;styleOwner;rafId=0;lastTime=0;visibilityAbortController=new AbortController;crashedPlugins=[];constructor(t={}){this.options={smoothness:.15,enableTouch:!1,autoDisableOnMobile:!0,cursor:"auto",hideOnLeave:!0,autoStart:!0,container:document.body,dataPrefix:"supermouse",zIndex:9999,inheritDataAttributes:!0,...t},this.styleOwner=T(),this.state={pointer:{...d},target:{...d},smooth:{...d},velocity:{x:0,y:0},displacement:{x:0,y:0},angle:0,isDown:!1,isHover:!1,isNative:!1,cursorMode:this.options.cursor,pointerTarget:null,hoverTarget:null,authoredCursor:null,reducedMotion:!1,hasReceivedInput:!1,shape:null,interaction:{},scope:null};const e=this.createScope({container:this.options.container,cursor:this.options.cursor,hoverSelectors:this.options.hoverSelectors,nativeCursorSelectors:this.options.nativeCursorSelectors,plugins:this.options.plugins,rules:this.options.rules,zIndex:this.options.zIndex});if(this.input=new w(this.state,this.options,s=>{s||this.reset()},s=>this.handleActiveScopeChange(s),s=>this.resolveScopeForNode(s),()=>this.applyCursorNow()),this.input.setActiveScope(e),this.installPlugins(e),this.options.scopes)for(const s of this.options.scopes)this.addScope(s);this.rebuildStylesheet(),this.bindVisibilityHandling(),this.options.autoStart&&this.startLoop()}get container(){return(this._activeScope??this._scopes[0]).container}get stage(){return(this._installingScope??this._activeScope??this._scopes[0]).stage.element}get isEnabled(){return this.input.isEnabled}get isRunning(){return this._running}use(t){return this.installPlugin(this._scopes[0],t),this}getPlugin(t){for(const e of this._scopes){const s=e.plugins.find(i=>i.name===t);if(s)return s}}getScope(t){const e=this.scopeByName.get(t);return e?this.scopeHandles.get(e):void 0}enablePlugin(t){const e=this.getPlugin(t);if(!e||e.isEnabled!==!1)return;e.isEnabled=!0;const s=this.findScopeForPlugin(e);s&&s===this._activeScope&&s.active&&this.scopeActivatePlugin(e)}disablePlugin(t){const e=this.getPlugin(t);if(!e||e.isEnabled===!1)return;const s=this.findScopeForPlugin(e);s!==null&&s===this._activeScope&&s.active?this.deactivatePlugin(e):e.isEnabled=!1}togglePlugin(t){const e=this.getPlugin(t);e&&(e.isEnabled===!1?this.enablePlugin(t):this.disablePlugin(t))}setCursor(t){this._activeScope&&this.setScopeCursor(this._activeScope,t)}addScope(t){const e=this.createScope(t);return this.installPlugins(e),this.rebuildStylesheet(),this.scopeHandles.get(e)}enable(){this.input.isEnabled=!0,this.input.hasSeenPointer&&(this.state.target.x=this.state.smooth.x=this.state.pointer.x,this.state.target.y=this.state.smooth.y=this.state.pointer.y,this.resetMotion(),this.state.hasReceivedInput=!0),this._activeScope&&this._activeScope.stage.setNativeCursor(this.resolveCursorState())}disable(t){this.input.isEnabled=!1,this._activeScope&&(this._activeScope.stage.setNativeCursor("auto"),this._activeScope.stage.setVisibility(!1)),t?.reset&&this.reset()}reset(){this.state.target={...d},this.state.smooth={...d},this.resetMotion(),this.state.angle=0,this.state.hasReceivedInput=!1,this.state.shape=null,this.state.interaction={}}start(){this.startLoop()}step(t){this.update(t)}destroy(){this._running=!1,cancelAnimationFrame(this.rafId),this.visibilityAbortController.abort(),this.input.destroy();for(const t of this._scopes){for(const e of t.plugins)try{e.destroy?.(this)}catch(s){console.error(s)}t.stage.destroy()}this._scopes=[],this.scopeByName.clear(),this.scopeHandles.clear(),this._activeScope=null,this.state.scope=null,A(this.styleOwner)}_running=!1;createScope(t){if(typeof t.container!="string"){const s=t.container;for(const i of this._scopes)if(i.containerSelector===null&&i.container===s){console.warn("[Supermouse] A scope is already registered for this container. The previous scope will no longer activate.");break}}const e=new P(t,{cursor:this.options.cursor,hoverSelectors:this.options.hoverSelectors??b,nativeCursorSelectors:this.options.nativeCursorSelectors??S,zIndex:this.options.zIndex,inheritDataAttributes:this.options.inheritDataAttributes,ruleEntries:this.options.rules?Object.entries(this.options.rules):[]});return this._scopes.push(e),e.name&&this.scopeByName.set(e.name,e),this.scopeHandles.set(e,this.buildScopeHandle(e)),e}buildScopeHandle(t){const e={get name(){return t.name},get container(){return t.resolved?t.container:null},get active(){return t.active},get resolved(){return t.resolved},destroy:()=>this.destroyScope(t),setCursor:s=>this.setScopeCursor(t,s),deactivate:()=>this.deactivateScope(t),activate:()=>this.activateScope(t),use:s=>(this.installPlugin(t,s),e),removePlugin:s=>this.removePluginFromScope(t,s),getPlugin:s=>t.plugins.find(i=>i.name===s)};return e}destroyScope(t){const e=this._scopes.indexOf(t);if(e!==-1){for(const s of t.plugins)try{s.destroy?.(this)}catch(i){console.error(i)}if(t.plugins.length=0,t.stage.destroy(),this._scopes.splice(e,1),t.name&&this.scopeByName.delete(t.name),this.scopeHandles.delete(t),this._activeScope===t){const s=this.resolveScopeForNode(t.container.parentElement)??this._scopes.find(i=>i.active)??null;this.input.setActiveScope(s),this.input.clearHover(),this.applyCursorNow()}this.rebuildStylesheet()}}setScopeCursor(t,e){t.cursorMode=e,t===this._activeScope&&(this.state.cursorMode=e,this.applyCursorNow())}deactivateScope(t){if(t.active&&(t.active=!1,this._activeScope===t)){const e=this.resolveScopeForNode(t.container.parentElement)??this._scopes.find(s=>s.active)??null;this.input.setActiveScope(e),this.input.clearHover(),this.applyCursorNow()}}activateScope(t){if(t.active=!0,this._activeScope!==t&&this.input.hasSeenPointer){if(t.containerSelector!==null&&!t.resolved){const e=document.querySelectorAll(t.containerSelector);for(const s of e)if(this.input.isPointerInside(s)){t.bind(s);break}}t.resolved&&this.input.isPointerInside(t.container)&&(this.input.setActiveScope(t),this.applyCursorNow())}}resolveScopeForNode(t){for(let e=t;e;e=e.parentElement)for(const s of this._scopes)if(s.active&&s.match(e))return s.bind(e),s;return null}applyCursorNow(){const t=this._activeScope;t&&(t.stage.setVisibility(this.resolveStageVisibility()),this.input.isEnabled&&t.stage.setNativeCursor(this.resolveCursorState()))}findScopeForPlugin(t){for(const e of this._scopes)if(e.plugins.includes(t))return e;return null}installPlugins(t){if(t.config.plugins)for(const e of t.config.plugins)this.installPlugin(t,e)}installPlugin(t,e){if(t.plugins.some(s=>s.name===e.name)){console.warn(`[Supermouse] Plugin "${e.name}" already installed.`);return}e.isEnabled??=!0,this._installingScope=t;try{e.install?.(this)}catch(s){console.error(`[Supermouse] Failed to install plugin '${e.name}'.`,s),this._installingScope=null;return}this._installingScope=null,t.plugins.push(e),t.plugins.sort((s,i)=>(s.priority??0)-(i.priority??0)),(t!==this._activeScope||!t.active)&&e.element&&(e.element.style.display="none")}removePluginFromScope(t,e){const s=t.plugins.findIndex(n=>n.name===e);if(s===-1)return;const i=t.plugins[s];t.plugins.splice(s,1);const h=()=>{try{i.onDisable?.(this),i.destroy?.(this)}catch(n){console.error(`[Supermouse] Plugin '${e}' cleanup threw:`,n)}i.element?.remove()},o=i.onBeforeDisable?.(this);o&&typeof o.then=="function"?Promise.resolve(o).then(h).catch(n=>{console.error(`[Supermouse] Plugin '${e}' onBeforeDisable threw:`,n),h()}):h()}handleActiveScopeChange(t){const e=this._activeScope;if(e!==t){if(e)for(const s of e.plugins)s.isEnabled!==!1&&this.scopeDeactivatePlugin(s);if(this._activeScope=t,t){this.state.cursorMode=t.cursorMode,this.state.scope={name:t.name,container:t.container};for(const s of t.plugins)s.isEnabled!==!1&&this.scopeActivatePlugin(s)}else this.state.scope=null}}deactivatePlugin(t){if(t.isEnabled===!1)return;t.isEnabled=!1;const e=()=>{t.element&&(t.element.style.display="none"),t.onDisable?.(this)},s=t.onBeforeDisable?.(this);s&&typeof s.then=="function"?Promise.resolve(s).then(e).catch(i=>{console.error(`[Supermouse] Plugin '${t.name}' onBeforeDisable threw:`,i),e()}):e()}scopeDeactivatePlugin(t){const e=()=>{t.element&&(t.element.style.display="none"),t.onDisable?.(this)},s=t.onBeforeDisable?.(this);s&&typeof s.then=="function"?Promise.resolve(s).then(e).catch(i=>{console.error(`[Supermouse] Plugin '${t.name}' onBeforeDisable threw:`,i),e()}):e()}scopeActivatePlugin(t){t.element&&(t.element.style.display=""),t.onEnable?.(this)}rebuildStylesheet(){const t=[":where(.supermouse-scope .supermouse-scope) { cursor: auto }"];for(const e of this._scopes)t.push(...e.buildRules());M(this.styleOwner,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,this.crashedPlugins.push(t)}}cleanupCrashedPlugins(){if(this.crashedPlugins.length!==0){for(const t of this.crashedPlugins){for(const e of this._scopes){const s=e.plugins.indexOf(t);if(s!==-1){e.plugins.splice(s,1);break}}try{t.onDisable?.(this),t.destroy?.(this)}catch(e){console.error(`[Supermouse] Failed to cleanup crashed plugin '${t.name}'.`,e)}t.element?.remove()}this.crashedPlugins=[]}}resolveStageVisibility(){return this.state.cursorMode==="native"?!1:this.state.cursorMode==="custom"||this.state.cursorMode==="both"?this.input.isEnabled&&this.state.hasReceivedInput:this.input.isEnabled&&!this.state.isNative&&this.state.hasReceivedInput}resolveCursorState(){return!this.input.isEnabled||this.state.cursorMode==="native"||this.state.cursorMode==="both"?"auto":this.state.cursorMode==="custom"?"none":this.state.isNative||!this.state.hasReceivedInput?"auto":"none"}resetMotion(){this.state.velocity={x:0,y:0},this.state.displacement={x:0,y:0}}update(t){const e=t-this.lastTime,s=Math.min(e/1e3,.1);this.lastTime=t;const i=this.input.getCurrentTarget();i&&!i.isConnected?this.input.clearHover():i&&this.input.parseDOMInteraction(i),this.input.isEnabled&&this.state.hasReceivedInput&&(this.state.target.x=this.state.pointer.x,this.state.target.y=this.state.pointer.y);const h=this._activeScope;if(h){h.stage.setVisibility(this.resolveStageVisibility()),this.input.isEnabled&&h.stage.setNativeCursor(this.resolveCursorState());for(let o=0;o<h.plugins.length;o++)this.runPluginSafe(h.plugins[o],e)}if(this.cleanupCrashedPlugins(),this.input.isEnabled){const o=this.state.reducedMotion?1e3:1/this.options.smoothness*2,n=this.state.smooth.x,f=this.state.smooth.y;this.state.smooth.x=C(this.state.smooth.x,this.state.target.x,o,s),this.state.smooth.y=C(this.state.smooth.y,this.state.target.y,o,s),this.state.displacement.x=this.state.target.x-this.state.smooth.x,this.state.displacement.y=this.state.target.y-this.state.smooth.y,s>0?(this.state.velocity.x=(this.state.smooth.x-n)/s,this.state.velocity.y=(this.state.smooth.y-f)/s):(this.state.velocity.x=0,this.state.velocity.y=0);const{x:c,y:r}=this.state.velocity;(Math.abs(c)>.1||Math.abs(r)>.1)&&(this.state.angle=Math.atan2(r,c)*(180/Math.PI))}}tick=t=>{this.update(t),this._running&&(this.rafId=requestAnimationFrame(this.tick))};bindVisibilityHandling(){document.addEventListener("visibilitychange",()=>{this._running&&(document.hidden?cancelAnimationFrame(this.rafId):(this.lastTime=performance.now(),this.rafId=requestAnimationFrame(this.tick)))},{signal:this.visibilityAbortController.signal})}startLoop(){this._running||(this._running=!0,!document.hidden&&(this.lastTime=performance.now(),this.rafId=requestAnimationFrame(this.tick)))}addHoverSelectors(t){const e=this._installingScope??this._activeScope??this._scopes[0];if(e)for(const s of t.split(",")){const i=s.trim();i&&e.hoverSelectors.add(i)}}}u.DEFAULT_HOVER_SELECTORS=b,u.DEFAULT_NATIVE_CURSOR_SELECTORS=S,u.Supermouse=I,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supermousejs/core",
3
- "version": "2.5.0-beta.4",
3
+ "version": "2.5.0-beta.5",
4
4
  "sideEffects": false,
5
5
  "files": [
6
6
  "dist",