@beforegolive/floating-ball 0.1.19 → 0.2.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/index.js ADDED
@@ -0,0 +1,538 @@
1
+ import ie, { useState as re, useRef as O, useEffect as te, useCallback as A } from "react";
2
+ var I = { exports: {} }, J = {};
3
+ /**
4
+ * @license React
5
+ * react-jsx-runtime.production.js
6
+ *
7
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ *
9
+ * This source code is licensed under the MIT license found in the
10
+ * LICENSE file in the root directory of this source tree.
11
+ */
12
+ var ne;
13
+ function fe() {
14
+ if (ne) return J;
15
+ ne = 1;
16
+ var t = Symbol.for("react.transitional.element"), n = Symbol.for("react.fragment");
17
+ function s(b, c, o) {
18
+ var l = null;
19
+ if (o !== void 0 && (l = "" + o), c.key !== void 0 && (l = "" + c.key), "key" in c) {
20
+ o = {};
21
+ for (var u in c)
22
+ u !== "key" && (o[u] = c[u]);
23
+ } else o = c;
24
+ return c = o.ref, {
25
+ $$typeof: t,
26
+ type: b,
27
+ key: l,
28
+ ref: c !== void 0 ? c : null,
29
+ props: o
30
+ };
31
+ }
32
+ return J.Fragment = n, J.jsx = s, J.jsxs = s, J;
33
+ }
34
+ var q = {};
35
+ /**
36
+ * @license React
37
+ * react-jsx-runtime.development.js
38
+ *
39
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
40
+ *
41
+ * This source code is licensed under the MIT license found in the
42
+ * LICENSE file in the root directory of this source tree.
43
+ */
44
+ var oe;
45
+ function de() {
46
+ return oe || (oe = 1, process.env.NODE_ENV !== "production" && (function() {
47
+ function t(e) {
48
+ if (e == null) return null;
49
+ if (typeof e == "function")
50
+ return e.$$typeof === Z ? null : e.displayName || e.name || null;
51
+ if (typeof e == "string") return e;
52
+ switch (e) {
53
+ case k:
54
+ return "Fragment";
55
+ case h:
56
+ return "Profiler";
57
+ case L:
58
+ return "StrictMode";
59
+ case C:
60
+ return "Suspense";
61
+ case M:
62
+ return "SuspenseList";
63
+ case V:
64
+ return "Activity";
65
+ }
66
+ if (typeof e == "object")
67
+ switch (typeof e.tag == "number" && console.error(
68
+ "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
69
+ ), e.$$typeof) {
70
+ case _:
71
+ return "Portal";
72
+ case S:
73
+ return e.displayName || "Context";
74
+ case z:
75
+ return (e._context.displayName || "Context") + ".Consumer";
76
+ case X:
77
+ var r = e.render;
78
+ return e = e.displayName, e || (e = r.displayName || r.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
79
+ case F:
80
+ return r = e.displayName || null, r !== null ? r : t(e.type) || "Memo";
81
+ case j:
82
+ r = e._payload, e = e._init;
83
+ try {
84
+ return t(e(r));
85
+ } catch {
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+ function n(e) {
91
+ return "" + e;
92
+ }
93
+ function s(e) {
94
+ try {
95
+ n(e);
96
+ var r = !1;
97
+ } catch {
98
+ r = !0;
99
+ }
100
+ if (r) {
101
+ r = console;
102
+ var i = r.error, d = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
103
+ return i.call(
104
+ r,
105
+ "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
106
+ d
107
+ ), n(e);
108
+ }
109
+ }
110
+ function b(e) {
111
+ if (e === k) return "<>";
112
+ if (typeof e == "object" && e !== null && e.$$typeof === j)
113
+ return "<...>";
114
+ try {
115
+ var r = t(e);
116
+ return r ? "<" + r + ">" : "<...>";
117
+ } catch {
118
+ return "<...>";
119
+ }
120
+ }
121
+ function c() {
122
+ var e = Y.A;
123
+ return e === null ? null : e.getOwner();
124
+ }
125
+ function o() {
126
+ return Error("react-stack-top-frame");
127
+ }
128
+ function l(e) {
129
+ if (G.call(e, "key")) {
130
+ var r = Object.getOwnPropertyDescriptor(e, "key").get;
131
+ if (r && r.isReactWarning) return !1;
132
+ }
133
+ return e.key !== void 0;
134
+ }
135
+ function u(e, r) {
136
+ function i() {
137
+ B || (B = !0, console.error(
138
+ "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
139
+ r
140
+ ));
141
+ }
142
+ i.isReactWarning = !0, Object.defineProperty(e, "key", {
143
+ get: i,
144
+ configurable: !0
145
+ });
146
+ }
147
+ function p() {
148
+ var e = t(this.type);
149
+ return a[e] || (a[e] = !0, console.error(
150
+ "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
151
+ )), e = this.props.ref, e !== void 0 ? e : null;
152
+ }
153
+ function E(e, r, i, d, H, Q) {
154
+ var g = i.ref;
155
+ return e = {
156
+ $$typeof: N,
157
+ type: e,
158
+ key: r,
159
+ props: i,
160
+ _owner: d
161
+ }, (g !== void 0 ? g : null) !== null ? Object.defineProperty(e, "ref", {
162
+ enumerable: !1,
163
+ get: p
164
+ }) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
165
+ configurable: !1,
166
+ enumerable: !1,
167
+ writable: !0,
168
+ value: 0
169
+ }), Object.defineProperty(e, "_debugInfo", {
170
+ configurable: !1,
171
+ enumerable: !1,
172
+ writable: !0,
173
+ value: null
174
+ }), Object.defineProperty(e, "_debugStack", {
175
+ configurable: !1,
176
+ enumerable: !1,
177
+ writable: !0,
178
+ value: H
179
+ }), Object.defineProperty(e, "_debugTask", {
180
+ configurable: !1,
181
+ enumerable: !1,
182
+ writable: !0,
183
+ value: Q
184
+ }), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
185
+ }
186
+ function f(e, r, i, d, H, Q) {
187
+ var g = r.children;
188
+ if (g !== void 0)
189
+ if (d)
190
+ if (ee(g)) {
191
+ for (d = 0; d < g.length; d++)
192
+ m(g[d]);
193
+ Object.freeze && Object.freeze(g);
194
+ } else
195
+ console.error(
196
+ "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
197
+ );
198
+ else m(g);
199
+ if (G.call(r, "key")) {
200
+ g = t(e);
201
+ var U = Object.keys(r).filter(function(ue) {
202
+ return ue !== "key";
203
+ });
204
+ d = 0 < U.length ? "{key: someKey, " + U.join(": ..., ") + ": ...}" : "{key: someKey}", w[g + d] || (U = 0 < U.length ? "{" + U.join(": ..., ") + ": ...}" : "{}", console.error(
205
+ `A props object containing a "key" prop is being spread into JSX:
206
+ let props = %s;
207
+ <%s {...props} />
208
+ React keys must be passed directly to JSX without using spread:
209
+ let props = %s;
210
+ <%s key={someKey} {...props} />`,
211
+ d,
212
+ g,
213
+ U,
214
+ g
215
+ ), w[g + d] = !0);
216
+ }
217
+ if (g = null, i !== void 0 && (s(i), g = "" + i), l(r) && (s(r.key), g = "" + r.key), "key" in r) {
218
+ i = {};
219
+ for (var K in r)
220
+ K !== "key" && (i[K] = r[K]);
221
+ } else i = r;
222
+ return g && u(
223
+ i,
224
+ typeof e == "function" ? e.displayName || e.name || "Unknown" : e
225
+ ), E(
226
+ e,
227
+ g,
228
+ i,
229
+ c(),
230
+ H,
231
+ Q
232
+ );
233
+ }
234
+ function m(e) {
235
+ x(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e !== null && e.$$typeof === j && (e._payload.status === "fulfilled" ? x(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
236
+ }
237
+ function x(e) {
238
+ return typeof e == "object" && e !== null && e.$$typeof === N;
239
+ }
240
+ var D = ie, N = Symbol.for("react.transitional.element"), _ = Symbol.for("react.portal"), k = Symbol.for("react.fragment"), L = Symbol.for("react.strict_mode"), h = Symbol.for("react.profiler"), z = Symbol.for("react.consumer"), S = Symbol.for("react.context"), X = Symbol.for("react.forward_ref"), C = Symbol.for("react.suspense"), M = Symbol.for("react.suspense_list"), F = Symbol.for("react.memo"), j = Symbol.for("react.lazy"), V = Symbol.for("react.activity"), Z = Symbol.for("react.client.reference"), Y = D.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, G = Object.prototype.hasOwnProperty, ee = Array.isArray, $ = console.createTask ? console.createTask : function() {
241
+ return null;
242
+ };
243
+ D = {
244
+ react_stack_bottom_frame: function(e) {
245
+ return e();
246
+ }
247
+ };
248
+ var B, a = {}, v = D.react_stack_bottom_frame.bind(
249
+ D,
250
+ o
251
+ )(), R = $(b(o)), w = {};
252
+ q.Fragment = k, q.jsx = function(e, r, i) {
253
+ var d = 1e4 > Y.recentlyCreatedOwnerStacks++;
254
+ return f(
255
+ e,
256
+ r,
257
+ i,
258
+ !1,
259
+ d ? Error("react-stack-top-frame") : v,
260
+ d ? $(b(e)) : R
261
+ );
262
+ }, q.jsxs = function(e, r, i) {
263
+ var d = 1e4 > Y.recentlyCreatedOwnerStacks++;
264
+ return f(
265
+ e,
266
+ r,
267
+ i,
268
+ !0,
269
+ d ? Error("react-stack-top-frame") : v,
270
+ d ? $(b(e)) : R
271
+ );
272
+ };
273
+ })()), q;
274
+ }
275
+ var ae;
276
+ function pe() {
277
+ return ae || (ae = 1, process.env.NODE_ENV === "production" ? I.exports = fe() : I.exports = de()), I.exports;
278
+ }
279
+ var T = pe();
280
+ let me = { data: "" }, be = (t) => {
281
+ if (typeof window == "object") {
282
+ let n = (t ? t.querySelector("#_goober") : window._goober) || Object.assign(document.createElement("style"), { innerHTML: " ", id: "_goober" });
283
+ return n.nonce = window.__nonce__, n.parentNode || (t || document.head).appendChild(n), n.firstChild;
284
+ }
285
+ return t || me;
286
+ }, ge = /(?:([\u0080-\uFFFF\w-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|(}\s*)/g, he = /\/\*[^]*?\*\/| +/g, le = /\n+/g, P = (t, n) => {
287
+ let s = "", b = "", c = "";
288
+ for (let o in t) {
289
+ let l = t[o];
290
+ o[0] == "@" ? o[1] == "i" ? s = o + " " + l + ";" : b += o[1] == "f" ? P(l, o) : o + "{" + P(l, o[1] == "k" ? "" : n) + "}" : typeof l == "object" ? b += P(l, n ? n.replace(/([^,])+/g, (u) => o.replace(/([^,]*:\S+\([^)]*\))|([^,])+/g, (p) => /&/.test(p) ? p.replace(/&/g, u) : u ? u + " " + p : p)) : o) : l != null && (o = /^--/.test(o) ? o : o.replace(/[A-Z]/g, "-$&").toLowerCase(), c += P.p ? P.p(o, l) : o + ":" + l + ";");
291
+ }
292
+ return s + (n && c ? n + "{" + c + "}" : c) + b;
293
+ }, y = {}, se = (t) => {
294
+ if (typeof t == "object") {
295
+ let n = "";
296
+ for (let s in t) n += s + se(t[s]);
297
+ return n;
298
+ }
299
+ return t;
300
+ }, Ee = (t, n, s, b, c) => {
301
+ let o = se(t), l = y[o] || (y[o] = ((p) => {
302
+ let E = 0, f = 11;
303
+ for (; E < p.length; ) f = 101 * f + p.charCodeAt(E++) >>> 0;
304
+ return "go" + f;
305
+ })(o));
306
+ if (!y[l]) {
307
+ let p = o !== t ? t : ((E) => {
308
+ let f, m, x = [{}];
309
+ for (; f = ge.exec(E.replace(he, "")); ) f[4] ? x.shift() : f[3] ? (m = f[3].replace(le, " ").trim(), x.unshift(x[0][m] = x[0][m] || {})) : x[0][f[1]] = f[2].replace(le, " ").trim();
310
+ return x[0];
311
+ })(t);
312
+ y[l] = P(c ? { ["@keyframes " + l]: p } : p, s ? "" : "." + l);
313
+ }
314
+ let u = s && y.g ? y.g : null;
315
+ return s && (y.g = y[l]), ((p, E, f, m) => {
316
+ m ? E.data = E.data.replace(m, p) : E.data.indexOf(p) === -1 && (E.data = f ? p + E.data : E.data + p);
317
+ })(y[l], n, b, u), l;
318
+ }, ve = (t, n, s) => t.reduce((b, c, o) => {
319
+ let l = n[o];
320
+ if (l && l.call) {
321
+ let u = l(s), p = u && u.props && u.props.className || /^go/.test(u) && u;
322
+ l = p ? "." + p : u && typeof u == "object" ? u.props ? "" : P(u, "") : u === !1 ? "" : u;
323
+ }
324
+ return b + c + (l ?? "");
325
+ }, "");
326
+ function W(t) {
327
+ let n = this || {}, s = t.call ? t(n.p) : t;
328
+ return Ee(s.unshift ? s.raw ? ve(s, [].slice.call(arguments, 1), n.p) : s.reduce((b, c) => Object.assign(b, c && c.call ? c(n.p) : c), {}) : s, be(n.target), n.g, n.o, n.k);
329
+ }
330
+ W.bind({ g: 1 });
331
+ W.bind({ k: 1 });
332
+ function _e(t, n, s, b) {
333
+ P.p = n;
334
+ }
335
+ _e();
336
+ const ce = W`
337
+ position: fixed;
338
+ display: flex;
339
+ flex-direction: column;
340
+ align-items: center;
341
+ justify-content: center;
342
+ cursor: grab;
343
+ user-select: none;
344
+ color: white;
345
+ font-size: 10px;
346
+ border: 2px solid rgba(255, 255, 255, 0.9);
347
+ backdrop-filter: blur(10px);
348
+ -webkit-backdrop-filter: blur(10px);
349
+ opacity: 0.85;
350
+ &:active {
351
+ cursor: grabbing;
352
+ }
353
+ `, Te = W`
354
+ position: fixed;
355
+ inset: 0;
356
+ z-index: 40;
357
+ `, xe = W`
358
+ position: fixed;
359
+ z-index: 50;
360
+ background-color: #ffffff;
361
+ border-radius: 8px;
362
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
363
+ border: 1px solid rgba(0, 0, 0, 0.1);
364
+ padding: 8px 0;
365
+ min-width: 144px;
366
+ `, Re = W`
367
+ width: 100%;
368
+ padding: 8px 16px;
369
+ display: flex;
370
+ align-items: center;
371
+ gap: 12px;
372
+ font-size: 14px;
373
+ color: #333;
374
+ cursor: pointer;
375
+ background: none;
376
+ border: none;
377
+ text-align: left;
378
+ &:hover {
379
+ background-color: rgba(0, 0, 0, 0.05);
380
+ }
381
+ `, we = 80, ye = 32, ke = 12, je = ({
382
+ extraMenuItems: t,
383
+ onClick: n,
384
+ storageKey: s = "floating-ball-position",
385
+ defaultPosition: b = { x: 16, y: 16 },
386
+ width: c = we,
387
+ height: o = ye,
388
+ borderRadius: l = ke,
389
+ bgColor: u = "rgb(34, 139, 34)",
390
+ className: p = "",
391
+ zIndex: E = 2999,
392
+ versionInfo: f
393
+ }) => {
394
+ const [m, x] = re(() => {
395
+ const a = localStorage.getItem(s);
396
+ if (a)
397
+ try {
398
+ return JSON.parse(a);
399
+ } catch {
400
+ return b;
401
+ }
402
+ return b;
403
+ }), [D, N] = re(!1), _ = O(!1), k = O({ x: 0, y: 0 }), L = O(m), h = O(null), z = O(!1), S = O(!1), X = O(null), C = O(null);
404
+ te(() => {
405
+ L.current = m;
406
+ }, [m]);
407
+ const M = A((a, v) => {
408
+ _.current = !1, k.current = { x: a, y: v };
409
+ }, []), F = A(
410
+ (a, v) => {
411
+ const R = a - k.current.x, w = v - k.current.y;
412
+ if ((Math.abs(R) > 5 || Math.abs(w) > 5) && (_.current = !0), _.current) {
413
+ const e = Math.max(0, Math.min(window.innerWidth - c, L.current.x + R)), r = Math.max(0, Math.min(window.innerHeight - o, L.current.y + w));
414
+ x({ x: e, y: r }), k.current = { x: a, y: v };
415
+ }
416
+ },
417
+ [c, o]
418
+ ), j = A(() => {
419
+ _.current && localStorage.setItem(s, JSON.stringify(L.current));
420
+ }, [s]), V = A(
421
+ (a) => {
422
+ if (a.button !== 0) return;
423
+ M(a.clientX, a.clientY);
424
+ const v = (w) => {
425
+ F(w.clientX, w.clientY);
426
+ }, R = () => {
427
+ j(), _.current = !1, document.removeEventListener("mousemove", v), document.removeEventListener("mouseup", R);
428
+ };
429
+ document.addEventListener("mousemove", v), document.addEventListener("mouseup", R);
430
+ },
431
+ [M, F, j]
432
+ ), Z = A(() => {
433
+ z.current || n !== !1 && (t && t.length > 0 ? (h.current && clearTimeout(h.current), h.current = setTimeout(() => {
434
+ h.current = null, !_.current && !S.current && (n ? n() : window.location.reload());
435
+ }, 300)) : _.current || (n ? n() : window.location.reload()));
436
+ }, [n, t]), Y = A(() => {
437
+ h.current && (clearTimeout(h.current), h.current = null), !_.current && t && t.length > 0 && N(!0), S.current = !0, setTimeout(() => {
438
+ S.current = !1;
439
+ }, 400);
440
+ }, [t]), G = A(
441
+ (a) => {
442
+ a.stopPropagation(), V(a);
443
+ },
444
+ [V]
445
+ );
446
+ te(() => {
447
+ const a = X.current;
448
+ if (!a) return;
449
+ const v = (r) => {
450
+ r.touches.length === 1 && M(r.touches[0].clientX, r.touches[0].clientY);
451
+ }, R = (r) => {
452
+ r.touches.length === 1 && (F(r.touches[0].clientX, r.touches[0].clientY), _.current && r.preventDefault());
453
+ }, w = (r) => {
454
+ if (j(), !_.current) {
455
+ const i = Date.now(), d = C.current;
456
+ if (d && i - d.time < 300 && Math.abs(m.x - d.x) < 10 && Math.abs(m.y - d.y) < 10) {
457
+ C.current = null, h.current && (clearTimeout(h.current), h.current = null), t && t.length > 0 && N(!0), S.current = !0, setTimeout(() => {
458
+ S.current = !1;
459
+ }, 400), z.current = !0;
460
+ return;
461
+ }
462
+ C.current = { time: i, x: m.x, y: m.y }, h.current && clearTimeout(h.current), t && t.length > 0 ? h.current = setTimeout(() => {
463
+ h.current = null, C.current = null, S.current || (n ? n() : window.location.reload());
464
+ }, 300) : n ? n() : window.location.reload(), z.current = !0;
465
+ }
466
+ }, e = () => {
467
+ Y();
468
+ };
469
+ return a.addEventListener("touchstart", v, { passive: !1 }), a.addEventListener("touchmove", R, { passive: !1 }), a.addEventListener("touchend", w), a.addEventListener("dblclick", e), () => {
470
+ a.removeEventListener("touchstart", v), a.removeEventListener("touchmove", R), a.removeEventListener("touchend", w), a.removeEventListener("dblclick", e);
471
+ };
472
+ }, [M, F, j, Y]);
473
+ const $ = [...[
474
+ { label: "刷新", icon: "🔄", action: () => window.location.reload() },
475
+ { label: "回到首页", icon: "🏠", action: () => {
476
+ window.location.href = "/";
477
+ } }
478
+ ], ...t || []], B = A(() => {
479
+ N(!1);
480
+ }, []);
481
+ return /* @__PURE__ */ T.jsxs(T.Fragment, { children: [
482
+ /* @__PURE__ */ T.jsxs(
483
+ "div",
484
+ {
485
+ ref: X,
486
+ className: `${ce} ${p}`.trim() || ce,
487
+ style: {
488
+ left: m.x,
489
+ top: m.y,
490
+ width: c,
491
+ height: o,
492
+ borderRadius: l,
493
+ zIndex: E,
494
+ backgroundColor: u
495
+ },
496
+ onMouseDown: G,
497
+ onClick: Z,
498
+ children: [
499
+ (f == null ? void 0 : f.buildTime) && /* @__PURE__ */ T.jsx("span", { style: { opacity: 0.8, lineHeight: 1.1, textAlign: "center", margin: 0 }, children: f.buildTime }),
500
+ (f == null ? void 0 : f.version) && /* @__PURE__ */ T.jsxs("span", { style: { opacity: 0.8, lineHeight: 1.1, textAlign: "center", margin: 0, fontWeight: "bold" }, children: [
501
+ "v",
502
+ f.version
503
+ ] })
504
+ ]
505
+ }
506
+ ),
507
+ D && $.length > 0 && /* @__PURE__ */ T.jsxs(T.Fragment, { children: [
508
+ /* @__PURE__ */ T.jsx("div", { className: Te, onClick: B }),
509
+ /* @__PURE__ */ T.jsx(
510
+ "div",
511
+ {
512
+ className: xe,
513
+ style: {
514
+ left: Math.max(16, Math.min(m.x, window.innerWidth - 160)),
515
+ top: m.y + o + 8
516
+ },
517
+ children: $.map((a, v) => /* @__PURE__ */ T.jsxs(
518
+ "button",
519
+ {
520
+ className: Re,
521
+ onClick: () => {
522
+ N(!1), a.action();
523
+ },
524
+ children: [
525
+ a.icon && /* @__PURE__ */ T.jsx("span", { children: a.icon }),
526
+ /* @__PURE__ */ T.jsx("span", { children: a.label })
527
+ ]
528
+ },
529
+ `${a.label}-${v}`
530
+ ))
531
+ }
532
+ )
533
+ ] })
534
+ ] });
535
+ };
536
+ export {
537
+ je as default
538
+ };
@@ -0,0 +1,2 @@
1
+ export { default } from './FloatingBall';
2
+ export type { FloatingBallProps, MenuItem, Position, VersionInfo } from '../types';
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@beforegolive/floating-ball",
3
- "version": "0.1.19",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
8
11
  "exports": {
9
12
  ".": {
10
13
  "types": "./dist/index.d.ts",
@@ -1,21 +0,0 @@
1
- # floating-ball-publish
2
-
3
- 发布 @beforegolive/floating-ball 包的完整流程。
4
-
5
- ## 流程
6
- 1. 检查代码是否有未提交的变动(git status)
7
- 2. 如有变动:
8
- - 提交 commit
9
- - 升级 patch 版本(npm version patch)
10
- 3. 构建(npm run build)
11
- 4. 发布到 npm(npm publish)
12
- 5. 推送到远端(git push)
13
-
14
- ## 使用
15
- ```
16
- /floating-ball-publish
17
- ```
18
-
19
- ## 注意事项
20
- - 需要在 floating-ball 项目目录下执行
21
- - npm 已配置 registry 和 authToken
package/index.html DELETED
@@ -1,12 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Floating Ball Demo</title>
7
- </head>
8
- <body>
9
- <div id="root"></div>
10
- <script type="module" src="/src/demo.tsx"></script>
11
- </body>
12
- </html>
@@ -1,357 +0,0 @@
1
- import { useState, useRef, useEffect, useCallback } from "react";
2
- import { css, setup } from "goober";
3
- import type { FloatingBallProps, MenuItem, Position, VersionInfo } from "../types";
4
-
5
- // 初始化 goober
6
- setup(css);
7
-
8
- // 样式定义
9
- const ballStyle = css`
10
- position: fixed;
11
- display: flex;
12
- flex-direction: column;
13
- align-items: center;
14
- justify-content: center;
15
- cursor: grab;
16
- user-select: none;
17
- color: white;
18
- font-size: 10px;
19
- border: 2px solid rgba(255, 255, 255, 0.9);
20
- backdrop-filter: blur(10px);
21
- -webkit-backdrop-filter: blur(10px);
22
- opacity: 0.85;
23
- &:active {
24
- cursor: grabbing;
25
- }
26
- `;
27
-
28
- const menuOverlayStyle = css`
29
- position: fixed;
30
- inset: 0;
31
- z-index: 40;
32
- `;
33
-
34
- const menuStyle = css`
35
- position: fixed;
36
- z-index: 50;
37
- background-color: #ffffff;
38
- border-radius: 8px;
39
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
40
- border: 1px solid rgba(0, 0, 0, 0.1);
41
- padding: 8px 0;
42
- min-width: 144px;
43
- `;
44
-
45
- const menuItemStyle = css`
46
- width: 100%;
47
- padding: 8px 16px;
48
- display: flex;
49
- align-items: center;
50
- gap: 12px;
51
- font-size: 14px;
52
- color: #333;
53
- cursor: pointer;
54
- background: none;
55
- border: none;
56
- text-align: left;
57
- &:hover {
58
- background-color: rgba(0, 0, 0, 0.05);
59
- }
60
- `;
61
-
62
- interface InternalProps extends FloatingBallProps {
63
- versionInfo?: VersionInfo;
64
- }
65
-
66
- const DEFAULT_WIDTH = 80;
67
- const DEFAULT_HEIGHT = 32;
68
- const DEFAULT_BORDER_RADIUS = 12;
69
-
70
- const FloatingBall = ({
71
- extraMenuItems,
72
- onClick,
73
- storageKey = "floating-ball-position",
74
- defaultPosition = { x: 16, y: 16 },
75
- width = DEFAULT_WIDTH,
76
- height = DEFAULT_HEIGHT,
77
- borderRadius = DEFAULT_BORDER_RADIUS,
78
- bgColor = "rgb(34, 139, 34)",
79
- className = "",
80
- zIndex = 2999,
81
- versionInfo,
82
- }: InternalProps) => {
83
- const [position, setPosition] = useState<Position>(() => {
84
- const saved = localStorage.getItem(storageKey);
85
- if (saved) {
86
- try {
87
- return JSON.parse(saved);
88
- } catch {
89
- return defaultPosition;
90
- }
91
- }
92
- return defaultPosition;
93
- });
94
-
95
- const [isMenuOpen, setIsMenuOpen] = useState(false);
96
- const isDraggingRef = useRef(false);
97
- const dragStartRef = useRef({ x: 0, y: 0 });
98
- const positionRef = useRef(position);
99
- const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
100
- const touchHandledRef = useRef(false);
101
- const doubleClickFiredRef = useRef(false);
102
- const ballRef = useRef<HTMLDivElement>(null);
103
- const lastTapRef = useRef<{ time: number; x: number; y: number } | null>(null);
104
-
105
- useEffect(() => {
106
- positionRef.current = position;
107
- }, [position]);
108
-
109
- const handleDragStart = useCallback((clientX: number, clientY: number) => {
110
- isDraggingRef.current = false;
111
- dragStartRef.current = { x: clientX, y: clientY };
112
- }, []);
113
-
114
- const handleDragMove = useCallback(
115
- (clientX: number, clientY: number) => {
116
- const dx = clientX - dragStartRef.current.x;
117
- const dy = clientY - dragStartRef.current.y;
118
- if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
119
- isDraggingRef.current = true;
120
- }
121
- if (isDraggingRef.current) {
122
- const newX = Math.max(0, Math.min(window.innerWidth - width, positionRef.current.x + dx));
123
- const newY = Math.max(0, Math.min(window.innerHeight - height, positionRef.current.y + dy));
124
- setPosition({ x: newX, y: newY });
125
- dragStartRef.current = { x: clientX, y: clientY };
126
- }
127
- },
128
- [width, height]
129
- );
130
-
131
- const handleDragEnd = useCallback(() => {
132
- if (isDraggingRef.current) {
133
- localStorage.setItem(storageKey, JSON.stringify(positionRef.current));
134
- }
135
- }, [storageKey]);
136
-
137
- const handleMouseDown = useCallback(
138
- (e: React.MouseEvent) => {
139
- if (e.button !== 0) return;
140
- handleDragStart(e.clientX, e.clientY);
141
-
142
- const handleMouseMove = (moveEvent: MouseEvent) => {
143
- handleDragMove(moveEvent.clientX, moveEvent.clientY);
144
- };
145
-
146
- const handleMouseUp = () => {
147
- handleDragEnd();
148
- isDraggingRef.current = false;
149
- document.removeEventListener("mousemove", handleMouseMove);
150
- document.removeEventListener("mouseup", handleMouseUp);
151
- };
152
-
153
- document.addEventListener("mousemove", handleMouseMove);
154
- document.addEventListener("mouseup", handleMouseUp);
155
- },
156
- [handleDragStart, handleDragMove, handleDragEnd]
157
- );
158
-
159
- const handleClick = useCallback(() => {
160
- if (touchHandledRef.current) {
161
- return;
162
- }
163
-
164
- if (onClick === false) return;
165
-
166
- if (extraMenuItems && extraMenuItems.length > 0) {
167
- if (clickTimerRef.current) {
168
- clearTimeout(clickTimerRef.current);
169
- }
170
-
171
- clickTimerRef.current = setTimeout(() => {
172
- clickTimerRef.current = null;
173
- if (!isDraggingRef.current && !doubleClickFiredRef.current) {
174
- onClick ? onClick() : window.location.reload();
175
- }
176
- }, 300);
177
- } else {
178
- if (!isDraggingRef.current) {
179
- onClick ? onClick() : window.location.reload();
180
- }
181
- }
182
- }, [onClick, extraMenuItems]);
183
-
184
- const handleDoubleClick = useCallback(() => {
185
- if (clickTimerRef.current) {
186
- clearTimeout(clickTimerRef.current);
187
- clickTimerRef.current = null;
188
- }
189
- if (!isDraggingRef.current && extraMenuItems && extraMenuItems.length > 0) {
190
- setIsMenuOpen(true);
191
- }
192
- doubleClickFiredRef.current = true;
193
- setTimeout(() => {
194
- doubleClickFiredRef.current = false;
195
- }, 400);
196
- }, [extraMenuItems]);
197
-
198
- const handleMouseDownBall = useCallback(
199
- (e: React.MouseEvent) => {
200
- e.stopPropagation();
201
- handleMouseDown(e);
202
- },
203
- [handleMouseDown]
204
- );
205
-
206
- useEffect(() => {
207
- const ball = ballRef.current;
208
- if (!ball) return;
209
-
210
- const onTouchStart = (e: TouchEvent) => {
211
- if (e.touches.length !== 1) return;
212
- handleDragStart(e.touches[0].clientX, e.touches[0].clientY);
213
- };
214
- const onTouchMove = (e: TouchEvent) => {
215
- if (e.touches.length !== 1) return;
216
- handleDragMove(e.touches[0].clientX, e.touches[0].clientY);
217
- if (isDraggingRef.current) {
218
- e.preventDefault();
219
- }
220
- };
221
- const onTouchEnd = (_e: TouchEvent) => {
222
- handleDragEnd();
223
- if (!isDraggingRef.current) {
224
- const now = Date.now();
225
- const lastTap = lastTapRef.current;
226
- const isMobileDoubleTap =
227
- lastTap &&
228
- now - lastTap.time < 300 &&
229
- Math.abs(position.x - lastTap.x) < 10 &&
230
- Math.abs(position.y - lastTap.y) < 10;
231
-
232
- if (isMobileDoubleTap) {
233
- lastTapRef.current = null;
234
- if (clickTimerRef.current) {
235
- clearTimeout(clickTimerRef.current);
236
- clickTimerRef.current = null;
237
- }
238
- if (extraMenuItems && extraMenuItems.length > 0) {
239
- setIsMenuOpen(true);
240
- }
241
- doubleClickFiredRef.current = true;
242
- setTimeout(() => {
243
- doubleClickFiredRef.current = false;
244
- }, 400);
245
- touchHandledRef.current = true;
246
- return;
247
- }
248
-
249
- lastTapRef.current = { time: now, x: position.x, y: position.y };
250
-
251
- if (clickTimerRef.current) {
252
- clearTimeout(clickTimerRef.current);
253
- }
254
-
255
- if (extraMenuItems && extraMenuItems.length > 0) {
256
- clickTimerRef.current = setTimeout(() => {
257
- clickTimerRef.current = null;
258
- lastTapRef.current = null;
259
- if (!doubleClickFiredRef.current) {
260
- onClick ? onClick() : window.location.reload();
261
- }
262
- }, 300);
263
- } else {
264
- onClick ? onClick() : window.location.reload();
265
- }
266
-
267
- touchHandledRef.current = true;
268
- }
269
- };
270
- const onDblClick = () => {
271
- handleDoubleClick();
272
- };
273
-
274
- ball.addEventListener("touchstart", onTouchStart, { passive: false });
275
- ball.addEventListener("touchmove", onTouchMove, { passive: false });
276
- ball.addEventListener("touchend", onTouchEnd);
277
- ball.addEventListener("dblclick", onDblClick);
278
-
279
- return () => {
280
- ball.removeEventListener("touchstart", onTouchStart);
281
- ball.removeEventListener("touchmove", onTouchMove);
282
- ball.removeEventListener("touchend", onTouchEnd);
283
- ball.removeEventListener("dblclick", onDblClick);
284
- };
285
- }, [handleDragStart, handleDragMove, handleDragEnd, handleDoubleClick]);
286
-
287
- const defaultMenuItems: MenuItem[] = [
288
- { label: "刷新", icon: "🔄", action: () => window.location.reload() },
289
- { label: "回到首页", icon: "🏠", action: () => { window.location.href = "/"; } },
290
- ];
291
- const menuItems: MenuItem[] = [...defaultMenuItems, ...(extraMenuItems || [])];
292
-
293
- const closeMenu = useCallback(() => {
294
- setIsMenuOpen(false);
295
- }, []);
296
-
297
- return (
298
- <>
299
- <div
300
- ref={ballRef}
301
- className={`${ballStyle} ${className}`.trim() || ballStyle}
302
- style={{
303
- left: position.x,
304
- top: position.y,
305
- width,
306
- height,
307
- borderRadius,
308
- zIndex,
309
- backgroundColor: bgColor,
310
- }}
311
- onMouseDown={handleMouseDownBall}
312
- onClick={handleClick}
313
- >
314
- {versionInfo?.buildTime && (
315
- <span style={{ opacity: 0.8, lineHeight: 1.1, textAlign: "center", margin: 0 }}>
316
- {versionInfo.buildTime}
317
- </span>
318
- )}
319
- {versionInfo?.version && (
320
- <span style={{ opacity: 0.8, lineHeight: 1.1, textAlign: "center", margin: 0, fontWeight: "bold" }}>
321
- v{versionInfo.version}
322
- </span>
323
- )}
324
- </div>
325
-
326
- {isMenuOpen && menuItems.length > 0 && (
327
- <>
328
- <div className={menuOverlayStyle} onClick={closeMenu} />
329
- <div
330
- className={menuStyle}
331
- style={{
332
- left: Math.max(16, Math.min(position.x, window.innerWidth - 160)),
333
- top: position.y + height + 8,
334
- }}
335
- >
336
- {menuItems.map((item, index) => (
337
- <button
338
- key={`${item.label}-${index}`}
339
- className={menuItemStyle}
340
- onClick={() => {
341
- setIsMenuOpen(false);
342
- item.action();
343
- }}
344
- >
345
- {item.icon && <span>{item.icon}</span>}
346
- <span>{item.label}</span>
347
- </button>
348
- ))}
349
- </div>
350
- </>
351
- )}
352
- </>
353
- );
354
- };
355
-
356
- export default FloatingBall;
357
- export type { FloatingBallProps, MenuItem, Position, VersionInfo };
package/src/demo.tsx DELETED
@@ -1,64 +0,0 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import { BrowserRouter, useNavigate } from 'react-router-dom';
4
- import FloatingBall from './FloatingBall';
5
-
6
- const VERSION = '1.0.0';
7
- const now = new Date();
8
- const BUILD_TIME = `${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}(${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')})`;
9
-
10
- function DemoApp() {
11
- const navigate = useNavigate();
12
-
13
- const extraMenuItems = [
14
- {
15
- label: '打开笔记页面',
16
- icon: '📝',
17
- action: () => {
18
- navigate('/notes');
19
- },
20
- },
21
- ];
22
-
23
- return (
24
- <div style={{ padding: '20px', fontFamily: 'system-ui' }}>
25
- <h1>Floating Ball Demo</h1>
26
- <p>点击悬浮球:刷新页面</p>
27
- <p>双击悬浮球:打开菜单</p>
28
- <p>拖拽悬浮球:移动位置</p>
29
-
30
- <div style={{ marginTop: '100px' }}>
31
- <h2>页面内容</h2>
32
- <p>这是演示页面,用于测试悬浮球组件。</p>
33
- </div>
34
-
35
- <FloatingBall
36
- extraMenuItems={extraMenuItems}
37
- storageKey="floating-ball-position"
38
- defaultPosition={{ x: 16, y: 16 }}
39
- width={80}
40
- height={32}
41
- borderRadius={12}
42
- bgColor="rgb(34, 139, 34)"
43
- versionInfo={{
44
- version: VERSION,
45
- buildTime: BUILD_TIME,
46
- }}
47
- />
48
- </div>
49
- );
50
- }
51
-
52
- function App() {
53
- return (
54
- <BrowserRouter>
55
- <DemoApp />
56
- </BrowserRouter>
57
- );
58
- }
59
-
60
- ReactDOM.createRoot(document.getElementById('root')!).render(
61
- <React.StrictMode>
62
- <App />
63
- </React.StrictMode>
64
- );
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "useDefineForClassFields": true,
5
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
- "module": "ESNext",
7
- "skipLibCheck": true,
8
- "moduleResolution": "bundler",
9
- "resolveJsonModule": true,
10
- "isolatedModules": true,
11
- "jsx": "react-jsx",
12
- "strict": true,
13
- "noUnusedLocals": true,
14
- "noUnusedParameters": true,
15
- "noFallthroughCasesInSwitch": true,
16
- "declaration": true,
17
- "declarationDir": "./dist",
18
- "noEmit": false
19
- },
20
- "include": ["src", "types"]
21
- }
package/types/index.ts DELETED
@@ -1,44 +0,0 @@
1
- export interface MenuItem {
2
- /** 显示的标签 */
3
- label: string;
4
- /** 图标(emoji 或 HTML) */
5
- icon?: string;
6
- /** 点击后的行为 */
7
- action: () => void;
8
- }
9
-
10
- export interface FloatingBallProps {
11
- /** 附加菜单项(常驻项:刷新、回到首页) */
12
- extraMenuItems?: MenuItem[];
13
- /** 刷新菜单项的点击行为 */
14
- onRefresh?: () => void;
15
- /** 回到首页菜单项的点击行为 */
16
- onGoHome?: () => void;
17
- /** 单击行为(默认刷新页面),设为 false 禁用单击 */
18
- onClick?: (() => void) | false;
19
- /** localStorage 存储位置持久化的 key */
20
- storageKey?: string;
21
- /** 默认位置 */
22
- defaultPosition?: Position;
23
- /** 悬浮球尺寸 */
24
- width?: number;
25
- height?: number;
26
- /** 圆角 */
27
- borderRadius?: number;
28
- /** 背景色,默认蓝色 */
29
- bgColor?: string;
30
- /** 样式类名 */
31
- className?: string;
32
- /** 层级 */
33
- zIndex?: number;
34
- }
35
-
36
- export interface Position {
37
- x: number;
38
- y: number;
39
- }
40
-
41
- export interface VersionInfo {
42
- version?: string;
43
- buildTime?: string;
44
- }
package/vite.config.ts DELETED
@@ -1,31 +0,0 @@
1
- import { defineConfig } from 'vite'
2
- import react from '@vitejs/plugin-react'
3
- import dts from 'vite-plugin-dts'
4
- import { resolve } from 'path'
5
-
6
- export default defineConfig({
7
- plugins: [react(), dts({
8
- include: 'src/index.ts',
9
- })],
10
- build: {
11
- lib: {
12
- entry: resolve(__dirname, 'src/index.ts'),
13
- name: 'FloatingBall',
14
- formats: ['es'],
15
- fileName: 'index',
16
- },
17
- rollupOptions: {
18
- external: ['react', 'react-dom', 'react-router-dom'],
19
- output: {
20
- globals: {
21
- react: 'React',
22
- 'react-dom': 'ReactDOM',
23
- 'react-router-dom': 'ReactRouterDOM',
24
- },
25
- },
26
- },
27
- },
28
- server: {
29
- open: true,
30
- },
31
- })
File without changes