@supermousejs/core 2.5.0-beta.3 → 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,52 @@
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
+
30
+ ## 2.5.0-beta.4
31
+
32
+ ### Patch Changes
33
+
34
+ - cb9bf9a: Beta.4
35
+
36
+ **Added**
37
+ - `state.authoredCursor` — the cursor value the page intends at the element under the pointer, resolved as if Supermouse's suppression were not active. Populated on every pointerTarget change, in all cursor modes. Read this to know what the page wants (`canvas { cursor: crosshair }`, `.drag-handle { cursor: grab }`, `[disabled] { cursor: not-allowed }`) rather than just whether the OS cursor should be shown (`state.isNative`).
38
+ - `state.pointerTarget` — the raw element under the pointer, regardless of hover selectors. Distinct from `state.hoverTarget`, which reports the nearest ancestor matching a hover selector.
39
+
40
+ **Fixed**
41
+ - Cursor detection in `auto` mode no longer reads its own suppression output. The previous implementation called `getComputedStyle(target).cursor` while the scope's hide class was active, so it always saw `"none"` and never triggered native fallback for authored exotic cursors. A probe attribute (`data-sm-probe`) is now excluded from every generated suppression rule, so the read sees the page's intended value.
42
+
43
+ **Changed**
44
+ - `@supermousejs/labs`: `SmartIcon` now reads `state.pointerTarget` and `state.authoredCursor` instead of relying on a broad hover-selector sweep. Hovering a `<p>` no longer sets `state.isHover` on the primary scope.
45
+ - `@supermousejs/labs`: `SmartIconOptions.useSemanticTags` renamed to `useSemanticDetection`. The old name referred to a `registerHoverTarget` sweep that no longer runs.
46
+
47
+ **Internal**
48
+ - Size budget raised to 6.5 kB gzip / 5.75 kB brotli to accommodate the beta.4 surface. Will be re-tightened once the shape settles.
49
+
3
50
  ## 2.5.0-beta.3
4
51
 
5
52
  ### 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.
