@supermousejs/core 2.4.3 → 2.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,11 +1,126 @@
1
- const c = "2.4.3";
2
- function p(o, t, e) {
3
- return o + (t - o) * e;
1
+ const w = {
2
+ rules: [
3
+ { selector: "input", native: !0, hide: !0 },
4
+ { selector: "textarea", native: !0, hide: !0 },
5
+ { selector: "select", native: !0, hide: !0 },
6
+ { selector: "[contenteditable]", native: !0, hide: !0 },
7
+ { selector: "a", native: !1, hide: !0 },
8
+ { selector: "button", native: !1, hide: !0 },
9
+ { selector: '[role="button"]', native: !1, hide: !0 },
10
+ { selector: "[tabindex]", native: !1, hide: !0 }
11
+ ]
12
+ };
13
+ function x(i) {
14
+ if (!i) return w;
15
+ if ("rules" in i) return i;
16
+ const t = [], e = new Set(i.native ?? []), s = new Set(i.hide ?? []), n = /* @__PURE__ */ new Set([...e, ...s]);
17
+ for (const r of n)
18
+ t.push({
19
+ selector: r,
20
+ native: e.has(r),
21
+ hide: s.has(r)
22
+ });
23
+ return { rules: t };
4
24
  }
5
- function d(o, t, e, s) {
6
- return p(o, t, 1 - Math.exp(-e * s));
25
+ function T(i) {
26
+ const t = [], e = [];
27
+ for (const s of i.rules)
28
+ s.native !== !1 && t.push(s.selector), s.hide !== !1 && e.push(s.selector);
29
+ return { native: t, hide: e };
7
30
  }
8
- const h = { x: -100, y: -100 }, g = /* @__PURE__ */ new Set(["input", "textarea", "select"]), f = /* @__PURE__ */ new Set([
31
+ let L = 0;
32
+ class A {
33
+ constructor(t, e) {
34
+ if (this.container = t, !t || !(t instanceof HTMLElement))
35
+ throw new Error(`[Supermouse] Invalid container: ${t}. Must be an HTMLElement.`);
36
+ t.isConnected;
37
+ const s = L++;
38
+ this.scopeClass = `supermouse-scope-${s}`, this.hideClass = `supermouse-hide-${s}`;
39
+ const n = t === document.body;
40
+ if (this.element = document.createElement("div"), Object.assign(this.element.style, {
41
+ position: n ? "fixed" : "absolute",
42
+ inset: "0px",
43
+ pointerEvents: "none",
44
+ zIndex: String(e),
45
+ opacity: "1",
46
+ transition: "opacity 0.15s ease"
47
+ }), !n) {
48
+ const r = window.getComputedStyle(t);
49
+ this.originalContainerPosition = r.position, r.position === "static" && (t.style.position = "relative");
50
+ }
51
+ this.originalContainerCursor = t.style.cursor, t.appendChild(this.element), t.classList.add("supermouse-scope", this.scopeClass);
52
+ }
53
+ container;
54
+ element;
55
+ scopeClass;
56
+ hideClass;
57
+ currentCursorState = null;
58
+ originalContainerPosition = "";
59
+ originalContainerCursor = "";
60
+ get containerElement() {
61
+ return this.container;
62
+ }
63
+ /** Full selector prefix: `.supermouse-scope-N.supermouse-hide-N`. */
64
+ getRulePrefix() {
65
+ return `.${this.scopeClass}.${this.hideClass}`;
66
+ }
67
+ /** CSS `:not()` chain that prevents the rules from leaking into nested scopes. */
68
+ getExclusion() {
69
+ return `:not(.${this.scopeClass} .supermouse-scope):not(.${this.scopeClass} .supermouse-scope *)`;
70
+ }
71
+ setVisibility(t) {
72
+ this.element.style.opacity = t ? "1" : "0";
73
+ }
74
+ setNativeCursor(t) {
75
+ t !== this.currentCursorState && (this.currentCursorState = t, this.container.classList.toggle(this.hideClass, t === "none"), t === "none" ? this.container.style.cursor = "none" : this.container === document.body ? this.container.style.cursor = this.originalContainerCursor : this.container.style.cursor = this.originalContainerCursor || "auto");
76
+ }
77
+ destroy() {
78
+ this.element.remove(), this.container.style.cursor = this.originalContainerCursor, this.container.classList.remove("supermouse-scope", this.scopeClass, this.hideClass), this.container !== document.body && this.originalContainerPosition === "static" && (this.container.style.position = "");
79
+ }
80
+ }
81
+ class I {
82
+ constructor(t, e) {
83
+ 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;
84
+ const s = t.cursorPolicy ? x(t.cursorPolicy) : e.cursorPolicy, n = T(s);
85
+ this.nativeSelectors = n.native, this.hideSelectors = n.hide, this.stage = new A(t.container, t.zIndex ?? e.zIndex);
86
+ }
87
+ config;
88
+ stage;
89
+ hoverSelectors;
90
+ plugins = [];
91
+ nativeSelectors;
92
+ hideSelectors;
93
+ inheritDataAttributes;
94
+ name;
95
+ cursorMode;
96
+ contains(t) {
97
+ return this.stage.containerElement.contains(t);
98
+ }
99
+ get container() {
100
+ return this.stage.containerElement;
101
+ }
102
+ get hoverSelectorString() {
103
+ return Array.from(this.hoverSelectors).join(", ");
104
+ }
105
+ get nativeSelectorString() {
106
+ return this.nativeSelectors.join(", ");
107
+ }
108
+ /** Build the CSS rules this scope contributes to the shared stylesheet. */
109
+ buildRules() {
110
+ const t = this.stage.getRulePrefix(), e = this.stage.getExclusion(), s = [
111
+ `${t}${e} { cursor: none !important; }`,
112
+ `${t} *${e} { cursor: none !important; }`
113
+ ];
114
+ for (const n of this.hideSelectors)
115
+ s.push(`${t} ${n}${e} { cursor: none !important; }`);
116
+ return s.push(`${t} label${e} { cursor: none !important; }`), s.push(`${t} select${e} { cursor: none !important; }`), s.push(
117
+ `${t} input[type="range"]${e}::-webkit-slider-thumb { cursor: none !important; }`
118
+ ), s.push(
119
+ `${t} input[type="range"]${e}::-moz-range-thumb { cursor: none !important; }`
120
+ ), s;
121
+ }
122
+ }
123
+ const f = { x: -100, y: -100 }, R = /* @__PURE__ */ new Set([
9
124
  "default",
10
125
  "auto",
11
126
  "pointer",
@@ -13,40 +128,99 @@ const h = { x: -100, y: -100 }, g = /* @__PURE__ */ new Set(["input", "textarea"
13
128
  "inherit",
14
129
  "grab",
15
130
  "grabbing"
16
- ]), m = [
131
+ ]), D = [
17
132
  "a",
18
133
  "button",
19
134
  "input",
20
135
  "textarea",
21
136
  "[data-hover]",
22
137
  "[data-cursor]"
23
- ];
24
- class v {
25
- constructor(t, e, s, i) {
26
- this.state = t, this.options = e, this.getHoverSelector = s, this.onEnableChange = i, this.dataPrefix = this.options.dataPrefix ?? "supermouse", this.normalizedDataPrefix = this.dataPrefix.toLowerCase(), this.ignoreAttribute = `data-${this.dataPrefix}-ignore`, this.ruleEntries = this.options.rules ? Object.entries(this.options.rules) : [], this.checkDeviceCapability(), this.checkMotionPreference(), this.setupContainerRectTracking(), this.bindEvents();
138
+ ], u = /* @__PURE__ */ new Map(), _ = /* @__PURE__ */ new Map();
139
+ let v = null, g = !1, m = !1, S = 0;
140
+ function C(i) {
141
+ const t = i.getBoundingClientRect();
142
+ return { left: t.left, top: t.top, width: t.width, height: t.height };
143
+ }
144
+ function M(i) {
145
+ u.set(i, C(i));
146
+ const t = _.get(i);
147
+ if (t) for (const e of t) e();
148
+ }
149
+ function y() {
150
+ for (const i of u.keys()) M(i);
151
+ }
152
+ function O() {
153
+ v || typeof ResizeObserver > "u" || (v = new ResizeObserver((i) => {
154
+ for (const t of i) {
155
+ const e = t.target;
156
+ u.has(e) && M(e);
157
+ }
158
+ }));
159
+ }
160
+ function $() {
161
+ typeof window > "u" || (g || (window.addEventListener("scroll", y, { passive: !0, capture: !0 }), g = !0), m || (window.addEventListener("resize", y, { passive: !0 }), m = !0));
162
+ }
163
+ function z(i, t) {
164
+ return S++, u.has(i) || (u.set(i, C(i)), O(), v?.observe(i), $()), () => {
165
+ S--, S === 0 && (v?.disconnect(), v = null, u.clear(), _.clear(), g && (window.removeEventListener("scroll", y, { capture: !0 }), g = !1), m && (window.removeEventListener("resize", y), m = !1));
166
+ };
167
+ }
168
+ function k(i) {
169
+ let t = u.get(i);
170
+ return t || (t = C(i), u.set(i, t)), t;
171
+ }
172
+ function N(i, t, e) {
173
+ if (i === document.body) return { x: t, y: e };
174
+ const s = k(i);
175
+ return { x: t - s.left, y: e - s.top };
176
+ }
177
+ class B {
178
+ constructor(t, e, s, n) {
179
+ this.state = t, this.options = e, this.onEnableChange = s, this.onActiveScopeChange = n, this.dataPrefix = e.dataPrefix ?? "supermouse", this.normalizedDataPrefix = this.dataPrefix.toLowerCase(), this.ignoreAttribute = `data-${this.dataPrefix}-ignore`, this.ruleEntries = e.rules ? Object.entries(e.rules) : [], this.checkDeviceCapability(), this.checkMotionPreference(), this.bindEvents();
27
180
  }
28
181
  state;
29
182
  options;
30
- getHoverSelector;
31
183
  onEnableChange;
184
+ onActiveScopeChange;
185
+ abortController = new AbortController();
32
186
  mediaQueryList;
33
187
  motionQuery;
34
188
  dataPrefix;
35
189
  normalizedDataPrefix;
36
190
  ignoreAttribute;
37
- abortController = new AbortController();
191
+ activeScope = null;
192
+ scopeByContainer = /* @__PURE__ */ new Map();
38
193
  nativeTarget = null;
39
- hasSeenPointer = !1;
40
- isEnabled = !0;
41
- containerRect = null;
42
- resizeObserver;
43
- /** Cached matched rules for the current interaction element. */
44
- matchedRules = [];
194
+ currentTarget = null;
45
195
  lastParsedTarget = null;
46
- /** Precomputed list of rule entries for faster iteration. */
47
196
  ruleEntries;
48
- /** The actual element currently under the pointer (regardless of hover selectors). */
49
- currentTarget = null;
197
+ releaseViewport = null;
198
+ hasSeenPointer = !1;
199
+ isEnabled = !0;
200
+ viewportX = 0;
201
+ viewportY = 0;
202
+ /** Cached matched rules per ancestor. Recomputed only when the hover target changes. */
203
+ cachedChain = [];
204
+ setScopes(t) {
205
+ this.scopeByContainer.clear();
206
+ for (const e of t) this.scopeByContainer.set(e.container, e);
207
+ }
208
+ setActiveScope(t) {
209
+ t !== this.activeScope && (this.activeScope = t, this.releaseViewport?.(), this.releaseViewport = t && t.container !== document.body ? z(t.container) : null, 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));
210
+ }
211
+ findScope(t) {
212
+ let e = t;
213
+ for (; e; ) {
214
+ const s = this.scopeByContainer.get(e);
215
+ if (s) return s;
216
+ e = e.parentElement;
217
+ }
218
+ return null;
219
+ }
220
+ applyPointerToState() {
221
+ const t = this.activeScope?.container ?? document.body, e = N(t, this.viewportX, this.viewportY);
222
+ this.state.pointer.x = e.x, this.state.pointer.y = e.y;
223
+ }
50
224
  checkDeviceCapability() {
51
225
  this.options.autoDisableOnMobile && (this.mediaQueryList = window.matchMedia("(pointer: fine)"), this.updateEnabledState(this.mediaQueryList.matches), this.mediaQueryList.addEventListener("change", (t) => this.updateEnabledState(t.matches), {
52
226
  signal: this.abortController.signal
@@ -64,82 +238,56 @@ class v {
64
238
  updateEnabledState(t) {
65
239
  this.isEnabled = t, this.onEnableChange(t);
66
240
  }
67
- /** Caches container rect; updates on resize/scroll/ResizeObserver. */
68
- setupContainerRectTracking() {
69
- const t = this.options.container;
70
- if (!t || t === document.body) return;
71
- const e = () => {
72
- this.containerRect = t.getBoundingClientRect();
73
- };
74
- e(), window.addEventListener("resize", e, { signal: this.abortController.signal }), window.addEventListener("scroll", e, {
75
- passive: !0,
76
- signal: this.abortController.signal
77
- }), typeof ResizeObserver < "u" && (this.resizeObserver = new ResizeObserver(e), this.resizeObserver.observe(t));
78
- }
79
- /**
80
- * Evaluates rules against the given element.
81
- * Selector matching is cached per element; only function values re-evaluated each frame.
82
- */
83
241
  parseDOMInteraction(t) {
242
+ const e = this.activeScope?.container ?? document.body, s = this.activeScope?.inheritDataAttributes ?? !0, n = this.normalizedDataPrefix;
84
243
  if (t !== this.lastParsedTarget) {
85
- this.lastParsedTarget = t, this.matchedRules = [];
86
- for (const [i, n] of this.ruleEntries)
87
- this.matchesSelector(t, i) && this.matchedRules.push({ selector: i, rules: n });
244
+ this.lastParsedTarget = t, this.cachedChain = [];
245
+ let o = t;
246
+ for (; o; ) {
247
+ const d = [];
248
+ for (const [a, h] of this.ruleEntries)
249
+ this.matchesSelector(o, a) && d.push({ selector: a, rules: h });
250
+ if (this.cachedChain.push({ element: o, matchedRules: d }), !s || o === e) break;
251
+ const c = o.parentElement;
252
+ if (!c || !e.contains(c)) break;
253
+ o = c;
254
+ }
88
255
  }
89
- const e = {};
90
- for (const { rules: i } of this.matchedRules)
91
- try {
92
- const n = typeof i == "function" ? i(t) : i;
93
- if (!n || typeof n != "object") continue;
94
- for (const [r, a] of Object.entries(n))
95
- e[r] = typeof a == "function" ? a(t) : a;
96
- } catch (n) {
97
- console.error("[Supermouse] Rule threw during evaluation:", n);
256
+ const r = {};
257
+ for (const { element: o, matchedRules: d } of this.cachedChain) {
258
+ const c = {};
259
+ for (const { rules: a } of d)
260
+ try {
261
+ const h = typeof a == "function" ? a(o) : a;
262
+ if (!h || typeof h != "object") continue;
263
+ for (const [b, p] of Object.entries(h))
264
+ c[b] = typeof p == "function" ? p(o) : p;
265
+ } catch {
266
+ }
267
+ for (const a in o.dataset) {
268
+ if (!a.toLowerCase().startsWith(n)) continue;
269
+ const h = a.slice(n.length);
270
+ if (!h) continue;
271
+ const b = h[0].toLowerCase() + h.slice(1), p = o.dataset[a];
272
+ c[b] = p == null || p === "" ? !0 : p;
98
273
  }
99
- const s = this.normalizedDataPrefix;
100
- for (const i in t.dataset) {
101
- if (!i.toLowerCase().startsWith(s)) continue;
102
- const n = i.slice(s.length);
103
- if (!n) continue;
104
- const r = t.dataset[i];
105
- e[n[0].toLowerCase() + n.slice(1)] = r === "" ? !0 : r;
274
+ for (const [a, h] of Object.entries(c))
275
+ a in r || (r[a] = h);
106
276
  }
107
- this.state.interaction = e;
277
+ this.state.interaction = r;
108
278
  }
109
- /**
110
- * Optimized selector matching:
111
- * - For simple selectors (no spaces), just call `element.matches`.
112
- * - For complex selectors with descendant combinators, fallback to splitting.
113
- */
114
279
  matchesSelector(t, e) {
115
280
  try {
116
- if (!/\s/.test(e.trim()))
117
- return t.matches(e);
118
- } catch {
119
- }
120
- const s = e.trim().split(/\s+/);
121
- if (s.length < 2) return !1;
122
- const i = s.pop(), n = s.join(" ");
123
- if (!i || !n) return !1;
124
- try {
125
- return t.matches(i) && !!t.closest(n);
281
+ return t.matches(e);
126
282
  } catch {
127
283
  return !1;
128
284
  }
129
285
  }
130
- isOutsideContainer(t) {
131
- const { container: e } = this.options;
132
- return !!e && e !== document.body && !e.contains(t);
133
- }
134
286
  resolveComputedCursor(t) {
135
287
  return window.getComputedStyle(t).cursor;
136
288
  }
137
289
  handleMove = (t) => {
138
- if (this.options.autoDisableOnMobile && t.pointerType === "touch" && !this.options.enableTouch)
139
- return;
140
- let e = t.clientX, s = t.clientY;
141
- const i = this.options.container;
142
- i && this.containerRect && i !== document.body && (e -= this.containerRect.left, s -= this.containerRect.top), this.state.pointer.x = e, this.state.pointer.y = s, this.hasSeenPointer = !0, this.isEnabled && (this.state.hasReceivedInput || (this.state.hasReceivedInput = !0, this.state.target.x = this.state.smooth.x = e, this.state.target.y = this.state.smooth.y = s));
290
+ 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)));
143
291
  };
144
292
  handleDown = () => {
145
293
  this.isEnabled && (this.state.isDown = !0);
@@ -149,157 +297,72 @@ class v {
149
297
  };
150
298
  handleMouseOver = (t) => {
151
299
  if (!this.isEnabled) return;
152
- const e = t.target;
153
- if (this.isOutsideContainer(e)) return;
300
+ const e = t.target, s = this.findScope(e);
301
+ if (s && s !== this.activeScope && this.setActiveScope(s), !this.activeScope) return;
154
302
  if (this.state.cursorMode === "auto" && e.closest(`[${this.ignoreAttribute}]`)) {
155
- this.state.isHover = !1, this.state.hoverTarget = null, this.state.interaction = {}, this.currentTarget = null, this.lastParsedTarget = null, this.matchedRules = [], this.state.isNative = !0, this.nativeTarget = e;
303
+ this.clearHover(), this.state.isNative = !0, this.nativeTarget = e;
156
304
  return;
157
305
  }
158
- if (this.state.isNative = !1, this.nativeTarget = null, this.currentTarget = e, this.parseDOMInteraction(e), this.state.cursorMode !== "auto") {
159
- const r = e.closest(this.getHoverSelector());
160
- r && (this.state.isHover = !0, this.state.hoverTarget = r);
161
- return;
306
+ this.state.isNative = !1, this.nativeTarget = null, this.currentTarget = e, this.parseDOMInteraction(e);
307
+ const n = e.closest(this.activeScope.hoverSelectorString);
308
+ if (n && (this.state.isHover = !0, this.state.hoverTarget = n), this.state.cursorMode === "auto") {
309
+ if (this.matchesSelector(e, this.activeScope.nativeSelectorString)) {
310
+ this.state.isNative = !0, this.nativeTarget = e;
311
+ return;
312
+ }
313
+ (e.isContentEditable || !R.has(this.resolveComputedCursor(e))) && (this.state.isNative = !0, this.nativeTarget = e);
162
314
  }
163
- const s = e.closest(this.getHoverSelector());
164
- s && (this.state.isHover = !0, this.state.hoverTarget = s);
165
- const i = g.has(e.localName) || e.isContentEditable, n = !f.has(this.resolveComputedCursor(e));
166
- (i || n) && (this.state.isNative = !0, this.nativeTarget = e);
167
315
  };
168
316
  handleMouseOut = (t) => {
169
317
  if (!this.isEnabled) return;
170
318
  const e = t.target, s = t.relatedTarget;
171
- this.isOutsideContainer(e) || ((e === this.state.hoverTarget || e.contains(this.state.hoverTarget)) && (!s || !this.state.hoverTarget?.contains(s)) && (this.state.isHover = !1, this.state.hoverTarget = null), this.nativeTarget && (e === this.nativeTarget || e.contains(this.nativeTarget)) && (!s || !this.nativeTarget.contains(s)) && (this.state.isNative = !1, this.nativeTarget = null), e === this.currentTarget && (this.currentTarget = null, this.lastParsedTarget = null, this.matchedRules = []));
172
- };
173
- /**
174
- * Handles the case where the pointer leaves the entire document.
175
- * This is more reliable than the `mouseleave` event on `document`.
176
- */
177
- handleDocumentMouseOut = (t) => {
178
- this.isEnabled && t.relatedTarget === null && this.options.hideOnLeave && this.handleWindowLeave();
179
- };
180
- handleWindowLeave = () => {
181
- this.options.hideOnLeave && (this.state.hasReceivedInput = !1, this.state.pointer = { ...h });
319
+ (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 });
182
320
  };
183
321
  clearHover() {
184
- this.state.isHover = !1, this.state.hoverTarget = null, this.state.isNative = !1, this.nativeTarget = null, this.state.interaction = {}, this.currentTarget = null, this.lastParsedTarget = null, this.matchedRules = [];
322
+ 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 = [];
185
323
  }
186
- /** Returns the raw element currently under the pointer. */
187
324
  getCurrentTarget() {
188
325
  return this.currentTarget;
189
326
  }
190
327
  bindEvents() {
191
328
  const { signal: t } = this.abortController;
192
- 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 });
193
- const s = !this.options.container || this.options.container === document.body ? document : this.options.container;
194
- s.addEventListener("mouseover", this.handleMouseOver, { signal: t }), s.addEventListener("mouseout", this.handleMouseOut, { signal: t }), document.addEventListener("mouseout", this.handleDocumentMouseOut, { signal: t });
329
+ 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 });
195
330
  }
196
331
  destroy() {
197
- this.abortController.abort(), this.resizeObserver?.disconnect();
332
+ this.abortController.abort(), this.releaseViewport?.(), this.cachedChain = [];
198
333
  }
199
334
  }
200
- let b = 0;
201
- class y {
202
- constructor(t = document.body, e = 9999) {
203
- if (this.container = t, this.zIndex = e, !t || !(t instanceof HTMLElement))
204
- throw new Error(`[Supermouse] Invalid container: ${t}. Must be an HTMLElement.`);
205
- t.isConnected || console.warn(
206
- "[Supermouse] container is not attached to the document — stage sizing/positioning will be wrong until it is."
207
- );
208
- const s = b++;
209
- this.id = `supermouse-style-${s}`, this.scopeClass = `supermouse-scope-${s}`, this.hideClass = `supermouse-hide-${s}`;
210
- const i = t === document.body;
211
- if (this.element = document.createElement("div"), Object.assign(this.element.style, {
212
- position: i ? "fixed" : "absolute",
213
- inset: "0px",
214
- pointerEvents: "none",
215
- zIndex: String(this.zIndex),
216
- opacity: "1",
217
- transition: "opacity 0.15s ease"
218
- }), !i) {
219
- const n = window.getComputedStyle(t);
220
- this.originalContainerPosition = n.position, n.position === "static" && (t.style.position = "relative");
221
- }
222
- this.originalContainerCursor = t.style.cursor, t.appendChild(this.element), this.styleTag = document.createElement("style"), this.styleTag.id = this.id, document.head.appendChild(this.styleTag), this.container.classList.add("supermouse-scope", this.scopeClass), this.updateCursorCSS();
223
- }
224
- container;
225
- zIndex;
226
- element;
227
- styleTag;
228
- id;
229
- scopeClass;
230
- hideClass;
231
- currentCursorState = null;
232
- originalContainerPosition = "";
233
- originalContainerCursor = "";
234
- /** Selectors that need explicit `cursor: none !important` to override UA styles. */
235
- selectors = /* @__PURE__ */ new Set([
236
- "a",
237
- "button",
238
- "input",
239
- "textarea",
240
- "select",
241
- '[role="button"]',
242
- "[tabindex]"
243
- ]);
244
- /** Batch add selectors. Comma‑separated groups are split and scoped individually. */
245
- addSelectors(t) {
246
- let e = !1;
247
- for (const s of t)
248
- s.split(",").forEach((i) => {
249
- const n = i.trim();
250
- n && !this.selectors.has(n) && (this.selectors.add(n), e = !0);
251
- });
252
- e && this.updateCursorCSS();
253
- }
254
- /** Add a single selector (or comma‑separated group). */
255
- addSelector(t) {
256
- this.addSelectors([t]);
257
- }
258
- setVisibility(t) {
259
- this.element.style.opacity = t ? "1" : "0";
260
- }
261
- /** Toggle native cursor visibility. */
262
- setNativeCursor(t) {
263
- t !== this.currentCursorState && (this.currentCursorState = t, this.container.classList.toggle(this.hideClass, t === "none"), this.container.style.cursor = t === "none" ? "none" : this.originalContainerCursor);
264
- }
265
- updateCursorCSS() {
266
- const t = Array.from(this.selectors);
267
- if (t.length === 0) {
268
- this.styleTag.innerText = "";
269
- return;
270
- }
271
- const e = `:not(.${this.scopeClass} .supermouse-scope):not(.${this.scopeClass} .supermouse-scope *)`, s = (a) => `.${this.scopeClass}.${this.hideClass} ${a}${e} { cursor: none !important; }`, i = t.map(s).join(`
272
- `), n = `.${this.scopeClass}.${this.hideClass} *${e} { cursor: none !important; }`, r = `.${this.scopeClass}.${this.hideClass} { cursor: none !important; }`;
273
- this.styleTag.innerText = `
274
- ${r}
275
- ${n}
276
- ${i}
277
- ${s("label")}
278
- ${s("select")}
279
- ${s('input[type="range"]::-webkit-slider-thumb')}
280
- ${s('input[type="range"]::-moz-range-thumb')}
281
- `;
282
- }
283
- destroy() {
284
- this.element.remove(), this.styleTag.remove(), this.container.style.cursor = this.originalContainerCursor, this.container.classList.remove("supermouse-scope", this.scopeClass, this.hideClass), this.container !== document.body && this.originalContainerPosition === "static" && (this.container.style.position = "");
285
- }
335
+ let l = null;
336
+ function H() {
337
+ l || typeof document > "u" || (l = document.createElement("style"), l.id = "supermouse-styles", document.head.appendChild(l));
338
+ }
339
+ function V(i) {
340
+ H(), l && (l.textContent = i.join(`
341
+ `));
342
+ }
343
+ function F() {
344
+ l?.remove(), l = null;
345
+ }
346
+ const E = "2.5.0-beta.1";
347
+ function j(i, t, e) {
348
+ return i + (t - i) * e;
349
+ }
350
+ function P(i, t, e, s) {
351
+ return j(i, t, 1 - Math.exp(-e * s));
286
352
  }
287
- class C {
288
- static version = c;
289
- version = c;
353
+ class Q {
354
+ static version = E;
355
+ version = E;
290
356
  state;
291
- /** Configuration options, fully resolved with defaults applied. */
292
357
  options;
293
- plugins = [];
294
- _stage;
358
+ _scopes = [];
359
+ _activeScope = null;
360
+ _installingScope = null;
361
+ scopeByContainer = /* @__PURE__ */ new Map();
295
362
  input;
296
363
  rafId = 0;
297
364
  lastTime = 0;
298
- isRunning = !1;
299
- isSuspended = !1;
300
365
  visibilityAbortController = new AbortController();
301
- hoverSelectors;
302
- hoverSelectorString;
303
366
  crashedPlugins = [];
304
367
  constructor(t = {}) {
305
368
  this.options = {
@@ -312,11 +375,12 @@ class C {
312
375
  container: document.body,
313
376
  dataPrefix: "supermouse",
314
377
  zIndex: 9999,
378
+ inheritDataAttributes: !0,
315
379
  ...t
316
380
  }, this.state = {
317
- pointer: { ...h },
318
- target: { ...h },
319
- smooth: { ...h },
381
+ pointer: { ...f },
382
+ target: { ...f },
383
+ smooth: { ...f },
320
384
  velocity: { x: 0, y: 0 },
321
385
  displacement: { x: 0, y: 0 },
322
386
  angle: 0,
@@ -329,130 +393,191 @@ class C {
329
393
  hasReceivedInput: !1,
330
394
  shape: null,
331
395
  interaction: {}
332
- }, this.hoverSelectors = new Set(this.options.hoverSelectors ?? m), this.hoverSelectorString = Array.from(this.hoverSelectors).join(", "), this._stage = new y(this.options.container, this.options.zIndex), this._stage.addSelectors(this.hoverSelectors), this.input = new v(
396
+ };
397
+ const e = this.createScope({
398
+ container: this.options.container,
399
+ cursor: this.options.cursor,
400
+ hoverSelectors: this.options.hoverSelectors,
401
+ cursorPolicy: this.options.cursorPolicy,
402
+ plugins: this.options.plugins,
403
+ zIndex: this.options.zIndex
404
+ });
405
+ if (this.input = new B(
333
406
  this.state,
334
407
  this.options,
335
- () => this.hoverSelectorString,
336
- (e) => {
337
- e || this.reset(!0);
338
- }
339
- ), this.options.plugins?.forEach((e) => this.use(e)), this.bindVisibilityHandling(), this.init();
408
+ (s) => {
409
+ s || this.reset();
410
+ },
411
+ (s) => this.handleActiveScopeChange(s)
412
+ ), this.input.setScopes(this._scopes), this.input.setActiveScope(e), this.installPlugins(e), this.options.scopes)
413
+ for (const s of this.options.scopes) this.addScope(s);
414
+ this.rebuildStylesheet(), this.bindVisibilityHandling(), this.options.autoStart && this.startLoop();
340
415
  }
341
- /** Look up a registered plugin by name. */
342
- getPlugin(t) {
343
- return this.plugins.find((e) => e.name === t);
416
+ // ─── Public API ───
417
+ get container() {
418
+ return (this._activeScope ?? this._scopes[0]).container;
419
+ }
420
+ get stage() {
421
+ return (this._installingScope ?? this._activeScope ?? this._scopes[0]).stage.element;
344
422
  }
345
- /** Whether the instance is not disabled/suspended and is processing input. */
346
423
  get isEnabled() {
347
424
  return this.input.isEnabled;
348
425
  }
349
- /** Enable a plugin by name. */
426
+ get isRunning() {
427
+ return this._running;
428
+ }
429
+ use(t) {
430
+ return this.installPlugin(this._scopes[0], t), this;
431
+ }
432
+ getPlugin(t) {
433
+ for (const e of this._scopes) {
434
+ const s = e.plugins.find((n) => n.name === t);
435
+ if (s) return s;
436
+ }
437
+ }
350
438
  enablePlugin(t) {
351
439
  const e = this.getPlugin(t);
352
440
  e && e.isEnabled === !1 && (e.isEnabled = !0, e.element && (e.element.style.display = ""), e.onEnable?.(this));
353
441
  }
354
- /** Disable a plugin by name and hide its element. */
355
442
  disablePlugin(t) {
356
443
  const e = this.getPlugin(t);
357
- if (e && e.isEnabled !== !1) {
358
- e.isEnabled = !1;
359
- const s = () => {
360
- e.element && (e.element.style.display = "none"), e.onDisable?.(this);
361
- }, i = e.onBeforeDisable?.(this);
362
- i && typeof i.then == "function" ? Promise.resolve(i).then(s).catch((n) => {
363
- console.error(`[Supermouse] Plugin '${e.name}' onBeforeDisable threw:`, n), s();
364
- }) : s();
365
- }
444
+ e && e.isEnabled !== !1 && this.deactivatePlugin(e);
366
445
  }
367
- /** Toggle a plugin's enabled state by name. */
368
446
  togglePlugin(t) {
369
447
  const e = this.getPlugin(t);
370
448
  e && (e.isEnabled === !1 ? this.enablePlugin(t) : this.disablePlugin(t));
371
449
  }
372
- /** Add a selector to hover detection and cursor suppression. */
373
- registerHoverTarget(t) {
374
- this.hoverSelectors.has(t) || (this.hoverSelectors.add(t), this.hoverSelectorString = Array.from(this.hoverSelectors).join(", "), this._stage.addSelector(t));
450
+ setCursor(t) {
451
+ this._activeScope && (this._activeScope.cursorMode = t, this.state.cursorMode = t);
452
+ }
453
+ addScope(t) {
454
+ const e = this.createScope(t);
455
+ return this.input.setScopes(this._scopes), this.installPlugins(e), this.rebuildStylesheet(), {
456
+ name: e.name,
457
+ container: e.container,
458
+ remove: () => this.removeScope(e),
459
+ setCursor: (s) => this.setScopeCursor(e, s)
460
+ };
375
461
  }
376
- /** The DOM element the instance is scoped to. */
377
- get container() {
378
- return this.options.container;
462
+ enable() {
463
+ 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());
379
464
  }
380
- /** The stage element that plugins append their visuals into. */
381
- get stage() {
382
- return this._stage.element;
465
+ disable(t) {
466
+ this.input.isEnabled = !1, this._activeScope && (this._activeScope.stage.setNativeCursor("auto"), this._activeScope.stage.setVisibility(!1)), t?.reset && this.reset();
383
467
  }
384
- /** Set the current cursor mode. */
385
- setCursor(t) {
386
- this.state.cursorMode = t;
468
+ reset() {
469
+ 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 = {};
387
470
  }
388
- init() {
389
- this.options.autoStart && this.startLoop();
471
+ start() {
472
+ this.startLoop();
390
473
  }
391
- /** Re‑enable input processing and re‑apply cursor state. */
392
- enable() {
393
- 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._stage.setNativeCursor(this.resolveCursorState());
394
- }
395
- /** Disable input processing and restore native cursor. */
396
- disable() {
397
- this.input.isEnabled = !1, this._stage.setNativeCursor("auto"), this._stage.setVisibility(!1), this.reset(!0);
398
- }
399
- /** Temporarily yield to a scoped instance. */
400
- suspend() {
401
- this.input.isEnabled && (this.isSuspended = !0, this.input.isEnabled = !1, this.input.clearHover(), this._stage.setVisibility(!1));
402
- }
403
- /** Resume from `suspend()`. */
404
- resume() {
405
- if (this.isSuspended) {
406
- this.isSuspended = !1, this.input.isEnabled = !0, this.state.hasReceivedInput && (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());
407
- for (let t = this.plugins.length - 1; t >= 0; t--)
408
- this.runPluginSafe(this.plugins[t], 0);
409
- this._stage.setVisibility(!0);
474
+ step(t) {
475
+ this.update(t);
476
+ }
477
+ destroy() {
478
+ this._running = !1, cancelAnimationFrame(this.rafId), this.visibilityAbortController.abort(), this.input.destroy();
479
+ for (const t of this._scopes) {
480
+ for (const e of t.plugins)
481
+ try {
482
+ e.destroy?.(this);
483
+ } catch {
484
+ }
485
+ t.stage.destroy();
486
+ }
487
+ this._scopes = [], this.scopeByContainer.clear(), this._activeScope = null, F();
488
+ }
489
+ // ─── Internal ───
490
+ _running = !1;
491
+ createScope(t) {
492
+ const e = new I(t, {
493
+ cursor: this.options.cursor,
494
+ hoverSelectors: this.options.hoverSelectors ?? D,
495
+ cursorPolicy: this.options.cursorPolicy ? x(this.options.cursorPolicy) : w,
496
+ zIndex: this.options.zIndex,
497
+ inheritDataAttributes: this.options.inheritDataAttributes
498
+ });
499
+ return this._scopes.push(e), this.scopeByContainer.set(e.container, e), e;
500
+ }
501
+ removeScope(t) {
502
+ const e = this._scopes.indexOf(t);
503
+ if (e !== -1) {
504
+ for (const s of t.plugins)
505
+ try {
506
+ s.destroy?.(this);
507
+ } catch {
508
+ }
509
+ t.plugins.length = 0, t.stage.destroy(), this._scopes.splice(e, 1), this.scopeByContainer.delete(t.container), this.input.setScopes(this._scopes), this._activeScope === t && this.input.setActiveScope(this._scopes[0] ?? null), this.rebuildStylesheet();
410
510
  }
411
511
  }
412
- /** Register a new plugin. */
413
- use(t) {
414
- if (this.plugins.some((e) => e.name === t.name))
415
- return console.warn(`[Supermouse] Plugin "${t.name}" already installed.`), this;
416
- t.isEnabled ??= !0;
417
- try {
418
- t.install?.(this);
419
- } catch (e) {
420
- return console.error(`[Supermouse] Failed to install plugin '${t.name}'.`, e), this;
512
+ setScopeCursor(t, e) {
513
+ t.cursorMode = e, t === this._activeScope && (this.state.cursorMode = e);
514
+ }
515
+ installPlugins(t) {
516
+ if (t.config.plugins)
517
+ for (const e of t.config.plugins) this.installPlugin(t, e);
518
+ }
519
+ installPlugin(t, e) {
520
+ if (!t.plugins.some((s) => s.name === e.name)) {
521
+ e.isEnabled ??= !0, this._installingScope = t;
522
+ try {
523
+ e.install?.(this);
524
+ } catch {
525
+ this._installingScope = null;
526
+ return;
527
+ }
528
+ this._installingScope = null, t.plugins.push(e), t.plugins.sort((s, n) => (s.priority ?? 0) - (n.priority ?? 0)), t !== this._activeScope && (e.isEnabled = !1, e.element && (e.element.style.display = "none"));
421
529
  }
422
- return this.plugins.push(t), this.plugins.sort((e, s) => (e.priority ?? 0) - (s.priority ?? 0)), this;
423
530
  }
424
- /** Reset physics; optionally clear all input state. */
425
- reset(t = !1) {
426
- this.state.target = { ...h }, this.state.smooth = { ...h }, this.resetMotion(), this.state.angle = 0, t && (this.state.hasReceivedInput = !1, this.state.shape = null, this.state.interaction = {});
531
+ handleActiveScopeChange(t) {
532
+ const e = this._activeScope;
533
+ if (e !== t) {
534
+ if (e)
535
+ for (const s of e.plugins) this.deactivatePlugin(s);
536
+ if (this._activeScope = t, t) {
537
+ this.state.cursorMode = t.cursorMode;
538
+ for (const s of t.plugins) this.activatePlugin(s);
539
+ }
540
+ }
427
541
  }
428
- startLoop() {
429
- this.isRunning || (this.isRunning = !0, !document.hidden && (this.lastTime = performance.now(), this.rafId = requestAnimationFrame(this.tick)));
542
+ activatePlugin(t) {
543
+ t.isEnabled === !1 && (t.isEnabled = !0, t.element && (t.element.style.display = ""), t.onEnable?.(this));
430
544
  }
431
- /** Start the animation loop. */
432
- start() {
433
- this.startLoop();
545
+ deactivatePlugin(t) {
546
+ if (t.isEnabled === !1) return;
547
+ t.isEnabled = !1;
548
+ const e = () => {
549
+ t.element && (t.element.style.display = "none"), t.onDisable?.(this);
550
+ }, s = t.onBeforeDisable?.(this);
551
+ s && typeof s.then == "function" ? Promise.resolve(s).then(e).catch((n) => {
552
+ e();
553
+ }) : e();
434
554
  }
435
- /** Manually step the animation loop. */
436
- step(t) {
437
- this.update(t);
555
+ rebuildStylesheet() {
556
+ const t = [];
557
+ for (const e of this._scopes) t.push(...e.buildRules());
558
+ V(t);
438
559
  }
439
560
  runPluginSafe(t, e) {
440
561
  if (t.isEnabled !== !1)
441
562
  try {
442
563
  t.update?.(this, e);
443
- } catch (s) {
444
- console.error(`[Supermouse] Plugin '${t.name}' crashed and has been disabled.`, s), t.isEnabled = !1, this.crashedPlugins.push(t);
564
+ } catch {
565
+ t.isEnabled = !1, this.crashedPlugins.push(t);
445
566
  }
446
567
  }
447
568
  cleanupCrashedPlugins() {
448
569
  if (this.crashedPlugins.length !== 0) {
449
570
  for (const t of this.crashedPlugins) {
450
- const e = this.plugins.indexOf(t);
451
- e > -1 && this.plugins.splice(e, 1);
571
+ for (const e of this._scopes) {
572
+ const s = e.plugins.indexOf(t);
573
+ if (s !== -1) {
574
+ e.plugins.splice(s, 1);
575
+ break;
576
+ }
577
+ }
452
578
  try {
453
579
  t.onDisable?.(this), t.destroy?.(this);
454
- } catch (s) {
455
- console.error(`[Supermouse] Failed to cleanup crashed plugin '${t.name}'.`, s);
580
+ } catch {
456
581
  }
457
582
  t.element?.remove();
458
583
  }
@@ -460,10 +585,10 @@ class C {
460
585
  }
461
586
  }
462
587
  resolveStageVisibility() {
463
- return this.state.cursorMode === "native" ? !1 : this.state.cursorMode === "both" ? this.input.isEnabled && this.state.hasReceivedInput : this.state.cursorMode === "custom" ? this.input.isEnabled && this.state.hasReceivedInput : this.input.isEnabled && !this.state.isNative && this.state.hasReceivedInput;
588
+ 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;
464
589
  }
465
590
  resolveCursorState() {
466
- return !this.input.isEnabled || this.state.cursorMode === "both" || this.state.cursorMode === "native" ? "auto" : this.state.cursorMode === "custom" ? "none" : this.state.isNative || !this.state.hasReceivedInput ? "auto" : "none";
591
+ 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";
467
592
  }
468
593
  resetMotion() {
469
594
  this.state.velocity = { x: 0, y: 0 }, this.state.displacement = { x: 0, y: 0 };
@@ -471,38 +596,68 @@ class C {
471
596
  update(t) {
472
597
  const e = t - this.lastTime, s = Math.min(e / 1e3, 0.1);
473
598
  this.lastTime = t;
474
- const i = this.input.getCurrentTarget();
475
- i && !i.isConnected ? this.input.clearHover() : i && this.input.parseDOMInteraction(i), this._stage.setVisibility(this.resolveStageVisibility()), this.input.isEnabled && this._stage.setNativeCursor(this.resolveCursorState()), this.input.isEnabled && this.state.hasReceivedInput && (this.state.target.x = this.state.pointer.x, this.state.target.y = this.state.pointer.y);
476
- for (let n = 0; n < this.plugins.length; n++)
477
- this.runPluginSafe(this.plugins[n], e);
478
- if (this.cleanupCrashedPlugins(), this.input.isEnabled) {
479
- const n = this.state.reducedMotion ? 1e3 : 1 / this.options.smoothness * 2, r = this.state.smooth.x, a = this.state.smooth.y;
480
- this.state.smooth.x = d(this.state.smooth.x, this.state.target.x, n, s), this.state.smooth.y = d(this.state.smooth.y, this.state.target.y, n, 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 - r) / s, this.state.velocity.y = (this.state.smooth.y - a) / s) : (this.state.velocity.x = 0, this.state.velocity.y = 0);
481
- const { x: l, y: u } = this.state.velocity;
482
- (Math.abs(l) > 0.1 || Math.abs(u) > 0.1) && (this.state.angle = Math.atan2(u, l) * (180 / Math.PI));
599
+ const n = this.input.getCurrentTarget();
600
+ n && !n.isConnected ? this.input.clearHover() : n && this.input.parseDOMInteraction(n);
601
+ const r = this._activeScope;
602
+ if (r) {
603
+ r.stage.setVisibility(this.resolveStageVisibility()), this.input.isEnabled && r.stage.setNativeCursor(this.resolveCursorState());
604
+ for (let o = 0; o < r.plugins.length; o++)
605
+ this.runPluginSafe(r.plugins[o], e);
606
+ }
607
+ if (this.cleanupCrashedPlugins(), this.input.isEnabled && this.state.hasReceivedInput && (this.state.target.x = this.state.pointer.x, this.state.target.y = this.state.pointer.y), this.input.isEnabled) {
608
+ const o = this.state.reducedMotion ? 1e3 : 1 / this.options.smoothness * 2, d = this.state.smooth.x, c = this.state.smooth.y;
609
+ this.state.smooth.x = P(this.state.smooth.x, this.state.target.x, o, s), this.state.smooth.y = P(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 - d) / s, this.state.velocity.y = (this.state.smooth.y - c) / s) : (this.state.velocity.x = 0, this.state.velocity.y = 0);
610
+ const { x: a, y: h } = this.state.velocity;
611
+ (Math.abs(a) > 0.1 || Math.abs(h) > 0.1) && (this.state.angle = Math.atan2(h, a) * (180 / Math.PI));
483
612
  }
484
613
  }
485
614
  tick = (t) => {
486
- this.update(t), this.isRunning && (this.rafId = requestAnimationFrame(this.tick));
615
+ this.update(t), this._running && (this.rafId = requestAnimationFrame(this.tick));
487
616
  };
488
- /** Pause rAF loop when tab hidden; resume on visible. */
489
617
  bindVisibilityHandling() {
490
618
  document.addEventListener(
491
619
  "visibilitychange",
492
620
  () => {
493
- this.isRunning && (document.hidden ? cancelAnimationFrame(this.rafId) : (this.lastTime = performance.now(), this.rafId = requestAnimationFrame(this.tick)));
621
+ this._running && (document.hidden ? cancelAnimationFrame(this.rafId) : (this.lastTime = performance.now(), this.rafId = requestAnimationFrame(this.tick)));
494
622
  },
495
623
  { signal: this.visibilityAbortController.signal }
496
624
  );
497
625
  }
498
- /** Destroy the instance, freeing all resources. */
499
- destroy() {
500
- this.isRunning = !1, cancelAnimationFrame(this.rafId), this.visibilityAbortController.abort(), this.input.destroy(), this._stage.destroy(), this.plugins.forEach((t) => t.destroy?.(this)), this.plugins = [];
626
+ startLoop() {
627
+ this._running || (this._running = !0, !document.hidden && (this.lastTime = performance.now(), this.rafId = requestAnimationFrame(this.tick)));
628
+ }
629
+ /**
630
+ * @deprecated Prefer setting `hoverSelectors` at scope creation, or use
631
+ * `definePlugin`'s `selector` option. This method is kept for raw-object
632
+ * plugins and runtime extension; it mutates the active scope's selector set.
633
+ */
634
+ registerHoverTarget(t) {
635
+ const e = this._installingScope ?? this._activeScope ?? this._scopes[0];
636
+ if (e)
637
+ for (const s of t.split(",")) {
638
+ const n = s.trim();
639
+ n && e.hoverSelectors.add(n);
640
+ }
641
+ }
642
+ /**
643
+ * @deprecated Read from the active scope instead. Exposed for tests and
644
+ * debugging only.
645
+ */
646
+ get hoverSelectors() {
647
+ const t = this._activeScope ?? this._scopes[0];
648
+ return t ? t.hoverSelectors : /* @__PURE__ */ new Set();
649
+ }
650
+ /**
651
+ * @deprecated Read from the active scope instead. Exposed for tests and
652
+ * debugging only.
653
+ */
654
+ get plugins() {
655
+ const t = this._activeScope ?? this._scopes[0];
656
+ return t ? t.plugins : [];
501
657
  }
502
658
  }
503
659
  export {
504
- m as DEFAULT_HOVER_SELECTORS,
505
- v as Input,
506
- y as Stage,
507
- C as Supermouse
660
+ w as DEFAULT_CURSOR_POLICY,
661
+ D as DEFAULT_HOVER_SELECTORS,
662
+ Q as Supermouse
508
663
  };