@supermousejs/utils 2.2.0 → 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/CHANGELOG.md CHANGED
@@ -1,74 +1,81 @@
1
- # @supermousejs/utils
2
-
3
- ## 2.2.0
4
-
5
- ### Minor Changes
6
-
7
- - 9fe1a7b: - Implemented idiomatic approach to using supermouse/utils
8
- - Rewrote `applyStyles`, `setTransform` and `setStyles` to share a global cache registry
9
- - 9fa6ece: Add `injectStyles` to safely handle injecting global CSS styles into the document head
10
-
11
- ### Patch Changes
12
-
13
- - 6d70c18: remove legacy package and update supermouse domain in readme
14
- - 14fb5b6: Updated tsconfig to be reference-compliant with core, utils and zoetrope when required
15
- - Updated dependencies [6d70c18]
16
- - Updated dependencies [2590af3]
17
- - Updated dependencies [14fb5b6]
18
- - @supermousejs/core@2.1.0
19
-
20
- ## 2.1.1
21
-
22
- ### Patch Changes
23
-
24
- - 67f771b: Add relevant npm metadata to package.json file
25
- - Updated dependencies [67f771b]
26
- - @supermousejs/core@2.0.5
27
-
28
- ## 2.1.0
29
-
30
- ### Minor Changes
31
-
32
- - 0a1652d: fixed build architecture and updated plugin metadata
33
-
34
- ## 2.0.4
35
-
36
- ### Patch Changes
37
-
38
- - 993dc67: Updated supemousejs packages with proper author, license and url descriptors to repo
39
- - Updated dependencies [993dc67]
40
- - @supermousejs/core@2.0.4
41
-
42
- ## 2.0.3
43
-
44
- ### Patch Changes
45
-
46
- - Updated dependencies
47
- - @supermousejs/core@2.0.3
48
-
49
- ## 2.0.2
50
-
51
- ### Patch Changes
52
-
53
- - ae219a0: Update READMEs with correct link to documentation
54
- - Updated dependencies [ae219a0]
55
- - @supermousejs/core@2.0.2
56
-
57
- ## 2.0.1
58
-
59
- ### Patch Changes
60
-
61
- - Add minimal README.md files to packages
62
- - Updated dependencies
63
- - @supermousejs/core@2.0.1
64
-
65
- ## 2.0.0
66
-
67
- ### Major Changes
68
-
69
- - Initial v2.0.0 release
70
-
71
- ### Patch Changes
72
-
73
- - Updated dependencies
74
- - @supermousejs/core@2.0.0
1
+ # @supermousejs/utils
2
+
3
+ ## 2.3.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [f6f44b2]
8
+ - @supermousejs/core@2.2.0
9
+
10
+ ## 2.2.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 9fe1a7b: - Implemented idiomatic approach to using supermouse/utils
15
+ - Rewrote `applyStyles`, `setTransform` and `setStyles` to share a global cache registry
16
+ - 9fa6ece: Add `injectStyles` to safely handle injecting global CSS styles into the document head
17
+
18
+ ### Patch Changes
19
+
20
+ - 6d70c18: remove legacy package and update supermouse domain in readme
21
+ - 14fb5b6: Updated tsconfig to be reference-compliant with core, utils and zoetrope when required
22
+ - Updated dependencies [6d70c18]
23
+ - Updated dependencies [2590af3]
24
+ - Updated dependencies [14fb5b6]
25
+ - @supermousejs/core@2.1.0
26
+
27
+ ## 2.1.1
28
+
29
+ ### Patch Changes
30
+
31
+ - 67f771b: Add relevant npm metadata to package.json file
32
+ - Updated dependencies [67f771b]
33
+ - @supermousejs/core@2.0.5
34
+
35
+ ## 2.1.0
36
+
37
+ ### Minor Changes
38
+
39
+ - 0a1652d: fixed build architecture and updated plugin metadata
40
+
41
+ ## 2.0.4
42
+
43
+ ### Patch Changes
44
+
45
+ - 993dc67: Updated supemousejs packages with proper author, license and url descriptors to repo
46
+ - Updated dependencies [993dc67]
47
+ - @supermousejs/core@2.0.4
48
+
49
+ ## 2.0.3
50
+
51
+ ### Patch Changes
52
+
53
+ - Updated dependencies
54
+ - @supermousejs/core@2.0.3
55
+
56
+ ## 2.0.2
57
+
58
+ ### Patch Changes
59
+
60
+ - ae219a0: Update READMEs with correct link to documentation
61
+ - Updated dependencies [ae219a0]
62
+ - @supermousejs/core@2.0.2
63
+
64
+ ## 2.0.1
65
+
66
+ ### Patch Changes
67
+
68
+ - Add minimal README.md files to packages
69
+ - Updated dependencies
70
+ - @supermousejs/core@2.0.1
71
+
72
+ ## 2.0.0
73
+
74
+ ### Major Changes
75
+
76
+ - Initial v2.0.0 release
77
+
78
+ ### Patch Changes
79
+
80
+ - Updated dependencies
81
+ - @supermousejs/core@2.0.0
package/dist/options.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { MouseState, ValueOrGetter } from '../../core/src';
1
+ import { MouseState, ValueOrGetter } from '../packages/core/src';
2
2
  /**
3
3
  * Returns a function that always resolves the option value.
4
4
  * Eliminates 'typeof' checks inside the render loop by normalizing
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Supermouse, SupermousePlugin } from '../../core/src';
1
+ import { Supermouse, SupermousePlugin } from '../packages/core/src';
2
2
  export interface BasePluginOptions {
3
3
  name?: string;
4
4
  isEnabled?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supermousejs/utils",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "main": "dist/index.umd.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -27,10 +27,10 @@
27
27
  "access": "public"
28
28
  },
29
29
  "peerDependencies": {
30
- "@supermousejs/core": "2.1.0"
30
+ "@supermousejs/core": "2.2.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@supermousejs/core": "2.1.0"
33
+ "@supermousejs/core": "2.2.0"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "vite build"
package/src/dom.ts CHANGED
@@ -1,145 +1,145 @@
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
- }
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,34 +1,34 @@
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
- }
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/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/plugin.ts CHANGED
@@ -1,147 +1,147 @@
1
- import type { Supermouse, SupermousePlugin } from "@supermousejs/core";
2
- import { normalize } from "./options";
3
- import { setStyle } from "./dom";
4
-
5
- export interface BasePluginOptions {
6
- name?: string;
7
- isEnabled?: boolean;
8
- }
9
-
10
- interface LogicConfig {
11
- name: string;
12
- priority?: number;
13
- install?: (app: Supermouse) => void;
14
- update?: (app: Supermouse, deltaTime: number) => void;
15
- destroy?: (app: Supermouse) => void;
16
- onEnable?: (app: Supermouse) => void;
17
- onDisable?: (app: Supermouse) => void;
18
- // Explicitly disallow 'create' here to ensure type separation
19
- create?: never;
20
- }
21
-
22
- // --- MODE B: VISUAL PLUGIN CONFIG ---
23
- interface VisualConfig<E extends HTMLElement, O extends object> {
24
- name: string;
25
- /** Automatically register this attribute selector */
26
- selector?: string;
27
- /** Create and return the DOM Element */
28
- create: (app: Supermouse) => E;
29
- /** Map option keys to CSS properties */
30
- styles?: Partial<Record<keyof O, keyof CSSStyleDeclaration>>;
31
- /** Update loop with access to the element */
32
- update?: (app: Supermouse, element: E, deltaTime: number) => void;
33
- onEnable?: (app: Supermouse, element: E) => void;
34
- onDisable?: (app: Supermouse, element: E) => void;
35
- cleanup?: (element: E) => void;
36
- destroy?: never; // Visual plugins use cleanup(), not destroy()
37
- }
38
-
39
- // Helper Type Guard to safely distinguish VisualConfig at runtime
40
- function isVisualConfig<E extends HTMLElement, O extends object>(
41
- config: LogicConfig | VisualConfig<E, O>
42
- ): config is VisualConfig<E, O> {
43
- return "create" in config && typeof (config as any).create === "function";
44
- }
45
-
46
- // --- THE OVERLOADS ---
47
-
48
- // Overload 1: Visual Plugin (Infer Element E and Options O)
49
- export function definePlugin<E extends HTMLElement, O extends BasePluginOptions>(
50
- config: VisualConfig<E, O>,
51
- userOptions?: O
52
- ): SupermousePlugin;
53
-
54
- // Overload 2: Logic Plugin
55
- export function definePlugin(
56
- config: LogicConfig,
57
- userOptions?: BasePluginOptions
58
- ): SupermousePlugin;
59
-
60
- // --- THE IMPLEMENTATION ---
61
-
62
- export function definePlugin(
63
- config: LogicConfig | VisualConfig<HTMLElement, any>,
64
- userOptions: any = {}
65
- ): SupermousePlugin {
66
- const name = userOptions.name || config.name;
67
- const initialEnabled = userOptions.isEnabled ?? true;
68
-
69
- // MODE A: VISUAL
70
- if (isVisualConfig(config)) {
71
- let element: HTMLElement;
72
-
73
- // PRE-COMPILE STYLE SETTERS
74
- const styleSetters: ((app: Supermouse, el: HTMLElement) => void)[] = [];
75
-
76
- if (config.styles) {
77
- for (const [optKey, cssProp] of Object.entries(config.styles)) {
78
- const getter = normalize(userOptions[optKey], undefined);
79
- const prop = cssProp as any;
80
-
81
- styleSetters.push((app, el) => {
82
- const val = getter(app.state);
83
- if (val !== undefined) {
84
- setStyle(el, prop, val);
85
- }
86
- });
87
- }
88
- }
89
-
90
- return {
91
- name,
92
- isEnabled: initialEnabled,
93
-
94
- install(app) {
95
- // 1. Create & Append
96
- element = config.create(app);
97
- if (config.selector) app.registerHoverTarget(config.selector);
98
-
99
- // 2. Handle Initial State
100
- if (this.isEnabled === false) {
101
- element.style.opacity = "0";
102
- }
103
- app.container.appendChild(element);
104
- },
105
-
106
- update(app, dt) {
107
- if (!element) return;
108
-
109
- // 3. Run Pre-compiled Style Setters
110
- for (let i = 0; i < styleSetters.length; i++) {
111
- styleSetters[i](app, element);
112
- }
113
-
114
- // 4. Run Custom Update
115
- config.update?.(app, element, dt);
116
- },
117
-
118
- onDisable(app) {
119
- if (!element) return;
120
- // Use setStyle to ensure cache remains in sync (0)
121
- setStyle(element, "opacity", 0);
122
- config.onDisable?.(app, element);
123
- },
124
-
125
- onEnable(app) {
126
- if (!element) return;
127
- setStyle(element, "opacity", 1);
128
- config.onEnable?.(app, element);
129
- },
130
-
131
- destroy() {
132
- if (!element) return;
133
- config.cleanup?.(element);
134
- element.remove();
135
- }
136
- };
137
- }
138
-
139
- // MODE B: LOGIC (Standard Pass-through)
140
- else {
141
- return {
142
- ...config,
143
- name,
144
- isEnabled: initialEnabled
145
- };
146
- }
147
- }
1
+ import type { Supermouse, SupermousePlugin } from "@supermousejs/core";
2
+ import { normalize } from "./options";
3
+ import { setStyle } from "./dom";
4
+
5
+ export interface BasePluginOptions {
6
+ name?: string;
7
+ isEnabled?: boolean;
8
+ }
9
+
10
+ interface LogicConfig {
11
+ name: string;
12
+ priority?: number;
13
+ install?: (app: Supermouse) => void;
14
+ update?: (app: Supermouse, deltaTime: number) => void;
15
+ destroy?: (app: Supermouse) => void;
16
+ onEnable?: (app: Supermouse) => void;
17
+ onDisable?: (app: Supermouse) => void;
18
+ // Explicitly disallow 'create' here to ensure type separation
19
+ create?: never;
20
+ }
21
+
22
+ // --- MODE B: VISUAL PLUGIN CONFIG ---
23
+ interface VisualConfig<E extends HTMLElement, O extends object> {
24
+ name: string;
25
+ /** Automatically register this attribute selector */
26
+ selector?: string;
27
+ /** Create and return the DOM Element */
28
+ create: (app: Supermouse) => E;
29
+ /** Map option keys to CSS properties */
30
+ styles?: Partial<Record<keyof O, keyof CSSStyleDeclaration>>;
31
+ /** Update loop with access to the element */
32
+ update?: (app: Supermouse, element: E, deltaTime: number) => void;
33
+ onEnable?: (app: Supermouse, element: E) => void;
34
+ onDisable?: (app: Supermouse, element: E) => void;
35
+ cleanup?: (element: E) => void;
36
+ destroy?: never; // Visual plugins use cleanup(), not destroy()
37
+ }
38
+
39
+ // Helper Type Guard to safely distinguish VisualConfig at runtime
40
+ function isVisualConfig<E extends HTMLElement, O extends object>(
41
+ config: LogicConfig | VisualConfig<E, O>
42
+ ): config is VisualConfig<E, O> {
43
+ return "create" in config && typeof (config as any).create === "function";
44
+ }
45
+
46
+ // --- THE OVERLOADS ---
47
+
48
+ // Overload 1: Visual Plugin (Infer Element E and Options O)
49
+ export function definePlugin<E extends HTMLElement, O extends BasePluginOptions>(
50
+ config: VisualConfig<E, O>,
51
+ userOptions?: O
52
+ ): SupermousePlugin;
53
+
54
+ // Overload 2: Logic Plugin
55
+ export function definePlugin(
56
+ config: LogicConfig,
57
+ userOptions?: BasePluginOptions
58
+ ): SupermousePlugin;
59
+
60
+ // --- THE IMPLEMENTATION ---
61
+
62
+ export function definePlugin(
63
+ config: LogicConfig | VisualConfig<HTMLElement, any>,
64
+ userOptions: any = {}
65
+ ): SupermousePlugin {
66
+ const name = userOptions.name || config.name;
67
+ const initialEnabled = userOptions.isEnabled ?? true;
68
+
69
+ // MODE A: VISUAL
70
+ if (isVisualConfig(config)) {
71
+ let element: HTMLElement;
72
+
73
+ // PRE-COMPILE STYLE SETTERS
74
+ const styleSetters: ((app: Supermouse, el: HTMLElement) => void)[] = [];
75
+
76
+ if (config.styles) {
77
+ for (const [optKey, cssProp] of Object.entries(config.styles)) {
78
+ const getter = normalize(userOptions[optKey], undefined);
79
+ const prop = cssProp as any;
80
+
81
+ styleSetters.push((app, el) => {
82
+ const val = getter(app.state);
83
+ if (val !== undefined) {
84
+ setStyle(el, prop, val);
85
+ }
86
+ });
87
+ }
88
+ }
89
+
90
+ return {
91
+ name,
92
+ isEnabled: initialEnabled,
93
+
94
+ install(app) {
95
+ // 1. Create & Append
96
+ element = config.create(app);
97
+ if (config.selector) app.registerHoverTarget(config.selector);
98
+
99
+ // 2. Handle Initial State
100
+ if (this.isEnabled === false) {
101
+ element.style.opacity = "0";
102
+ }
103
+ app.container.appendChild(element);
104
+ },
105
+
106
+ update(app, dt) {
107
+ if (!element) return;
108
+
109
+ // 3. Run Pre-compiled Style Setters
110
+ for (let i = 0; i < styleSetters.length; i++) {
111
+ styleSetters[i](app, element);
112
+ }
113
+
114
+ // 4. Run Custom Update
115
+ config.update?.(app, element, dt);
116
+ },
117
+
118
+ onDisable(app) {
119
+ if (!element) return;
120
+ // Use setStyle to ensure cache remains in sync (0)
121
+ setStyle(element, "opacity", 0);
122
+ config.onDisable?.(app, element);
123
+ },
124
+
125
+ onEnable(app) {
126
+ if (!element) return;
127
+ setStyle(element, "opacity", 1);
128
+ config.onEnable?.(app, element);
129
+ },
130
+
131
+ destroy() {
132
+ if (!element) return;
133
+ config.cleanup?.(element);
134
+ element.remove();
135
+ }
136
+ };
137
+ }
138
+
139
+ // MODE B: LOGIC (Standard Pass-through)
140
+ else {
141
+ return {
142
+ ...config,
143
+ name,
144
+ isEnabled: initialEnabled
145
+ };
146
+ }
147
+ }
package/tsconfig.json CHANGED
@@ -1,11 +1,9 @@
1
- {
2
- "extends": "../../tsconfig.composite-lib.json",
3
- "compilerOptions": {
4
- "rootDir": "src",
5
- "outDir": "dist"
6
- },
7
- "include": ["src"],
8
- "references": [
9
- { "path": "../core" }
10
- ]
11
- }
1
+ {
2
+ "extends": "../../tsconfig.composite-lib.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"],
8
+ "references": [{ "path": "../core" }]
9
+ }