@supermousejs/utils 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/dom.ts CHANGED
@@ -1,131 +1,145 @@
1
-
2
- /**
3
- * Applies a dictionary of styles to an HTMLElement.
4
- */
5
- export function applyStyles(el: HTMLElement, styles: Partial<CSSStyleDeclaration>) {
6
- Object.assign(el.style, styles);
7
- }
8
-
9
- // WeakMap to store previous styles for elements to prevent DOM thrashing
10
- const styleCache = new WeakMap<HTMLElement, Record<string, string | number>>();
11
-
12
- /**
13
- * Smart Style Setter.
14
- * Only writes to the DOM if the value has actually changed.
15
- */
16
- export function setStyle(el: HTMLElement, property: keyof CSSStyleDeclaration, value: string | number) {
17
- let cache = styleCache.get(el);
18
- if (!cache) {
19
- cache = {};
20
- styleCache.set(el, cache);
21
- }
22
-
23
- // Only write to DOM if value changed
24
- if (cache[property as string] !== value) {
25
- // @ts-ignore - Dynamic access
26
- el.style[property] = value;
27
- cache[property as string] = value;
28
- }
29
- }
30
-
31
- /**
32
- * Universal Transform Setter.
33
- * Handles centering (-50%) automatically.
34
- *
35
- * @param el The element
36
- * @param x X Position (px)
37
- * @param y Y Position (px)
38
- * @param rotation Rotation (deg) - Default 0
39
- * @param scaleX Scale X - Default 1
40
- * @param scaleY Scale Y - Default 1
41
- * @param skewX Skew X (deg) - Default 0
42
- * @param skewY Skew Y (deg) - Default 0
43
- */
44
- export function setTransform(
45
- el: HTMLElement,
46
- x: number,
47
- y: number,
48
- rotation: number = 0,
49
- scaleX: number = 1,
50
- scaleY: number = 1,
51
- skewX: number = 0,
52
- skewY: number = 0
53
- ) {
54
- el.style.transform = `
55
- translate3d(${x}px, ${y}px, 0)
56
- translate(-50%, -50%)
57
- rotate(${rotation}deg)
58
- skew(${skewX}deg, ${skewY}deg)
59
- scale(${scaleX}, ${scaleY})
60
- `;
61
- }
62
-
63
- /**
64
- * Calculates the bounding rectangle of an element relative to a container.
65
- * Useful for logic plugins when the cursor is confined to a specific div.
66
- */
67
- export function projectRect(element: HTMLElement, container: HTMLElement = document.body): DOMRect {
68
- const rect = element.getBoundingClientRect();
69
-
70
- if (container !== document.body) {
71
- const containerRect = container.getBoundingClientRect();
72
- const x = rect.left - containerRect.left;
73
- const y = rect.top - containerRect.top;
74
-
75
- return {
76
- x,
77
- y,
78
- width: rect.width,
79
- height: rect.height,
80
- top: y,
81
- left: x,
82
- right: x + rect.width,
83
- bottom: y + rect.height,
84
- toJSON: () => ({})
85
- } as DOMRect;
86
- }
87
-
88
- return rect;
89
- }
90
-
91
- /**
92
- * Creates a standard Supermouse actor element with optimal performance settings.
93
- * Includes absolute positioning, pointer-events: none, and will-change: transform.
94
- *
95
- * @param tagName The HTML tag to create (default: 'div')
96
- */
97
- export function createActor(tagName: string = 'div'): HTMLElement {
98
- const el = document.createElement(tagName);
99
- applyStyles(el, {
100
- position: 'absolute',
101
- top: '0',
102
- left: '0',
103
- pointerEvents: 'none',
104
- boxSizing: 'border-box',
105
- display: 'block',
106
- willChange: 'transform'
107
- });
108
- return el;
109
- }
110
-
111
- /**
112
- * Creates a circular HTML div using the standard actor base.
113
- */
114
- export function createCircle(size: number, color: string): HTMLDivElement {
115
- const el = createActor('div') as HTMLDivElement;
116
- applyStyles(el, {
117
- width: `${size}px`,
118
- height: `${size}px`,
119
- borderRadius: '50%',
120
- backgroundColor: color,
121
- });
122
- return el;
123
- }
124
-
125
- /**
126
- * Legacy alias for createActor.
127
- * @deprecated Use createActor() instead.
128
- */
129
- export function createDiv(): HTMLDivElement {
130
- return createActor('div') as HTMLDivElement;
131
- }
1
+ /**
2
+ * Injects global CSS styles into the document head safely.
3
+ * Checks for existing IDs to prevent duplication during SPA routing or HMR.
4
+ *
5
+ * @param id A unique identifier for this style block
6
+ * @param css A string of CSS rules to inject.
7
+ */
8
+ export const injectStyles = (id: string, css: string) => {
9
+ if (typeof document === "undefined") return;
10
+ if (document.getElementById(id)) return;
11
+
12
+ const style = document.createElement("style");
13
+ style.id = id;
14
+ style.innerHTML = css;
15
+ document.head.appendChild(style);
16
+ };
17
+
18
+ // WeakMap to store previous styles for elements to prevent DOM thrashing
19
+ const styleCache = new WeakMap<HTMLElement, Record<string, string | number>>();
20
+
21
+ /**
22
+ * Smart Style Setter (Batch).
23
+ * Only writes to the DOM if the value has actually changed.
24
+ * @param el The element to style
25
+ * @param styles An object of CSS properties and values
26
+ */
27
+ export function applyStyles(el: HTMLElement, styles: Partial<CSSStyleDeclaration>) {
28
+ if (typeof document === "undefined" || !el) return;
29
+
30
+ let cache = styleCache.get(el);
31
+ if (!cache) {
32
+ cache = {};
33
+ styleCache.set(el, cache);
34
+ }
35
+
36
+ for (const prop in styles) {
37
+ const value = (styles as any)[prop];
38
+ if (cache[prop] !== value) {
39
+ (el.style as any)[prop] = value;
40
+ cache[prop] = value;
41
+ }
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Smart Style Setter (Single).
47
+ * Proxies to applyStyles for consistency.
48
+ * @param el The element to style
49
+ * @param property The CSS property to set
50
+ * @param value The value to set for the property
51
+ */
52
+ export function setStyle(
53
+ el: HTMLElement,
54
+ property: keyof CSSStyleDeclaration,
55
+ value: string | number
56
+ ) {
57
+ applyStyles(el, { [property]: value } as any);
58
+ }
59
+
60
+ /**
61
+ * Universal Transform Setter.
62
+ * Handles centering (-50%) automatically.
63
+ *
64
+ * @param el The element
65
+ * @param x X Position (px)
66
+ * @param y Y Position (px)
67
+ * @param rotation Rotation (deg) - Default 0
68
+ * @param scaleX Scale X - Default 1
69
+ * @param scaleY Scale Y - Default 1
70
+ * @param skewX Skew X (deg) - Default 0
71
+ * @param skewY Skew Y (deg) - Default 0
72
+ */
73
+ export function setTransform(
74
+ el: HTMLElement,
75
+ x: number,
76
+ y: number,
77
+ rotation: number = 0,
78
+ scaleX: number = 1,
79
+ scaleY: number = 1,
80
+ skewX: number = 0,
81
+ skewY: number = 0
82
+ ) {
83
+ const transform = `translate3d(${x}px, ${y}px, 0) translate(-50%, -50%) rotate(${rotation}deg) skew(${skewX}deg, ${skewY}deg) scale(${scaleX}, ${scaleY})`;
84
+
85
+ setStyle(el, "transform", transform);
86
+ }
87
+
88
+ /**
89
+ * Calculates the bounding rectangle of an element relative to a container.
90
+ */
91
+ export function projectRect(element: HTMLElement, container: HTMLElement = document.body): DOMRect {
92
+ const rect = element.getBoundingClientRect();
93
+
94
+ if (container !== document.body) {
95
+ const containerRect = container.getBoundingClientRect();
96
+ const x = rect.left - containerRect.left;
97
+ const y = rect.top - containerRect.top;
98
+
99
+ return new DOMRect(x, y, rect.width, rect.height);
100
+ }
101
+
102
+ return rect;
103
+ }
104
+
105
+ /**
106
+ * Creates a standard Supermouse actor element with optimal performance settings.
107
+ * Includes absolute positioning, pointer-events: none, and will-change: transform.
108
+ *
109
+ * @param tagName The HTML tag to create (default: 'div')
110
+ */
111
+ export function createActor(tagName: string = "div"): HTMLElement {
112
+ const el = document.createElement(tagName);
113
+ applyStyles(el, {
114
+ position: "absolute",
115
+ top: "0",
116
+ left: "0",
117
+ pointerEvents: "none",
118
+ boxSizing: "border-box",
119
+ display: "block",
120
+ willChange: "transform"
121
+ });
122
+ return el;
123
+ }
124
+
125
+ /**
126
+ * Creates a circular HTML div using the standard actor base.
127
+ */
128
+ export function createCircle(size: number, color: string): HTMLDivElement {
129
+ const el = createActor("div") as HTMLDivElement;
130
+ applyStyles(el, {
131
+ width: `${size}px`,
132
+ height: `${size}px`,
133
+ borderRadius: "50%",
134
+ backgroundColor: color
135
+ });
136
+ return el;
137
+ }
138
+
139
+ /**
140
+ * Legacy alias for createActor.
141
+ * @deprecated Use createActor() instead.
142
+ */
143
+ export function createDiv(): HTMLDivElement {
144
+ return createActor("div") as HTMLDivElement;
145
+ }
package/src/effects.ts CHANGED
@@ -1,35 +1,34 @@
1
-
2
- import { dist, angle, clamp } from './math';
3
-
4
- /**
5
- * Calculates Rotation and Scale based on velocity to create a "Squash and Stretch" effect.
6
- *
7
- * @param vx Velocity X
8
- * @param vy Velocity Y
9
- * @param intensity Stretch factor (default: 0.004)
10
- * @param maxStretch Max stretch percentage (default: 0.5 = 150% length)
11
- */
12
- export function getVelocityDistortion(vx: number, vy: number, intensity = 0.004, maxStretch = 0.5) {
13
- const speed = dist(vx, vy);
14
-
15
- // Deadzone: If moving too slow, don't rotate (prevents jittering at rest)
16
- if (speed < 0.1) {
17
- return { rotation: 0, scaleX: 1, scaleY: 1 };
18
- }
19
-
20
- // 1. Point towards movement
21
- const rotation = angle(vx, vy);
22
-
23
- // 2. Stretch based on speed
24
- const stretch = clamp(speed * intensity, 0, maxStretch);
25
-
26
- // 3. Scale X grows, Scale Y shrinks (to preserve volume-ish)
27
- const scaleX = 1 + stretch;
28
- const scaleY = 1 - (stretch * 0.5); // Squash factor
29
-
30
- return {
31
- rotation,
32
- scaleX,
33
- scaleY
34
- };
35
- }
1
+ import { dist, angle, clamp } from "./math";
2
+
3
+ /**
4
+ * Calculates Rotation and Scale based on velocity to create a "Squash and Stretch" effect.
5
+ *
6
+ * @param vx Velocity X
7
+ * @param vy Velocity Y
8
+ * @param intensity Stretch factor (default: 0.004)
9
+ * @param maxStretch Max stretch percentage (default: 0.5 = 150% length)
10
+ */
11
+ export function getVelocityDistortion(vx: number, vy: number, intensity = 0.004, maxStretch = 0.5) {
12
+ const speed = dist(vx, vy);
13
+
14
+ // Deadzone: If moving too slow, don't rotate (prevents jittering at rest)
15
+ if (speed < 0.1) {
16
+ return { rotation: 0, scaleX: 1, scaleY: 1 };
17
+ }
18
+
19
+ // 1. Point towards movement
20
+ const rotation = angle(vx, vy);
21
+
22
+ // 2. Stretch based on speed
23
+ const stretch = clamp(speed * intensity, 0, maxStretch);
24
+
25
+ // 3. Scale X grows, Scale Y shrinks (to preserve volume-ish)
26
+ const scaleX = 1 + stretch;
27
+ const scaleY = 1 - stretch * 0.5; // Squash factor
28
+
29
+ return {
30
+ rotation,
31
+ scaleX,
32
+ scaleY
33
+ };
34
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
- import * as math from './math';
2
- import * as dom from './dom';
3
- import * as effects from './effects';
4
-
5
- export { math, dom, effects };
6
- export * from './layers';
7
- export * from './css';
8
- export * from './plugin';
9
- export * from './options';
10
- export * from './doctor';
1
+ import * as math from "./math";
2
+ import * as dom from "./dom";
3
+ import * as effects from "./effects";
4
+
5
+ export { math, dom, effects };
6
+ export * from "./layers";
7
+ export * from "./css";
8
+ export * from "./plugin";
9
+ export * from "./options";
10
+ export * from "./doctor";
package/src/layers.ts CHANGED
@@ -1,17 +1,17 @@
1
- /**
2
- * Standard Z-Index layers for the Supermouse ecosystem.
3
- * Relative to the Supermouse Container.
4
- */
5
- export const Layers = {
6
- /** The top-most layer. For text, tooltips, and crucial UI. */
7
- OVERLAY: '400',
8
-
9
- /** The main cursor layer. For the primary Dot/Pointer. */
10
- CURSOR: '300',
11
-
12
- /** The secondary layer. For Rings, brackets, or followers. */
13
- FOLLOWER: '200',
14
-
15
- /** The background layer. For trails, sparkles, and particles. */
16
- TRACE: '100',
17
- } as const;
1
+ /**
2
+ * Standard Z-Index layers for the Supermouse ecosystem.
3
+ * Relative to the Supermouse Container.
4
+ */
5
+ export const Layers = {
6
+ /** The top-most layer. For text, tooltips, and crucial UI. */
7
+ OVERLAY: "400",
8
+
9
+ /** The main cursor layer. For the primary Dot/Pointer. */
10
+ CURSOR: "300",
11
+
12
+ /** The secondary layer. For Rings, brackets, or followers. */
13
+ FOLLOWER: "200",
14
+
15
+ /** The background layer. For trails, sparkles, and particles. */
16
+ TRACE: "100"
17
+ } as const;
package/src/math.ts CHANGED
@@ -1,60 +1,60 @@
1
- /**
2
- * Linear Interpolation between two values.
3
- */
4
- export function lerp(start: number, end: number, factor: number): number {
5
- return start + (end - start) * factor;
6
- }
7
-
8
- /**
9
- * Frame-rate independent damping (Time-based Lerp).
10
- * Ensures smooth animation consistent across 60hz, 120hz, etc.
11
- *
12
- * @param a Current value
13
- * @param b Target value
14
- * @param lambda Smoothing factor (approx 1-20). Higher is faster.
15
- * @param dt Delta time in seconds (not milliseconds)
16
- */
17
- export function damp(a: number, b: number, lambda: number, dt: number): number {
18
- return lerp(a, b, 1 - Math.exp(-lambda * dt));
19
- }
20
-
21
- /**
22
- * Linear Interpolation between two angles in degrees, taking the shortest path.
23
- * Handles wrap-around at 360 degrees.
24
- */
25
- export function lerpAngle(start: number, end: number, factor: number): number {
26
- const diff = ((((end - start) % 360) + 540) % 360) - 180;
27
- return start + diff * factor;
28
- }
29
-
30
- /**
31
- * Returns a random number between min and max.
32
- * Usage: math.random(10, 20) -> 14.5
33
- */
34
- export function random(min: number, max: number): number {
35
- return Math.random() * (max - min) + min;
36
- }
37
-
38
- /**
39
- * Constrains a value between a minimum and maximum.
40
- */
41
- export function clamp(value: number, min: number, max: number): number {
42
- return Math.min(Math.max(value, min), max);
43
- }
44
-
45
- /**
46
- * Calculates the distance (hypotenuse) between two points (or magnitude of a vector).
47
- * If x2/y2 are omitted, calculates magnitude of vector x1/y1.
48
- */
49
- export function dist(x1: number, y1: number, x2: number = 0, y2: number = 0): number {
50
- const dx = x1 - x2;
51
- const dy = y1 - y2;
52
- return Math.sqrt(dx * dx + dy * dy);
53
- }
54
-
55
- /**
56
- * Calculates the angle in degrees between two points (or vector direction).
57
- */
58
- export function angle(x: number, y: number): number {
59
- return Math.atan2(y, x) * (180 / Math.PI);
60
- }
1
+ /**
2
+ * Linear Interpolation between two values.
3
+ */
4
+ export function lerp(start: number, end: number, factor: number): number {
5
+ return start + (end - start) * factor;
6
+ }
7
+
8
+ /**
9
+ * Frame-rate independent damping (Time-based Lerp).
10
+ * Ensures smooth animation consistent across 60hz, 120hz, etc.
11
+ *
12
+ * @param a Current value
13
+ * @param b Target value
14
+ * @param lambda Smoothing factor (approx 1-20). Higher is faster.
15
+ * @param dt Delta time in seconds (not milliseconds)
16
+ */
17
+ export function damp(a: number, b: number, lambda: number, dt: number): number {
18
+ return lerp(a, b, 1 - Math.exp(-lambda * dt));
19
+ }
20
+
21
+ /**
22
+ * Linear Interpolation between two angles in degrees, taking the shortest path.
23
+ * Handles wrap-around at 360 degrees.
24
+ */
25
+ export function lerpAngle(start: number, end: number, factor: number): number {
26
+ const diff = ((((end - start) % 360) + 540) % 360) - 180;
27
+ return start + diff * factor;
28
+ }
29
+
30
+ /**
31
+ * Returns a random number between min and max.
32
+ * Usage: math.random(10, 20) -> 14.5
33
+ */
34
+ export function random(min: number, max: number): number {
35
+ return Math.random() * (max - min) + min;
36
+ }
37
+
38
+ /**
39
+ * Constrains a value between a minimum and maximum.
40
+ */
41
+ export function clamp(value: number, min: number, max: number): number {
42
+ return Math.min(Math.max(value, min), max);
43
+ }
44
+
45
+ /**
46
+ * Calculates the distance (hypotenuse) between two points (or magnitude of a vector).
47
+ * If x2/y2 are omitted, calculates magnitude of vector x1/y1.
48
+ */
49
+ export function dist(x1: number, y1: number, x2: number = 0, y2: number = 0): number {
50
+ const dx = x1 - x2;
51
+ const dy = y1 - y2;
52
+ return Math.sqrt(dx * dx + dy * dy);
53
+ }
54
+
55
+ /**
56
+ * Calculates the angle in degrees between two points (or vector direction).
57
+ */
58
+ export function angle(x: number, y: number): number {
59
+ return Math.atan2(y, x) * (180 / Math.PI);
60
+ }
package/src/options.ts CHANGED
@@ -1,22 +1,22 @@
1
- import type { MouseState, ValueOrGetter } from '@supermousejs/core';
2
-
3
- /**
4
- * Returns a function that always resolves the option value.
5
- * Eliminates 'typeof' checks inside the render loop by normalizing
6
- * static values into getter functions during initialization.
7
- *
8
- * @param option The option passed by the user
9
- * @param defaultValue Fallback value
10
- */
11
- export function normalize<T>(
12
- option: ValueOrGetter<T> | undefined,
13
- defaultValue: T
14
- ): (state: MouseState) => T {
15
- if (option === undefined) {
16
- return () => defaultValue;
17
- }
18
- if (typeof option === 'function') {
19
- return option as (state: MouseState) => T;
20
- }
21
- return () => option;
22
- }
1
+ import type { MouseState, ValueOrGetter } from "@supermousejs/core";
2
+
3
+ /**
4
+ * Returns a function that always resolves the option value.
5
+ * Eliminates 'typeof' checks inside the render loop by normalizing
6
+ * static values into getter functions during initialization.
7
+ *
8
+ * @param option The option passed by the user
9
+ * @param defaultValue Fallback value
10
+ */
11
+ export function normalize<T>(
12
+ option: ValueOrGetter<T> | undefined,
13
+ defaultValue: T
14
+ ): (state: MouseState) => T {
15
+ if (option === undefined) {
16
+ return () => defaultValue;
17
+ }
18
+ if (typeof option === "function") {
19
+ return option as (state: MouseState) => T;
20
+ }
21
+ return () => option;
22
+ }