15
- */
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 `[]`.
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.
24
19
  */
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.
@@ -74,6 +62,16 @@ export declare interface MouseState {
74
62
  isNative: boolean;
75
63
  /** Current cursor mode: auto, custom, native, or both. */
76
64
  cursorMode: CursorMode;
65
+ /**
66
+ * The raw element currently under the pointer, regardless of whether it
67
+ * matches any hover selector.
68
+ */
69
+ pointerTarget: HTMLElement | null;
70
+ /**
71
+ * The authored cursor value at `pointerTarget`, resolved as if
72
+ * Supermouse's suppression were not active.
73
+ */
74
+ authoredCursor: string | null;
77
75
  /** Currently hovered DOM element, if any. */
78
76
  hoverTarget: HTMLElement | null;
79
77
  /** User has `prefers-reduced-motion` enabled. */
@@ -116,8 +114,11 @@ export declare interface ScopeConfig {
116
114
  cursor?: CursorMode;
117
115
  /** Hover selectors for this scope. Inherits from top-level if omitted. */
118
116
  hoverSelectors?: string[];
119
- /** Custom cursor policy for this scope. Inherits from top-level if omitted. */
120
- 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[];
121
122
  /** Plugins installed when this scope activates. */
122
123
  plugins?: SupermousePlugin[];
123
124
  /**
@@ -303,12 +304,21 @@ export declare class Supermouse {
303
304
  private bindVisibilityHandling;
304
305
  private startLoop;
305
306
  /**
306
- * Adds one or more hover selectors to the current scope. Intended for
307
- * raw-object plugins during `install`. Plugins written with `definePlugin`
308
- * should use the `selector` option instead, and consumers setting up a
309
- * 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.
310
320
  */
311
- registerHoverTarget(selector: string): void;
321
+ addHoverSelectors(selectors: string): void;
312
322
  }
313
323
 
314
324
  export declare type SupermouseInstance = Supermouse;
@@ -344,10 +354,8 @@ export declare interface SupermouseOptions {
344
354
  * @default "auto"
345
355
  */
346
356
  cursor?: CursorMode;
347
- /**
348
- * Custom cursor policy. Defaults to `DEFAULT_CURSOR_POLICY` from `./policy`.
349
- */
350
- cursorPolicy?: CursorPolicyInput;
357
+ /** Native cursor selectors for this scope. Inherits from top-level if omitted. */
358
+ nativeCursorSelectors?: string[];
351
359
  /**
352
360
  * Additional scopes registered at construction. The primary scope is
353
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 P {
1
+ let S = 0;
2
+ class b {
23
3
  element;
24
4
  scopeClass;
25
5
  hideClass;
@@ -37,7 +17,7 @@ class P {
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 P {
92
72
  this._detach();
93
73
  }
94
74
  }
95
- class C {
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 P(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 C {
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 C {
159
136
  return this.nativeSelectors.join(", ");
160
137
  }
161
138
  buildRules() {
162
- const t = this.stage.getRulePrefix(), e = this.stage.getExclusion(), s = [
163
- `${t}${e} { cursor: none !important; }`,
164
- `${t} *${e} { cursor: none !important; }`
165
- ];
166
- for (const i of this.hideSelectors)
167
- s.push(`${t} ${i}${e} { cursor: none !important; }`);
168
- return s.push(
169
- `${t} input[type="range"]${e}::-webkit-slider-thumb { cursor: none !important; }`
170
- ), s.push(
139
+ const t = this.stage.getRulePrefix(), e = this.stage.getExclusion(), s = ":not([data-sm-probe])";
140
+ return [
141
+ `${t}${e}${s} { cursor: none !important; }`,
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
- ), s;
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 {
@@ -290,8 +265,17 @@ class w {
290
265
  return !1;
291
266
  }
292
267
  }
293
- resolveComputedCursor(t) {
294
- return window.getComputedStyle(t).cursor;
268
+ /**
269
+ * Reads the element's authored cursor value without our own suppression
270
+ * interfering.
271
+ */
272
+ resolveAuthoredCursor(t) {
273
+ t.setAttribute("data-sm-probe", "");
274
+ try {
275
+ return window.getComputedStyle(t).cursor;
276
+ } finally {
277
+ t.removeAttribute("data-sm-probe");
278
+ }
295
279
  }
296
280
  handleMove = (t) => {
297
281
  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)));
@@ -317,23 +301,25 @@ class w {
317
301
  this.clearHover(), this.state.isNative = !0, this.nativeTarget = t;
318
302
  return;
319
303
  }
320
- this.state.isNative = !1, this.nativeTarget = null, this.currentTarget = t, this.parseDOMInteraction(t);
321
- const e = t.closest(this.activeScope.hoverSelectorString);
322
- if (e && (this.state.isHover = !0, this.state.hoverTarget = e), this.state.cursorMode === "auto") {
304
+ this.state.isNative = !1, this.nativeTarget = null, this.currentTarget = t, this.state.pointerTarget = t;
305
+ const e = this.resolveAuthoredCursor(t);
306
+ this.state.authoredCursor = e, this.parseDOMInteraction(t);
307
+ const s = t.closest(this.activeScope.hoverSelectorString);
308
+ if (s && (this.state.isHover = !0, this.state.hoverTarget = s), this.state.cursorMode === "auto") {
323
309
  if (this.matchesSelector(t, this.activeScope.nativeSelectorString)) {
324
310
  this.state.isNative = !0, this.nativeTarget = t;
325
311
  return;
326
312
  }
327
- (t.isContentEditable || !E.has(this.resolveComputedCursor(t))) && (this.state.isNative = !0, this.nativeTarget = t);
313
+ (t.isContentEditable || !C.has(e)) && (this.state.isNative = !0, this.nativeTarget = t);
328
314
  }
329
315
  }
330
316
  handleMouseOut = (t) => {
331
317
  if (!this.isEnabled) return;
332
318
  const e = t.target, s = t.relatedTarget;
333
- (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.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 });
334
320
  };
