@supermousejs/labs 2.0.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 +14 -0
- package/LICENSE.md +21 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.mjs +233 -0
- package/dist/index.umd.js +1 -0
- package/meta.json +59 -0
- package/package.json +31 -0
- package/src/SmartIcon.ts +239 -0
- package/src/SmartRing.ts +105 -0
- package/src/Sparkles.ts +168 -0
- package/src/TextRing.ts +156 -0
- package/src/index.ts +5 -0
- package/tsconfig.json +21 -0
- package/vite.config.ts +22 -0
package/CHANGELOG.md
ADDED
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sijibomi Olusunmbola
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SupermousePlugin } from '../../core/src/index.ts';
|
|
2
|
+
import { ValueOrGetter } from '../../core/src/index.ts';
|
|
3
|
+
|
|
4
|
+
export declare const SmartIcon: (options: SmartIconOptions) => SupermousePlugin;
|
|
5
|
+
|
|
6
|
+
export declare type SmartIconAnchor = "center" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
7
|
+
|
|
8
|
+
export declare interface SmartIconMap {
|
|
9
|
+
[key: string]: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare interface SmartIconOptions {
|
|
13
|
+
name?: string;
|
|
14
|
+
isEnabled?: boolean;
|
|
15
|
+
icons: SmartIconMap;
|
|
16
|
+
defaultState?: string;
|
|
17
|
+
useSemanticTags?: boolean;
|
|
18
|
+
transitionDuration?: number;
|
|
19
|
+
size?: ValueOrGetter<number>;
|
|
20
|
+
color?: ValueOrGetter<string>;
|
|
21
|
+
offset?: [number, number];
|
|
22
|
+
anchor?: ValueOrGetter<SmartIconAnchor>;
|
|
23
|
+
followStrategy?: ValueOrGetter<"smooth" | "raw">;
|
|
24
|
+
rotateWithVelocity?: ValueOrGetter<boolean>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export declare const SmartRing: (options?: SmartRingOptions) => SupermousePlugin;
|
|
28
|
+
|
|
29
|
+
export declare interface SmartRingOptions {
|
|
30
|
+
name?: string;
|
|
31
|
+
isEnabled?: boolean;
|
|
32
|
+
size?: ValueOrGetter<number>;
|
|
33
|
+
hoverSize?: ValueOrGetter<number>;
|
|
34
|
+
color?: ValueOrGetter<string>;
|
|
35
|
+
fill?: ValueOrGetter<string>;
|
|
36
|
+
borderWidth?: ValueOrGetter<number>;
|
|
37
|
+
mixBlendMode?: string;
|
|
38
|
+
enableSkew?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export declare const Sparkles: (options?: SparklesOptions) => SupermousePlugin;
|
|
42
|
+
|
|
43
|
+
export declare interface SparklesOptions {
|
|
44
|
+
name?: string;
|
|
45
|
+
isEnabled?: boolean;
|
|
46
|
+
color?: ValueOrGetter<string>;
|
|
47
|
+
/** Number of particles to keep in the pool. Default 30. */
|
|
48
|
+
count?: number;
|
|
49
|
+
/** How fast particles fade out (0.01 - 0.1). Default 0.05. */
|
|
50
|
+
decay?: number;
|
|
51
|
+
/** Pixels of movement required to spawn a particle. Lower = denser trail. Default 10. */
|
|
52
|
+
frequency?: number;
|
|
53
|
+
/** Random position offset for spawning. Default 5. */
|
|
54
|
+
scatter?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export declare const TextRing: (options?: TextRingOptions) => SupermousePlugin;
|
|
58
|
+
|
|
59
|
+
export declare interface TextRingOptions {
|
|
60
|
+
name?: string;
|
|
61
|
+
isEnabled?: boolean;
|
|
62
|
+
text?: ValueOrGetter<string>;
|
|
63
|
+
radius?: ValueOrGetter<number>;
|
|
64
|
+
fontSize?: ValueOrGetter<number>;
|
|
65
|
+
speed?: ValueOrGetter<number>;
|
|
66
|
+
color?: ValueOrGetter<string>;
|
|
67
|
+
opacity?: ValueOrGetter<number>;
|
|
68
|
+
className?: string;
|
|
69
|
+
spread?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { }
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { normalize as A, definePlugin as N, dom as n, math as w, Layers as k, effects as F } from "@supermousejs/utils";
|
|
2
|
+
import { getCirclePath as H, getCircumference as X, formatLoopText as Y } from "@supermousejs/zoetrope";
|
|
3
|
+
function U(t, l) {
|
|
4
|
+
const o = t.tagName.toLowerCase();
|
|
5
|
+
if (o === "input" || o === "textarea" || t.isContentEditable) {
|
|
6
|
+
const i = t.type;
|
|
7
|
+
if (["button", "submit", "checkbox", "radio", "range", "color"].includes(
|
|
8
|
+
i
|
|
9
|
+
)) {
|
|
10
|
+
if (l.pointer)
|
|
11
|
+
return "pointer";
|
|
12
|
+
} else if (l.text) return "text";
|
|
13
|
+
} else if ((o === "a" || o === "button" || t.closest("a") || t.closest("button")) && l.pointer)
|
|
14
|
+
return "pointer";
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const q = (t) => {
|
|
18
|
+
let l, o = t.defaultState || "default", i = t.defaultState || "default", d = null, T = null, C = !1, x, h = 0, p = 0;
|
|
19
|
+
const E = A(t.size, 24), r = A(t.followStrategy, "smooth"), S = A(t.anchor, "center"), v = A(t.rotateWithVelocity, !1), y = t.useSemanticTags ?? !0, c = t.transitionDuration ?? 200, s = t.offset ? t.offset[0] : 0, R = t.offset ? t.offset[1] : 0;
|
|
20
|
+
return N(
|
|
21
|
+
{
|
|
22
|
+
name: t.name || "smart-icon",
|
|
23
|
+
selector: "[data-supermouse-icon]",
|
|
24
|
+
create: () => {
|
|
25
|
+
const a = n.createActor("div");
|
|
26
|
+
return a.style.zIndex = k.CURSOR, l = n.createActor("div"), n.applyStyles(l, {
|
|
27
|
+
width: "100%",
|
|
28
|
+
height: "100%",
|
|
29
|
+
display: "flex",
|
|
30
|
+
alignItems: "center",
|
|
31
|
+
justifyContent: "center",
|
|
32
|
+
transformOrigin: "center center",
|
|
33
|
+
transform: "scale(1)",
|
|
34
|
+
// Pre-baked ease. Note: If duration changes dynamically, this won't update.
|
|
35
|
+
transition: `transform ${c / 2}ms cubic-bezier(0.16, 1, 0.3, 1)`
|
|
36
|
+
}), l.innerHTML = t.icons[o] || "", a.appendChild(l), a;
|
|
37
|
+
},
|
|
38
|
+
styles: {
|
|
39
|
+
color: "color"
|
|
40
|
+
},
|
|
41
|
+
update: (a, g) => {
|
|
42
|
+
const e = t.icons, m = a.state.hoverTarget;
|
|
43
|
+
let f = t.defaultState || "default";
|
|
44
|
+
if (m) {
|
|
45
|
+
m !== d && (d = m, T = y ? U(m, e) : null);
|
|
46
|
+
const L = a.state.interaction?.icon;
|
|
47
|
+
L && e[L] ? f = L : T && (f = T);
|
|
48
|
+
} else
|
|
49
|
+
d = null, T = null;
|
|
50
|
+
f !== o && !C && (e[f] || f === (t.defaultState || "default")) && (i = f, C = !0, clearTimeout(x), l.style.transform = "scale(0)", x = setTimeout(() => {
|
|
51
|
+
o = i, l.innerHTML = e[o] || "", l.style.transform = "scale(1)", x = setTimeout(() => {
|
|
52
|
+
C = !1;
|
|
53
|
+
}, c / 2);
|
|
54
|
+
}, c / 2));
|
|
55
|
+
const b = E(a.state);
|
|
56
|
+
n.setStyle(g, "width", `${b}px`), n.setStyle(g, "height", `${b}px`);
|
|
57
|
+
let u = 0, z = 0;
|
|
58
|
+
const $ = b / 2, O = S(a.state);
|
|
59
|
+
O !== "center" && (O.includes("left") && (u = $), O.includes("right") && (u = -$), O.includes("top") && (z = $), O.includes("bottom") && (z = -$));
|
|
60
|
+
const P = o === "pointer" || o === "text";
|
|
61
|
+
if (v(a.state) && !P && !a.state.reducedMotion) {
|
|
62
|
+
const { x: L, y: I } = a.state.velocity;
|
|
63
|
+
w.dist(L, I) > 1 && (p = w.angle(L, I)), h = w.lerpAngle(
|
|
64
|
+
h,
|
|
65
|
+
p,
|
|
66
|
+
0.15
|
|
67
|
+
);
|
|
68
|
+
} else
|
|
69
|
+
h = w.lerpAngle(h, 0, 0.15);
|
|
70
|
+
const M = r(a.state) === "raw" ? a.state.pointer : a.state.smooth;
|
|
71
|
+
n.setTransform(
|
|
72
|
+
g,
|
|
73
|
+
M.x + s + u,
|
|
74
|
+
M.y + R + z,
|
|
75
|
+
h
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
cleanup() {
|
|
79
|
+
clearTimeout(x);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
t
|
|
83
|
+
);
|
|
84
|
+
}, V = (t = {}) => {
|
|
85
|
+
const l = A(t.size, 20), o = A(t.hoverSize, 40), i = A(t.color, "#ffffff"), d = A(t.borderWidth, 2), T = A(t.fill, "transparent");
|
|
86
|
+
let C = 20, x = 20, h = 0, p = 1, E = 1;
|
|
87
|
+
return N(
|
|
88
|
+
{
|
|
89
|
+
name: t.name || "smart-ring",
|
|
90
|
+
selector: "[data-supermouse-color]",
|
|
91
|
+
create: (r) => {
|
|
92
|
+
const S = n.createCircle(l(r.state), T(r.state));
|
|
93
|
+
return n.applyStyles(S, {
|
|
94
|
+
zIndex: k.FOLLOWER,
|
|
95
|
+
mixBlendMode: t.mixBlendMode || "difference",
|
|
96
|
+
transition: "opacity 0.2s ease, border-radius 0.2s ease",
|
|
97
|
+
borderStyle: "solid"
|
|
98
|
+
}), S;
|
|
99
|
+
},
|
|
100
|
+
update: (r, S) => {
|
|
101
|
+
const v = l(r.state), y = r.state.shape;
|
|
102
|
+
let c = v, s = v, R = "50%", a = i(r.state);
|
|
103
|
+
y ? (c = y.width, s = y.height, R = `${y.borderRadius}px`) : (r.state.isHover && (c = o(r.state), s = o(r.state)), r.state.isDown && (c *= 0.9, s *= 0.9)), r.state.interaction.color && (a = r.state.interaction.color), C = w.lerp(C, c, 0.2), x = w.lerp(x, s, 0.2), n.setStyle(S, "width", `${C}px`), n.setStyle(S, "height", `${x}px`), n.setStyle(S, "borderRadius", R), n.setStyle(S, "borderColor", a), n.setStyle(S, "backgroundColor", T(r.state)), n.setStyle(S, "borderWidth", `${d(r.state)}px`);
|
|
104
|
+
let g = 0, e = 1, m = 1;
|
|
105
|
+
if (!y && t.enableSkew && !r.state.reducedMotion) {
|
|
106
|
+
const { velocity: u } = r.state, z = F.getVelocityDistortion(u.x, u.y);
|
|
107
|
+
g = z.rotation, e = z.scaleX, m = z.scaleY, h = w.lerpAngle(h, g, 0.15);
|
|
108
|
+
} else
|
|
109
|
+
h = 0;
|
|
110
|
+
p = w.lerp(p, e, 0.15), E = w.lerp(E, m, 0.15);
|
|
111
|
+
const { x: f, y: b } = r.state.smooth;
|
|
112
|
+
n.setTransform(S, f, b, h, p, E);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
t
|
|
116
|
+
);
|
|
117
|
+
}, G = (t = {}) => {
|
|
118
|
+
const l = t.count || 30, o = t.decay || 0.05, i = t.frequency || 10, d = t.scatter || 5, C = A(t.color, "#ff00ff"), x = [];
|
|
119
|
+
let h = 0, p = 0, E = !1, r = 0;
|
|
120
|
+
const S = (v, y, c) => {
|
|
121
|
+
const s = x.find((e) => !e.isActive);
|
|
122
|
+
if (!s) return;
|
|
123
|
+
s.isActive = !0, s.life = 1, s.x = v + w.random(-d, d), s.y = y + w.random(-d, d);
|
|
124
|
+
const R = w.random(0, Math.PI * 2), a = w.random(0.5, 1.5);
|
|
125
|
+
s.vx = Math.cos(R) * a, s.vy = Math.sin(R) * a, s.scale = w.random(0.5, 1.2), s.color = c;
|
|
126
|
+
const g = w.random(2, 5);
|
|
127
|
+
n.setStyle(s.el, "width", `${g}px`), n.setStyle(s.el, "height", `${g}px`), n.setStyle(s.el, "backgroundColor", c), n.setStyle(s.el, "opacity", "1"), n.setTransform(s.el, s.x, s.y, 0, s.scale, s.scale);
|
|
128
|
+
};
|
|
129
|
+
return N(
|
|
130
|
+
{
|
|
131
|
+
name: t.name || "sparkles",
|
|
132
|
+
install(v) {
|
|
133
|
+
for (let y = 0; y < l; y++) {
|
|
134
|
+
const c = n.createCircle(0, "transparent");
|
|
135
|
+
n.applyStyles(c, {
|
|
136
|
+
zIndex: k.TRACE,
|
|
137
|
+
opacity: "0",
|
|
138
|
+
willChange: "transform, opacity",
|
|
139
|
+
transition: "none"
|
|
140
|
+
}), v.container.appendChild(c), x.push({
|
|
141
|
+
el: c,
|
|
142
|
+
isActive: !1,
|
|
143
|
+
x: 0,
|
|
144
|
+
y: 0,
|
|
145
|
+
vx: 0,
|
|
146
|
+
vy: 0,
|
|
147
|
+
life: 0,
|
|
148
|
+
scale: 1,
|
|
149
|
+
color: ""
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
update(v) {
|
|
154
|
+
const { x: y, y: c } = v.state.pointer;
|
|
155
|
+
E || (h = y, p = c, E = !0);
|
|
156
|
+
const s = y - h, R = c - p, a = Math.hypot(s, R);
|
|
157
|
+
if (r += a, r > i) {
|
|
158
|
+
const g = C(v.state), e = Math.floor(r / i);
|
|
159
|
+
for (let m = 0; m < e; m++) {
|
|
160
|
+
const f = m / e, b = h + s * f, u = p + R * f;
|
|
161
|
+
S(b, u, g);
|
|
162
|
+
}
|
|
163
|
+
r = r % i;
|
|
164
|
+
}
|
|
165
|
+
h = y, p = c;
|
|
166
|
+
for (let g = 0; g < l; g++) {
|
|
167
|
+
const e = x[g];
|
|
168
|
+
if (e.isActive)
|
|
169
|
+
if (e.x += e.vx, e.y += e.vy, e.life -= o, e.life <= 0)
|
|
170
|
+
e.isActive = !1, e.el.style.opacity = "0";
|
|
171
|
+
else {
|
|
172
|
+
e.el.style.opacity = String(e.life);
|
|
173
|
+
const m = e.scale * e.life;
|
|
174
|
+
n.setTransform(e.el, e.x, e.y, 0, m, m);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
destroy() {
|
|
179
|
+
x.forEach((v) => v.el.remove()), x.length = 0;
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
t
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
let j = 0;
|
|
186
|
+
const J = (t = {}) => {
|
|
187
|
+
let l, o, i, d, T;
|
|
188
|
+
const C = `supermouse-text-ring-path-${j++}`, x = "SUPERMOUSE • SUPERMOUSE • ", h = 60, p = 12, E = 0.5, r = t.className || "", S = t.spread ?? !1, v = A(t.text, x), y = A(t.radius, h), c = A(t.fontSize, p), s = A(t.speed, E), R = A(t.opacity, 1);
|
|
189
|
+
let a = 0, g = "", e = 0, m = 0;
|
|
190
|
+
return N({
|
|
191
|
+
name: "text-ring",
|
|
192
|
+
selector: "[data-supermouse-text-ring]",
|
|
193
|
+
create: (f) => {
|
|
194
|
+
const b = n.createActor("div");
|
|
195
|
+
n.applyStyles(b, {
|
|
196
|
+
zIndex: k.FOLLOWER,
|
|
197
|
+
transition: "opacity 0.2s ease",
|
|
198
|
+
opacity: "1",
|
|
199
|
+
width: "0px",
|
|
200
|
+
height: "0px",
|
|
201
|
+
overflow: "visible",
|
|
202
|
+
display: "flex",
|
|
203
|
+
alignItems: "center",
|
|
204
|
+
justifyContent: "center"
|
|
205
|
+
}), r && b.classList.add(...r.split(" ").filter(Boolean)), l = document.createElementNS("http://www.w3.org/2000/svg", "svg"), l.style.overflow = "visible", l.style.position = "absolute", l.style.left = "0", l.style.top = "0", o = document.createElementNS("http://www.w3.org/2000/svg", "path"), o.setAttribute("id", C), o.setAttribute("fill", "none"), d = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
206
|
+
const u = c(f.state);
|
|
207
|
+
return d.setAttribute("font-size", `${u}px`), m = u, d.setAttribute("fill", "currentColor"), d.style.textTransform = "uppercase", d.style.letterSpacing = "2px", i = document.createElementNS("http://www.w3.org/2000/svg", "textPath"), i.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", `#${C}`), i.setAttribute("href", `#${C}`), i.setAttribute("startOffset", "0%"), T = document.createTextNode(""), i.appendChild(T), d.appendChild(i), l.appendChild(o), l.appendChild(d), b.appendChild(l), b;
|
|
208
|
+
},
|
|
209
|
+
styles: {
|
|
210
|
+
color: "color"
|
|
211
|
+
},
|
|
212
|
+
update: (f, b) => {
|
|
213
|
+
let u = v(f.state);
|
|
214
|
+
const z = y(f.state), $ = c(f.state), O = s(f.state), P = R(f.state);
|
|
215
|
+
n.setStyle(b, "opacity", String(P));
|
|
216
|
+
const M = f.state.interaction;
|
|
217
|
+
if (M.textRing && typeof M.textRing == "string" ? u = M.textRing : M.text && typeof M.text == "string" && (u = M.text), z !== e && (o.setAttribute("d", H(z)), e = z), S) {
|
|
218
|
+
const W = X(z);
|
|
219
|
+
i.setAttribute("textLength", String(W)), i.setAttribute("lengthAdjust", "spacing"), u = Y(u, !0);
|
|
220
|
+
} else
|
|
221
|
+
i.removeAttribute("textLength"), i.removeAttribute("lengthAdjust");
|
|
222
|
+
$ !== m && (d.setAttribute("font-size", `${$}px`), m = $), u !== g && (T.textContent = u, g = u), a += O;
|
|
223
|
+
const { x: L, y: I } = f.state.smooth;
|
|
224
|
+
n.setTransform(b, L, I, a);
|
|
225
|
+
}
|
|
226
|
+
}, t);
|
|
227
|
+
};
|
|
228
|
+
export {
|
|
229
|
+
q as SmartIcon,
|
|
230
|
+
V as SmartRing,
|
|
231
|
+
G as Sparkles,
|
|
232
|
+
J as TextRing
|
|
233
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(C,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("@supermousejs/utils"),require("@supermousejs/zoetrope")):typeof define=="function"&&define.amd?define(["exports","@supermousejs/utils","@supermousejs/zoetrope"],e):(C=typeof globalThis<"u"?globalThis:C||self,e(C.SupermouseLabs={},C.SupermouseUtils,C.SupermouseZoetrope))})(this,(function(C,e,P){"use strict";function N(t,a){const c=t.tagName.toLowerCase();if(c==="input"||c==="textarea"||t.isContentEditable){const i=t.type;if(["button","submit","checkbox","radio","range","color"].includes(i)){if(a.pointer)return"pointer"}else if(a.text)return"text"}else if((c==="a"||c==="button"||t.closest("a")||t.closest("button"))&&a.pointer)return"pointer";return null}const k=t=>{let a,c=t.defaultState||"default",i=t.defaultState||"default",f=null,w=null,p=!1,S,g=0,z=0;const R=e.normalize(t.size,24),o=e.normalize(t.followStrategy,"smooth"),x=e.normalize(t.anchor,"center"),v=e.normalize(t.rotateWithVelocity,!1),h=t.useSemanticTags??!0,l=t.transitionDuration??200,n=t.offset?t.offset[0]:0,T=t.offset?t.offset[1]:0;return e.definePlugin({name:t.name||"smart-icon",selector:"[data-supermouse-icon]",create:()=>{const s=e.dom.createActor("div");return s.style.zIndex=e.Layers.CURSOR,a=e.dom.createActor("div"),e.dom.applyStyles(a,{width:"100%",height:"100%",display:"flex",alignItems:"center",justifyContent:"center",transformOrigin:"center center",transform:"scale(1)",transition:`transform ${l/2}ms cubic-bezier(0.16, 1, 0.3, 1)`}),a.innerHTML=t.icons[c]||"",s.appendChild(a),s},styles:{color:"color"},update:(s,y)=>{const r=t.icons,u=s.state.hoverTarget;let d=t.defaultState||"default";if(u){u!==f&&(f=u,w=h?N(u,r):null);const M=s.state.interaction?.icon;M&&r[M]?d=M:w&&(d=w)}else f=null,w=null;d!==c&&!p&&(r[d]||d===(t.defaultState||"default"))&&(i=d,p=!0,clearTimeout(S),a.style.transform="scale(0)",S=setTimeout(()=>{c=i,a.innerHTML=r[c]||"",a.style.transform="scale(1)",S=setTimeout(()=>{p=!1},l/2)},l/2));const b=R(s.state);e.dom.setStyle(y,"width",`${b}px`),e.dom.setStyle(y,"height",`${b}px`);let m=0,A=0;const L=b/2,$=x(s.state);$!=="center"&&($.includes("left")&&(m=L),$.includes("right")&&(m=-L),$.includes("top")&&(A=L),$.includes("bottom")&&(A=-L));const I=c==="pointer"||c==="text";if(v(s.state)&&!I&&!s.state.reducedMotion){const{x:M,y:O}=s.state.velocity;e.math.dist(M,O)>1&&(z=e.math.angle(M,O)),g=e.math.lerpAngle(g,z,.15)}else g=e.math.lerpAngle(g,0,.15);const E=o(s.state)==="raw"?s.state.pointer:s.state.smooth;e.dom.setTransform(y,E.x+n+m,E.y+T+A,g)},cleanup(){clearTimeout(S)}},t)},W=(t={})=>{const a=e.normalize(t.size,20),c=e.normalize(t.hoverSize,40),i=e.normalize(t.color,"#ffffff"),f=e.normalize(t.borderWidth,2),w=e.normalize(t.fill,"transparent");let p=20,S=20,g=0,z=1,R=1;return e.definePlugin({name:t.name||"smart-ring",selector:"[data-supermouse-color]",create:o=>{const x=e.dom.createCircle(a(o.state),w(o.state));return e.dom.applyStyles(x,{zIndex:e.Layers.FOLLOWER,mixBlendMode:t.mixBlendMode||"difference",transition:"opacity 0.2s ease, border-radius 0.2s ease",borderStyle:"solid"}),x},update:(o,x)=>{const v=a(o.state),h=o.state.shape;let l=v,n=v,T="50%",s=i(o.state);h?(l=h.width,n=h.height,T=`${h.borderRadius}px`):(o.state.isHover&&(l=c(o.state),n=c(o.state)),o.state.isDown&&(l*=.9,n*=.9)),o.state.interaction.color&&(s=o.state.interaction.color),p=e.math.lerp(p,l,.2),S=e.math.lerp(S,n,.2),e.dom.setStyle(x,"width",`${p}px`),e.dom.setStyle(x,"height",`${S}px`),e.dom.setStyle(x,"borderRadius",T),e.dom.setStyle(x,"borderColor",s),e.dom.setStyle(x,"backgroundColor",w(o.state)),e.dom.setStyle(x,"borderWidth",`${f(o.state)}px`);let y=0,r=1,u=1;if(!h&&t.enableSkew&&!o.state.reducedMotion){const{velocity:m}=o.state,A=e.effects.getVelocityDistortion(m.x,m.y);y=A.rotation,r=A.scaleX,u=A.scaleY,g=e.math.lerpAngle(g,y,.15)}else g=0;z=e.math.lerp(z,r,.15),R=e.math.lerp(R,u,.15);const{x:d,y:b}=o.state.smooth;e.dom.setTransform(x,d,b,g,z,R)}},t)},F=(t={})=>{const a=t.count||30,c=t.decay||.05,i=t.frequency||10,f=t.scatter||5,p=e.normalize(t.color,"#ff00ff"),S=[];let g=0,z=0,R=!1,o=0;const x=(v,h,l)=>{const n=S.find(r=>!r.isActive);if(!n)return;n.isActive=!0,n.life=1,n.x=v+e.math.random(-f,f),n.y=h+e.math.random(-f,f);const T=e.math.random(0,Math.PI*2),s=e.math.random(.5,1.5);n.vx=Math.cos(T)*s,n.vy=Math.sin(T)*s,n.scale=e.math.random(.5,1.2),n.color=l;const y=e.math.random(2,5);e.dom.setStyle(n.el,"width",`${y}px`),e.dom.setStyle(n.el,"height",`${y}px`),e.dom.setStyle(n.el,"backgroundColor",l),e.dom.setStyle(n.el,"opacity","1"),e.dom.setTransform(n.el,n.x,n.y,0,n.scale,n.scale)};return e.definePlugin({name:t.name||"sparkles",install(v){for(let h=0;h<a;h++){const l=e.dom.createCircle(0,"transparent");e.dom.applyStyles(l,{zIndex:e.Layers.TRACE,opacity:"0",willChange:"transform, opacity",transition:"none"}),v.container.appendChild(l),S.push({el:l,isActive:!1,x:0,y:0,vx:0,vy:0,life:0,scale:1,color:""})}},update(v){const{x:h,y:l}=v.state.pointer;R||(g=h,z=l,R=!0);const n=h-g,T=l-z,s=Math.hypot(n,T);if(o+=s,o>i){const y=p(v.state),r=Math.floor(o/i);for(let u=0;u<r;u++){const d=u/r,b=g+n*d,m=z+T*d;x(b,m,y)}o=o%i}g=h,z=l;for(let y=0;y<a;y++){const r=S[y];if(r.isActive)if(r.x+=r.vx,r.y+=r.vy,r.life-=c,r.life<=0)r.isActive=!1,r.el.style.opacity="0";else{r.el.style.opacity=String(r.life);const u=r.scale*r.life;e.dom.setTransform(r.el,r.x,r.y,0,u,u)}}},destroy(){S.forEach(v=>v.el.remove()),S.length=0}},t)};let H=0;const U=(t={})=>{let a,c,i,f,w;const p=`supermouse-text-ring-path-${H++}`,S="SUPERMOUSE • SUPERMOUSE • ",g=60,z=12,R=.5,o=t.className||"",x=t.spread??!1,v=e.normalize(t.text,S),h=e.normalize(t.radius,g),l=e.normalize(t.fontSize,z),n=e.normalize(t.speed,R),T=e.normalize(t.opacity,1);let s=0,y="",r=0,u=0;return e.definePlugin({name:"text-ring",selector:"[data-supermouse-text-ring]",create:d=>{const b=e.dom.createActor("div");e.dom.applyStyles(b,{zIndex:e.Layers.FOLLOWER,transition:"opacity 0.2s ease",opacity:"1",width:"0px",height:"0px",overflow:"visible",display:"flex",alignItems:"center",justifyContent:"center"}),o&&b.classList.add(...o.split(" ").filter(Boolean)),a=document.createElementNS("http://www.w3.org/2000/svg","svg"),a.style.overflow="visible",a.style.position="absolute",a.style.left="0",a.style.top="0",c=document.createElementNS("http://www.w3.org/2000/svg","path"),c.setAttribute("id",p),c.setAttribute("fill","none"),f=document.createElementNS("http://www.w3.org/2000/svg","text");const m=l(d.state);return f.setAttribute("font-size",`${m}px`),u=m,f.setAttribute("fill","currentColor"),f.style.textTransform="uppercase",f.style.letterSpacing="2px",i=document.createElementNS("http://www.w3.org/2000/svg","textPath"),i.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",`#${p}`),i.setAttribute("href",`#${p}`),i.setAttribute("startOffset","0%"),w=document.createTextNode(""),i.appendChild(w),f.appendChild(i),a.appendChild(c),a.appendChild(f),b.appendChild(a),b},styles:{color:"color"},update:(d,b)=>{let m=v(d.state);const A=h(d.state),L=l(d.state),$=n(d.state),I=T(d.state);e.dom.setStyle(b,"opacity",String(I));const E=d.state.interaction;if(E.textRing&&typeof E.textRing=="string"?m=E.textRing:E.text&&typeof E.text=="string"&&(m=E.text),A!==r&&(c.setAttribute("d",P.getCirclePath(A)),r=A),x){const j=P.getCircumference(A);i.setAttribute("textLength",String(j)),i.setAttribute("lengthAdjust","spacing"),m=P.formatLoopText(m,!0)}else i.removeAttribute("textLength"),i.removeAttribute("lengthAdjust");L!==u&&(f.setAttribute("font-size",`${L}px`),u=L),m!==y&&(w.textContent=m,y=m),s+=$;const{x:M,y:O}=d.state.smooth;e.dom.setTransform(b,M,O,s)}},t)};C.SmartIcon=k,C.SmartRing=W,C.Sparkles=F,C.TextRing=U,Object.defineProperty(C,Symbol.toStringTag,{value:"Module"})}));
|
package/meta.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
|
|
2
|
+
[
|
|
3
|
+
{
|
|
4
|
+
"id": "smart-icon",
|
|
5
|
+
"name": "SmartIcon",
|
|
6
|
+
"package": "@supermousejs/labs",
|
|
7
|
+
"description": "Advanced state machine that morphs SVGs based on semantic context.",
|
|
8
|
+
"code": "import { SmartIcon } from '@supermousejs/labs';\n\napp.use(SmartIcon({ icons: { ... } }));",
|
|
9
|
+
"recipeId": "context-icon",
|
|
10
|
+
"icon": "labs",
|
|
11
|
+
"options": [
|
|
12
|
+
{ "name": "icons", "type": "Record<string, string>", "default": "{}", "description": "Map of state names to SVG strings.", "reactive": false },
|
|
13
|
+
{ "name": "size", "type": "number", "default": "24", "description": "Size of the icon container.", "reactive": true },
|
|
14
|
+
{ "name": "color", "type": "string", "default": "'black'", "description": "Icon fill color (currentColor).", "reactive": true },
|
|
15
|
+
{ "name": "anchor", "type": "string", "default": "'center'", "description": "Alignment (center, top-left, etc).", "reactive": true },
|
|
16
|
+
{ "name": "offset", "type": "[number, number]", "default": "[0, 0]", "description": "Fixed offset.", "reactive": false },
|
|
17
|
+
{ "name": "transitionDuration", "type": "number", "default": "200", "description": "Morph transition time in ms.", "reactive": false }
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "smart-ring",
|
|
22
|
+
"name": "SmartRing",
|
|
23
|
+
"package": "@supermousejs/labs",
|
|
24
|
+
"description": "A reactive ring that distorts with velocity and adapts to stuck elements.",
|
|
25
|
+
"code": "import { SmartRing } from '@supermousejs/labs';\n\napp.use(SmartRing({ size: 24, color: '#f59e0b', enableSkew: true }));",
|
|
26
|
+
"icon": "ring",
|
|
27
|
+
"options": [
|
|
28
|
+
{ "name": "size", "type": "number", "default": "20", "description": "Base diameter.", "reactive": true },
|
|
29
|
+
{ "name": "hoverSize", "type": "number", "default": "40", "description": "Diameter on hover.", "reactive": true },
|
|
30
|
+
{ "name": "enableSkew", "type": "boolean", "default": "false", "description": "Distort shape based on velocity.", "reactive": false }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "sparkles",
|
|
35
|
+
"name": "Sparkles",
|
|
36
|
+
"package": "@supermousejs/labs",
|
|
37
|
+
"description": "Emits particle trails based on movement velocity.",
|
|
38
|
+
"code": "import { Sparkles } from '@supermousejs/labs';\n\napp.use(Sparkles({ color: '#f59e0b', frequency: 10 }));",
|
|
39
|
+
"icon": "trail",
|
|
40
|
+
"options": [
|
|
41
|
+
{ "name": "color", "type": "string", "default": "'#ff00ff'", "description": "Particle color.", "reactive": true },
|
|
42
|
+
{ "name": "frequency", "type": "number", "default": "10", "description": "Pixels per spawn.", "reactive": false },
|
|
43
|
+
{ "name": "decay", "type": "number", "default": "0.05", "description": "Fade rate.", "reactive": false }
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "text-ring",
|
|
48
|
+
"name": "TextRing",
|
|
49
|
+
"package": "@supermousejs/labs",
|
|
50
|
+
"description": "Rotates text around the cursor position.",
|
|
51
|
+
"code": "import { TextRing } from '@supermousejs/labs';\n\napp.use(TextRing({ text: 'LOADING • ', radius: 40 }));",
|
|
52
|
+
"icon": "text",
|
|
53
|
+
"options": [
|
|
54
|
+
{ "name": "text", "type": "string", "default": "'SUPERMOUSE • '", "description": "Text content.", "reactive": true },
|
|
55
|
+
{ "name": "radius", "type": "number", "default": "60", "description": "Ring radius.", "reactive": true },
|
|
56
|
+
{ "name": "speed", "type": "number", "default": "0.5", "description": "Rotation speed deg/frame.", "reactive": true }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
]
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@supermousejs/labs",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"main": "dist/index.umd.js",
|
|
5
|
+
"module": "dist/index.mjs",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@supermousejs/zoetrope": "2.0.0",
|
|
9
|
+
"@supermousejs/core": "2.0.0",
|
|
10
|
+
"@supermousejs/utils": "2.0.0"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@supermousejs/core": "2.0.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@supermousejs/core": "2.0.0"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.mjs",
|
|
22
|
+
"require": "./dist/index.umd.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "vite build"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/SmartIcon.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type { ValueOrGetter } from "@supermousejs/core";
|
|
2
|
+
import { definePlugin, normalize, dom, math, Layers } from "@supermousejs/utils";
|
|
3
|
+
|
|
4
|
+
export interface SmartIconMap {
|
|
5
|
+
[key: string]: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type SmartIconAnchor =
|
|
9
|
+
| "center"
|
|
10
|
+
| "top-left"
|
|
11
|
+
| "top-right"
|
|
12
|
+
| "bottom-left"
|
|
13
|
+
| "bottom-right";
|
|
14
|
+
|
|
15
|
+
export interface SmartIconOptions {
|
|
16
|
+
name?: string;
|
|
17
|
+
isEnabled?: boolean;
|
|
18
|
+
icons: SmartIconMap;
|
|
19
|
+
defaultState?: string;
|
|
20
|
+
useSemanticTags?: boolean;
|
|
21
|
+
transitionDuration?: number;
|
|
22
|
+
size?: ValueOrGetter<number>;
|
|
23
|
+
color?: ValueOrGetter<string>;
|
|
24
|
+
offset?: [number, number];
|
|
25
|
+
anchor?: ValueOrGetter<SmartIconAnchor>;
|
|
26
|
+
followStrategy?: ValueOrGetter<"smooth" | "raw">;
|
|
27
|
+
rotateWithVelocity?: ValueOrGetter<boolean>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveSemanticState(
|
|
31
|
+
target: HTMLElement,
|
|
32
|
+
icons: SmartIconMap
|
|
33
|
+
): string | null {
|
|
34
|
+
const tag = target.tagName.toLowerCase();
|
|
35
|
+
|
|
36
|
+
if (tag === "input" || tag === "textarea" || target.isContentEditable) {
|
|
37
|
+
const type = (target as HTMLInputElement).type;
|
|
38
|
+
if (
|
|
39
|
+
!["button", "submit", "checkbox", "radio", "range", "color"].includes(
|
|
40
|
+
type
|
|
41
|
+
)
|
|
42
|
+
) {
|
|
43
|
+
if (icons["text"]) return "text";
|
|
44
|
+
} else if (icons["pointer"]) {
|
|
45
|
+
return "pointer";
|
|
46
|
+
}
|
|
47
|
+
} else if (
|
|
48
|
+
tag === "a" ||
|
|
49
|
+
tag === "button" ||
|
|
50
|
+
target.closest("a") ||
|
|
51
|
+
target.closest("button")
|
|
52
|
+
) {
|
|
53
|
+
if (icons["pointer"]) return "pointer";
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const SmartIcon = (options: SmartIconOptions) => {
|
|
59
|
+
let contentWrapper: HTMLDivElement;
|
|
60
|
+
|
|
61
|
+
// State
|
|
62
|
+
let currentState = options.defaultState || "default";
|
|
63
|
+
let targetState = options.defaultState || "default";
|
|
64
|
+
|
|
65
|
+
let lastTarget: HTMLElement | null = null;
|
|
66
|
+
let cachedSemanticState: string | null = null;
|
|
67
|
+
|
|
68
|
+
let isTransitioning = false;
|
|
69
|
+
let transitionTimer: ReturnType<typeof setTimeout>;
|
|
70
|
+
|
|
71
|
+
// Rotation State
|
|
72
|
+
let currentRotation = 0;
|
|
73
|
+
let lastTargetRotation = 0;
|
|
74
|
+
|
|
75
|
+
// Normalized Getters (Pre-calculated)
|
|
76
|
+
const getSize = normalize(options.size, 24);
|
|
77
|
+
const getStrategy = normalize(options.followStrategy, "smooth");
|
|
78
|
+
const getAnchor = normalize(options.anchor, "center");
|
|
79
|
+
const getShouldRotate = normalize(options.rotateWithVelocity, false);
|
|
80
|
+
|
|
81
|
+
const useSemanticTags = options.useSemanticTags ?? true;
|
|
82
|
+
const duration = options.transitionDuration ?? 200;
|
|
83
|
+
const userOffX = options.offset ? options.offset[0] : 0;
|
|
84
|
+
const userOffY = options.offset ? options.offset[1] : 0;
|
|
85
|
+
|
|
86
|
+
return definePlugin<HTMLDivElement, SmartIconOptions>(
|
|
87
|
+
{
|
|
88
|
+
name: options.name || "smart-icon",
|
|
89
|
+
selector: "[data-supermouse-icon]",
|
|
90
|
+
|
|
91
|
+
create: () => {
|
|
92
|
+
const el = dom.createActor("div") as HTMLDivElement;
|
|
93
|
+
el.style.zIndex = Layers.CURSOR;
|
|
94
|
+
|
|
95
|
+
contentWrapper = dom.createActor("div") as HTMLDivElement;
|
|
96
|
+
dom.applyStyles(contentWrapper, {
|
|
97
|
+
width: "100%",
|
|
98
|
+
height: "100%",
|
|
99
|
+
display: "flex",
|
|
100
|
+
alignItems: "center",
|
|
101
|
+
justifyContent: "center",
|
|
102
|
+
transformOrigin: "center center",
|
|
103
|
+
transform: "scale(1)",
|
|
104
|
+
// Pre-baked ease. Note: If duration changes dynamically, this won't update.
|
|
105
|
+
transition: `transform ${
|
|
106
|
+
duration / 2
|
|
107
|
+
}ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
contentWrapper.innerHTML = options.icons[currentState] || "";
|
|
111
|
+
|
|
112
|
+
el.appendChild(contentWrapper);
|
|
113
|
+
return el;
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
styles: {
|
|
117
|
+
color: "color",
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
update: (app, el) => {
|
|
121
|
+
const icons = options.icons;
|
|
122
|
+
const target = app.state.hoverTarget;
|
|
123
|
+
|
|
124
|
+
// 1. Determine Next State
|
|
125
|
+
let nextState = options.defaultState || "default";
|
|
126
|
+
|
|
127
|
+
if (target) {
|
|
128
|
+
// A. Update Cache if target changed (Performance Fix)
|
|
129
|
+
if (target !== lastTarget) {
|
|
130
|
+
lastTarget = target;
|
|
131
|
+
cachedSemanticState = useSemanticTags
|
|
132
|
+
? resolveSemanticState(target, icons)
|
|
133
|
+
: null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// B. Resolve Logic
|
|
137
|
+
// Check Unified Interaction State (Attribute)
|
|
138
|
+
const attrSmartIcon = app.state.interaction?.icon; // Optional chaining safety
|
|
139
|
+
|
|
140
|
+
if (attrSmartIcon && icons[attrSmartIcon]) {
|
|
141
|
+
nextState = attrSmartIcon;
|
|
142
|
+
} else if (cachedSemanticState) {
|
|
143
|
+
nextState = cachedSemanticState;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
lastTarget = null;
|
|
147
|
+
cachedSemanticState = null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 2. Handle State Transition
|
|
151
|
+
if (nextState !== currentState && !isTransitioning) {
|
|
152
|
+
if (
|
|
153
|
+
icons[nextState] ||
|
|
154
|
+
nextState === (options.defaultState || "default")
|
|
155
|
+
) {
|
|
156
|
+
targetState = nextState;
|
|
157
|
+
isTransitioning = true;
|
|
158
|
+
|
|
159
|
+
// Clean up any pending timer to avoid race conditions
|
|
160
|
+
clearTimeout(transitionTimer);
|
|
161
|
+
|
|
162
|
+
// Scale Down
|
|
163
|
+
contentWrapper.style.transform = "scale(0)";
|
|
164
|
+
|
|
165
|
+
transitionTimer = setTimeout(() => {
|
|
166
|
+
currentState = targetState;
|
|
167
|
+
contentWrapper.innerHTML = icons[currentState] || "";
|
|
168
|
+
contentWrapper.style.transform = "scale(1)";
|
|
169
|
+
|
|
170
|
+
transitionTimer = setTimeout(() => {
|
|
171
|
+
isTransitioning = false;
|
|
172
|
+
}, duration / 2);
|
|
173
|
+
}, duration / 2);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 3. Layout & Styling
|
|
178
|
+
const size = getSize(app.state);
|
|
179
|
+
dom.setStyle(el, "width", `${size}px`);
|
|
180
|
+
dom.setStyle(el, "height", `${size}px`);
|
|
181
|
+
|
|
182
|
+
// 4. Calculate Anchor
|
|
183
|
+
let anchorX = 0;
|
|
184
|
+
let anchorY = 0;
|
|
185
|
+
const half = size / 2;
|
|
186
|
+
const anchor = getAnchor(app.state);
|
|
187
|
+
|
|
188
|
+
if (anchor !== "center") {
|
|
189
|
+
if (anchor.includes("left")) anchorX = half;
|
|
190
|
+
if (anchor.includes("right")) anchorX = -half;
|
|
191
|
+
if (anchor.includes("top")) anchorY = half;
|
|
192
|
+
if (anchor.includes("bottom")) anchorY = -half;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 5. Rotation
|
|
196
|
+
const isSemanticState =
|
|
197
|
+
currentState === "pointer" || currentState === "text";
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
getShouldRotate(app.state) &&
|
|
201
|
+
!isSemanticState &&
|
|
202
|
+
!app.state.reducedMotion
|
|
203
|
+
) {
|
|
204
|
+
const { x: vx, y: vy } = app.state.velocity;
|
|
205
|
+
const speed = math.dist(vx, vy);
|
|
206
|
+
|
|
207
|
+
if (speed > 1) {
|
|
208
|
+
lastTargetRotation = math.angle(vx, vy);
|
|
209
|
+
}
|
|
210
|
+
// Assuming math.lerpAngle handles the 360 wrap logic correctly
|
|
211
|
+
currentRotation = math.lerpAngle(
|
|
212
|
+
currentRotation,
|
|
213
|
+
lastTargetRotation,
|
|
214
|
+
0.15
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
currentRotation = math.lerpAngle(currentRotation, 0, 0.15);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 6. Render
|
|
221
|
+
const pos =
|
|
222
|
+
getStrategy(app.state) === "raw"
|
|
223
|
+
? app.state.pointer
|
|
224
|
+
: app.state.smooth;
|
|
225
|
+
dom.setTransform(
|
|
226
|
+
el,
|
|
227
|
+
pos.x + userOffX + anchorX,
|
|
228
|
+
pos.y + userOffY + anchorY,
|
|
229
|
+
currentRotation
|
|
230
|
+
);
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
cleanup() {
|
|
234
|
+
clearTimeout(transitionTimer);
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
options
|
|
238
|
+
);
|
|
239
|
+
};
|
package/src/SmartRing.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { ValueOrGetter } from "@supermousejs/core";
|
|
2
|
+
import { definePlugin, normalize, dom, math, effects, Layers } from "@supermousejs/utils";
|
|
3
|
+
|
|
4
|
+
export interface SmartRingOptions {
|
|
5
|
+
name?: string;
|
|
6
|
+
isEnabled?: boolean;
|
|
7
|
+
size?: ValueOrGetter<number>;
|
|
8
|
+
hoverSize?: ValueOrGetter<number>;
|
|
9
|
+
color?: ValueOrGetter<string>;
|
|
10
|
+
fill?: ValueOrGetter<string>;
|
|
11
|
+
borderWidth?: ValueOrGetter<number>;
|
|
12
|
+
mixBlendMode?: string;
|
|
13
|
+
enableSkew?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SmartRing = (options: SmartRingOptions = {}) => {
|
|
17
|
+
const getSize = normalize(options.size, 20);
|
|
18
|
+
const getHoverSize = normalize(options.hoverSize, 40);
|
|
19
|
+
const getColor = normalize(options.color, "#ffffff");
|
|
20
|
+
const getBorder = normalize(options.borderWidth, 2);
|
|
21
|
+
const getFill = normalize(options.fill, "transparent");
|
|
22
|
+
|
|
23
|
+
let currentW = 20;
|
|
24
|
+
let currentH = 20;
|
|
25
|
+
let currentRot = 0;
|
|
26
|
+
let currentScaleX = 1;
|
|
27
|
+
let currentScaleY = 1;
|
|
28
|
+
|
|
29
|
+
return definePlugin<HTMLDivElement, SmartRingOptions>(
|
|
30
|
+
{
|
|
31
|
+
name: options.name || "smart-ring",
|
|
32
|
+
selector: "[data-supermouse-color]",
|
|
33
|
+
|
|
34
|
+
create: (app) => {
|
|
35
|
+
const el = dom.createCircle(getSize(app.state), getFill(app.state));
|
|
36
|
+
dom.applyStyles(el, {
|
|
37
|
+
zIndex: Layers.FOLLOWER,
|
|
38
|
+
mixBlendMode: options.mixBlendMode || "difference",
|
|
39
|
+
transition: "opacity 0.2s ease, border-radius 0.2s ease",
|
|
40
|
+
borderStyle: "solid",
|
|
41
|
+
});
|
|
42
|
+
return el;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
update: (app, el) => {
|
|
46
|
+
const baseSize = getSize(app.state);
|
|
47
|
+
const shape = app.state.shape;
|
|
48
|
+
|
|
49
|
+
let targetW = baseSize;
|
|
50
|
+
let targetH = baseSize;
|
|
51
|
+
let targetRadius = "50%";
|
|
52
|
+
let color = getColor(app.state);
|
|
53
|
+
|
|
54
|
+
if (shape) {
|
|
55
|
+
targetW = shape.width;
|
|
56
|
+
targetH = shape.height;
|
|
57
|
+
targetRadius = `${shape.borderRadius}px`;
|
|
58
|
+
} else {
|
|
59
|
+
if (app.state.isHover) {
|
|
60
|
+
targetW = getHoverSize(app.state);
|
|
61
|
+
targetH = getHoverSize(app.state);
|
|
62
|
+
}
|
|
63
|
+
if (app.state.isDown) {
|
|
64
|
+
targetW *= 0.9;
|
|
65
|
+
targetH *= 0.9;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (app.state.interaction.color) color = app.state.interaction.color;
|
|
70
|
+
|
|
71
|
+
currentW = math.lerp(currentW, targetW, 0.2);
|
|
72
|
+
currentH = math.lerp(currentH, targetH, 0.2);
|
|
73
|
+
|
|
74
|
+
dom.setStyle(el, "width", `${currentW}px`);
|
|
75
|
+
dom.setStyle(el, "height", `${currentH}px`);
|
|
76
|
+
dom.setStyle(el, "borderRadius", targetRadius);
|
|
77
|
+
dom.setStyle(el, "borderColor", color);
|
|
78
|
+
dom.setStyle(el, "backgroundColor", getFill(app.state));
|
|
79
|
+
dom.setStyle(el, "borderWidth", `${getBorder(app.state)}px`);
|
|
80
|
+
|
|
81
|
+
let targetRot = 0;
|
|
82
|
+
let targetScaleX = 1;
|
|
83
|
+
let targetScaleY = 1;
|
|
84
|
+
|
|
85
|
+
if (!shape && options.enableSkew && !app.state.reducedMotion) {
|
|
86
|
+
const { velocity } = app.state;
|
|
87
|
+
const dist = effects.getVelocityDistortion(velocity.x, velocity.y);
|
|
88
|
+
targetRot = dist.rotation;
|
|
89
|
+
targetScaleX = dist.scaleX;
|
|
90
|
+
targetScaleY = dist.scaleY;
|
|
91
|
+
currentRot = math.lerpAngle(currentRot, targetRot, 0.15);
|
|
92
|
+
} else {
|
|
93
|
+
currentRot = 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
currentScaleX = math.lerp(currentScaleX, targetScaleX, 0.15);
|
|
97
|
+
currentScaleY = math.lerp(currentScaleY, targetScaleY, 0.15);
|
|
98
|
+
|
|
99
|
+
const { x, y } = app.state.smooth;
|
|
100
|
+
dom.setTransform(el, x, y, currentRot, currentScaleX, currentScaleY);
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
options
|
|
104
|
+
);
|
|
105
|
+
};
|
package/src/Sparkles.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { ValueOrGetter } from "@supermousejs/core";
|
|
2
|
+
import { definePlugin, normalize, dom, math, Layers } from "@supermousejs/utils";
|
|
3
|
+
|
|
4
|
+
export interface SparklesOptions {
|
|
5
|
+
name?: string;
|
|
6
|
+
isEnabled?: boolean;
|
|
7
|
+
color?: ValueOrGetter<string>;
|
|
8
|
+
/** Number of particles to keep in the pool. Default 30. */
|
|
9
|
+
count?: number;
|
|
10
|
+
/** How fast particles fade out (0.01 - 0.1). Default 0.05. */
|
|
11
|
+
decay?: number;
|
|
12
|
+
/** Pixels of movement required to spawn a particle. Lower = denser trail. Default 10. */
|
|
13
|
+
frequency?: number;
|
|
14
|
+
/** Random position offset for spawning. Default 5. */
|
|
15
|
+
scatter?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Particle {
|
|
19
|
+
el: HTMLDivElement;
|
|
20
|
+
isActive: boolean;
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
vx: number;
|
|
24
|
+
vy: number;
|
|
25
|
+
life: number; // 0.0 to 1.0
|
|
26
|
+
scale: number;
|
|
27
|
+
color: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Sparkles = (options: SparklesOptions = {}) => {
|
|
31
|
+
const poolSize = options.count || 30;
|
|
32
|
+
const decayRate = options.decay || 0.05;
|
|
33
|
+
const frequency = options.frequency || 10;
|
|
34
|
+
const scatter = options.scatter || 5;
|
|
35
|
+
|
|
36
|
+
const defColor = "#ff00ff";
|
|
37
|
+
const getColor = normalize(options.color, defColor);
|
|
38
|
+
|
|
39
|
+
const pool: Particle[] = [];
|
|
40
|
+
|
|
41
|
+
// Track previous position for interpolation
|
|
42
|
+
let lx = 0;
|
|
43
|
+
let ly = 0;
|
|
44
|
+
let hasMoved = false;
|
|
45
|
+
let accumulatedDist = 0;
|
|
46
|
+
|
|
47
|
+
const activateParticle = (x: number, y: number, color: string) => {
|
|
48
|
+
const p = pool.find((item) => !item.isActive);
|
|
49
|
+
if (!p) return;
|
|
50
|
+
|
|
51
|
+
p.isActive = true;
|
|
52
|
+
p.life = 1.0;
|
|
53
|
+
|
|
54
|
+
// Spawn with slight scatter around the calculated point
|
|
55
|
+
p.x = x + math.random(-scatter, scatter);
|
|
56
|
+
p.y = y + math.random(-scatter, scatter);
|
|
57
|
+
|
|
58
|
+
// Pure radial burst
|
|
59
|
+
const angle = math.random(0, Math.PI * 2);
|
|
60
|
+
const speed = math.random(0.5, 1.5);
|
|
61
|
+
p.vx = Math.cos(angle) * speed;
|
|
62
|
+
p.vy = Math.sin(angle) * speed;
|
|
63
|
+
|
|
64
|
+
p.scale = math.random(0.5, 1.2);
|
|
65
|
+
p.color = color;
|
|
66
|
+
|
|
67
|
+
// Apply immediate visual updates
|
|
68
|
+
const size = math.random(2, 5);
|
|
69
|
+
dom.setStyle(p.el, "width", `${size}px`);
|
|
70
|
+
dom.setStyle(p.el, "height", `${size}px`);
|
|
71
|
+
dom.setStyle(p.el, "backgroundColor", color);
|
|
72
|
+
dom.setStyle(p.el, "opacity", "1");
|
|
73
|
+
dom.setTransform(p.el, p.x, p.y, 0, p.scale, p.scale);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return definePlugin(
|
|
77
|
+
{
|
|
78
|
+
name: options.name || "sparkles",
|
|
79
|
+
|
|
80
|
+
install(app) {
|
|
81
|
+
for (let i = 0; i < poolSize; i++) {
|
|
82
|
+
const el = dom.createCircle(0, "transparent");
|
|
83
|
+
dom.applyStyles(el, {
|
|
84
|
+
zIndex: Layers.TRACE,
|
|
85
|
+
opacity: "0",
|
|
86
|
+
willChange: "transform, opacity",
|
|
87
|
+
transition: "none",
|
|
88
|
+
});
|
|
89
|
+
app.container.appendChild(el);
|
|
90
|
+
pool.push({
|
|
91
|
+
el,
|
|
92
|
+
isActive: false,
|
|
93
|
+
x: 0,
|
|
94
|
+
y: 0,
|
|
95
|
+
vx: 0,
|
|
96
|
+
vy: 0,
|
|
97
|
+
life: 0,
|
|
98
|
+
scale: 1,
|
|
99
|
+
color: "",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
update(app) {
|
|
105
|
+
const { x: cx, y: cy } = app.state.pointer;
|
|
106
|
+
|
|
107
|
+
// --- 1. SPAWN LOGIC (Interpolated) ---
|
|
108
|
+
if (!hasMoved) {
|
|
109
|
+
lx = cx;
|
|
110
|
+
ly = cy;
|
|
111
|
+
hasMoved = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const dx = cx - lx;
|
|
115
|
+
const dy = cy - ly;
|
|
116
|
+
const dist = Math.hypot(dx, dy);
|
|
117
|
+
|
|
118
|
+
accumulatedDist += dist;
|
|
119
|
+
|
|
120
|
+
// If we have moved enough to spawn one or more particles
|
|
121
|
+
if (accumulatedDist > frequency) {
|
|
122
|
+
const color = getColor(app.state);
|
|
123
|
+
|
|
124
|
+
// How many particles fit in this movement?
|
|
125
|
+
const steps = Math.floor(accumulatedDist / frequency);
|
|
126
|
+
|
|
127
|
+
// Interpolate backwards from current position
|
|
128
|
+
for (let i = 0; i < steps; i++) {
|
|
129
|
+
const t = i / steps;
|
|
130
|
+
const spawnX = lx + dx * t;
|
|
131
|
+
const spawnY = ly + dy * t;
|
|
132
|
+
activateParticle(spawnX, spawnY, color);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
accumulatedDist = accumulatedDist % frequency;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
lx = cx;
|
|
139
|
+
ly = cy;
|
|
140
|
+
|
|
141
|
+
// --- 2. PHYSICS LOOP ---
|
|
142
|
+
for (let i = 0; i < poolSize; i++) {
|
|
143
|
+
const p = pool[i];
|
|
144
|
+
if (!p.isActive) continue;
|
|
145
|
+
|
|
146
|
+
p.x += p.vx;
|
|
147
|
+
p.y += p.vy;
|
|
148
|
+
p.life -= decayRate;
|
|
149
|
+
|
|
150
|
+
if (p.life <= 0) {
|
|
151
|
+
p.isActive = false;
|
|
152
|
+
p.el.style.opacity = "0";
|
|
153
|
+
} else {
|
|
154
|
+
p.el.style.opacity = String(p.life);
|
|
155
|
+
const currentScale = p.scale * p.life;
|
|
156
|
+
dom.setTransform(p.el, p.x, p.y, 0, currentScale, currentScale);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
destroy() {
|
|
162
|
+
pool.forEach((p) => p.el.remove());
|
|
163
|
+
pool.length = 0;
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
options
|
|
167
|
+
);
|
|
168
|
+
};
|
package/src/TextRing.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { ValueOrGetter } from '@supermousejs/core';
|
|
2
|
+
import { definePlugin, normalize, dom, Layers } from '@supermousejs/utils';
|
|
3
|
+
import { getCirclePath, getCircumference, formatLoopText } from '@supermousejs/zoetrope';
|
|
4
|
+
|
|
5
|
+
export interface TextRingOptions {
|
|
6
|
+
name?: string;
|
|
7
|
+
isEnabled?: boolean;
|
|
8
|
+
text?: ValueOrGetter<string>;
|
|
9
|
+
radius?: ValueOrGetter<number>;
|
|
10
|
+
fontSize?: ValueOrGetter<number>;
|
|
11
|
+
speed?: ValueOrGetter<number>;
|
|
12
|
+
color?: ValueOrGetter<string>;
|
|
13
|
+
opacity?: ValueOrGetter<number>;
|
|
14
|
+
className?: string;
|
|
15
|
+
spread?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let instanceCount = 0;
|
|
19
|
+
|
|
20
|
+
export const TextRing = (options: TextRingOptions = {}) => {
|
|
21
|
+
let svg: SVGSVGElement;
|
|
22
|
+
let pathEl: SVGPathElement;
|
|
23
|
+
let textPathEl: SVGTextPathElement;
|
|
24
|
+
let textEl: SVGTextElement;
|
|
25
|
+
let textNode: Text;
|
|
26
|
+
|
|
27
|
+
const pathId = `supermouse-text-ring-path-${instanceCount++}`;
|
|
28
|
+
|
|
29
|
+
const defText = 'SUPERMOUSE • SUPERMOUSE • ';
|
|
30
|
+
const defRadius = 60;
|
|
31
|
+
const defFontSize = 12;
|
|
32
|
+
const defSpeed = 0.5;
|
|
33
|
+
const className = options.className || '';
|
|
34
|
+
const spread = options.spread ?? false;
|
|
35
|
+
|
|
36
|
+
const getText = normalize(options.text, defText);
|
|
37
|
+
const getRadius = normalize(options.radius, defRadius);
|
|
38
|
+
const getFontSize = normalize(options.fontSize, defFontSize);
|
|
39
|
+
const getSpeed = normalize(options.speed, defSpeed);
|
|
40
|
+
const getOpacity = normalize(options.opacity, 1);
|
|
41
|
+
|
|
42
|
+
let currentRotation = 0;
|
|
43
|
+
let lastText = '';
|
|
44
|
+
let lastRadius = 0;
|
|
45
|
+
let lastFontSize = 0;
|
|
46
|
+
|
|
47
|
+
return definePlugin<HTMLDivElement, TextRingOptions>({
|
|
48
|
+
name: 'text-ring',
|
|
49
|
+
selector: '[data-supermouse-text-ring]',
|
|
50
|
+
|
|
51
|
+
create: (app) => {
|
|
52
|
+
const container = dom.createActor('div') as HTMLDivElement;
|
|
53
|
+
dom.applyStyles(container, {
|
|
54
|
+
zIndex: Layers.FOLLOWER,
|
|
55
|
+
transition: 'opacity 0.2s ease',
|
|
56
|
+
opacity: '1',
|
|
57
|
+
width: '0px',
|
|
58
|
+
height: '0px',
|
|
59
|
+
overflow: 'visible',
|
|
60
|
+
display: 'flex',
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
justifyContent: 'center'
|
|
63
|
+
});
|
|
64
|
+
if (className) {
|
|
65
|
+
container.classList.add(...className.split(' ').filter(Boolean));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
69
|
+
svg.style.overflow = 'visible';
|
|
70
|
+
svg.style.position = 'absolute';
|
|
71
|
+
svg.style.left = '0';
|
|
72
|
+
svg.style.top = '0';
|
|
73
|
+
|
|
74
|
+
pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
75
|
+
pathEl.setAttribute('id', pathId);
|
|
76
|
+
pathEl.setAttribute('fill', 'none');
|
|
77
|
+
|
|
78
|
+
textEl = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
79
|
+
|
|
80
|
+
const fs = getFontSize(app.state);
|
|
81
|
+
textEl.setAttribute('font-size', `${fs}px`);
|
|
82
|
+
lastFontSize = fs;
|
|
83
|
+
|
|
84
|
+
textEl.setAttribute('fill', 'currentColor');
|
|
85
|
+
textEl.style.textTransform = 'uppercase';
|
|
86
|
+
textEl.style.letterSpacing = '2px';
|
|
87
|
+
|
|
88
|
+
textPathEl = document.createElementNS('http://www.w3.org/2000/svg', 'textPath');
|
|
89
|
+
textPathEl.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#${pathId}`);
|
|
90
|
+
textPathEl.setAttribute('href', `#${pathId}`);
|
|
91
|
+
textPathEl.setAttribute('startOffset', '0%');
|
|
92
|
+
|
|
93
|
+
textNode = document.createTextNode('');
|
|
94
|
+
|
|
95
|
+
textPathEl.appendChild(textNode);
|
|
96
|
+
textEl.appendChild(textPathEl);
|
|
97
|
+
svg.appendChild(pathEl);
|
|
98
|
+
svg.appendChild(textEl);
|
|
99
|
+
container.appendChild(svg);
|
|
100
|
+
|
|
101
|
+
return container;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
styles: {
|
|
105
|
+
color: 'color'
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
update: (app, container) => {
|
|
109
|
+
let text = getText(app.state);
|
|
110
|
+
const radius = getRadius(app.state);
|
|
111
|
+
const fontSize = getFontSize(app.state);
|
|
112
|
+
const speed = getSpeed(app.state);
|
|
113
|
+
const opacity = getOpacity(app.state);
|
|
114
|
+
|
|
115
|
+
// Force update opacity
|
|
116
|
+
dom.setStyle(container, 'opacity', String(opacity));
|
|
117
|
+
|
|
118
|
+
const ia = app.state.interaction;
|
|
119
|
+
// Prefer specific "textRing" property, fallback to generic "text", then default option
|
|
120
|
+
if (ia.textRing && typeof ia.textRing === 'string') {
|
|
121
|
+
text = ia.textRing;
|
|
122
|
+
} else if (ia.text && typeof ia.text === 'string') {
|
|
123
|
+
text = ia.text;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (radius !== lastRadius) {
|
|
127
|
+
pathEl.setAttribute('d', getCirclePath(radius));
|
|
128
|
+
lastRadius = radius;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (spread) {
|
|
132
|
+
const circum = getCircumference(radius);
|
|
133
|
+
textPathEl.setAttribute('textLength', String(circum));
|
|
134
|
+
textPathEl.setAttribute('lengthAdjust', 'spacing');
|
|
135
|
+
text = formatLoopText(text, true);
|
|
136
|
+
} else {
|
|
137
|
+
textPathEl.removeAttribute('textLength');
|
|
138
|
+
textPathEl.removeAttribute('lengthAdjust');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (fontSize !== lastFontSize) {
|
|
142
|
+
textEl.setAttribute('font-size', `${fontSize}px`);
|
|
143
|
+
lastFontSize = fontSize;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (text !== lastText) {
|
|
147
|
+
textNode.textContent = text;
|
|
148
|
+
lastText = text;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
currentRotation += speed;
|
|
152
|
+
const { x, y } = app.state.smooth;
|
|
153
|
+
dom.setTransform(container, x, y, currentRotation);
|
|
154
|
+
}
|
|
155
|
+
}, options);
|
|
156
|
+
};
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"include": [
|
|
4
|
+
"src"
|
|
5
|
+
],
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"baseUrl": ".",
|
|
9
|
+
"paths": {
|
|
10
|
+
"@supermousejs/core": [
|
|
11
|
+
"../core/src/index.ts"
|
|
12
|
+
],
|
|
13
|
+
"@supermousejs/utils": [
|
|
14
|
+
"../utils/src/index.ts"
|
|
15
|
+
],
|
|
16
|
+
"@supermousejs/zoetrope": [
|
|
17
|
+
"../zoetrope/src/index.ts"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import dts from 'vite-plugin-dts';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
build: {
|
|
7
|
+
lib: {
|
|
8
|
+
entry: path.resolve(__dirname, 'src/index.ts'),
|
|
9
|
+
name: 'SupermouseLabs',
|
|
10
|
+
fileName: (format) => format === 'es' ? 'index.mjs' : 'index.umd.js',
|
|
11
|
+
},
|
|
12
|
+
rollupOptions: {
|
|
13
|
+
external: ['@supermousejs/core', '@supermousejs/utils', '@supermousejs/zoetrope'],
|
|
14
|
+
output: {
|
|
15
|
+
globals: {
|
|
16
|
+
'@supermousejs/core': 'SupermouseCore'
|
|
17
|
+
, '@supermousejs/utils': 'SupermouseUtils', '@supermousejs/zoetrope': 'SupermouseZoetrope'}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
plugins: [dts({ rollupTypes: true })]
|
|
22
|
+
});
|