@marianmeres/stuic 2.1.19 → 2.1.20
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/components/Circle/Circle.svelte +41 -0
- package/dist/components/Circle/Circle.svelte.d.ts +7 -0
- package/dist/components/Spinner/SpinnerCircle.svelte +17 -0
- package/dist/components/Spinner/SpinnerCircle.svelte.d.ts +7 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/oscillate.d.ts +4 -0
- package/dist/utils/oscillate.js +17 -0
- package/dist/utils/svg-circle.d.ts +16 -0
- package/dist/utils/svg-circle.js +76 -0
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { svgCircle, type SvgCircleOptions } from "../../utils/svg-circle.js";
|
|
4
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
strokeWidth = 10,
|
|
8
|
+
completeness = 1,
|
|
9
|
+
bgStrokeColor,
|
|
10
|
+
class: classProp = "",
|
|
11
|
+
roundedEdges = true,
|
|
12
|
+
rotate = 0,
|
|
13
|
+
strokeWidthRatio = 0,
|
|
14
|
+
style,
|
|
15
|
+
}: Partial<SvgCircleOptions> & { style?: string } = $props();
|
|
16
|
+
|
|
17
|
+
let container: HTMLDivElement = $state()!;
|
|
18
|
+
|
|
19
|
+
const circle = svgCircle({
|
|
20
|
+
strokeWidth,
|
|
21
|
+
completeness,
|
|
22
|
+
bgStrokeColor,
|
|
23
|
+
roundedEdges,
|
|
24
|
+
rotate,
|
|
25
|
+
strokeWidthRatio,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
$effect(() => {
|
|
29
|
+
container.appendChild(circle.svg);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
$effect(() => {
|
|
33
|
+
circle.setCompleteness(completeness);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
$effect(() => {
|
|
37
|
+
circle.setRotate(rotate);
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<div bind:this={container} class={twMerge("size-6", classProp)} {style}></div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type SvgCircleOptions } from "../../utils/svg-circle.js";
|
|
2
|
+
type $$ComponentProps = Partial<SvgCircleOptions> & {
|
|
3
|
+
style?: string;
|
|
4
|
+
};
|
|
5
|
+
declare const Circle: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type Circle = ReturnType<typeof Circle>;
|
|
7
|
+
export default Circle;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createTickerRAF } from "@marianmeres/ticker";
|
|
3
|
+
import Circle from "../Circle/Circle.svelte";
|
|
4
|
+
import { oscillate } from "../../utils/oscillate.js";
|
|
5
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
class: classProp = "",
|
|
9
|
+
bgStrokeColor = "rgba(0 0 0 / .1)",
|
|
10
|
+
}: { class?: string; bgStrokeColor?: string } = $props();
|
|
11
|
+
|
|
12
|
+
const ticker = createTickerRAF(50, true);
|
|
13
|
+
|
|
14
|
+
let completeness = $derived(oscillate($ticker / 1000, 0.15, 0.85, 1));
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<Circle {completeness} class={twMerge("animate-spin", classProp)} {bgStrokeColor} />
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./maybe-json-parse.js";
|
|
|
14
14
|
export * from "./maybe-json-stringify.js";
|
|
15
15
|
export * from "./nl2br.js";
|
|
16
16
|
export * from "./omit-pick.js";
|
|
17
|
+
export * from "./oscillate.js";
|
|
17
18
|
export * from "./paint.js";
|
|
18
19
|
export * from "./persistent-state.svelte.js";
|
|
19
20
|
export * from "./prefers-reduced-motion.svelte.js";
|
|
@@ -23,6 +24,7 @@ export * from "./seconds.js";
|
|
|
23
24
|
export * from "./sleep.js";
|
|
24
25
|
export * from "./storage-abstraction.js";
|
|
25
26
|
export * from "./str-hash.js";
|
|
27
|
+
export * from "./svg-circle.js";
|
|
26
28
|
export * from "./switch.svelte.js";
|
|
27
29
|
export * from "./throttle.js";
|
|
28
30
|
export * from "./tr.js";
|
package/dist/utils/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./maybe-json-parse.js";
|
|
|
14
14
|
export * from "./maybe-json-stringify.js";
|
|
15
15
|
export * from "./nl2br.js";
|
|
16
16
|
export * from "./omit-pick.js";
|
|
17
|
+
export * from "./oscillate.js";
|
|
17
18
|
export * from "./paint.js";
|
|
18
19
|
export * from "./persistent-state.svelte.js";
|
|
19
20
|
export * from "./prefers-reduced-motion.svelte.js";
|
|
@@ -23,6 +24,7 @@ export * from "./seconds.js";
|
|
|
23
24
|
export * from "./sleep.js";
|
|
24
25
|
export * from "./storage-abstraction.js";
|
|
25
26
|
export * from "./str-hash.js";
|
|
27
|
+
export * from "./svg-circle.js";
|
|
26
28
|
export * from "./switch.svelte.js";
|
|
27
29
|
export * from "./throttle.js";
|
|
28
30
|
export * from "./tr.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns an oscillating value (sine wave) between a min and max.
|
|
3
|
+
*/
|
|
4
|
+
export function oscillate(time, min = 0, max = 1, speed = 1) {
|
|
5
|
+
// Calculate the midpoint (the center of the oscillation)
|
|
6
|
+
// e.g., if min=10, max=50, midpoint is (50+10)/2 = 30
|
|
7
|
+
const midpoint = (max + min) / 2;
|
|
8
|
+
// Calculate the amplitude (half the distance between min and max)
|
|
9
|
+
// e.g., if min=10, max=50, amplitude is (50-10)/2 = 20
|
|
10
|
+
const amplitude = (max - min) / 2;
|
|
11
|
+
// Calculate the oscillation
|
|
12
|
+
// 1. Math.sin(time * speed) gives a value between -1 and 1
|
|
13
|
+
// 2. Multiplying by amplitude scales it to [-amplitude, amplitude] (e.g., [-20, 20])
|
|
14
|
+
// 3. Adding midpoint shifts the range to [midpoint - amplitude, midpoint + amplitude]
|
|
15
|
+
// (e.g., [30 - 20, 30 + 20] which is [10, 50])
|
|
16
|
+
return midpoint + Math.sin(time * speed) * amplitude;
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface SvgCircleOptions {
|
|
2
|
+
radius: number;
|
|
3
|
+
strokeWidth: number;
|
|
4
|
+
completeness: number;
|
|
5
|
+
class: string;
|
|
6
|
+
bgStrokeColor: string;
|
|
7
|
+
roundedEdges: boolean;
|
|
8
|
+
rotate: number;
|
|
9
|
+
strokeWidthRatio: number;
|
|
10
|
+
}
|
|
11
|
+
/** Will construct and return svg DOM element based on input options */
|
|
12
|
+
export declare function svgCircle(options?: Partial<SvgCircleOptions>): {
|
|
13
|
+
svg: SVGSVGElement;
|
|
14
|
+
setCompleteness(completeness: number): void;
|
|
15
|
+
setRotate(rotate: number): void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
function _normalize_completness(v) {
|
|
2
|
+
return Math.max(0, Math.min(1, v));
|
|
3
|
+
}
|
|
4
|
+
function _normalize_rotate(v) {
|
|
5
|
+
return `${v % 360}`;
|
|
6
|
+
}
|
|
7
|
+
function _normalize_cls(v) {
|
|
8
|
+
return [
|
|
9
|
+
...new Set(v
|
|
10
|
+
.split(" ")
|
|
11
|
+
.map((v) => v.trim())
|
|
12
|
+
.filter(Boolean)),
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
/** Will construct and return svg DOM element based on input options */
|
|
16
|
+
export function svgCircle(options = {}) {
|
|
17
|
+
let { strokeWidth = 10, completeness = 1, bgStrokeColor = "", class: classProp = "", roundedEdges = true, rotate = 0, strokeWidthRatio = 0, } = options ?? {};
|
|
18
|
+
completeness = _normalize_completness(completeness);
|
|
19
|
+
// calculate radius based on viewBox, leaving room for stroke
|
|
20
|
+
let actualStrokeWidth = strokeWidth;
|
|
21
|
+
if (strokeWidthRatio) {
|
|
22
|
+
const maxStrokeWidth = strokeWidthRatio * 50; // percentage of radius
|
|
23
|
+
actualStrokeWidth = Math.min(strokeWidth, maxStrokeWidth);
|
|
24
|
+
}
|
|
25
|
+
const radius = 50 - actualStrokeWidth / 2;
|
|
26
|
+
const center = 50;
|
|
27
|
+
const circumference = 2 * Math.PI * radius;
|
|
28
|
+
const dashArray = circumference;
|
|
29
|
+
const dashOffset = circumference * (1 - completeness);
|
|
30
|
+
const linecap = roundedEdges ? "round" : "butt";
|
|
31
|
+
// the svg element
|
|
32
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
33
|
+
svg.setAttribute("width", "100%");
|
|
34
|
+
svg.setAttribute("height", "100%");
|
|
35
|
+
svg.setAttribute("viewBox", "0 0 100 100");
|
|
36
|
+
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
37
|
+
if (classProp)
|
|
38
|
+
svg.classList.add(..._normalize_cls(classProp));
|
|
39
|
+
// optional background
|
|
40
|
+
if (bgStrokeColor) {
|
|
41
|
+
const bgCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
42
|
+
bgCircle.setAttribute("cx", `${center}`);
|
|
43
|
+
bgCircle.setAttribute("cy", `${center}`);
|
|
44
|
+
bgCircle.setAttribute("r", `${radius}`);
|
|
45
|
+
bgCircle.setAttribute("fill", "none");
|
|
46
|
+
bgCircle.setAttribute("stroke", bgStrokeColor);
|
|
47
|
+
bgCircle.setAttribute("stroke-width", `${actualStrokeWidth}`);
|
|
48
|
+
svg.appendChild(bgCircle);
|
|
49
|
+
}
|
|
50
|
+
// the circle
|
|
51
|
+
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
52
|
+
circle.setAttribute("cx", `${center}`);
|
|
53
|
+
circle.setAttribute("cy", `${center}`);
|
|
54
|
+
circle.setAttribute("r", `${radius}`);
|
|
55
|
+
circle.setAttribute("fill", "none");
|
|
56
|
+
circle.setAttribute("stroke", "currentColor");
|
|
57
|
+
circle.setAttribute("stroke-width", `${actualStrokeWidth}`);
|
|
58
|
+
circle.setAttribute("stroke-dasharray", `${dashArray}`);
|
|
59
|
+
circle.setAttribute("stroke-dashoffset", `${dashOffset}`);
|
|
60
|
+
circle.setAttribute("stroke-linecap", linecap);
|
|
61
|
+
circle.setAttribute("transform-origin", "center");
|
|
62
|
+
circle.setAttribute("transform", `rotate(${_normalize_rotate(rotate)})`);
|
|
63
|
+
//
|
|
64
|
+
svg.appendChild(circle);
|
|
65
|
+
return {
|
|
66
|
+
svg,
|
|
67
|
+
setCompleteness(completeness) {
|
|
68
|
+
completeness = _normalize_completness(completeness);
|
|
69
|
+
const dashOffset = circumference * (1 - completeness);
|
|
70
|
+
circle.setAttribute("stroke-dashoffset", `${dashOffset}`);
|
|
71
|
+
},
|
|
72
|
+
setRotate(rotate) {
|
|
73
|
+
circle.setAttribute("transform", `rotate(${_normalize_rotate(rotate)})`);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|