335
321
  clearHover() {
336
- 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.cachedChain = [];
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 = [];
337
323
  }
338
324
  getCurrentTarget() {
339
325
  return this.currentTarget;
@@ -346,27 +332,27 @@ class w {
346
332
  this.abortController.abort(), this.cachedChain = [];
347
333
  }
348
334
  }
349
- let x = 0;
335
+ let w = 0;
350
336
  const f = /* @__PURE__ */ new Map();
351
- function M() {
352
- return x++;
337
+ function x() {
338
+ return w++;
353
339
  }
354
- function T(n, t) {
355
- let e = f.get(n);
356
- 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(`
357
343
  `);
358
344
  }
359
- function A(n) {
360
- f.get(n)?.remove(), f.delete(n);
345
+ function M(a) {
346
+ f.get(a)?.remove(), f.delete(a);
361
347
  }
362
- const g = "2.5.0-beta.3";
363
- function D(n, t, e) {
364
- 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;
365
351
  }
366
- function m(n, t, e, s) {
367
- 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));
368
354
  }
369
- class I {
355
+ class D {
370
356
  static version = g;
371
357
  version = g;
372
358
  state;
@@ -395,7 +381,7 @@ class I {
395
381
  zIndex: 9999,
396
382
  inheritDataAttributes: !0,
397
383
  ...t
398
- }, this.styleOwner = M(), this.state = {
384
+ }, this.styleOwner = x(), this.state = {
399
385
  pointer: { ...d },
400
386
  target: { ...d },
401
387
  smooth: { ...d },
@@ -406,7 +392,9 @@ class I {
406
392
  isHover: !1,
407
393
  isNative: !1,
408
394
  cursorMode: this.options.cursor,
395
+ pointerTarget: null,
409
396
  hoverTarget: null,
397
+ authoredCursor: null,
410
398
  reducedMotion: !1,
411
399
  hasReceivedInput: !1,
412
400
  shape: null,
@@ -417,12 +405,12 @@ class I {
417
405
  container: this.options.container,
418
406
  cursor: this.options.cursor,
419
407
  hoverSelectors: this.options.hoverSelectors,
420
- cursorPolicy: this.options.cursorPolicy,
408
+ nativeCursorSelectors: this.options.nativeCursorSelectors,
421
409
  plugins: this.options.plugins,
422
410
  rules: this.options.rules,
423
411
  zIndex: this.options.zIndex
424
412
  });
425
- if (this.input = new w(
413
+ if (this.input = new _(
426
414
  this.state,
427
415
  this.options,
428
416
  (s) => {
@@ -541,7 +529,7 @@ class I {
541
529
  }
542
530
  t.stage.destroy();
543
531
  }
544
- 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);
545
533
  }
546
534
  // ─── Internal ───
547
535
  _running = !1;
@@ -556,10 +544,10 @@ class I {
556
544
  break;
557
545
  }
558
546
  }
559
- const e = new C(t, {
547
+ const e = new y(t, {
560
548
  cursor: this.options.cursor,
561
- hoverSelectors: this.options.hoverSelectors ?? _,
562
- cursorPolicy: this.options.cursorPolicy ? b(this.options.cursorPolicy) : S,
549
+ hoverSelectors: this.options.hoverSelectors ?? E,
550
+ nativeCursorSelectors: this.options.nativeCursorSelectors ?? P,
563
551
  zIndex: this.options.zIndex,
564
552
  inheritDataAttributes: this.options.inheritDataAttributes,
565
553
  ruleEntries: this.options.rules ? Object.entries(this.options.rules) : []
@@ -692,20 +680,20 @@ class I {
692
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");
693
681
  }
694
682
  removePluginFromScope(t, e) {
695
- const s = t.plugins.findIndex((o) => o.name === e);
683
+ const s = t.plugins.findIndex((n) => n.name === e);
696
684
  if (s === -1) return;
697
685
  const i = t.plugins[s];
698
686
  t.plugins.splice(s, 1);
699
687
  const h = () => {
700
688
  try {
701
689
  i.onDisable?.(this), i.destroy?.(this);
702
- } catch (o) {
703
- console.error(`[Supermouse] Plugin '${e}' cleanup threw:`, o);
690
+ } catch (n) {
691
+ console.error(`[Supermouse] Plugin '${e}' cleanup threw:`, n);
704
692
  }
705
693
  i.element?.remove();
706
- }, r = i.onBeforeDisable?.(this);
707
- r && typeof r.then == "function" ? Promise.resolve(r).then(h).catch((o) => {
708
- 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();
709
697
  }) : h();
710
698
  }
711
699
  handleActiveScopeChange(t) {
@@ -798,14 +786,14 @@ class I {
798
786
  const h = this._activeScope;
799
787
  if (h) {
800
788
  h.stage.setVisibility(this.resolveStageVisibility()), this.input.isEnabled && h.stage.setNativeCursor(this.resolveCursorState());
801
- for (let r = 0; r < h.plugins.length; r++)
802
- this.runPluginSafe(h.plugins[r], e);
789
+ for (let o = 0; o < h.plugins.length; o++)
790
+ this.runPluginSafe(h.plugins[o], e);
803
791
  }
804
792
  if (this.cleanupCrashedPlugins(), this.input.isEnabled) {
805
- const r = this.state.reducedMotion ? 1e3 : 1 / this.options.smoothness * 2, o = this.state.smooth.x, p = this.state.smooth.y;
806
- 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);
807
- const { x: c, y: a } = this.state.velocity;
808
- (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));
809
797
  }
810
798
  }
811
799
  tick = (t) => {
@@ -824,12 +812,21 @@ class I {
824
812
  this._running || (this._running = !0, !document.hidden && (this.lastTime = performance.now(), this.rafId = requestAnimationFrame(this.tick)));
825
813
  }
826
814
  /**
827
- * Adds one or more hover selectors to the current scope. Intended for
828
- * raw-object plugins during `install`. Plugins written with `definePlugin`
829
- * should use the `selector` option instead, and consumers setting up a
830
- * 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.
831
828
  */
832
- registerHoverTarget(t) {
829
+ addHoverSelectors(t) {
833
830
  const e = this._installingScope ?? this._activeScope ?? this._scopes[0];
834
831
  if (e)
835
832
  for (const s of t.split(",")) {
@@ -839,7 +836,7 @@ class I {
839
836
  }
840
837
  }
841
838
  export {
842
- S as DEFAULT_CURSOR_POLICY,
843
- _ as DEFAULT_HOVER_SELECTORS,
844
- I as Supermouse
839
+ E as DEFAULT_HOVER_SELECTORS,
840
+ P as DEFAULT_NATIVE_CURSOR_SELECTORS,
841
+ D as Supermouse
845
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=[`${t}${e} { cursor: none !important; }`,`${t} *${e} { cursor: none !important; }`];for(const i of this.hideSelectors)s.push(`${t} ${i}${e} { cursor: none !important; }`);return s.push(`${t} input[type="range"]${e}::-webkit-slider-thumb { cursor: none !important; }`),s.push(`${t} input[type="range"]${e}::-moz-range-thumb { cursor: none !important; }`),s}}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}}resolveComputedCursor(t){return window.getComputedStyle(t).cursor}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.parseDOMInteraction(t);const e=t.closest(this.activeScope.hoverSelectorString);if(e&&(this.state.isHover=!0,this.state.hoverTarget=e),this.state.cursorMode==="auto"){if(this.matchesSelector(t,this.activeScope.nativeSelectorString)){this.state.isNative=!0,this.nativeTarget=t;return}(t.isContentEditable||!w.has(this.resolveComputedCursor(t)))&&(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.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.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.3";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,hoverTarget: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.3",
3
+ "version": "2.5.0-beta.5",
4
4
  "sideEffects": false,
5
5
  "files": [
6
6
  "dist",