@sansalgo/spot-player 0.1.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/dist/index.css +40 -0
- package/dist/index.d.mts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +95 -0
- package/dist/index.mjs +70 -0
- package/package.json +35 -0
package/dist/index.css
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sansalgo/spot-player
|
|
3
|
+
*
|
|
4
|
+
* Import this file once in your app:
|
|
5
|
+
* import "@sansalgo/spot-player/style.css";
|
|
6
|
+
*
|
|
7
|
+
* Customize via CSS variables on any ancestor element (or :root):
|
|
8
|
+
*
|
|
9
|
+
* .my-player {
|
|
10
|
+
* --spot-on: #111111; /* lit pixel color
|
|
11
|
+
* --spot-off: #e4e4e4; /* unlit pixel color
|
|
12
|
+
* --spot-gap: 1px; /* gap between pixels (when gap prop is true)
|
|
13
|
+
* --spot-radius: 0px; /* pixel border-radius
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
.spot-player {
|
|
18
|
+
display: grid;
|
|
19
|
+
grid-template-columns: repeat(7, 1fr);
|
|
20
|
+
aspect-ratio: 1 / 1;
|
|
21
|
+
width: 100%;
|
|
22
|
+
gap: 0;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.spot-player--gap {
|
|
27
|
+
gap: var(--spot-gap, 1px);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.spot-pixel {
|
|
31
|
+
width: 100%;
|
|
32
|
+
aspect-ratio: 1 / 1;
|
|
33
|
+
background-color: var(--spot-off, #e4e4e4);
|
|
34
|
+
border-radius: var(--spot-radius, 0px);
|
|
35
|
+
transition: background-color 75ms linear;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.spot-pixel--on {
|
|
39
|
+
background-color: var(--spot-on, #111111);
|
|
40
|
+
}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/** An array of active pixel indices (0–48) representing one animation frame. */
|
|
4
|
+
type Frame = number[];
|
|
5
|
+
interface SpotPlayerProps {
|
|
6
|
+
/** Animation frames — each frame is an array of active pixel indices (0–48). */
|
|
7
|
+
frames: Frame[];
|
|
8
|
+
/** Render a gap between pixels. Default: true. */
|
|
9
|
+
gap?: boolean;
|
|
10
|
+
/** Play the animation automatically. Default: true. */
|
|
11
|
+
isPlaying?: boolean;
|
|
12
|
+
/** Milliseconds per frame. Default: 120. */
|
|
13
|
+
duration?: number;
|
|
14
|
+
/** Full loops before stopping. -1 = infinite. Default: -1. */
|
|
15
|
+
repeatCount?: number;
|
|
16
|
+
/** Called when repeatCount is reached. */
|
|
17
|
+
onComplete?: () => void;
|
|
18
|
+
/**
|
|
19
|
+
* CSS class for sizing — e.g. set width/height here.
|
|
20
|
+
* The grid always fills its container and keeps a 1:1 aspect ratio.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Inline size
|
|
24
|
+
* <SpotPlayer frames={...} style={{ width: 64 }} />
|
|
25
|
+
*
|
|
26
|
+
* // Tailwind
|
|
27
|
+
* <SpotPlayer frames={...} className="w-16" />
|
|
28
|
+
*/
|
|
29
|
+
className?: string;
|
|
30
|
+
style?: React.CSSProperties;
|
|
31
|
+
}
|
|
32
|
+
declare function SpotPlayer({ frames, gap, isPlaying, duration, repeatCount, onComplete, className, style, }: SpotPlayerProps): react_jsx_runtime.JSX.Element;
|
|
33
|
+
|
|
34
|
+
export { type Frame, SpotPlayer, type SpotPlayerProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/** An array of active pixel indices (0–48) representing one animation frame. */
|
|
4
|
+
type Frame = number[];
|
|
5
|
+
interface SpotPlayerProps {
|
|
6
|
+
/** Animation frames — each frame is an array of active pixel indices (0–48). */
|
|
7
|
+
frames: Frame[];
|
|
8
|
+
/** Render a gap between pixels. Default: true. */
|
|
9
|
+
gap?: boolean;
|
|
10
|
+
/** Play the animation automatically. Default: true. */
|
|
11
|
+
isPlaying?: boolean;
|
|
12
|
+
/** Milliseconds per frame. Default: 120. */
|
|
13
|
+
duration?: number;
|
|
14
|
+
/** Full loops before stopping. -1 = infinite. Default: -1. */
|
|
15
|
+
repeatCount?: number;
|
|
16
|
+
/** Called when repeatCount is reached. */
|
|
17
|
+
onComplete?: () => void;
|
|
18
|
+
/**
|
|
19
|
+
* CSS class for sizing — e.g. set width/height here.
|
|
20
|
+
* The grid always fills its container and keeps a 1:1 aspect ratio.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Inline size
|
|
24
|
+
* <SpotPlayer frames={...} style={{ width: 64 }} />
|
|
25
|
+
*
|
|
26
|
+
* // Tailwind
|
|
27
|
+
* <SpotPlayer frames={...} className="w-16" />
|
|
28
|
+
*/
|
|
29
|
+
className?: string;
|
|
30
|
+
style?: React.CSSProperties;
|
|
31
|
+
}
|
|
32
|
+
declare function SpotPlayer({ frames, gap, isPlaying, duration, repeatCount, onComplete, className, style, }: SpotPlayerProps): react_jsx_runtime.JSX.Element;
|
|
33
|
+
|
|
34
|
+
export { type Frame, SpotPlayer, type SpotPlayerProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.tsx
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
SpotPlayer: () => SpotPlayer
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var import_react = require("react");
|
|
27
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
28
|
+
var TOTAL = 49;
|
|
29
|
+
function SpotPlayer({
|
|
30
|
+
frames,
|
|
31
|
+
gap = true,
|
|
32
|
+
isPlaying = true,
|
|
33
|
+
duration = 120,
|
|
34
|
+
repeatCount = -1,
|
|
35
|
+
onComplete,
|
|
36
|
+
className,
|
|
37
|
+
style
|
|
38
|
+
}) {
|
|
39
|
+
const gridRef = (0, import_react.useRef)(null);
|
|
40
|
+
const currentIndex = (0, import_react.useRef)(0);
|
|
41
|
+
const repeats = (0, import_react.useRef)(0);
|
|
42
|
+
const intervalRef = (0, import_react.useRef)(null);
|
|
43
|
+
const applyFrame = (0, import_react.useCallback)(
|
|
44
|
+
(dots, frameIndex) => {
|
|
45
|
+
const frame = frames[frameIndex];
|
|
46
|
+
if (!frame) return;
|
|
47
|
+
const active = new Set(frame);
|
|
48
|
+
dots.forEach((dot, i) => {
|
|
49
|
+
dot.classList.toggle("spot-pixel--on", active.has(i));
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
[frames]
|
|
53
|
+
);
|
|
54
|
+
(0, import_react.useEffect)(() => {
|
|
55
|
+
currentIndex.current = 0;
|
|
56
|
+
repeats.current = 0;
|
|
57
|
+
}, [frames]);
|
|
58
|
+
(0, import_react.useEffect)(() => {
|
|
59
|
+
const el = gridRef.current;
|
|
60
|
+
if (!el) return;
|
|
61
|
+
if (intervalRef.current) {
|
|
62
|
+
clearInterval(intervalRef.current);
|
|
63
|
+
intervalRef.current = null;
|
|
64
|
+
}
|
|
65
|
+
if (!isPlaying || frames.length === 0) return;
|
|
66
|
+
if (currentIndex.current >= frames.length) currentIndex.current = 0;
|
|
67
|
+
const dots = Array.from(el.children);
|
|
68
|
+
applyFrame(dots, currentIndex.current);
|
|
69
|
+
intervalRef.current = setInterval(() => {
|
|
70
|
+
currentIndex.current = (currentIndex.current + 1) % frames.length;
|
|
71
|
+
applyFrame(dots, currentIndex.current);
|
|
72
|
+
if (currentIndex.current === 0) {
|
|
73
|
+
repeats.current += 1;
|
|
74
|
+
if (repeatCount !== -1 && repeats.current >= repeatCount) {
|
|
75
|
+
clearInterval(intervalRef.current);
|
|
76
|
+
intervalRef.current = null;
|
|
77
|
+
onComplete == null ? void 0 : onComplete();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}, duration);
|
|
81
|
+
return () => {
|
|
82
|
+
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
83
|
+
};
|
|
84
|
+
}, [frames, isPlaying, applyFrame, duration, repeatCount, onComplete]);
|
|
85
|
+
const rootClass = [
|
|
86
|
+
"spot-player",
|
|
87
|
+
gap ? "spot-player--gap" : "",
|
|
88
|
+
className != null ? className : ""
|
|
89
|
+
].filter(Boolean).join(" ");
|
|
90
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: gridRef, className: rootClass, style, children: Array.from({ length: TOTAL }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "spot-pixel" }, i)) });
|
|
91
|
+
}
|
|
92
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
93
|
+
0 && (module.exports = {
|
|
94
|
+
SpotPlayer
|
|
95
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/index.tsx
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var TOTAL = 49;
|
|
5
|
+
function SpotPlayer({
|
|
6
|
+
frames,
|
|
7
|
+
gap = true,
|
|
8
|
+
isPlaying = true,
|
|
9
|
+
duration = 120,
|
|
10
|
+
repeatCount = -1,
|
|
11
|
+
onComplete,
|
|
12
|
+
className,
|
|
13
|
+
style
|
|
14
|
+
}) {
|
|
15
|
+
const gridRef = useRef(null);
|
|
16
|
+
const currentIndex = useRef(0);
|
|
17
|
+
const repeats = useRef(0);
|
|
18
|
+
const intervalRef = useRef(null);
|
|
19
|
+
const applyFrame = useCallback(
|
|
20
|
+
(dots, frameIndex) => {
|
|
21
|
+
const frame = frames[frameIndex];
|
|
22
|
+
if (!frame) return;
|
|
23
|
+
const active = new Set(frame);
|
|
24
|
+
dots.forEach((dot, i) => {
|
|
25
|
+
dot.classList.toggle("spot-pixel--on", active.has(i));
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
[frames]
|
|
29
|
+
);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
currentIndex.current = 0;
|
|
32
|
+
repeats.current = 0;
|
|
33
|
+
}, [frames]);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const el = gridRef.current;
|
|
36
|
+
if (!el) return;
|
|
37
|
+
if (intervalRef.current) {
|
|
38
|
+
clearInterval(intervalRef.current);
|
|
39
|
+
intervalRef.current = null;
|
|
40
|
+
}
|
|
41
|
+
if (!isPlaying || frames.length === 0) return;
|
|
42
|
+
if (currentIndex.current >= frames.length) currentIndex.current = 0;
|
|
43
|
+
const dots = Array.from(el.children);
|
|
44
|
+
applyFrame(dots, currentIndex.current);
|
|
45
|
+
intervalRef.current = setInterval(() => {
|
|
46
|
+
currentIndex.current = (currentIndex.current + 1) % frames.length;
|
|
47
|
+
applyFrame(dots, currentIndex.current);
|
|
48
|
+
if (currentIndex.current === 0) {
|
|
49
|
+
repeats.current += 1;
|
|
50
|
+
if (repeatCount !== -1 && repeats.current >= repeatCount) {
|
|
51
|
+
clearInterval(intervalRef.current);
|
|
52
|
+
intervalRef.current = null;
|
|
53
|
+
onComplete == null ? void 0 : onComplete();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}, duration);
|
|
57
|
+
return () => {
|
|
58
|
+
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
59
|
+
};
|
|
60
|
+
}, [frames, isPlaying, applyFrame, duration, repeatCount, onComplete]);
|
|
61
|
+
const rootClass = [
|
|
62
|
+
"spot-player",
|
|
63
|
+
gap ? "spot-player--gap" : "",
|
|
64
|
+
className != null ? className : ""
|
|
65
|
+
].filter(Boolean).join(" ");
|
|
66
|
+
return /* @__PURE__ */ jsx("div", { ref: gridRef, className: rootClass, style, children: Array.from({ length: TOTAL }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "spot-pixel" }, i)) });
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
SpotPlayer
|
|
70
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sansalgo/spot-player",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A resizable 7×7 pixel animation player React component",
|
|
5
|
+
"keywords": ["pixel", "animation", "react", "component"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.cjs",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./style.css": "./dist/index.css"
|
|
17
|
+
},
|
|
18
|
+
"files": ["dist"],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"prepublishOnly": "bun run build"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": ">=18",
|
|
26
|
+
"react-dom": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/react": "^19",
|
|
30
|
+
"react": "19.2.4",
|
|
31
|
+
"react-dom": "19.2.4",
|
|
32
|
+
"tsup": "^8.0.0",
|
|
33
|
+
"typescript": "^5"
|
|
34
|
+
}
|
|
35
|
+
}
|