fancy-ui-svelte 0.4.1 → 0.5.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/README.md +18 -4
- package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte +168 -32
- package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte.d.ts +7 -0
- package/dist/fancy-ui/fluid-cursor/FluidCursor.test.js +36 -9
- package/dist/fancy-ui/fluid-cursor/index.d.ts +4 -1
- package/dist/fancy-ui/fluid-cursor/index.js +4 -1
- package/dist/fancy-ui/registry.js +1463 -0
- package/dist/fancy-ui/shimmer-button/ShimmerButton.svelte +1 -1
- package/dist/fancy-ui/tailwind.css +2 -2
- package/dist/types.d.ts +35 -0
- package/package.json +7 -1
- package/dist/fancy-ui/fluid-cursor/FluidCursorAdvanced.svelte +0 -1556
- package/dist/fancy-ui/fluid-cursor/FluidCursorAdvanced.svelte.d.ts +0 -33
- package/dist/fancy-ui/fluid-cursor/FluidCursorAdvanced.test.d.ts +0 -1
- package/dist/fancy-ui/fluid-cursor/FluidCursorAdvanced.test.js +0 -86
package/README.md
CHANGED
|
@@ -119,21 +119,35 @@ Beautiful animation and UI components for **Svelte 5**.
|
|
|
119
119
|
**Install via npm:**
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
|
-
npm install fancy-ui
|
|
122
|
+
npm install fancy-ui-svelte
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
Then import any component:
|
|
126
126
|
|
|
127
127
|
```ts
|
|
128
|
-
import { Marquee, BorderBeam, Confetti } from 'fancy-ui';
|
|
128
|
+
import { Marquee, BorderBeam, Confetti } from 'fancy-ui-svelte';
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
Add the Tailwind integration to your app's CSS (e.g. `src/app.css`):
|
|
132
132
|
|
|
133
133
|
```css
|
|
134
|
-
@import "
|
|
134
|
+
@import "tailwindcss";
|
|
135
|
+
@import "fancy-ui-svelte/tailwind.css";
|
|
135
136
|
```
|
|
136
137
|
|
|
138
|
+
This single import tells Tailwind v4 to scan the library's components so all utility classes are generated automatically. No manual `@source` path needed.
|
|
139
|
+
|
|
140
|
+
<details>
|
|
141
|
+
<summary>Alternative: manual @source (without the CSS import)</summary>
|
|
142
|
+
|
|
143
|
+
```css
|
|
144
|
+
@import "tailwindcss";
|
|
145
|
+
@source "../node_modules/fancy-ui-svelte/dist";
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The path is relative to your CSS file location.
|
|
149
|
+
</details>
|
|
150
|
+
|
|
137
151
|
**Or browse and copy a component:**
|
|
138
152
|
1. Find the component you need in the [live demo](https://fancy-ui.rama.app)
|
|
139
153
|
2. Copy the source from `src/lib/fancy-ui/[component-name]/`
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { cn } from "../../utils.js";
|
|
3
3
|
import { onMount } from "svelte";
|
|
4
4
|
|
|
5
|
+
// Module-level singleton registry
|
|
6
|
+
let activeInstance: (() => void) | null = null;
|
|
7
|
+
|
|
5
8
|
interface ColorRGB {
|
|
6
9
|
r: number;
|
|
7
10
|
g: number;
|
|
@@ -27,6 +30,13 @@
|
|
|
27
30
|
fluidColors?: string[];
|
|
28
31
|
colorIntensity?: number;
|
|
29
32
|
class?: string;
|
|
33
|
+
autoSplat?: boolean;
|
|
34
|
+
autoSplatInterval?: number;
|
|
35
|
+
interactive?: boolean;
|
|
36
|
+
pauseWhenHidden?: boolean;
|
|
37
|
+
splatOnMount?: boolean;
|
|
38
|
+
allowMultiple?: boolean;
|
|
39
|
+
contained?: boolean;
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
let {
|
|
@@ -48,6 +58,13 @@
|
|
|
48
58
|
fluidColors,
|
|
49
59
|
colorIntensity = 0.15,
|
|
50
60
|
class: className = "",
|
|
61
|
+
autoSplat = false,
|
|
62
|
+
autoSplatInterval = 1500,
|
|
63
|
+
interactive = true,
|
|
64
|
+
pauseWhenHidden = true,
|
|
65
|
+
splatOnMount = false,
|
|
66
|
+
allowMultiple = false,
|
|
67
|
+
contained = true,
|
|
51
68
|
}: Props = $props();
|
|
52
69
|
|
|
53
70
|
function hexToRgb(hex: string): ColorRGB {
|
|
@@ -956,6 +973,28 @@
|
|
|
956
973
|
return Math.floor(input * pixelRatio);
|
|
957
974
|
}
|
|
958
975
|
|
|
976
|
+
let canvasRectCache: DOMRect | null = null;
|
|
977
|
+
function updateCanvasRectCache() {
|
|
978
|
+
if (contained && canvas) canvasRectCache = canvas.getBoundingClientRect();
|
|
979
|
+
}
|
|
980
|
+
if (contained) {
|
|
981
|
+
window.addEventListener("resize", updateCanvasRectCache, { passive: true });
|
|
982
|
+
window.addEventListener("scroll", updateCanvasRectCache, { passive: true });
|
|
983
|
+
updateCanvasRectCache();
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function getCanvasPos(clientX: number, clientY: number): { x: number; y: number } {
|
|
987
|
+
if (contained) {
|
|
988
|
+
if (!canvasRectCache) updateCanvasRectCache();
|
|
989
|
+
const rect = canvasRectCache!;
|
|
990
|
+
return {
|
|
991
|
+
x: scaleByPixelRatio(clientX - rect.left),
|
|
992
|
+
y: scaleByPixelRatio(clientY - rect.top),
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
return { x: scaleByPixelRatio(clientX), y: scaleByPixelRatio(clientY) };
|
|
996
|
+
}
|
|
997
|
+
|
|
959
998
|
// Simulation Setup
|
|
960
999
|
updateKeywords();
|
|
961
1000
|
initFramebuffers();
|
|
@@ -996,7 +1035,10 @@
|
|
|
996
1035
|
return cachedFluidColorsScaled;
|
|
997
1036
|
}
|
|
998
1037
|
|
|
1038
|
+
let isVisible = true;
|
|
1039
|
+
|
|
999
1040
|
function updateFrame() {
|
|
1041
|
+
if (!isVisible) return;
|
|
1000
1042
|
const dt = calcDeltaTime();
|
|
1001
1043
|
if (resizeCanvas()) initFramebuffers();
|
|
1002
1044
|
updateColors(dt);
|
|
@@ -1247,6 +1289,36 @@
|
|
|
1247
1289
|
dye.swap();
|
|
1248
1290
|
}
|
|
1249
1291
|
|
|
1292
|
+
function multipleSplats(steps: number) {
|
|
1293
|
+
// Simulate a cursor sweep along a random arc — each step on its own frame
|
|
1294
|
+
// so the velocity field has time to evolve between injections.
|
|
1295
|
+
const angle = Math.random() * Math.PI * 2;
|
|
1296
|
+
const cx = 0.25 + Math.random() * 0.5;
|
|
1297
|
+
const cy = 0.25 + Math.random() * 0.5;
|
|
1298
|
+
const arcSpan = Math.PI * (0.8 + Math.random() * 0.8);
|
|
1299
|
+
let i = 0;
|
|
1300
|
+
|
|
1301
|
+
function step() {
|
|
1302
|
+
if (i >= steps) return;
|
|
1303
|
+
const t = i / Math.max(1, steps - 1);
|
|
1304
|
+
const a = angle + t * arcSpan;
|
|
1305
|
+
const r = 0.15 + 0.05 * Math.sin(t * Math.PI);
|
|
1306
|
+
const x = cx + Math.cos(a) * r;
|
|
1307
|
+
const y = cy + Math.sin(a) * r;
|
|
1308
|
+
const color = generateColor();
|
|
1309
|
+
splat(
|
|
1310
|
+
x,
|
|
1311
|
+
y,
|
|
1312
|
+
-Math.sin(a) * config.SPLAT_FORCE * 0.5,
|
|
1313
|
+
Math.cos(a) * config.SPLAT_FORCE * 0.5,
|
|
1314
|
+
color
|
|
1315
|
+
);
|
|
1316
|
+
i++;
|
|
1317
|
+
if (i < steps) requestAnimationFrame(step);
|
|
1318
|
+
}
|
|
1319
|
+
requestAnimationFrame(step);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1250
1322
|
function correctRadius(radius: number) {
|
|
1251
1323
|
const aspectRatio = canvas.width / canvas.height;
|
|
1252
1324
|
if (aspectRatio > 1) radius *= aspectRatio;
|
|
@@ -1360,37 +1432,30 @@
|
|
|
1360
1432
|
// Event Listeners
|
|
1361
1433
|
function handleMouseDown(e: MouseEvent) {
|
|
1362
1434
|
const pointer = pointers[0];
|
|
1363
|
-
const posX =
|
|
1364
|
-
const posY = scaleByPixelRatio(e.clientY);
|
|
1435
|
+
const { x: posX, y: posY } = getCanvasPos(e.clientX, e.clientY);
|
|
1365
1436
|
updatePointerDownData(pointer, -1, posX, posY);
|
|
1366
1437
|
clickSplat(pointer);
|
|
1367
1438
|
}
|
|
1368
1439
|
|
|
1369
1440
|
function handleFirstMouseMove(e: MouseEvent) {
|
|
1370
1441
|
const pointer = pointers[0];
|
|
1371
|
-
const posX =
|
|
1372
|
-
const posY = scaleByPixelRatio(e.clientY);
|
|
1442
|
+
const { x: posX, y: posY } = getCanvasPos(e.clientX, e.clientY);
|
|
1373
1443
|
const color = generateColor();
|
|
1374
|
-
updateFrame();
|
|
1375
1444
|
updatePointerMoveData(pointer, posX, posY, color);
|
|
1376
1445
|
document.body.removeEventListener("mousemove", handleFirstMouseMove);
|
|
1377
1446
|
}
|
|
1378
1447
|
|
|
1379
1448
|
function handleMouseMove(e: MouseEvent) {
|
|
1380
1449
|
const pointer = pointers[0];
|
|
1381
|
-
const posX =
|
|
1382
|
-
|
|
1383
|
-
const color = pointer.color;
|
|
1384
|
-
updatePointerMoveData(pointer, posX, posY, color);
|
|
1450
|
+
const { x: posX, y: posY } = getCanvasPos(e.clientX, e.clientY);
|
|
1451
|
+
updatePointerMoveData(pointer, posX, posY, pointer.color);
|
|
1385
1452
|
}
|
|
1386
1453
|
|
|
1387
1454
|
function handleFirstTouchStart(e: TouchEvent) {
|
|
1388
1455
|
const touches = e.targetTouches;
|
|
1389
1456
|
const pointer = pointers[0];
|
|
1390
1457
|
for (let i = 0; i < touches.length; i++) {
|
|
1391
|
-
const posX =
|
|
1392
|
-
const posY = scaleByPixelRatio(touches[i].clientY);
|
|
1393
|
-
updateFrame();
|
|
1458
|
+
const { x: posX, y: posY } = getCanvasPos(touches[i].clientX, touches[i].clientY);
|
|
1394
1459
|
updatePointerDownData(pointer, touches[i].identifier, posX, posY);
|
|
1395
1460
|
}
|
|
1396
1461
|
document.body.removeEventListener("touchstart", handleFirstTouchStart);
|
|
@@ -1400,8 +1465,7 @@
|
|
|
1400
1465
|
const touches = e.targetTouches;
|
|
1401
1466
|
const pointer = pointers[0];
|
|
1402
1467
|
for (let i = 0; i < touches.length; i++) {
|
|
1403
|
-
const posX =
|
|
1404
|
-
const posY = scaleByPixelRatio(touches[i].clientY);
|
|
1468
|
+
const { x: posX, y: posY } = getCanvasPos(touches[i].clientX, touches[i].clientY);
|
|
1405
1469
|
updatePointerDownData(pointer, touches[i].identifier, posX, posY);
|
|
1406
1470
|
}
|
|
1407
1471
|
}
|
|
@@ -1410,36 +1474,108 @@
|
|
|
1410
1474
|
const touches = e.targetTouches;
|
|
1411
1475
|
const pointer = pointers[0];
|
|
1412
1476
|
for (let i = 0; i < touches.length; i++) {
|
|
1413
|
-
const posX =
|
|
1414
|
-
const posY = scaleByPixelRatio(touches[i].clientY);
|
|
1477
|
+
const { x: posX, y: posY } = getCanvasPos(touches[i].clientX, touches[i].clientY);
|
|
1415
1478
|
updatePointerMoveData(pointer, posX, posY, pointer.color);
|
|
1416
1479
|
}
|
|
1417
1480
|
}
|
|
1418
1481
|
|
|
1419
1482
|
// Add event listeners
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1483
|
+
if (interactive) {
|
|
1484
|
+
window.addEventListener("mousedown", handleMouseDown);
|
|
1485
|
+
document.body.addEventListener("mousemove", handleFirstMouseMove);
|
|
1486
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
1487
|
+
document.body.addEventListener("touchstart", handleFirstTouchStart);
|
|
1488
|
+
window.addEventListener("touchstart", handleTouchStart, false);
|
|
1489
|
+
window.addEventListener("touchmove", handleTouchMove, false);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// Pause animation when scrolled out of view
|
|
1493
|
+
let observer: IntersectionObserver | null = null;
|
|
1494
|
+
if (pauseWhenHidden) {
|
|
1495
|
+
observer = new IntersectionObserver(
|
|
1496
|
+
([entry]) => {
|
|
1497
|
+
const wasVisible = isVisible;
|
|
1498
|
+
isVisible = entry.isIntersecting;
|
|
1499
|
+
if (isVisible && !wasVisible) {
|
|
1500
|
+
lastUpdateTime = Date.now();
|
|
1501
|
+
animationFrameId = requestAnimationFrame(updateFrame);
|
|
1502
|
+
}
|
|
1503
|
+
},
|
|
1504
|
+
{ threshold: 0 }
|
|
1505
|
+
);
|
|
1506
|
+
observer.observe(canvas);
|
|
1507
|
+
}
|
|
1426
1508
|
|
|
1427
1509
|
// Start animation
|
|
1428
1510
|
updateFrame();
|
|
1511
|
+
if (splatOnMount) multipleSplats(Math.floor(Math.random() * 6) + 10);
|
|
1512
|
+
|
|
1513
|
+
// Auto-splat timer
|
|
1514
|
+
let autoSplatTimer: ReturnType<typeof setInterval> | null = null;
|
|
1515
|
+
if (autoSplat) {
|
|
1516
|
+
autoSplatTimer = setInterval(() => {
|
|
1517
|
+
const color = generateColor();
|
|
1518
|
+
splat(
|
|
1519
|
+
Math.random(),
|
|
1520
|
+
Math.random(),
|
|
1521
|
+
(Math.random() - 0.5) * 200,
|
|
1522
|
+
(Math.random() - 0.5) * 200,
|
|
1523
|
+
color
|
|
1524
|
+
);
|
|
1525
|
+
}, autoSplatInterval);
|
|
1526
|
+
}
|
|
1429
1527
|
|
|
1430
1528
|
// Cleanup
|
|
1431
|
-
|
|
1529
|
+
function cleanup() {
|
|
1432
1530
|
cancelAnimationFrame(animationFrameId);
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1531
|
+
if (observer) observer.disconnect();
|
|
1532
|
+
if (autoSplatTimer) clearInterval(autoSplatTimer);
|
|
1533
|
+
if (interactive) {
|
|
1534
|
+
window.removeEventListener("mousedown", handleMouseDown);
|
|
1535
|
+
document.body.removeEventListener("mousemove", handleFirstMouseMove);
|
|
1536
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
1537
|
+
document.body.removeEventListener("touchstart", handleFirstTouchStart);
|
|
1538
|
+
window.removeEventListener("touchstart", handleTouchStart);
|
|
1539
|
+
window.removeEventListener("touchmove", handleTouchMove);
|
|
1540
|
+
}
|
|
1541
|
+
if (contained) {
|
|
1542
|
+
window.removeEventListener("resize", updateCanvasRectCache);
|
|
1543
|
+
window.removeEventListener("scroll", updateCanvasRectCache);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
if (!allowMultiple) {
|
|
1548
|
+
if (activeInstance) {
|
|
1549
|
+
if (import.meta.env.DEV) {
|
|
1550
|
+
console.warn(
|
|
1551
|
+
"[FluidCursor] Destroying previous instance. Only one instance is allowed by default. Use `allowMultiple={true}` to opt out of singleton behavior."
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
activeInstance();
|
|
1555
|
+
}
|
|
1556
|
+
activeInstance = cleanup;
|
|
1557
|
+
return () => {
|
|
1558
|
+
cleanup();
|
|
1559
|
+
if (activeInstance === cleanup) {
|
|
1560
|
+
activeInstance = null;
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
return cleanup;
|
|
1440
1566
|
});
|
|
1441
1567
|
</script>
|
|
1442
1568
|
|
|
1443
|
-
<div
|
|
1444
|
-
|
|
1569
|
+
<div
|
|
1570
|
+
class={cn(
|
|
1571
|
+
contained
|
|
1572
|
+
? "pointer-events-none absolute inset-0 h-full w-full"
|
|
1573
|
+
: "pointer-events-none fixed top-0 left-0 z-50 size-full",
|
|
1574
|
+
className
|
|
1575
|
+
)}
|
|
1576
|
+
>
|
|
1577
|
+
<canvas
|
|
1578
|
+
bind:this={canvasRef}
|
|
1579
|
+
class={contained ? "block h-full w-full" : "block h-screen w-screen"}
|
|
1580
|
+
></canvas>
|
|
1445
1581
|
</div>
|
|
@@ -22,6 +22,13 @@ interface Props {
|
|
|
22
22
|
fluidColors?: string[];
|
|
23
23
|
colorIntensity?: number;
|
|
24
24
|
class?: string;
|
|
25
|
+
autoSplat?: boolean;
|
|
26
|
+
autoSplatInterval?: number;
|
|
27
|
+
interactive?: boolean;
|
|
28
|
+
pauseWhenHidden?: boolean;
|
|
29
|
+
splatOnMount?: boolean;
|
|
30
|
+
allowMultiple?: boolean;
|
|
31
|
+
contained?: boolean;
|
|
25
32
|
}
|
|
26
33
|
declare const FluidCursor: import("svelte").Component<Props, {}, "">;
|
|
27
34
|
type FluidCursor = ReturnType<typeof FluidCursor>;
|
|
@@ -13,24 +13,27 @@ describe("FluidCursor", () => {
|
|
|
13
13
|
const canvas = container.querySelector("canvas");
|
|
14
14
|
expect(canvas).toBeInTheDocument();
|
|
15
15
|
});
|
|
16
|
-
it('canvas has id="fluid"', () => {
|
|
17
|
-
const { container } = render(FluidCursor);
|
|
18
|
-
const canvas = container.querySelector("canvas#fluid");
|
|
19
|
-
expect(canvas).toBeInTheDocument();
|
|
20
|
-
});
|
|
21
16
|
it("container has pointer-events-none class", () => {
|
|
22
17
|
const { container } = render(FluidCursor);
|
|
23
18
|
const div = container.firstElementChild;
|
|
24
19
|
expect(div?.className).toContain("pointer-events-none");
|
|
25
20
|
});
|
|
26
|
-
it("
|
|
21
|
+
it("defaults to contained mode (absolute positioning)", () => {
|
|
27
22
|
const { container } = render(FluidCursor);
|
|
28
23
|
const div = container.firstElementChild;
|
|
29
|
-
expect(div?.className).toContain("
|
|
24
|
+
expect(div?.className).toContain("absolute");
|
|
25
|
+
expect(div?.className).toContain("inset-0");
|
|
30
26
|
});
|
|
31
|
-
it("container
|
|
27
|
+
it("canvas fills container in contained mode", () => {
|
|
32
28
|
const { container } = render(FluidCursor);
|
|
29
|
+
const canvas = container.querySelector("canvas");
|
|
30
|
+
expect(canvas?.className).toContain("h-full");
|
|
31
|
+
expect(canvas?.className).toContain("w-full");
|
|
32
|
+
});
|
|
33
|
+
it("uses fixed positioning when contained={false}", () => {
|
|
34
|
+
const { container } = render(FluidCursor, { props: { contained: false } });
|
|
33
35
|
const div = container.firstElementChild;
|
|
36
|
+
expect(div?.className).toContain("fixed");
|
|
34
37
|
expect(div?.className).toContain("z-50");
|
|
35
38
|
});
|
|
36
39
|
it("applies custom class names", () => {
|
|
@@ -38,6 +41,31 @@ describe("FluidCursor", () => {
|
|
|
38
41
|
const div = container.firstElementChild;
|
|
39
42
|
expect(div?.className).toContain("my-fluid");
|
|
40
43
|
});
|
|
44
|
+
describe("interaction props", () => {
|
|
45
|
+
it("interactive={false} does not register mouse event listeners", () => {
|
|
46
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
47
|
+
render(FluidCursor, { props: { interactive: false } });
|
|
48
|
+
const mousedownCalls = addSpy.mock.calls.filter(([event]) => event === "mousedown");
|
|
49
|
+
expect(mousedownCalls).toHaveLength(0);
|
|
50
|
+
const mousemoveCalls = addSpy.mock.calls.filter(([event]) => event === "mousemove");
|
|
51
|
+
expect(mousemoveCalls).toHaveLength(0);
|
|
52
|
+
addSpy.mockRestore();
|
|
53
|
+
});
|
|
54
|
+
it("mounts without error with autoSplat={true} and splatOnMount={true}", () => {
|
|
55
|
+
const { container } = render(FluidCursor, {
|
|
56
|
+
props: { autoSplat: true, splatOnMount: true },
|
|
57
|
+
});
|
|
58
|
+
expect(container.querySelector("canvas")).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
it("pauseWhenHidden={false} does not instantiate IntersectionObserver", () => {
|
|
61
|
+
const observeSpy = vi.fn();
|
|
62
|
+
const mockObserver = vi.fn(() => ({ observe: observeSpy, disconnect: vi.fn() }));
|
|
63
|
+
vi.stubGlobal("IntersectionObserver", mockObserver);
|
|
64
|
+
render(FluidCursor, { props: { pauseWhenHidden: false } });
|
|
65
|
+
expect(mockObserver).not.toHaveBeenCalled();
|
|
66
|
+
vi.unstubAllGlobals();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
41
69
|
describe("color props", () => {
|
|
42
70
|
it("renders without error when fluidColor is provided", () => {
|
|
43
71
|
const { container } = render(FluidCursor, { props: { fluidColor: "#00ffcc" } });
|
|
@@ -50,7 +78,6 @@ describe("FluidCursor", () => {
|
|
|
50
78
|
expect(container.querySelector("canvas")).toBeInTheDocument();
|
|
51
79
|
});
|
|
52
80
|
it("renders without error when fluidColor and fluidColors are both provided (fluidColor takes priority)", () => {
|
|
53
|
-
// Both props accepted without throw — fluidColor priority is exercised at runtime
|
|
54
81
|
const { container } = render(FluidCursor, {
|
|
55
82
|
props: { fluidColor: "#ff0000", fluidColors: ["#00ffcc", "#7700ff"] },
|
|
56
83
|
});
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { default as FluidCursor } from "./FluidCursor.svelte";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Use `FluidCursor` instead. `FluidCursorAdvanced` has been merged into `FluidCursor`.
|
|
4
|
+
*/
|
|
5
|
+
export { default as FluidCursorAdvanced } from "./FluidCursor.svelte";
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { default as FluidCursor } from "./FluidCursor.svelte";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Use `FluidCursor` instead. `FluidCursorAdvanced` has been merged into `FluidCursor`.
|
|
4
|
+
*/
|
|
5
|
+
export { default as FluidCursorAdvanced } from "./FluidCursor.svelte";
|