fancy-ui-svelte 0.4.0 → 0.4.1
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/dist/fancy-ui/animated-beam/README.md +0 -21
- package/dist/fancy-ui/confetti/Confetti.svelte +3 -3
- package/dist/fancy-ui/confetti/Confetti.svelte.d.ts +1 -1
- package/dist/fancy-ui/confetti/README.md +1 -1
- package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte +72 -5
- package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte.d.ts +4 -1
- package/dist/fancy-ui/fluid-cursor/FluidCursor.test.js +34 -1
- package/dist/fancy-ui/fluid-cursor/FluidCursorAdvanced.svelte +1556 -0
- package/dist/fancy-ui/fluid-cursor/FluidCursorAdvanced.svelte.d.ts +33 -0
- package/dist/fancy-ui/fluid-cursor/FluidCursorAdvanced.test.d.ts +1 -0
- package/dist/fancy-ui/fluid-cursor/FluidCursorAdvanced.test.js +86 -0
- package/dist/fancy-ui/fluid-cursor/README.md +25 -0
- package/dist/fancy-ui/fluid-cursor/index.d.ts +1 -0
- package/dist/fancy-ui/fluid-cursor/index.js +1 -0
- package/dist/fancy-ui/index.d.ts +2 -0
- package/dist/fancy-ui/index.js +2 -0
- package/dist/fancy-ui/number-ticker/NumberTicker.svelte +1 -4
- package/dist/fancy-ui/registry.js +10 -0
- package/package.json +2 -1
|
@@ -12,27 +12,6 @@ Creates animated SVG beams that connect two elements with smooth gradients and c
|
|
|
12
12
|
- Reversible animation direction
|
|
13
13
|
- Position offsets for fine-tuning
|
|
14
14
|
|
|
15
|
-
## Implementation Notes
|
|
16
|
-
|
|
17
|
-
### Vue → Svelte Conversions
|
|
18
|
-
- `ref()` → `$state()`
|
|
19
|
-
- `computed()` → `$derived()`
|
|
20
|
-
- `watchEffect()` → `$effect()` (via `onMount`)
|
|
21
|
-
- Template refs → `bind:this`
|
|
22
|
-
- Props handling with destructuring defaults
|
|
23
|
-
|
|
24
|
-
### Key Differences
|
|
25
|
-
1. **Element References**: Svelte uses `bind:this` instead of Vue's `ref` template attribute
|
|
26
|
-
2. **Lifecycle**: Setup logic moved to `onMount` with cleanup return
|
|
27
|
-
3. **Derived Values**: Functions returning derived values (x1, x2, y1, y2) instead of computed refs
|
|
28
|
-
4. **Conditional Rendering**: Used `{#if}` blocks to ensure refs are defined before rendering beams
|
|
29
|
-
|
|
30
|
-
### Implementation Details
|
|
31
|
-
- ResizeObserver for responsive path updates
|
|
32
|
-
- SVG path calculation using quadratic Bezier curves (Q command)
|
|
33
|
-
- Direction detection for vertical/horizontal beams
|
|
34
|
-
- Unique gradient IDs to prevent conflicts with multiple instances
|
|
35
|
-
|
|
36
15
|
## Demo
|
|
37
16
|
See `/demo/animated-beam` for usage examples.
|
|
38
17
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
interface Props {
|
|
12
12
|
options?: ConfettiOptions;
|
|
13
13
|
globalOptions?: ConfettiGlobalOptions;
|
|
14
|
-
|
|
14
|
+
manualStart?: boolean;
|
|
15
15
|
class?: string;
|
|
16
16
|
children?: Snippet;
|
|
17
17
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
let {
|
|
20
20
|
options = {},
|
|
21
21
|
globalOptions = {},
|
|
22
|
-
|
|
22
|
+
manualStart = false,
|
|
23
23
|
class: className = "",
|
|
24
24
|
children,
|
|
25
25
|
}: Props = $props();
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
resize: true,
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
if (!
|
|
42
|
+
if (!manualStart) {
|
|
43
43
|
fire();
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -11,7 +11,7 @@ Canvas wrapper that creates a confetti instance.
|
|
|
11
11
|
|------|------|---------|-------------|
|
|
12
12
|
| `options` | `ConfettiOptions` | `{}` | Default confetti options |
|
|
13
13
|
| `globalOptions` | `ConfettiGlobalOptions` | `{}` | Canvas creation options |
|
|
14
|
-
| `
|
|
14
|
+
| `manualStart` | `boolean` | `false` | Skip auto-fire on mount |
|
|
15
15
|
| `class` | `string` | `''` | Canvas CSS classes |
|
|
16
16
|
|
|
17
17
|
### ConfettiButton
|
|
@@ -21,8 +21,11 @@
|
|
|
21
21
|
splatForce?: number;
|
|
22
22
|
shading?: boolean;
|
|
23
23
|
colorUpdateSpeed?: number;
|
|
24
|
-
backColor?: ColorRGB;
|
|
24
|
+
backColor?: ColorRGB | string;
|
|
25
25
|
transparent?: boolean;
|
|
26
|
+
fluidColor?: string;
|
|
27
|
+
fluidColors?: string[];
|
|
28
|
+
colorIntensity?: number;
|
|
26
29
|
class?: string;
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -41,9 +44,32 @@
|
|
|
41
44
|
colorUpdateSpeed = 10,
|
|
42
45
|
backColor = { r: 0.5, g: 0, b: 0 },
|
|
43
46
|
transparent = true,
|
|
47
|
+
fluidColor,
|
|
48
|
+
fluidColors,
|
|
49
|
+
colorIntensity = 0.15,
|
|
44
50
|
class: className = "",
|
|
45
51
|
}: Props = $props();
|
|
46
52
|
|
|
53
|
+
function hexToRgb(hex: string): ColorRGB {
|
|
54
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
55
|
+
if (!result) {
|
|
56
|
+
console.warn(
|
|
57
|
+
`[FluidCursor] Invalid hex color: "${hex}". Expected format: "#rrggbb". Falling back to white.`
|
|
58
|
+
);
|
|
59
|
+
return { r: 1, g: 1, b: 1 };
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
r: parseInt(result[1], 16) / 255,
|
|
63
|
+
g: parseInt(result[2], 16) / 255,
|
|
64
|
+
b: parseInt(result[3], 16) / 255,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const clampedColorIntensity = Math.max(0, Math.min(1, colorIntensity));
|
|
69
|
+
|
|
70
|
+
const resolvedBackColor: ColorRGB =
|
|
71
|
+
typeof backColor === "string" ? hexToRgb(backColor) : backColor;
|
|
72
|
+
|
|
47
73
|
let canvasRef: HTMLCanvasElement;
|
|
48
74
|
|
|
49
75
|
onMount(() => {
|
|
@@ -94,7 +120,7 @@
|
|
|
94
120
|
SHADING: shading,
|
|
95
121
|
COLOR_UPDATE_SPEED: colorUpdateSpeed,
|
|
96
122
|
PAUSED: false,
|
|
97
|
-
BACK_COLOR:
|
|
123
|
+
BACK_COLOR: resolvedBackColor,
|
|
98
124
|
TRANSPARENT: transparent,
|
|
99
125
|
};
|
|
100
126
|
|
|
@@ -936,8 +962,40 @@
|
|
|
936
962
|
|
|
937
963
|
let lastUpdateTime = Date.now();
|
|
938
964
|
let colorUpdateTimer = 0.0;
|
|
965
|
+
let colorIndex = 0;
|
|
939
966
|
let animationFrameId: number;
|
|
940
967
|
|
|
968
|
+
// Cached pre-scaled colors to avoid re-parsing hex on every splat
|
|
969
|
+
let cachedFluidColorHex: string | undefined;
|
|
970
|
+
let cachedFluidColor: ColorRGB | null = null;
|
|
971
|
+
let cachedFluidColorsRef: string[] | undefined;
|
|
972
|
+
let cachedFluidColorsScaled: ColorRGB[] = [];
|
|
973
|
+
|
|
974
|
+
function getScaledColor(hex: string): ColorRGB {
|
|
975
|
+
const { r, g, b } = hexToRgb(hex);
|
|
976
|
+
return {
|
|
977
|
+
r: r * clampedColorIntensity,
|
|
978
|
+
g: g * clampedColorIntensity,
|
|
979
|
+
b: b * clampedColorIntensity,
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function getCachedFluidColor(hex: string): ColorRGB {
|
|
984
|
+
if (cachedFluidColor === null || cachedFluidColorHex !== hex) {
|
|
985
|
+
cachedFluidColorHex = hex;
|
|
986
|
+
cachedFluidColor = getScaledColor(hex);
|
|
987
|
+
}
|
|
988
|
+
return cachedFluidColor;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function getCachedFluidColors(colors: string[]): ColorRGB[] {
|
|
992
|
+
if (cachedFluidColorsRef !== colors) {
|
|
993
|
+
cachedFluidColorsRef = colors;
|
|
994
|
+
cachedFluidColorsScaled = colors.map(getScaledColor);
|
|
995
|
+
}
|
|
996
|
+
return cachedFluidColorsScaled;
|
|
997
|
+
}
|
|
998
|
+
|
|
941
999
|
function updateFrame() {
|
|
942
1000
|
const dt = calcDeltaTime();
|
|
943
1001
|
if (resizeCanvas()) initFramebuffers();
|
|
@@ -1232,10 +1290,19 @@
|
|
|
1232
1290
|
}
|
|
1233
1291
|
|
|
1234
1292
|
function generateColor(): ColorRGB {
|
|
1293
|
+
if (fluidColor) {
|
|
1294
|
+
return getCachedFluidColor(fluidColor);
|
|
1295
|
+
}
|
|
1296
|
+
if (fluidColors && fluidColors.length > 0) {
|
|
1297
|
+
const colors = getCachedFluidColors(fluidColors);
|
|
1298
|
+
const idx = colorIndex % colors.length;
|
|
1299
|
+
colorIndex++;
|
|
1300
|
+
return colors[idx];
|
|
1301
|
+
}
|
|
1235
1302
|
const c = HSVtoRGB(Math.random(), 1.0, 1.0);
|
|
1236
|
-
c.r *=
|
|
1237
|
-
c.g *=
|
|
1238
|
-
c.b *=
|
|
1303
|
+
c.r *= clampedColorIntensity;
|
|
1304
|
+
c.g *= clampedColorIntensity;
|
|
1305
|
+
c.b *= clampedColorIntensity;
|
|
1239
1306
|
return c;
|
|
1240
1307
|
}
|
|
1241
1308
|
|
|
@@ -16,8 +16,11 @@ interface Props {
|
|
|
16
16
|
splatForce?: number;
|
|
17
17
|
shading?: boolean;
|
|
18
18
|
colorUpdateSpeed?: number;
|
|
19
|
-
backColor?: ColorRGB;
|
|
19
|
+
backColor?: ColorRGB | string;
|
|
20
20
|
transparent?: boolean;
|
|
21
|
+
fluidColor?: string;
|
|
22
|
+
fluidColors?: string[];
|
|
23
|
+
colorIntensity?: number;
|
|
21
24
|
class?: string;
|
|
22
25
|
}
|
|
23
26
|
declare const FluidCursor: import("svelte").Component<Props, {}, "">;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { render, cleanup } from "@testing-library/svelte";
|
|
2
|
-
import { afterEach, describe, it, expect } from "vitest";
|
|
2
|
+
import { afterEach, describe, it, expect, vi } from "vitest";
|
|
3
3
|
import FluidCursor from "./FluidCursor.svelte";
|
|
4
4
|
describe("FluidCursor", () => {
|
|
5
5
|
afterEach(cleanup);
|
|
@@ -38,4 +38,37 @@ describe("FluidCursor", () => {
|
|
|
38
38
|
const div = container.firstElementChild;
|
|
39
39
|
expect(div?.className).toContain("my-fluid");
|
|
40
40
|
});
|
|
41
|
+
describe("color props", () => {
|
|
42
|
+
it("renders without error when fluidColor is provided", () => {
|
|
43
|
+
const { container } = render(FluidCursor, { props: { fluidColor: "#00ffcc" } });
|
|
44
|
+
expect(container.querySelector("canvas")).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
it("renders without error when fluidColors is provided", () => {
|
|
47
|
+
const { container } = render(FluidCursor, {
|
|
48
|
+
props: { fluidColors: ["#ff0080", "#00ffcc", "#7700ff"] },
|
|
49
|
+
});
|
|
50
|
+
expect(container.querySelector("canvas")).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
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
|
+
const { container } = render(FluidCursor, {
|
|
55
|
+
props: { fluidColor: "#ff0000", fluidColors: ["#00ffcc", "#7700ff"] },
|
|
56
|
+
});
|
|
57
|
+
expect(container.querySelector("canvas")).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
it("renders without error when backColor is a hex string", () => {
|
|
60
|
+
const { container } = render(FluidCursor, { props: { backColor: "#1a1a2e" } });
|
|
61
|
+
expect(container.querySelector("canvas")).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
it("warns and falls back when an invalid hex string is passed to backColor", () => {
|
|
64
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
65
|
+
render(FluidCursor, { props: { backColor: "not-a-color" } });
|
|
66
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining("[FluidCursor] Invalid hex color"));
|
|
67
|
+
warn.mockRestore();
|
|
68
|
+
});
|
|
69
|
+
it("renders without error when colorIntensity is out of [0,1] range (clamped internally)", () => {
|
|
70
|
+
const { container } = render(FluidCursor, { props: { colorIntensity: 5 } });
|
|
71
|
+
expect(container.querySelector("canvas")).toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
41
74
|
});
|