floating-player 1.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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Floating Video Player
2
+
3
+ A draggable and resizable floating video player (Picture-in-Picture) component for React.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install floating-video-player
9
+ ```
10
+
11
+ ## Peer Dependencies
12
+
13
+ ```bash
14
+ npm install react react-dom
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ import React, { useState } from 'react';
21
+ import { FloatingVideoPlayer } from 'floating-video-player';
22
+
23
+ function App() {
24
+ const [isOpen, setIsOpen] = useState(false);
25
+
26
+ return (
27
+ <div>
28
+ <button onClick={() => setIsOpen(true)}>Open Video</button>
29
+
30
+ <FloatingVideoPlayer
31
+ open={isOpen}
32
+ onClose={() => setIsOpen(false)}
33
+ content="https://example.com/video.mp4"
34
+ initialWidth={400}
35
+ aspectRatio={16 / 9}
36
+ showControls={true}
37
+ handleColor="#212121"
38
+ backgroundColor="#ffffff"
39
+ />
40
+ </div>
41
+ );
42
+ }
43
+
44
+ export default App;
45
+ ```
46
+
47
+ ## Props
48
+
49
+ | Prop | Type | Default | Description |
50
+ |------|------|---------|-------------|
51
+ | `open` | `boolean` | - | Controls whether the player is visible |
52
+ | `onClose` | `() => void` | - | Callback when close button is clicked |
53
+ | `content` | `string \| null \| undefined` | - | Video URL source |
54
+ | `initialWidth` | `number` | `window.innerWidth / 3` | Initial width in pixels |
55
+ | `initialHeight` | `number` | Calculated from aspect ratio | Initial height in pixels |
56
+ | `initialPosition` | `{ x: number, y: number }` | Bottom-right | Initial position |
57
+ | `aspectRatio` | `number` | `16/9` | Video aspect ratio |
58
+ | `showControls` | `boolean` | `true` | Show drag handle and close button |
59
+ | `className` | `string` | `''` | Additional CSS class |
60
+ | `style` | `React.CSSProperties` | - | Additional inline styles |
61
+ | `handleColor` | `string` | `'#212121'` | Background color of drag handle |
62
+ | `backgroundColor` | `string` | `'#ffffff'` | Background color of container |
63
+
64
+ ## Features
65
+
66
+ - Draggable via mouse and touch
67
+ - Resizable from bottom-right corner
68
+ - Maintains aspect ratio while resizing
69
+ - Mobile responsive
70
+ - Automatic boundary constraints
71
+ - Autoplay on open
72
+ - No external UI library dependencies
73
+
74
+ ## License
75
+
76
+ MIT
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+
3
+ interface FloatingVideoPlayerProps {
4
+ open: boolean;
5
+ onClose: () => void;
6
+ content: string | undefined | null;
7
+ showControls?: boolean;
8
+ className?: string;
9
+ handleColor?: string;
10
+ backgroundColor?: string;
11
+ }
12
+ declare const FloatingVideoPlayer: React.FC<FloatingVideoPlayerProps>;
13
+
14
+ export { FloatingVideoPlayer, type FloatingVideoPlayerProps };
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+
3
+ interface FloatingVideoPlayerProps {
4
+ open: boolean;
5
+ onClose: () => void;
6
+ content: string | undefined | null;
7
+ showControls?: boolean;
8
+ className?: string;
9
+ handleColor?: string;
10
+ backgroundColor?: string;
11
+ }
12
+ declare const FloatingVideoPlayer: React.FC<FloatingVideoPlayerProps>;
13
+
14
+ export { FloatingVideoPlayer, type FloatingVideoPlayerProps };
package/dist/index.js ADDED
@@ -0,0 +1,211 @@
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.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FloatingVideoPlayer: () => FloatingVideoPlayer_default
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/FloatingVideoPlayer.tsx
28
+ var import_react = require("react");
29
+ var import_jsx_runtime = require("react/jsx-runtime");
30
+ var FloatingVideoPlayer = ({
31
+ open,
32
+ onClose,
33
+ content,
34
+ showControls = true,
35
+ className = "",
36
+ handleColor = "#212121",
37
+ backgroundColor = "#ffffff"
38
+ }) => {
39
+ const [pos, setPos] = (0, import_react.useState)({ x: 0, y: 0 });
40
+ const [size, setSize] = (0, import_react.useState)({ w: 480, h: 270 });
41
+ const isDragging = (0, import_react.useRef)(false);
42
+ const isResizing = (0, import_react.useRef)(false);
43
+ const dragOffset = (0, import_react.useRef)({ x: 0, y: 0 });
44
+ const startSize = (0, import_react.useRef)({ w: 0, h: 0 });
45
+ const startPos = (0, import_react.useRef)({ x: 0, y: 0 });
46
+ const containerRef = (0, import_react.useRef)(null);
47
+ const videoRef = (0, import_react.useRef)(null);
48
+ (0, import_react.useEffect)(() => {
49
+ if (open) {
50
+ const w = Math.min(window.innerWidth * 0.4, 480);
51
+ const h = 270;
52
+ setSize({ w, h });
53
+ setPos({ x: window.innerWidth - w - 10, y: window.innerHeight - h - 10 });
54
+ }
55
+ }, [open]);
56
+ (0, import_react.useEffect)(() => {
57
+ const onMouseMove = (e) => {
58
+ if (isDragging.current) {
59
+ let newX = e.clientX - dragOffset.current.x;
60
+ let newY = e.clientY - dragOffset.current.y;
61
+ newX = Math.max(0, Math.min(window.innerWidth - size.w, newX));
62
+ newY = Math.max(0, Math.min(window.innerHeight - size.h, newY));
63
+ setPos({ x: newX, y: newY });
64
+ }
65
+ if (isResizing.current) {
66
+ const deltaX = e.clientX - startPos.current.x;
67
+ const deltaY = e.clientY - startPos.current.y;
68
+ const newW = Math.max(200, Math.min(startSize.current.w + deltaX, window.innerWidth));
69
+ const newH = Math.max(150, startSize.current.h + deltaY);
70
+ setSize({ w: newW, h: newH });
71
+ }
72
+ };
73
+ const onMouseUp = () => {
74
+ isDragging.current = false;
75
+ isResizing.current = false;
76
+ document.body.style.cursor = "default";
77
+ document.body.style.userSelect = "auto";
78
+ };
79
+ document.addEventListener("mousemove", onMouseMove);
80
+ document.addEventListener("mouseup", onMouseUp);
81
+ return () => {
82
+ document.removeEventListener("mousemove", onMouseMove);
83
+ document.removeEventListener("mouseup", onMouseUp);
84
+ };
85
+ }, [size]);
86
+ const onDragStart = (e) => {
87
+ if (e.target.classList.contains("resize-handle")) return;
88
+ e.preventDefault();
89
+ isDragging.current = true;
90
+ document.body.style.cursor = "grabbing";
91
+ document.body.style.userSelect = "none";
92
+ const rect = containerRef.current?.getBoundingClientRect();
93
+ if (rect) {
94
+ dragOffset.current = { x: e.clientX - rect.left, y: e.clientY - rect.top };
95
+ }
96
+ };
97
+ const onResizeStart = (e) => {
98
+ e.preventDefault();
99
+ e.stopPropagation();
100
+ isResizing.current = true;
101
+ document.body.style.cursor = "nwse-resize";
102
+ document.body.style.userSelect = "none";
103
+ startPos.current = { x: e.clientX, y: e.clientY };
104
+ startSize.current = { w: size.w, h: size.h };
105
+ };
106
+ (0, import_react.useEffect)(() => {
107
+ if (open && videoRef.current) {
108
+ videoRef.current.play().catch(() => {
109
+ });
110
+ }
111
+ }, [open]);
112
+ if (!open || !content) return null;
113
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
114
+ "div",
115
+ {
116
+ ref: containerRef,
117
+ className,
118
+ style: {
119
+ position: "fixed",
120
+ zIndex: 999999,
121
+ borderRadius: "8px",
122
+ overflow: "hidden",
123
+ display: "flex",
124
+ flexDirection: "column",
125
+ width: size.w,
126
+ height: size.h,
127
+ left: pos.x,
128
+ top: pos.y,
129
+ backgroundColor,
130
+ boxShadow: "0 8px 32px rgba(0,0,0,0.3)"
131
+ },
132
+ children: [
133
+ showControls && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
134
+ "div",
135
+ {
136
+ onMouseDown: onDragStart,
137
+ style: {
138
+ cursor: "grab",
139
+ display: "flex",
140
+ justifyContent: "space-between",
141
+ alignItems: "center",
142
+ backgroundColor: handleColor,
143
+ color: "#ffffff",
144
+ padding: "8px 12px",
145
+ userSelect: "none"
146
+ },
147
+ children: [
148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "12px", opacity: 0.8 }, children: "\u22EE\u22EE Drag" }),
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
150
+ "button",
151
+ {
152
+ onClick: onClose,
153
+ style: {
154
+ background: "rgba(255,255,255,0.2)",
155
+ border: "none",
156
+ color: "#fff",
157
+ cursor: "pointer",
158
+ padding: "4px 8px",
159
+ borderRadius: "4px",
160
+ fontSize: "12px"
161
+ },
162
+ children: "\u2715"
163
+ }
164
+ )
165
+ ]
166
+ }
167
+ ),
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, backgroundColor: "#000", position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
169
+ "video",
170
+ {
171
+ ref: videoRef,
172
+ style: { width: "100%", height: "100%", objectFit: "contain", display: "block" },
173
+ controls: true,
174
+ preload: "auto",
175
+ playsInline: true,
176
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("source", { src: content, type: "video/mp4" })
177
+ }
178
+ ) }),
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
180
+ "div",
181
+ {
182
+ className: "resize-handle",
183
+ onMouseDown: onResizeStart,
184
+ style: {
185
+ position: "absolute",
186
+ right: 0,
187
+ bottom: 0,
188
+ width: "20px",
189
+ height: "20px",
190
+ cursor: "nwse-resize",
191
+ backgroundColor: "rgba(255,255,255,0.3)",
192
+ borderTopLeftRadius: "4px",
193
+ display: "flex",
194
+ alignItems: "center",
195
+ justifyContent: "center",
196
+ color: "#333",
197
+ fontSize: "12px",
198
+ fontWeight: "bold"
199
+ },
200
+ children: "\u2921"
201
+ }
202
+ )
203
+ ]
204
+ }
205
+ );
206
+ };
207
+ var FloatingVideoPlayer_default = FloatingVideoPlayer;
208
+ // Annotate the CommonJS export names for ESM import in node:
209
+ 0 && (module.exports = {
210
+ FloatingVideoPlayer
211
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,184 @@
1
+ // src/FloatingVideoPlayer.tsx
2
+ import { useState, useRef, useEffect } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var FloatingVideoPlayer = ({
5
+ open,
6
+ onClose,
7
+ content,
8
+ showControls = true,
9
+ className = "",
10
+ handleColor = "#212121",
11
+ backgroundColor = "#ffffff"
12
+ }) => {
13
+ const [pos, setPos] = useState({ x: 0, y: 0 });
14
+ const [size, setSize] = useState({ w: 480, h: 270 });
15
+ const isDragging = useRef(false);
16
+ const isResizing = useRef(false);
17
+ const dragOffset = useRef({ x: 0, y: 0 });
18
+ const startSize = useRef({ w: 0, h: 0 });
19
+ const startPos = useRef({ x: 0, y: 0 });
20
+ const containerRef = useRef(null);
21
+ const videoRef = useRef(null);
22
+ useEffect(() => {
23
+ if (open) {
24
+ const w = Math.min(window.innerWidth * 0.4, 480);
25
+ const h = 270;
26
+ setSize({ w, h });
27
+ setPos({ x: window.innerWidth - w - 10, y: window.innerHeight - h - 10 });
28
+ }
29
+ }, [open]);
30
+ useEffect(() => {
31
+ const onMouseMove = (e) => {
32
+ if (isDragging.current) {
33
+ let newX = e.clientX - dragOffset.current.x;
34
+ let newY = e.clientY - dragOffset.current.y;
35
+ newX = Math.max(0, Math.min(window.innerWidth - size.w, newX));
36
+ newY = Math.max(0, Math.min(window.innerHeight - size.h, newY));
37
+ setPos({ x: newX, y: newY });
38
+ }
39
+ if (isResizing.current) {
40
+ const deltaX = e.clientX - startPos.current.x;
41
+ const deltaY = e.clientY - startPos.current.y;
42
+ const newW = Math.max(200, Math.min(startSize.current.w + deltaX, window.innerWidth));
43
+ const newH = Math.max(150, startSize.current.h + deltaY);
44
+ setSize({ w: newW, h: newH });
45
+ }
46
+ };
47
+ const onMouseUp = () => {
48
+ isDragging.current = false;
49
+ isResizing.current = false;
50
+ document.body.style.cursor = "default";
51
+ document.body.style.userSelect = "auto";
52
+ };
53
+ document.addEventListener("mousemove", onMouseMove);
54
+ document.addEventListener("mouseup", onMouseUp);
55
+ return () => {
56
+ document.removeEventListener("mousemove", onMouseMove);
57
+ document.removeEventListener("mouseup", onMouseUp);
58
+ };
59
+ }, [size]);
60
+ const onDragStart = (e) => {
61
+ if (e.target.classList.contains("resize-handle")) return;
62
+ e.preventDefault();
63
+ isDragging.current = true;
64
+ document.body.style.cursor = "grabbing";
65
+ document.body.style.userSelect = "none";
66
+ const rect = containerRef.current?.getBoundingClientRect();
67
+ if (rect) {
68
+ dragOffset.current = { x: e.clientX - rect.left, y: e.clientY - rect.top };
69
+ }
70
+ };
71
+ const onResizeStart = (e) => {
72
+ e.preventDefault();
73
+ e.stopPropagation();
74
+ isResizing.current = true;
75
+ document.body.style.cursor = "nwse-resize";
76
+ document.body.style.userSelect = "none";
77
+ startPos.current = { x: e.clientX, y: e.clientY };
78
+ startSize.current = { w: size.w, h: size.h };
79
+ };
80
+ useEffect(() => {
81
+ if (open && videoRef.current) {
82
+ videoRef.current.play().catch(() => {
83
+ });
84
+ }
85
+ }, [open]);
86
+ if (!open || !content) return null;
87
+ return /* @__PURE__ */ jsxs(
88
+ "div",
89
+ {
90
+ ref: containerRef,
91
+ className,
92
+ style: {
93
+ position: "fixed",
94
+ zIndex: 999999,
95
+ borderRadius: "8px",
96
+ overflow: "hidden",
97
+ display: "flex",
98
+ flexDirection: "column",
99
+ width: size.w,
100
+ height: size.h,
101
+ left: pos.x,
102
+ top: pos.y,
103
+ backgroundColor,
104
+ boxShadow: "0 8px 32px rgba(0,0,0,0.3)"
105
+ },
106
+ children: [
107
+ showControls && /* @__PURE__ */ jsxs(
108
+ "div",
109
+ {
110
+ onMouseDown: onDragStart,
111
+ style: {
112
+ cursor: "grab",
113
+ display: "flex",
114
+ justifyContent: "space-between",
115
+ alignItems: "center",
116
+ backgroundColor: handleColor,
117
+ color: "#ffffff",
118
+ padding: "8px 12px",
119
+ userSelect: "none"
120
+ },
121
+ children: [
122
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", opacity: 0.8 }, children: "\u22EE\u22EE Drag" }),
123
+ /* @__PURE__ */ jsx(
124
+ "button",
125
+ {
126
+ onClick: onClose,
127
+ style: {
128
+ background: "rgba(255,255,255,0.2)",
129
+ border: "none",
130
+ color: "#fff",
131
+ cursor: "pointer",
132
+ padding: "4px 8px",
133
+ borderRadius: "4px",
134
+ fontSize: "12px"
135
+ },
136
+ children: "\u2715"
137
+ }
138
+ )
139
+ ]
140
+ }
141
+ ),
142
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, backgroundColor: "#000", position: "relative" }, children: /* @__PURE__ */ jsx(
143
+ "video",
144
+ {
145
+ ref: videoRef,
146
+ style: { width: "100%", height: "100%", objectFit: "contain", display: "block" },
147
+ controls: true,
148
+ preload: "auto",
149
+ playsInline: true,
150
+ children: /* @__PURE__ */ jsx("source", { src: content, type: "video/mp4" })
151
+ }
152
+ ) }),
153
+ /* @__PURE__ */ jsx(
154
+ "div",
155
+ {
156
+ className: "resize-handle",
157
+ onMouseDown: onResizeStart,
158
+ style: {
159
+ position: "absolute",
160
+ right: 0,
161
+ bottom: 0,
162
+ width: "20px",
163
+ height: "20px",
164
+ cursor: "nwse-resize",
165
+ backgroundColor: "rgba(255,255,255,0.3)",
166
+ borderTopLeftRadius: "4px",
167
+ display: "flex",
168
+ alignItems: "center",
169
+ justifyContent: "center",
170
+ color: "#333",
171
+ fontSize: "12px",
172
+ fontWeight: "bold"
173
+ },
174
+ children: "\u2921"
175
+ }
176
+ )
177
+ ]
178
+ }
179
+ );
180
+ };
181
+ var FloatingVideoPlayer_default = FloatingVideoPlayer;
182
+ export {
183
+ FloatingVideoPlayer_default as FloatingVideoPlayer
184
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "floating-player",
3
+ "version": "1.0.0",
4
+ "description": "A draggable and resizable floating video player component for React",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.es.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --dts --external react react-dom",
13
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "peerDependencies": {
17
+ "react": ">=16.8.0",
18
+ "react-dom": ">=16.8.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/react": "^18.2.0",
22
+ "@types/react-dom": "^18.2.0",
23
+ "react": "^18.2.0",
24
+ "react-dom": "^18.2.0",
25
+ "tsup": "^8.0.0",
26
+ "typescript": "^5.0.0"
27
+ },
28
+ "keywords": [
29
+ "react",
30
+ "video",
31
+ "player",
32
+ "floating",
33
+ "pip",
34
+ "picture-in-picture",
35
+ "draggable",
36
+ "resizable"
37
+ ],
38
+ "author": "Your Name <your.email@example.com>",
39
+ "license": "MIT"
40
+ }