sparkle-ripple 3.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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2025 Litrain
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ <h1 align="center">✨ sparkle-ripple</h1>
2
+ <p align="center">Bring Material 3(You) Ripple Effect to your <b>React</b> projects!</p>
3
+ <p align="center">Previously, it was named <b>m3-ripple</b>.</p>
4
+ <div align="center">
5
+
6
+ [![NPM Version](https://img.shields.io/npm/v/sparkle-ripple?style=for-the-badge&logo=npm&logoColor=white&labelColor=%235c4b39&color=%23363024)](https://www.npmjs.com/package/sparkle-ripple)
7
+ [![npm package minimized gzipped size (scoped)](https://img.shields.io/bundlejs/size/sparkle-ripple?style=for-the-badge&labelColor=%235c4b39&color=%23363024)](#)
8
+ [![GitHub License](https://img.shields.io/github/license/yuyake-litrain/sparkle-ripple?style=for-the-badge&labelColor=%235c4b39&color=%23363024)](https://github.com/yuyake-litrain/sparkle-ripple/blob/main/LICENSE)
9
+ [![NPM Downloads](https://img.shields.io/npm/dy/sparkle-ripple?style=for-the-badge&logo=npm&logoColor=white&labelColor=%235c4b39&color=%23363024)](https://npmtrends.com/sparkle-ripple)
10
+ [![GitHub Repo stars](https://img.shields.io/github/stars/yuyake-litrain/sparkle-ripple?style=for-the-badge&labelColor=%235c4b39&color=%23363024)](#)
11
+ [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/yuyake-litrain/sparkle-ripple/main.yml?style=for-the-badge&labelColor=%235c4b39&color=%23363024)](https://github.com/yuyake-litrain/sparkle-ripple/actions/workflows/main.yml)
12
+ </div>
13
+ <div align="center"><a href="https://m3ripple.js.org/"><b>Live Demo</b></a></div><br />
14
+
15
+ <div align="center">
16
+ <video src="https://github.com/user-attachments/assets/5b8cd5e6-5c91-4ca1-bc4d-50d5781a8be9" />
17
+ </div>
18
+
19
+ ## Features
20
+ - ✨ Ripple Effect with sparkle easily realized on the web
21
+ - 😍 Well-tuned behavior with no faltering
22
+ - 🎨 Highly customizable in terms of ripple color, number of sparkles, clarity, etc.
23
+ - ⚡ High speed drawing for Sparkles by canvas
24
+ - ✅ Ripple effect in Material 2 is also supported
25
+
26
+ ## Getting Started
27
+ ### Install
28
+ #### Bun
29
+ ```bash
30
+ bun install sparkle-ripple
31
+ ```
32
+ #### Others
33
+ <details>
34
+ <summary>npm</summary>
35
+ <pre>npm i sparkle-ripple</pre>
36
+ </details>
37
+ <details>
38
+ <summary>pnpm</summary>
39
+ <pre>pnpm add sparkle-ripple</pre>
40
+ </details>
41
+ <details>
42
+ <summary>Yarn</summary>
43
+ <pre>yarn add sparkle-ripple</pre>
44
+ </details>
45
+
46
+ ### Use
47
+ Import `<RippleContainer>` component(by default, it's rendered as `<div />`) and set props.
48
+
49
+ #### Example
50
+ ```tsx
51
+ import { RippleContainer } from 'sparkle-ripple'; //import it
52
+ import 'sparkle-ripple/css' // import css
53
+
54
+ import styles from './some_css_file.module.css';
55
+
56
+ const YourComponent = () => {
57
+ return (
58
+ <RippleContainer
59
+ as = 'div'
60
+ isMaterial3 = {true}
61
+ beforeRippleFn = {(event) =>{}}
62
+ className = {styles.rippleContainer}
63
+ rippleColor = "hsla(29,81%,84%,0.15)"
64
+ sparklesColorRGB = "255 255 255"
65
+ opacity_level1 = "0.4"
66
+ opacity_level2 = "0.1"
67
+ sparklesMaxCount = 2048
68
+ >
69
+ <div className={styles.children} />
70
+ </RippleContainer>
71
+ );
72
+ };
73
+
74
+ export default YourComponent;
75
+ ```
76
+ <div align="center">
77
+
78
+ |Property|optional|explanation|default|type|
79
+ |----|----|----|----|----|
80
+ |`as`|yes|What element is RippleContainer rendered as.|`"div"`|`ElementType`|
81
+ |`isMaterial3`|yes|Whether to use ripple of Material 3.|`true`|`boolean`|
82
+ |`beforeRippleFn`|yes|A function to be executed when a click occurs and just before the ripple is displayed (used for example to display a button shadow).|`()=>{}`|`(event: React.MouseEvent \| React.TouchEvent) => void`|
83
+ |`className`|yes|Since RippleContainer is rendered as the element which is selected by `as` property, this is the ClassName of that element.|`""`|`string`|
84
+ |`children`|yes|Child Elements of RippleContainer.|`undefined`|`ReactNode`|
85
+ |`rippleColor`|yes|Ripple Effect Colors. If transparency is not specified, the overlap will not be visible when multiple clicks are made.|`"#ffffff35"`|`string`|
86
+ |`sparklesColorRGB`|yes|Specify sparkle color as space-separated RGB. Transparency cannot be specified.|`"255 255 255"`|`string`|
87
+ |`opacity_level1`|yes|Transparency just before the sparkle disappears. \*The transparency when initially displayed is calculated by the current progress of the Ripple Effect.|`"0.2"`|`string`|
88
+ |`opacity_level2`|yes|Transparency just before Sparkles disappear.Set after `opacity_level1`.|`"0.1"`|`string`|
89
+ |`sparklesMaxCount`|yes|Total amount of dots representing sparkle.|`2048`|`number`|
90
+ |Other Properties|yes|You can set other properties to add to the rendered element. e.g., `href` attribute of the RippleContainer whose rendered as `<a></a>` element.|-|-|
91
+
92
+ </div>
@@ -0,0 +1,17 @@
1
+ declare const Ripple: import('react').MemoExoticComponent<({ ripple_width, offsetX, offsetY, isMaterial3, rippleColor, rippleTime, time, tobeDeleted, waitingTime, deleteRipple, sparklesColorRGB, opacity_level1, opacity_level2, sparklesMaxCount, }: {
2
+ ripple_width: number;
3
+ offsetX: number;
4
+ offsetY: number;
5
+ isMaterial3?: boolean;
6
+ rippleColor?: string;
7
+ rippleTime?: number;
8
+ waitingTime?: number;
9
+ time: number;
10
+ tobeDeleted: boolean;
11
+ sparklesColorRGB?: string;
12
+ opacity_level1?: string;
13
+ opacity_level2?: string;
14
+ sparklesMaxCount?: number;
15
+ deleteRipple: (createdTime: number) => void;
16
+ }) => import("react/jsx-runtime").JSX.Element>;
17
+ export default Ripple;
@@ -0,0 +1,10 @@
1
+ import { RefObject } from 'react';
2
+ declare const SparklesCanvas: import('react').MemoExoticComponent<({ ripple_width, rippleRef, sparklesColorRGB, opacity_level1, opacity_level2, sparklesMaxCount, }: {
3
+ ripple_width: number;
4
+ rippleRef: RefObject<HTMLSpanElement | null>;
5
+ sparklesColorRGB?: string;
6
+ sparklesMaxCount?: number;
7
+ opacity_level1?: string;
8
+ opacity_level2?: string;
9
+ }) => import("react/jsx-runtime").JSX.Element>;
10
+ export default SparklesCanvas;
@@ -0,0 +1,22 @@
1
+ import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
2
+ export type RippleObj = {
3
+ ripple_width: number;
4
+ offsetY: number;
5
+ offsetX: number;
6
+ time: number;
7
+ isMaterial3: boolean;
8
+ tobeDeleted: boolean;
9
+ };
10
+ type Props<T extends ElementType = 'div'> = Omit<ComponentPropsWithoutRef<T>, 'as'> & {
11
+ as?: T;
12
+ isMaterial3?: boolean;
13
+ beforeRippleFn?: (event: React.MouseEvent | React.TouchEvent) => void;
14
+ children?: ReactNode;
15
+ rippleColor?: string;
16
+ sparklesColorRGB?: string;
17
+ opacity_level1?: string;
18
+ opacity_level2?: string;
19
+ sparklesMaxCount?: number;
20
+ };
21
+ declare const RippleContainer: <T extends ElementType = "div">({ as, isMaterial3, beforeRippleFn, rippleColor, sparklesColorRGB, opacity_level1, opacity_level2, sparklesMaxCount, className, children, onMouseDown, onTouchStart, onTouchMove, onMouseUp, onMouseLeave, onTouchEnd, onTouchCancel, ...rest }: Props<T>) => import("react/jsx-runtime").JSX.Element;
22
+ export default RippleContainer;
@@ -0,0 +1,2 @@
1
+ import { default as RippleContainer } from './components/ripple_container';
2
+ export { RippleContainer };
@@ -0,0 +1,19 @@
1
+ declare class Sparkle {
2
+ xpos: number;
3
+ ypos: number;
4
+ width: number;
5
+ height: number;
6
+ constructor(xpos: number, ypos: number, width: number, height: number);
7
+ }
8
+ export declare class Sparkles {
9
+ sparkles: Sparkle[];
10
+ color: string;
11
+ constructor(sparkles: Sparkle[], color: string);
12
+ }
13
+ type SparklesColor = {
14
+ rgb: string;
15
+ opacity_level1: string;
16
+ opacity_level2: string;
17
+ };
18
+ export declare function drawSparkles(sparkle_collection: Sparkles[], context: CanvasRenderingContext2D, ripple: HTMLSpanElement, ripple_width: number, sparklesColor?: SparklesColor, convLevel?: number, spaklesMaxCount?: number): void;
19
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare function calcRippleWidth(offsetX: number | null, offsetY: number | null, clientWidth: number | null, clientHeight: number | null): number | undefined;
2
+ export declare const offsetXFromCurrent: (rect: DOMRect, clientX: number) => number;
3
+ export declare const offsetYFromCurrent: (rect: DOMRect, clientY: number) => number;
4
+ export declare const rippleElemWidthOriginal: (rect: DOMRect, clientX: number, clientY: number, clientWidth: number, clientHeight: number) => number | undefined;
@@ -0,0 +1 @@
1
+ ._ripple_17x6w_1{position:absolute;border-radius:100%;z-index:-1;transform:translate(-50%) translateY(-50%);pointer-events:none;display:flex;align-items:center;justify-content:center;-webkit-user-select:none;user-select:none}._sparkles_canvas_qe9de_1{position:absolute;-webkit-user-select:none;user-select:none}._rippleContainer_qv2ad_1{overflow:hidden;position:relative;z-index:0}
@@ -0,0 +1,393 @@
1
+ import { jsx as k, jsxs as K } from "react/jsx-runtime";
2
+ import { memo as X, useRef as p, useEffect as B, useState as Y, useCallback as j } from "react";
3
+ function E(t) {
4
+ var e, o, n = "";
5
+ if (typeof t == "string" || typeof t == "number") n += t;
6
+ else if (typeof t == "object") if (Array.isArray(t)) {
7
+ var s = t.length;
8
+ for (e = 0; e < s; e++) t[e] && (o = E(t[e])) && (n && (n += " "), n += o);
9
+ } else for (o in t) t[o] && (n && (n += " "), n += o);
10
+ return n;
11
+ }
12
+ function q() {
13
+ for (var t, e, o = 0, n = "", s = arguments.length; o < s; o++) (t = arguments[o]) && (e = E(t)) && (n && (n += " "), n += e);
14
+ return n;
15
+ }
16
+ function Q(t, e, o, n) {
17
+ if (!t || !e || !o || !n) return;
18
+ const s = t, a = o - t, i = e, c = n - e;
19
+ let u, l;
20
+ return s >= a ? u = s : u = a, i >= c ? l = i : l = c, Math.sqrt(
21
+ u ** 2 + l ** 2
22
+ ) * 2;
23
+ }
24
+ const D = (t, e) => e - t.left, O = (t, e) => e - t.top, N = (t, e, o, n, s) => Q(
25
+ D(t, e),
26
+ O(t, o),
27
+ n,
28
+ s
29
+ ), Z = "_ripple_17x6w_1", G = {
30
+ ripple: Z
31
+ };
32
+ class L {
33
+ xpos;
34
+ ypos;
35
+ width;
36
+ height;
37
+ constructor(e, o, n, s) {
38
+ this.xpos = e, this.ypos = o, this.width = n, this.height = s;
39
+ }
40
+ }
41
+ class _ {
42
+ sparkles;
43
+ color;
44
+ constructor(e, o) {
45
+ this.sparkles = e, this.color = o;
46
+ }
47
+ }
48
+ function v(t, e, o, n) {
49
+ const s = t * (Math.PI / 180), a = Math.cos(s) * n + (Math.floor(Math.random() * (e / o)) - Math.floor(Math.random() * (e / o))), i = Math.sin(s) * n + (Math.floor(Math.random() * (e / o)) - Math.floor(Math.random() * (e / o)));
50
+ return { xpos: a, ypos: i };
51
+ }
52
+ function tt(t, e, o) {
53
+ return `rgb(${o.rgb} / ${Math.abs(
54
+ (Math.random() - Math.random()) * 0.3 + (1 - t / e) - (1 - 0.6)
55
+ ).toFixed(2)})`;
56
+ }
57
+ function I(t, e, o, n, s = {
58
+ rgb: "255 255 255",
59
+ opacity_level1: "0.2",
60
+ opacity_level2: "0.1"
61
+ }, a = 6, i = 2048) {
62
+ const c = o.clientWidth / 2, u = o.clientWidth / 2.6;
63
+ if (e.clearRect(0, 0, n, n), c === 0 || a === 0)
64
+ return;
65
+ const l = [];
66
+ let d = o.clientWidth;
67
+ d > i && (d = i);
68
+ const x = [];
69
+ for (let g = 0; g < d; g++)
70
+ x.push(Math.floor(Math.random() * 360));
71
+ for (const g of x) {
72
+ const f = v(
73
+ g,
74
+ c,
75
+ a,
76
+ u
77
+ );
78
+ l.push(new L(f.xpos, f.ypos, 1, 1));
79
+ }
80
+ const S = tt(
81
+ c,
82
+ n,
83
+ s
84
+ );
85
+ t.push(new _(l, S)), t.length > 5 && (t.splice(0, 1), t[0].color = `rgb(${s.rgb} / ${s.opacity_level1})`, t[1].color = `rgb(${s.rgb} / ${s.opacity_level2})`);
86
+ for (const g of t) {
87
+ e.fillStyle = g.color;
88
+ for (const f of g.sparkles)
89
+ e.fillRect(
90
+ n / 2 + f.xpos,
91
+ n / 2 + f.ypos,
92
+ f.width,
93
+ f.height
94
+ );
95
+ }
96
+ window.requestAnimationFrame(
97
+ () => I(t, e, o, n)
98
+ );
99
+ }
100
+ const rt = "_sparkles_canvas_qe9de_1", nt = {
101
+ sparkles_canvas: rt
102
+ }, et = X(
103
+ ({
104
+ ripple_width: t,
105
+ rippleRef: e,
106
+ sparklesColorRGB: o = "255 255 255",
107
+ opacity_level1: n = "0.2",
108
+ opacity_level2: s = "0.1",
109
+ sparklesMaxCount: a = 2048
110
+ }) => {
111
+ const i = p(null), c = p(null);
112
+ return B(() => {
113
+ const u = window.devicePixelRatio;
114
+ c.current && (c.current.width = Math.floor(t * u), c.current.height = Math.floor(t * u));
115
+ const l = c.current?.getContext("2d");
116
+ if (!l)
117
+ return;
118
+ const d = [];
119
+ return l.scale(u, u), i.current = requestAnimationFrame(() => {
120
+ e.current && I(
121
+ d,
122
+ l,
123
+ e.current,
124
+ t,
125
+ {
126
+ rgb: o,
127
+ opacity_level1: n,
128
+ opacity_level2: s
129
+ },
130
+ 6,
131
+ a
132
+ );
133
+ }), c.current?.animate(
134
+ {
135
+ opacity: [1, 0]
136
+ },
137
+ {
138
+ fill: "forwards",
139
+ duration: 600
140
+ }
141
+ ), () => {
142
+ i.current && cancelAnimationFrame(i.current);
143
+ };
144
+ }, [
145
+ t,
146
+ e,
147
+ o,
148
+ a,
149
+ n,
150
+ s
151
+ ]), /* @__PURE__ */ k(
152
+ "canvas",
153
+ {
154
+ ref: c,
155
+ style: {
156
+ width: `${t}px`,
157
+ height: `${t}px`
158
+ },
159
+ className: q("sparkles_canvas", nt.sparkles_canvas)
160
+ }
161
+ );
162
+ }
163
+ ), ot = X(
164
+ ({
165
+ ripple_width: t,
166
+ offsetX: e,
167
+ offsetY: o,
168
+ isMaterial3: n = !0,
169
+ rippleColor: s = "#ffffff35",
170
+ rippleTime: a = 500,
171
+ time: i,
172
+ tobeDeleted: c = !1,
173
+ // waiting time for ripple animation
174
+ waitingTime: u = 290,
175
+ deleteRipple: l,
176
+ sparklesColorRGB: d = "255 255 255",
177
+ opacity_level1: x = "0.2",
178
+ opacity_level2: S = "0.1",
179
+ sparklesMaxCount: g = 2048
180
+ }) => {
181
+ const f = p(null), y = p(null), C = p(!1), [F, P] = Y(!1), $ = j(async () => {
182
+ if (f.current && y.current) {
183
+ if (y.current.currentTime < u) {
184
+ const M = a - y.current.currentTime - u;
185
+ await new Promise((W) => {
186
+ setTimeout(W, M);
187
+ });
188
+ } else
189
+ await y.current.finished;
190
+ if (!f.current)
191
+ return;
192
+ await f.current.animate(
193
+ {
194
+ opacity: [1, 0]
195
+ },
196
+ {
197
+ fill: "forwards",
198
+ duration: 200,
199
+ easing: "cubic-bezier(0.11, 0, 0.5, 0)"
200
+ }
201
+ ).finished;
202
+ }
203
+ l(i);
204
+ }, [l, n, a, i, u]);
205
+ return B(() => {
206
+ c || (y.current = f.current?.animate(
207
+ {
208
+ width: n ? [`${t / 6}px`, `${t}px`] : [`${t / 3}px`, `${t}px`],
209
+ height: n ? [`${t / 6}px`, `${t}px`] : [`${t / 3}px`, `${t}px`],
210
+ background: n ? [
211
+ `radial-gradient(circle closest-side, ${s} 0%, transparent)`,
212
+ `radial-gradient(circle closest-side, ${s} 80%, transparent)`
213
+ ] : [s, s]
214
+ },
215
+ {
216
+ fill: "forwards",
217
+ duration: n ? 400 : a,
218
+ easing: n ? "cubic-bezier(0,0.49,0,1)" : "cubic-bezier(0.1, 0.8, 1, 0.80)"
219
+ }
220
+ ), (async () => (await y.current?.ready, P(!0)))());
221
+ }, [n, a, s, t, c]), B(() => {
222
+ c && (C.current || (C.current = !0, $()));
223
+ }, [c, $]), /* @__PURE__ */ k(
224
+ "span",
225
+ {
226
+ className: q("ripple", G.ripple),
227
+ style: {
228
+ width: t,
229
+ height: t,
230
+ left: e,
231
+ top: o
232
+ },
233
+ ref: f,
234
+ children: n && F && /* @__PURE__ */ k(
235
+ et,
236
+ {
237
+ ripple_width: t,
238
+ rippleRef: f,
239
+ sparklesColorRGB: d,
240
+ opacity_level1: x,
241
+ opacity_level2: S,
242
+ sparklesMaxCount: g
243
+ }
244
+ )
245
+ }
246
+ );
247
+ }
248
+ ), st = "_rippleContainer_qv2ad_1", ct = {
249
+ rippleContainer: st
250
+ }, ft = ({
251
+ as: t,
252
+ isMaterial3: e = !0,
253
+ beforeRippleFn: o,
254
+ rippleColor: n = "#ffffff35",
255
+ sparklesColorRGB: s = "255 255 255",
256
+ opacity_level1: a = "0.2",
257
+ opacity_level2: i = "0.1",
258
+ sparklesMaxCount: c = 2048,
259
+ className: u,
260
+ children: l,
261
+ onMouseDown: d,
262
+ onTouchStart: x,
263
+ onTouchMove: S,
264
+ onMouseUp: g,
265
+ onMouseLeave: f,
266
+ onTouchEnd: y,
267
+ onTouchCancel: C,
268
+ ...F
269
+ }) => {
270
+ const [P, $] = Y([]), b = p(!1), M = p(!1);
271
+ function W(r) {
272
+ if (M.current)
273
+ return;
274
+ const h = N(
275
+ r.currentTarget.getBoundingClientRect(),
276
+ r.clientX,
277
+ r.clientY,
278
+ r.currentTarget.clientWidth,
279
+ r.currentTarget.clientHeight
280
+ ), m = D(
281
+ r.currentTarget.getBoundingClientRect(),
282
+ r.clientX
283
+ ), T = O(
284
+ r.currentTarget.getBoundingClientRect(),
285
+ r.clientY
286
+ );
287
+ o && o(r), z(h, m, T);
288
+ }
289
+ function z(r, h, m, T = !1) {
290
+ if (M.current && !T || !r)
291
+ return;
292
+ const R = r / 0.8, A = (/* @__PURE__ */ new Date()).getTime();
293
+ $((J) => [
294
+ ...J,
295
+ {
296
+ ripple_width: R,
297
+ offsetX: h,
298
+ offsetY: m,
299
+ time: A,
300
+ isMaterial3: e,
301
+ tobeDeleted: !1
302
+ }
303
+ ]);
304
+ }
305
+ async function w(r) {
306
+ (r.type === "touchend" || r.type === "touchcancel") && await new Promise((h) => setTimeout(h, 100)), $((h) => h.length === 0 ? [] : h.map((m) => ({ ...m, tobeDeleted: !0 }))), b.current = !1;
307
+ }
308
+ const H = j((r) => {
309
+ $((h) => h.filter((m) => m.time !== r));
310
+ }, []), U = async (r) => {
311
+ const h = N(
312
+ r.currentTarget.getBoundingClientRect(),
313
+ r.changedTouches[0].clientX,
314
+ r.changedTouches[0].clientY,
315
+ r.currentTarget.clientWidth,
316
+ r.currentTarget.clientHeight
317
+ ), m = D(
318
+ r.currentTarget.getBoundingClientRect(),
319
+ r.changedTouches[0].clientX
320
+ ), T = O(
321
+ r.currentTarget.getBoundingClientRect(),
322
+ r.changedTouches[0].clientY
323
+ );
324
+ M.current = !0, await new Promise((R) => {
325
+ setTimeout(R, 100);
326
+ }), setTimeout(() => {
327
+ M.current = !1;
328
+ }, 1e3), b.current || (o && o(r), z(h, m, T, !0)), b.current = !1;
329
+ }, V = () => {
330
+ b.current = !0;
331
+ };
332
+ return /* @__PURE__ */ K(
333
+ t ?? "div",
334
+ {
335
+ ...F,
336
+ className: q(ct.rippleContainer, u ?? ""),
337
+ onMouseDown: (r) => {
338
+ W(r), d?.(r);
339
+ },
340
+ onTouchStart: (r) => {
341
+ U(r), x?.(r);
342
+ },
343
+ onTouchMove: (r) => {
344
+ V(), S?.(r);
345
+ },
346
+ onTouchEnd: (r) => {
347
+ w(r), y?.(r);
348
+ },
349
+ onMouseUp: (r) => {
350
+ w(r), g?.(r);
351
+ },
352
+ onMouseLeave: (r) => {
353
+ w(r), f?.(r);
354
+ },
355
+ onTouchCancel: (r) => {
356
+ w(r), C?.(r);
357
+ },
358
+ children: [
359
+ P.map(
360
+ ({
361
+ ripple_width: r,
362
+ offsetX: h,
363
+ offsetY: m,
364
+ isMaterial3: T,
365
+ time: R,
366
+ tobeDeleted: A
367
+ }) => /* @__PURE__ */ k(
368
+ ot,
369
+ {
370
+ time: R,
371
+ ripple_width: r,
372
+ offsetX: h,
373
+ offsetY: m,
374
+ isMaterial3: T,
375
+ tobeDeleted: A,
376
+ deleteRipple: H,
377
+ rippleColor: n,
378
+ sparklesColorRGB: s,
379
+ opacity_level1: a,
380
+ opacity_level2: i,
381
+ sparklesMaxCount: c
382
+ },
383
+ R
384
+ )
385
+ ),
386
+ l
387
+ ]
388
+ }
389
+ );
390
+ };
391
+ export {
392
+ ft as RippleContainer
393
+ };
@@ -0,0 +1 @@
1
+ (function(T,y){typeof exports=="object"&&typeof module<"u"?y(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],y):(T=typeof globalThis<"u"?globalThis:T||self,y(T["sparkle-ripple"]={},T.ReactJSXRuntime,T.React))})(this,(function(T,y,i){"use strict";function D(t){var r,s,e="";if(typeof t=="string"||typeof t=="number")e+=t;else if(typeof t=="object")if(Array.isArray(t)){var o=t.length;for(r=0;r<o;r++)t[r]&&(s=D(t[r]))&&(e&&(e+=" "),e+=s)}else for(s in t)t[s]&&(e&&(e+=" "),e+=s);return e}function F(){for(var t,r,s=0,e="",o=arguments.length;s<o;s++)(t=arguments[s])&&(r=D(t))&&(e&&(e+=" "),e+=r);return e}function N(t,r,s,e){if(!t||!r||!s||!e)return;const o=t,a=s-t,u=r,c=e-r;let f,h;return o>=a?f=o:f=a,u>=c?h=u:h=c,Math.sqrt(f**2+h**2)*2}const W=(t,r)=>r-t.left,A=(t,r)=>r-t.top,X=(t,r,s,e,o)=>N(W(t,r),A(t,s),e,o),Y={ripple:"_ripple_17x6w_1"};class I{xpos;ypos;width;height;constructor(r,s,e,o){this.xpos=r,this.ypos=s,this.width=e,this.height=o}}class H{sparkles;color;constructor(r,s){this.sparkles=r,this.color=s}}function J(t,r,s,e){const o=t*(Math.PI/180),a=Math.cos(o)*e+(Math.floor(Math.random()*(r/s))-Math.floor(Math.random()*(r/s))),u=Math.sin(o)*e+(Math.floor(Math.random()*(r/s))-Math.floor(Math.random()*(r/s)));return{xpos:a,ypos:u}}function U(t,r,s){return`rgb(${s.rgb} / ${Math.abs((Math.random()-Math.random())*.3+(1-t/r)-(1-.6)).toFixed(2)})`}function z(t,r,s,e,o={rgb:"255 255 255",opacity_level1:"0.2",opacity_level2:"0.1"},a=6,u=2048){const c=s.clientWidth/2,f=s.clientWidth/2.6;if(r.clearRect(0,0,e,e),c===0||a===0)return;const h=[];let p=s.clientWidth;p>u&&(p=u);const b=[];for(let g=0;g<p;g++)b.push(Math.floor(Math.random()*360));for(const g of b){const l=J(g,c,a,f);h.push(new I(l.xpos,l.ypos,1,1))}const k=U(c,e,o);t.push(new H(h,k)),t.length>5&&(t.splice(0,1),t[0].color=`rgb(${o.rgb} / ${o.opacity_level1})`,t[1].color=`rgb(${o.rgb} / ${o.opacity_level2})`);for(const g of t){r.fillStyle=g.color;for(const l of g.sparkles)r.fillRect(e/2+l.xpos,e/2+l.ypos,l.width,l.height)}window.requestAnimationFrame(()=>z(t,r,s,e))}const V={sparkles_canvas:"_sparkles_canvas_qe9de_1"},K=i.memo(({ripple_width:t,rippleRef:r,sparklesColorRGB:s="255 255 255",opacity_level1:e="0.2",opacity_level2:o="0.1",sparklesMaxCount:a=2048})=>{const u=i.useRef(null),c=i.useRef(null);return i.useEffect(()=>{const f=window.devicePixelRatio;c.current&&(c.current.width=Math.floor(t*f),c.current.height=Math.floor(t*f));const h=c.current?.getContext("2d");if(!h)return;const p=[];return h.scale(f,f),u.current=requestAnimationFrame(()=>{r.current&&z(p,h,r.current,t,{rgb:s,opacity_level1:e,opacity_level2:o},6,a)}),c.current?.animate({opacity:[1,0]},{fill:"forwards",duration:600}),()=>{u.current&&cancelAnimationFrame(u.current)}},[t,r,s,a,e,o]),y.jsx("canvas",{ref:c,style:{width:`${t}px`,height:`${t}px`},className:F("sparkles_canvas",V.sparkles_canvas)})}),Q=i.memo(({ripple_width:t,offsetX:r,offsetY:s,isMaterial3:e=!0,rippleColor:o="#ffffff35",rippleTime:a=500,time:u,tobeDeleted:c=!1,waitingTime:f=290,deleteRipple:h,sparklesColorRGB:p="255 255 255",opacity_level1:b="0.2",opacity_level2:k="0.1",sparklesMaxCount:g=2048})=>{const l=i.useRef(null),x=i.useRef(null),w=i.useRef(!1),[q,O]=i.useState(!1),$=i.useCallback(async()=>{if(l.current&&x.current){if(x.current.currentTime<f){const M=a-x.current.currentTime-f;await new Promise(j=>{setTimeout(j,M)})}else await x.current.finished;if(!l.current)return;await l.current.animate({opacity:[1,0]},{fill:"forwards",duration:200,easing:"cubic-bezier(0.11, 0, 0.5, 0)"}).finished}h(u)},[h,e,a,u,f]);return i.useEffect(()=>{c||(x.current=l.current?.animate({width:e?[`${t/6}px`,`${t}px`]:[`${t/3}px`,`${t}px`],height:e?[`${t/6}px`,`${t}px`]:[`${t/3}px`,`${t}px`],background:e?[`radial-gradient(circle closest-side, ${o} 0%, transparent)`,`radial-gradient(circle closest-side, ${o} 80%, transparent)`]:[o,o]},{fill:"forwards",duration:e?400:a,easing:e?"cubic-bezier(0,0.49,0,1)":"cubic-bezier(0.1, 0.8, 1, 0.80)"}),(async()=>(await x.current?.ready,O(!0)))())},[e,a,o,t,c]),i.useEffect(()=>{c&&(w.current||(w.current=!0,$()))},[c,$]),y.jsx("span",{className:F("ripple",Y.ripple),style:{width:t,height:t,left:r,top:s},ref:l,children:e&&q&&y.jsx(K,{ripple_width:t,rippleRef:l,sparklesColorRGB:p,opacity_level1:b,opacity_level2:k,sparklesMaxCount:g})})}),Z={rippleContainer:"_rippleContainer_qv2ad_1"},G=({as:t,isMaterial3:r=!0,beforeRippleFn:s,rippleColor:e="#ffffff35",sparklesColorRGB:o="255 255 255",opacity_level1:a="0.2",opacity_level2:u="0.1",sparklesMaxCount:c=2048,className:f,children:h,onMouseDown:p,onTouchStart:b,onTouchMove:k,onMouseUp:g,onMouseLeave:l,onTouchEnd:x,onTouchCancel:w,...q})=>{const[O,$]=i.useState([]),S=i.useRef(!1),M=i.useRef(!1);function j(n){if(M.current)return;const d=X(n.currentTarget.getBoundingClientRect(),n.clientX,n.clientY,n.currentTarget.clientWidth,n.currentTarget.clientHeight),m=W(n.currentTarget.getBoundingClientRect(),n.clientX),R=A(n.currentTarget.getBoundingClientRect(),n.clientY);s&&s(n),E(d,m,R)}function E(n,d,m,R=!1){if(M.current&&!R||!n)return;const C=n/.8,B=new Date().getTime();$(nt=>[...nt,{ripple_width:C,offsetX:d,offsetY:m,time:B,isMaterial3:r,tobeDeleted:!1}])}async function P(n){(n.type==="touchend"||n.type==="touchcancel")&&await new Promise(d=>setTimeout(d,100)),$(d=>d.length===0?[]:d.map(m=>({...m,tobeDeleted:!0}))),S.current=!1}const L=i.useCallback(n=>{$(d=>d.filter(m=>m.time!==n))},[]),_=async n=>{const d=X(n.currentTarget.getBoundingClientRect(),n.changedTouches[0].clientX,n.changedTouches[0].clientY,n.currentTarget.clientWidth,n.currentTarget.clientHeight),m=W(n.currentTarget.getBoundingClientRect(),n.changedTouches[0].clientX),R=A(n.currentTarget.getBoundingClientRect(),n.changedTouches[0].clientY);M.current=!0,await new Promise(C=>{setTimeout(C,100)}),setTimeout(()=>{M.current=!1},1e3),S.current||(s&&s(n),E(d,m,R,!0)),S.current=!1},v=()=>{S.current=!0},tt=t??"div";return y.jsxs(tt,{...q,className:F(Z.rippleContainer,f??""),onMouseDown:n=>{j(n),p?.(n)},onTouchStart:n=>{_(n),b?.(n)},onTouchMove:n=>{v(),k?.(n)},onTouchEnd:n=>{P(n),x?.(n)},onMouseUp:n=>{P(n),g?.(n)},onMouseLeave:n=>{P(n),l?.(n)},onTouchCancel:n=>{P(n),w?.(n)},children:[O.map(({ripple_width:n,offsetX:d,offsetY:m,isMaterial3:R,time:C,tobeDeleted:B})=>y.jsx(Q,{time:C,ripple_width:n,offsetX:d,offsetY:m,isMaterial3:R,tobeDeleted:B,deleteRipple:L,rippleColor:e,sparklesColorRGB:o,opacity_level1:a,opacity_level2:u,sparklesMaxCount:c},C)),h]})};T.RippleContainer=G,Object.defineProperty(T,Symbol.toStringTag,{value:"Module"})}));
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "sparkle-ripple",
3
+ "version": "3.0.0",
4
+ "author": "yuyake-litrain",
5
+ "homepage": "https://m3ripple.js.org",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/yuyake-litrain/sparkle-ripple.git"
9
+ },
10
+ "main": "./dist/sparkle-ripple.umd.cjs",
11
+ "module": "./dist/sparkle-ripple.js",
12
+ "devDependencies": {
13
+ "@biomejs/biome": "^2.3.10",
14
+ "@types/node": "^22.19.3",
15
+ "@types/react": "^19.2.7",
16
+ "@types/react-dom": "^19.2.3",
17
+ "@vitejs/plugin-react-swc": "^4.2.2",
18
+ "@vitest/browser": "^3.2.4",
19
+ "@vitest/coverage-v8": "^3.2.4",
20
+ "clsx": "^2.1.1",
21
+ "concurrently": "^9.2.1",
22
+ "globals": "^16.5.0",
23
+ "playwright": "^1.57.0",
24
+ "typed-css-modules": "^0.9.1",
25
+ "typescript": "5.9.3",
26
+ "vite": "^7.3.0",
27
+ "vite-plugin-dts": "^4.5.4",
28
+ "vitest": "^3.2.4"
29
+ },
30
+ "peerDependencies": {
31
+ "@types/react": "^18.0.0 || ^19.0.0",
32
+ "react": "^18.0.0 || ^19.0.0",
33
+ "react-dom": "^18.0.0 || ^19.0.0"
34
+ },
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/sparkle-ripple.d.ts",
38
+ "import": "./dist/sparkle-ripple.js",
39
+ "require": "./dist/sparkle-ripple.umd.cjs"
40
+ },
41
+ "./css": "./dist/sparkle-ripple.css"
42
+ },
43
+ "description": "Bring Material 3(You) Ripple Effect to your projects!",
44
+ "files": [
45
+ "dist"
46
+ ],
47
+ "keywords": [
48
+ "Ripple",
49
+ "Ripple Effect",
50
+ "Material Design",
51
+ "Material You",
52
+ "react",
53
+ "web",
54
+ "front-end"
55
+ ],
56
+ "license": "MIT",
57
+ "private": false,
58
+ "scripts": {
59
+ "build": "tsc && vite build",
60
+ "lint": "biome lint --write",
61
+ "format": "biome format --write",
62
+ "check": "biome check --write",
63
+ "preview": "vite preview",
64
+ "preinstall": "npx only-allow bun"
65
+ },
66
+ "type": "module",
67
+ "types": "./dist/index.d.ts"
68
+ }