@rencar-dev/feature-modules-public 0.0.7
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 +215 -0
- package/dist/hooks/index.cjs +51 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +15 -0
- package/dist/hooks/index.d.ts +15 -0
- package/dist/hooks/index.js +24 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/presence/index.cjs +459 -0
- package/dist/presence/index.cjs.map +1 -0
- package/dist/presence/index.d.cts +231 -0
- package/dist/presence/index.d.ts +231 -0
- package/dist/presence/index.js +425 -0
- package/dist/presence/index.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// src/presence/core/socket.ts
|
|
2
|
+
import { io } from "socket.io-client";
|
|
3
|
+
var config = null;
|
|
4
|
+
var sharedSocket = null;
|
|
5
|
+
function initPresenceSocket(presenceConfig) {
|
|
6
|
+
config = presenceConfig;
|
|
7
|
+
if (sharedSocket) {
|
|
8
|
+
sharedSocket.disconnect();
|
|
9
|
+
sharedSocket = null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function getPresenceSocket() {
|
|
13
|
+
if (!config) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"[presence-module] Socket not initialized. Call initPresenceSocket() first."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (sharedSocket && sharedSocket.connected) {
|
|
19
|
+
return sharedSocket;
|
|
20
|
+
}
|
|
21
|
+
if (!sharedSocket) {
|
|
22
|
+
sharedSocket = io(config.url, {
|
|
23
|
+
transports: config.transports ?? ["websocket"]
|
|
24
|
+
});
|
|
25
|
+
} else if (!sharedSocket.connected) {
|
|
26
|
+
sharedSocket.connect();
|
|
27
|
+
}
|
|
28
|
+
return sharedSocket;
|
|
29
|
+
}
|
|
30
|
+
function disconnectPresenceSocket() {
|
|
31
|
+
if (sharedSocket) {
|
|
32
|
+
sharedSocket.disconnect();
|
|
33
|
+
sharedSocket = null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function isPresenceSocketInitialized() {
|
|
37
|
+
return config !== null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/presence/hooks/usePresence.ts
|
|
41
|
+
import { useEffect, useState, useCallback } from "react";
|
|
42
|
+
var DEFAULT_HEARTBEAT_INTERVAL = 10 * 60 * 1e3;
|
|
43
|
+
function usePresence(options) {
|
|
44
|
+
const {
|
|
45
|
+
roomId,
|
|
46
|
+
currentUser,
|
|
47
|
+
enabled = true,
|
|
48
|
+
heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL
|
|
49
|
+
} = options;
|
|
50
|
+
const [users, setUsers] = useState([]);
|
|
51
|
+
const normalizeUsers2 = useCallback((incoming) => {
|
|
52
|
+
if (Array.isArray(incoming)) {
|
|
53
|
+
return incoming;
|
|
54
|
+
}
|
|
55
|
+
if (incoming && typeof incoming === "object" && Array.isArray(incoming.users)) {
|
|
56
|
+
return incoming.users;
|
|
57
|
+
}
|
|
58
|
+
return [];
|
|
59
|
+
}, []);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!enabled) {
|
|
62
|
+
setUsers([]);
|
|
63
|
+
}
|
|
64
|
+
}, [enabled]);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!enabled) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const socket = getPresenceSocket();
|
|
70
|
+
const joinRoom = () => {
|
|
71
|
+
socket.emit("presence:join", { roomId, user: currentUser });
|
|
72
|
+
};
|
|
73
|
+
if (socket.connected) {
|
|
74
|
+
joinRoom();
|
|
75
|
+
}
|
|
76
|
+
socket.on("connect", joinRoom);
|
|
77
|
+
const heartbeat = () => {
|
|
78
|
+
socket.emit("presence:heartbeat", { roomId });
|
|
79
|
+
};
|
|
80
|
+
heartbeat();
|
|
81
|
+
const heartbeatIntervalId = window.setInterval(
|
|
82
|
+
heartbeat,
|
|
83
|
+
heartbeatInterval
|
|
84
|
+
);
|
|
85
|
+
const handlePresenceUpdate = (incoming) => {
|
|
86
|
+
setUsers(normalizeUsers2(incoming));
|
|
87
|
+
};
|
|
88
|
+
socket.on("presence:update", handlePresenceUpdate);
|
|
89
|
+
const handleBeforeUnload = () => {
|
|
90
|
+
socket.emit("presence:leave", { roomId, userId: currentUser.userId });
|
|
91
|
+
};
|
|
92
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
93
|
+
return () => {
|
|
94
|
+
socket.off("connect", joinRoom);
|
|
95
|
+
socket.off("presence:update", handlePresenceUpdate);
|
|
96
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
97
|
+
window.clearInterval(heartbeatIntervalId);
|
|
98
|
+
socket.emit("presence:leave", { roomId, userId: currentUser.userId });
|
|
99
|
+
};
|
|
100
|
+
}, [
|
|
101
|
+
roomId,
|
|
102
|
+
currentUser.userId,
|
|
103
|
+
currentUser.name,
|
|
104
|
+
enabled,
|
|
105
|
+
heartbeatInterval,
|
|
106
|
+
normalizeUsers2
|
|
107
|
+
]);
|
|
108
|
+
return users;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/presence/hooks/usePresenceWatch.ts
|
|
112
|
+
import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
113
|
+
function normalizeUsers(value) {
|
|
114
|
+
if (Array.isArray(value)) return value;
|
|
115
|
+
if (value && typeof value === "object" && Array.isArray(value.users)) {
|
|
116
|
+
return value.users;
|
|
117
|
+
}
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
function usePresenceWatch(options) {
|
|
121
|
+
const { roomIds, enabled = true } = options;
|
|
122
|
+
const [roomsUsers, setRoomsUsers] = useState2(
|
|
123
|
+
{}
|
|
124
|
+
);
|
|
125
|
+
const normalizedRoomIds = useMemo(() => {
|
|
126
|
+
const uniq = Array.from(new Set((roomIds ?? []).filter(Boolean)));
|
|
127
|
+
return uniq;
|
|
128
|
+
}, [roomIds]);
|
|
129
|
+
const watchedRef = useRef(/* @__PURE__ */ new Set());
|
|
130
|
+
useEffect2(() => {
|
|
131
|
+
if (!enabled) {
|
|
132
|
+
setRoomsUsers({});
|
|
133
|
+
watchedRef.current = /* @__PURE__ */ new Set();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const socket = getPresenceSocket();
|
|
137
|
+
const toWatch = normalizedRoomIds.filter((r) => !watchedRef.current.has(r));
|
|
138
|
+
const toUnwatch = Array.from(watchedRef.current).filter(
|
|
139
|
+
(r) => !normalizedRoomIds.includes(r)
|
|
140
|
+
);
|
|
141
|
+
if (toWatch.length > 0) {
|
|
142
|
+
socket.emit("presence:watch", { roomIds: toWatch });
|
|
143
|
+
for (const r of toWatch) watchedRef.current.add(r);
|
|
144
|
+
}
|
|
145
|
+
if (toUnwatch.length > 0) {
|
|
146
|
+
socket.emit("presence:unwatch", { roomIds: toUnwatch });
|
|
147
|
+
for (const r of toUnwatch) watchedRef.current.delete(r);
|
|
148
|
+
setRoomsUsers((prev) => {
|
|
149
|
+
const next = { ...prev };
|
|
150
|
+
for (const r of toUnwatch) delete next[r];
|
|
151
|
+
return next;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}, [enabled, normalizedRoomIds]);
|
|
155
|
+
useEffect2(() => {
|
|
156
|
+
if (!enabled) return;
|
|
157
|
+
const socket = getPresenceSocket();
|
|
158
|
+
const handleSnapshot = (payload) => {
|
|
159
|
+
const rooms = payload?.rooms ?? {};
|
|
160
|
+
setRoomsUsers((prev) => {
|
|
161
|
+
const next = { ...prev };
|
|
162
|
+
for (const [roomId, users] of Object.entries(rooms)) {
|
|
163
|
+
next[roomId] = normalizeUsers(users);
|
|
164
|
+
}
|
|
165
|
+
return next;
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
const handleUpdate = (payload) => {
|
|
169
|
+
if (!payload || typeof payload !== "object") return;
|
|
170
|
+
const { roomId, users } = payload;
|
|
171
|
+
if (!roomId) return;
|
|
172
|
+
if (!watchedRef.current.has(roomId)) return;
|
|
173
|
+
setRoomsUsers((prev) => ({ ...prev, [roomId]: normalizeUsers(users) }));
|
|
174
|
+
};
|
|
175
|
+
socket.on("presence:snapshot", handleSnapshot);
|
|
176
|
+
socket.on("presence:update", handleUpdate);
|
|
177
|
+
const handleConnect = () => {
|
|
178
|
+
const ids = Array.from(watchedRef.current);
|
|
179
|
+
if (ids.length > 0) socket.emit("presence:watch", { roomIds: ids });
|
|
180
|
+
};
|
|
181
|
+
socket.on("connect", handleConnect);
|
|
182
|
+
return () => {
|
|
183
|
+
socket.off("presence:snapshot", handleSnapshot);
|
|
184
|
+
socket.off("presence:update", handleUpdate);
|
|
185
|
+
socket.off("connect", handleConnect);
|
|
186
|
+
const ids = Array.from(watchedRef.current);
|
|
187
|
+
if (ids.length > 0) socket.emit("presence:unwatch", { roomIds: ids });
|
|
188
|
+
watchedRef.current = /* @__PURE__ */ new Set();
|
|
189
|
+
};
|
|
190
|
+
}, [enabled]);
|
|
191
|
+
return roomsUsers;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/presence/headless/usePresenceAvatarsState.ts
|
|
195
|
+
import { useMemo as useMemo2, useState as useState3, useCallback as useCallback3 } from "react";
|
|
196
|
+
function getInitialFromUser(user) {
|
|
197
|
+
const base = (user?.name || user?.userId || "").trim();
|
|
198
|
+
if (!base) return "?";
|
|
199
|
+
const ch = base.charAt(0);
|
|
200
|
+
return /[a-z]/i.test(ch) ? ch.toUpperCase() : ch;
|
|
201
|
+
}
|
|
202
|
+
function usePresenceAvatarsState(options) {
|
|
203
|
+
const { users, maxVisible = 3 } = options;
|
|
204
|
+
const safeUsers = Array.isArray(users) ? users : [];
|
|
205
|
+
const visibleUsers = useMemo2(
|
|
206
|
+
() => safeUsers.slice(0, maxVisible),
|
|
207
|
+
[safeUsers, maxVisible]
|
|
208
|
+
);
|
|
209
|
+
const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);
|
|
210
|
+
const [hoveredIndex, setHoveredIndex] = useState3(null);
|
|
211
|
+
const hoveredUser = hoveredIndex != null ? visibleUsers[hoveredIndex] : null;
|
|
212
|
+
const baseTopZ = visibleUsers.length + 1;
|
|
213
|
+
const getInitial = useCallback3((user) => {
|
|
214
|
+
return getInitialFromUser(user);
|
|
215
|
+
}, []);
|
|
216
|
+
const getZIndex = useCallback3(
|
|
217
|
+
(index, isHovered) => {
|
|
218
|
+
return isHovered ? 100 : baseTopZ - index;
|
|
219
|
+
},
|
|
220
|
+
[baseTopZ]
|
|
221
|
+
);
|
|
222
|
+
return {
|
|
223
|
+
visibleUsers,
|
|
224
|
+
moreCount,
|
|
225
|
+
hoveredUser,
|
|
226
|
+
hoveredIndex,
|
|
227
|
+
setHoveredIndex,
|
|
228
|
+
getInitial,
|
|
229
|
+
getZIndex
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/presence/headless/usePresenceFloatingState.ts
|
|
234
|
+
import { useCallback as useCallback4, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState4 } from "react";
|
|
235
|
+
function usePresenceFloatingState(options) {
|
|
236
|
+
const { users, maxVisible = 8, initialPosition = { top: 166 } } = options;
|
|
237
|
+
const containerRef = useRef2(null);
|
|
238
|
+
const [dragging, setDragging] = useState4(false);
|
|
239
|
+
const [hasDragged, setHasDragged] = useState4(false);
|
|
240
|
+
const [dragOffset, setDragOffset] = useState4({
|
|
241
|
+
x: 0,
|
|
242
|
+
y: 0
|
|
243
|
+
});
|
|
244
|
+
const [position, setPosition] = useState4({
|
|
245
|
+
top: initialPosition.top,
|
|
246
|
+
left: 0
|
|
247
|
+
});
|
|
248
|
+
const [hoveredUser, setHoveredUser] = useState4(null);
|
|
249
|
+
const [tooltipTop, setTooltipTop] = useState4(0);
|
|
250
|
+
const safeUsers = Array.isArray(users) ? users : [];
|
|
251
|
+
const visibleUsers = safeUsers.slice(0, maxVisible);
|
|
252
|
+
const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);
|
|
253
|
+
useEffect3(() => {
|
|
254
|
+
if (!hasDragged) return;
|
|
255
|
+
const el = containerRef.current;
|
|
256
|
+
if (!el) return;
|
|
257
|
+
const adjustPosition = () => {
|
|
258
|
+
const rect = el.getBoundingClientRect();
|
|
259
|
+
const maxLeft = window.innerWidth - rect.width;
|
|
260
|
+
const maxTop = window.innerHeight - rect.height - 8;
|
|
261
|
+
setPosition((prev) => ({
|
|
262
|
+
left: Math.min(Math.max(0, prev.left), Math.max(0, maxLeft)),
|
|
263
|
+
top: Math.min(Math.max(8, prev.top), Math.max(8, maxTop))
|
|
264
|
+
}));
|
|
265
|
+
};
|
|
266
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
267
|
+
adjustPosition();
|
|
268
|
+
});
|
|
269
|
+
resizeObserver.observe(el);
|
|
270
|
+
window.addEventListener("resize", adjustPosition);
|
|
271
|
+
return () => {
|
|
272
|
+
resizeObserver.disconnect();
|
|
273
|
+
window.removeEventListener("resize", adjustPosition);
|
|
274
|
+
};
|
|
275
|
+
}, [hasDragged]);
|
|
276
|
+
const getEventCoordinates = useCallback4(
|
|
277
|
+
(e) => {
|
|
278
|
+
if ("touches" in e && e.touches.length > 0) {
|
|
279
|
+
return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY };
|
|
280
|
+
}
|
|
281
|
+
if ("changedTouches" in e && e.changedTouches.length > 0) {
|
|
282
|
+
return {
|
|
283
|
+
clientX: e.changedTouches[0].clientX,
|
|
284
|
+
clientY: e.changedTouches[0].clientY
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
if ("clientX" in e) {
|
|
288
|
+
return { clientX: e.clientX, clientY: e.clientY };
|
|
289
|
+
}
|
|
290
|
+
return { clientX: 0, clientY: 0 };
|
|
291
|
+
},
|
|
292
|
+
[]
|
|
293
|
+
);
|
|
294
|
+
const onMouseDownHeader = useCallback4(
|
|
295
|
+
(e) => {
|
|
296
|
+
const el = containerRef.current;
|
|
297
|
+
if (!el) return;
|
|
298
|
+
const rect = el.getBoundingClientRect();
|
|
299
|
+
if (!hasDragged) {
|
|
300
|
+
setHasDragged(true);
|
|
301
|
+
setPosition({
|
|
302
|
+
top: rect.top,
|
|
303
|
+
left: rect.left
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
setDragOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
307
|
+
setDragging(true);
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
},
|
|
310
|
+
[hasDragged]
|
|
311
|
+
);
|
|
312
|
+
const onTouchStartHeader = useCallback4(
|
|
313
|
+
(e) => {
|
|
314
|
+
const el = containerRef.current;
|
|
315
|
+
if (!el) return;
|
|
316
|
+
const rect = el.getBoundingClientRect();
|
|
317
|
+
const { clientX, clientY } = getEventCoordinates(e);
|
|
318
|
+
if (!hasDragged) {
|
|
319
|
+
setHasDragged(true);
|
|
320
|
+
setPosition({
|
|
321
|
+
top: rect.top,
|
|
322
|
+
left: rect.left
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
setDragOffset({ x: clientX - rect.left, y: clientY - rect.top });
|
|
326
|
+
setDragging(true);
|
|
327
|
+
},
|
|
328
|
+
[hasDragged, getEventCoordinates]
|
|
329
|
+
);
|
|
330
|
+
useEffect3(() => {
|
|
331
|
+
if (!dragging) return;
|
|
332
|
+
const el = containerRef.current;
|
|
333
|
+
const onMove = (e) => {
|
|
334
|
+
if (!el) return;
|
|
335
|
+
const rect = el.getBoundingClientRect();
|
|
336
|
+
const { clientX, clientY } = getEventCoordinates(e);
|
|
337
|
+
const newLeft = clientX - dragOffset.x;
|
|
338
|
+
const newTop = clientY - dragOffset.y;
|
|
339
|
+
const maxLeft = window.innerWidth - rect.width;
|
|
340
|
+
const maxTop = window.innerHeight - rect.height - 8;
|
|
341
|
+
setPosition({
|
|
342
|
+
left: Math.min(Math.max(0, newLeft), Math.max(0, maxLeft)),
|
|
343
|
+
top: Math.min(Math.max(8, newTop), Math.max(8, maxTop))
|
|
344
|
+
});
|
|
345
|
+
if ("touches" in e) {
|
|
346
|
+
e.preventDefault();
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
const onUp = () => setDragging(false);
|
|
350
|
+
window.addEventListener("mousemove", onMove);
|
|
351
|
+
window.addEventListener("mouseup", onUp, { once: true });
|
|
352
|
+
window.addEventListener("touchmove", onMove, { passive: false });
|
|
353
|
+
window.addEventListener("touchend", onUp, { once: true });
|
|
354
|
+
window.addEventListener("touchcancel", onUp, { once: true });
|
|
355
|
+
return () => {
|
|
356
|
+
window.removeEventListener("mousemove", onMove);
|
|
357
|
+
window.removeEventListener("mouseup", onUp);
|
|
358
|
+
window.removeEventListener("touchmove", onMove);
|
|
359
|
+
window.removeEventListener("touchend", onUp);
|
|
360
|
+
window.removeEventListener("touchcancel", onUp);
|
|
361
|
+
};
|
|
362
|
+
}, [dragging, dragOffset.x, dragOffset.y, getEventCoordinates]);
|
|
363
|
+
const inlineStyle = useMemo3(() => {
|
|
364
|
+
if (hasDragged) {
|
|
365
|
+
return {
|
|
366
|
+
position: "fixed",
|
|
367
|
+
top: position.top,
|
|
368
|
+
left: position.left,
|
|
369
|
+
right: "auto"
|
|
370
|
+
};
|
|
371
|
+
} else {
|
|
372
|
+
return {
|
|
373
|
+
position: "fixed",
|
|
374
|
+
top: position.top,
|
|
375
|
+
right: 0,
|
|
376
|
+
left: "auto"
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}, [position.top, position.left, hasDragged]);
|
|
380
|
+
const onAvatarEnter = useCallback4(
|
|
381
|
+
(e, user) => {
|
|
382
|
+
const container = containerRef.current;
|
|
383
|
+
if (!container) {
|
|
384
|
+
setHoveredUser(user);
|
|
385
|
+
setTooltipTop(0);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const containerRect = container.getBoundingClientRect();
|
|
389
|
+
const targetRect = e.currentTarget.getBoundingClientRect();
|
|
390
|
+
const topWithinContainer = targetRect.top - containerRect.top;
|
|
391
|
+
setHoveredUser(user);
|
|
392
|
+
setTooltipTop(topWithinContainer);
|
|
393
|
+
},
|
|
394
|
+
[]
|
|
395
|
+
);
|
|
396
|
+
const onAvatarLeave = useCallback4(() => {
|
|
397
|
+
setHoveredUser(null);
|
|
398
|
+
}, []);
|
|
399
|
+
return {
|
|
400
|
+
containerRef,
|
|
401
|
+
position,
|
|
402
|
+
isDragging: dragging,
|
|
403
|
+
hasDragged,
|
|
404
|
+
inlineStyle,
|
|
405
|
+
visibleUsers,
|
|
406
|
+
moreCount,
|
|
407
|
+
hoveredUser,
|
|
408
|
+
tooltipTop,
|
|
409
|
+
onMouseDownHeader,
|
|
410
|
+
onTouchStartHeader,
|
|
411
|
+
onAvatarEnter,
|
|
412
|
+
onAvatarLeave
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
export {
|
|
416
|
+
disconnectPresenceSocket,
|
|
417
|
+
getPresenceSocket,
|
|
418
|
+
initPresenceSocket,
|
|
419
|
+
isPresenceSocketInitialized,
|
|
420
|
+
usePresence,
|
|
421
|
+
usePresenceAvatarsState,
|
|
422
|
+
usePresenceFloatingState,
|
|
423
|
+
usePresenceWatch
|
|
424
|
+
};
|
|
425
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/presence/core/socket.ts","../../src/presence/hooks/usePresence.ts","../../src/presence/hooks/usePresenceWatch.ts","../../src/presence/headless/usePresenceAvatarsState.ts","../../src/presence/headless/usePresenceFloatingState.ts"],"sourcesContent":["import { io, Socket } from 'socket.io-client';\nimport type { PresenceConfig } from './types';\n\nlet config: PresenceConfig | null = null;\nlet sharedSocket: Socket | null = null;\n\n/**\n * Initialize the presence socket with configuration\n * Must be called before using any presence hooks\n */\nexport function initPresenceSocket(presenceConfig: PresenceConfig): void {\n config = presenceConfig;\n\n // If socket already exists, disconnect and recreate\n if (sharedSocket) {\n sharedSocket.disconnect();\n sharedSocket = null;\n }\n}\n\n/**\n * Get the shared socket instance\n * @throws Error if socket is not initialized\n */\nexport function getPresenceSocket(): Socket {\n if (!config) {\n throw new Error(\n '[presence-module] Socket not initialized. Call initPresenceSocket() first.',\n );\n }\n\n if (sharedSocket && sharedSocket.connected) {\n return sharedSocket;\n }\n\n if (!sharedSocket) {\n sharedSocket = io(config.url, {\n transports: config.transports ?? ['websocket'],\n });\n } else if (!sharedSocket.connected) {\n sharedSocket.connect();\n }\n\n return sharedSocket;\n}\n\n/**\n * Disconnect and cleanup the socket connection\n */\nexport function disconnectPresenceSocket(): void {\n if (sharedSocket) {\n sharedSocket.disconnect();\n sharedSocket = null;\n }\n}\n\n/**\n * Check if socket is initialized\n */\nexport function isPresenceSocketInitialized(): boolean {\n return config !== null;\n}\n","import { useEffect, useState, useCallback } from 'react';\nimport { getPresenceSocket } from '../core/socket';\nimport type { PresenceUser, UsePresenceOptions } from '../core/types';\n\nconst DEFAULT_HEARTBEAT_INTERVAL = 10 * 60 * 1000; // 10 minutes\n\n/**\n * Hook to join a presence room and track users in that room\n *\n * @param options - Configuration options\n * @returns Array of users currently in the room\n *\n * @example\n * ```tsx\n * const users = usePresence({\n * roomId: 'my-app:page-123',\n * currentUser: { userId: '1', name: 'John' },\n * });\n * ```\n */\nexport function usePresence(options: UsePresenceOptions): PresenceUser[] {\n const {\n roomId,\n currentUser,\n enabled = true,\n heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL,\n } = options;\n\n const [users, setUsers] = useState<PresenceUser[]>([]);\n\n // Normalize users from server response\n const normalizeUsers = useCallback((incoming: unknown): PresenceUser[] => {\n if (Array.isArray(incoming)) {\n return incoming as PresenceUser[];\n }\n if (\n incoming &&\n typeof incoming === 'object' &&\n Array.isArray((incoming as { users?: unknown }).users)\n ) {\n return (incoming as { users: unknown[] }).users as PresenceUser[];\n }\n return [];\n }, []);\n\n // Clear users when disabled\n useEffect(() => {\n if (!enabled) {\n setUsers([]);\n }\n }, [enabled]);\n\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const socket = getPresenceSocket();\n\n const joinRoom = () => {\n socket.emit('presence:join', { roomId, user: currentUser });\n };\n\n // Join immediately if connected, otherwise wait for connect event\n if (socket.connected) {\n joinRoom();\n }\n\n socket.on('connect', joinRoom);\n\n // Heartbeat to extend TTL\n const heartbeat = () => {\n socket.emit('presence:heartbeat', { roomId });\n };\n heartbeat();\n const heartbeatIntervalId = window.setInterval(\n heartbeat,\n heartbeatInterval,\n );\n\n // Handle presence updates from server\n const handlePresenceUpdate = (incoming: unknown) => {\n setUsers(normalizeUsers(incoming));\n };\n socket.on('presence:update', handlePresenceUpdate);\n\n // Notify server when tab/window closes\n const handleBeforeUnload = () => {\n socket.emit('presence:leave', { roomId, userId: currentUser.userId });\n };\n window.addEventListener('beforeunload', handleBeforeUnload);\n\n return () => {\n socket.off('connect', joinRoom);\n socket.off('presence:update', handlePresenceUpdate);\n window.removeEventListener('beforeunload', handleBeforeUnload);\n window.clearInterval(heartbeatIntervalId);\n socket.emit('presence:leave', { roomId, userId: currentUser.userId });\n };\n }, [\n roomId,\n currentUser.userId,\n currentUser.name,\n enabled,\n heartbeatInterval,\n normalizeUsers,\n ]);\n\n return users;\n}\n","import { useEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport { getPresenceSocket } from '../core/socket';\nimport type { PresenceUser, UsePresenceWatchOptions } from '../core/types';\n\ntype SnapshotPayload = {\n rooms?: Record<string, unknown>;\n};\n\ntype UpdatePayload = {\n roomId?: string;\n users?: unknown;\n};\n\nfunction normalizeUsers(value: unknown): PresenceUser[] {\n if (Array.isArray(value)) return value as PresenceUser[];\n if (\n value &&\n typeof value === 'object' &&\n Array.isArray((value as { users?: unknown }).users)\n ) {\n return (value as { users: unknown[] }).users as PresenceUser[];\n }\n return [];\n}\n\n/**\n * Hook to watch multiple rooms without joining them\n * (read-only observation, current user is not added to the rooms)\n *\n * @param options - Configuration options\n * @returns Map of roomId to array of users in that room\n *\n * @example\n * ```tsx\n * const roomsUsers = usePresenceWatch({\n * roomIds: ['room-1', 'room-2', 'room-3'],\n * });\n * // roomsUsers = { 'room-1': [...], 'room-2': [...], ... }\n * ```\n */\nexport function usePresenceWatch(\n options: UsePresenceWatchOptions,\n): Record<string, PresenceUser[]> {\n const { roomIds, enabled = true } = options;\n\n const [roomsUsers, setRoomsUsers] = useState<Record<string, PresenceUser[]>>(\n {},\n );\n\n const normalizedRoomIds = useMemo(() => {\n const uniq = Array.from(new Set((roomIds ?? []).filter(Boolean)));\n return uniq;\n }, [roomIds]);\n\n const watchedRef = useRef<Set<string>>(new Set());\n\n // Handle watch/unwatch based on roomIds changes\n useEffect(() => {\n if (!enabled) {\n setRoomsUsers({});\n watchedRef.current = new Set();\n return;\n }\n\n const socket = getPresenceSocket();\n\n const toWatch = normalizedRoomIds.filter((r) => !watchedRef.current.has(r));\n const toUnwatch = Array.from(watchedRef.current).filter(\n (r) => !normalizedRoomIds.includes(r),\n );\n\n if (toWatch.length > 0) {\n socket.emit('presence:watch', { roomIds: toWatch });\n for (const r of toWatch) watchedRef.current.add(r);\n }\n\n if (toUnwatch.length > 0) {\n socket.emit('presence:unwatch', { roomIds: toUnwatch });\n for (const r of toUnwatch) watchedRef.current.delete(r);\n setRoomsUsers((prev) => {\n const next = { ...prev };\n for (const r of toUnwatch) delete next[r];\n return next;\n });\n }\n }, [enabled, normalizedRoomIds]);\n\n // Handle socket events\n useEffect(() => {\n if (!enabled) return;\n\n const socket = getPresenceSocket();\n\n const handleSnapshot = (payload: SnapshotPayload) => {\n const rooms = payload?.rooms ?? {};\n setRoomsUsers((prev) => {\n const next = { ...prev };\n for (const [roomId, users] of Object.entries(rooms)) {\n next[roomId] = normalizeUsers(users);\n }\n return next;\n });\n };\n\n const handleUpdate = (payload: unknown) => {\n if (!payload || typeof payload !== 'object') return;\n const { roomId, users } = payload as UpdatePayload;\n if (!roomId) return;\n if (!watchedRef.current.has(roomId)) return;\n setRoomsUsers((prev) => ({ ...prev, [roomId]: normalizeUsers(users) }));\n };\n\n socket.on('presence:snapshot', handleSnapshot);\n socket.on('presence:update', handleUpdate);\n\n // Re-watch on reconnect\n const handleConnect = () => {\n const ids = Array.from(watchedRef.current);\n if (ids.length > 0) socket.emit('presence:watch', { roomIds: ids });\n };\n socket.on('connect', handleConnect);\n\n return () => {\n socket.off('presence:snapshot', handleSnapshot);\n socket.off('presence:update', handleUpdate);\n socket.off('connect', handleConnect);\n\n const ids = Array.from(watchedRef.current);\n if (ids.length > 0) socket.emit('presence:unwatch', { roomIds: ids });\n watchedRef.current = new Set();\n };\n }, [enabled]);\n\n return roomsUsers;\n}\n","import { useMemo, useState, useCallback } from 'react';\nimport type {\n PresenceUser,\n UsePresenceAvatarsStateOptions,\n UsePresenceAvatarsStateReturn,\n} from '../core/types';\n\n/**\n * Extract initial character from user name/id\n */\nfunction getInitialFromUser(user: PresenceUser): string {\n const base = (user?.name || user?.userId || '').trim();\n if (!base) return '?';\n const ch = base.charAt(0);\n return /[a-z]/i.test(ch) ? ch.toUpperCase() : ch;\n}\n\n/**\n * Headless hook for presence avatars UI state management\n *\n * This hook provides all the state and logic needed to build\n * a presence avatars component without any styling.\n *\n * @param options - Configuration options\n * @returns State and handlers for building presence avatars UI\n *\n * @example\n * ```tsx\n * const {\n * visibleUsers,\n * moreCount,\n * hoveredUser,\n * setHoveredIndex,\n * getInitial,\n * getZIndex,\n * } = usePresenceAvatarsState({ users, maxVisible: 3 });\n *\n * return (\n * <div>\n * {visibleUsers.map((user, idx) => (\n * <span\n * key={user.userId}\n * style={{ zIndex: getZIndex(idx, hoveredUser?.userId === user.userId) }}\n * onMouseEnter={() => setHoveredIndex(idx)}\n * onMouseLeave={() => setHoveredIndex(null)}\n * >\n * {getInitial(user)}\n * </span>\n * ))}\n * {moreCount > 0 && <span>+{moreCount}</span>}\n * </div>\n * );\n * ```\n */\nexport function usePresenceAvatarsState(\n options: UsePresenceAvatarsStateOptions,\n): UsePresenceAvatarsStateReturn {\n const { users, maxVisible = 3 } = options;\n\n const safeUsers = Array.isArray(users) ? users : [];\n\n const visibleUsers = useMemo(\n () => safeUsers.slice(0, maxVisible),\n [safeUsers, maxVisible],\n );\n\n const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);\n\n const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\n\n const hoveredUser = hoveredIndex != null ? visibleUsers[hoveredIndex] : null;\n\n const baseTopZ = visibleUsers.length + 1;\n\n const getInitial = useCallback((user: PresenceUser): string => {\n return getInitialFromUser(user);\n }, []);\n\n const getZIndex = useCallback(\n (index: number, isHovered: boolean): number => {\n return isHovered ? 100 : baseTopZ - index;\n },\n [baseTopZ],\n );\n\n return {\n visibleUsers,\n moreCount,\n hoveredUser,\n hoveredIndex,\n setHoveredIndex,\n getInitial,\n getZIndex,\n };\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n PresenceUser,\n UsePresenceFloatingStateOptions,\n UsePresenceFloatingStateReturn,\n} from \"../core/types\";\n\n/**\n * Headless hook for presence floating UI state management\n *\n * This hook provides all the state and logic needed to build\n * a draggable floating presence panel without any styling.\n *\n * @param options - Configuration options\n * @returns State and handlers for building floating presence UI\n *\n * @example\n * ```tsx\n * const {\n * containerRef,\n * inlineStyle,\n * isDragging,\n * visibleUsers,\n * moreCount,\n * hoveredUser,\n * tooltipTop,\n * onMouseDownHeader,\n * onAvatarEnter,\n * onAvatarLeave,\n * } = usePresenceFloatingState({ users, maxVisible: 8 });\n *\n * return (\n * <div ref={containerRef} style={inlineStyle} onMouseDown={onMouseDownHeader}>\n * <div className=\"header\">열람중 {users.length}</div>\n * <div className=\"body\">\n * {visibleUsers.map((user) => (\n * <span\n * key={user.userId}\n * onMouseEnter={(e) => onAvatarEnter(e, user)}\n * onMouseLeave={onAvatarLeave}\n * >\n * {user.name}\n * </span>\n * ))}\n * </div>\n * </div>\n * );\n * ```\n */\nexport function usePresenceFloatingState(\n options: UsePresenceFloatingStateOptions\n): UsePresenceFloatingStateReturn {\n const { users, maxVisible = 8, initialPosition = { top: 166 } } = options;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const [dragging, setDragging] = useState(false);\n const [hasDragged, setHasDragged] = useState(false);\n const [dragOffset, setDragOffset] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n });\n const [position, setPosition] = useState<{ top: number; left: number }>({\n top: initialPosition.top,\n left: 0,\n });\n const [hoveredUser, setHoveredUser] = useState<PresenceUser | null>(null);\n const [tooltipTop, setTooltipTop] = useState<number>(0);\n\n const safeUsers = Array.isArray(users) ? users : [];\n const visibleUsers = safeUsers.slice(0, maxVisible);\n const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);\n\n // Position adjustment after drag (resize handling)\n useEffect(() => {\n if (!hasDragged) return;\n\n const el = containerRef.current;\n if (!el) return;\n\n const adjustPosition = () => {\n const rect = el.getBoundingClientRect();\n const maxLeft = window.innerWidth - rect.width;\n const maxTop = window.innerHeight - rect.height - 8;\n setPosition((prev) => ({\n left: Math.min(Math.max(0, prev.left), Math.max(0, maxLeft)),\n top: Math.min(Math.max(8, prev.top), Math.max(8, maxTop)),\n }));\n };\n\n const resizeObserver = new ResizeObserver(() => {\n adjustPosition();\n });\n resizeObserver.observe(el);\n\n window.addEventListener(\"resize\", adjustPosition);\n\n return () => {\n resizeObserver.disconnect();\n window.removeEventListener(\"resize\", adjustPosition);\n };\n }, [hasDragged]);\n\n // Helper to get coordinates from mouse or touch event\n const getEventCoordinates = useCallback(\n (e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) => {\n if (\"touches\" in e && e.touches.length > 0) {\n return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY };\n }\n if (\"changedTouches\" in e && e.changedTouches.length > 0) {\n return {\n clientX: e.changedTouches[0].clientX,\n clientY: e.changedTouches[0].clientY,\n };\n }\n if (\"clientX\" in e) {\n return { clientX: e.clientX, clientY: e.clientY };\n }\n return { clientX: 0, clientY: 0 };\n },\n []\n );\n\n const onMouseDownHeader = useCallback(\n (e: React.MouseEvent) => {\n const el = containerRef.current;\n if (!el) return;\n const rect = el.getBoundingClientRect();\n\n // On first drag, switch to left-based positioning\n if (!hasDragged) {\n setHasDragged(true);\n setPosition({\n top: rect.top,\n left: rect.left,\n });\n }\n\n setDragOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top });\n setDragging(true);\n e.preventDefault();\n },\n [hasDragged]\n );\n\n const onTouchStartHeader = useCallback(\n (e: React.TouchEvent) => {\n const el = containerRef.current;\n if (!el) return;\n const rect = el.getBoundingClientRect();\n const { clientX, clientY } = getEventCoordinates(e);\n\n // On first drag, switch to left-based positioning\n if (!hasDragged) {\n setHasDragged(true);\n setPosition({\n top: rect.top,\n left: rect.left,\n });\n }\n\n setDragOffset({ x: clientX - rect.left, y: clientY - rect.top });\n setDragging(true);\n // Note: Don't prevent default to allow scrolling detection\n },\n [hasDragged, getEventCoordinates]\n );\n\n // Handle drag movement (mouse and touch)\n useEffect(() => {\n if (!dragging) return;\n\n const el = containerRef.current;\n\n const onMove = (e: MouseEvent | TouchEvent) => {\n if (!el) return;\n const rect = el.getBoundingClientRect();\n const { clientX, clientY } = getEventCoordinates(e);\n const newLeft = clientX - dragOffset.x;\n const newTop = clientY - dragOffset.y;\n const maxLeft = window.innerWidth - rect.width;\n const maxTop = window.innerHeight - rect.height - 8;\n setPosition({\n left: Math.min(Math.max(0, newLeft), Math.max(0, maxLeft)),\n top: Math.min(Math.max(8, newTop), Math.max(8, maxTop)),\n });\n\n // Prevent scrolling while dragging on touch\n if (\"touches\" in e) {\n e.preventDefault();\n }\n };\n\n const onUp = () => setDragging(false);\n\n // Mouse events\n window.addEventListener(\"mousemove\", onMove);\n window.addEventListener(\"mouseup\", onUp, { once: true });\n\n // Touch events\n window.addEventListener(\"touchmove\", onMove, { passive: false });\n window.addEventListener(\"touchend\", onUp, { once: true });\n window.addEventListener(\"touchcancel\", onUp, { once: true });\n\n return () => {\n window.removeEventListener(\"mousemove\", onMove);\n window.removeEventListener(\"mouseup\", onUp);\n window.removeEventListener(\"touchmove\", onMove);\n window.removeEventListener(\"touchend\", onUp);\n window.removeEventListener(\"touchcancel\", onUp);\n };\n }, [dragging, dragOffset.x, dragOffset.y, getEventCoordinates]);\n\n const inlineStyle = useMemo<React.CSSProperties>(() => {\n if (hasDragged) {\n return {\n position: \"fixed\",\n top: position.top,\n left: position.left,\n right: \"auto\",\n };\n } else {\n return {\n position: \"fixed\",\n top: position.top,\n right: 0,\n left: \"auto\",\n };\n }\n }, [position.top, position.left, hasDragged]);\n\n const onAvatarEnter = useCallback(\n (e: React.MouseEvent, user: PresenceUser) => {\n const container = containerRef.current;\n if (!container) {\n setHoveredUser(user);\n setTooltipTop(0);\n return;\n }\n const containerRect = container.getBoundingClientRect();\n const targetRect = (\n e.currentTarget as HTMLElement\n ).getBoundingClientRect();\n const topWithinContainer = targetRect.top - containerRect.top;\n setHoveredUser(user);\n setTooltipTop(topWithinContainer);\n },\n []\n );\n\n const onAvatarLeave = useCallback(() => {\n setHoveredUser(null);\n }, []);\n\n return {\n containerRef,\n position,\n isDragging: dragging,\n hasDragged,\n inlineStyle,\n visibleUsers,\n moreCount,\n hoveredUser,\n tooltipTop,\n onMouseDownHeader,\n onTouchStartHeader,\n onAvatarEnter,\n onAvatarLeave,\n };\n}\n"],"mappings":"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","names":["normalizeUsers","useEffect","useState","useState","useEffect","useMemo","useState","useCallback","useCallback","useEffect","useMemo","useRef","useState"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rencar-dev/feature-modules-public",
|
|
3
|
+
"version": "0.0.7",
|
|
4
|
+
"description": "Rencar feature modules - hooks, utils, and UI components",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./hooks": {
|
|
8
|
+
"types": "./dist/hooks/index.d.ts",
|
|
9
|
+
"import": "./dist/hooks/index.js",
|
|
10
|
+
"require": "./dist/hooks/index.cjs"
|
|
11
|
+
},
|
|
12
|
+
"./dist/hooks": {
|
|
13
|
+
"types": "./dist/hooks/index.d.ts",
|
|
14
|
+
"import": "./dist/hooks/index.js",
|
|
15
|
+
"require": "./dist/hooks/index.cjs"
|
|
16
|
+
},
|
|
17
|
+
"./presence": {
|
|
18
|
+
"types": "./dist/presence/index.d.ts",
|
|
19
|
+
"import": "./dist/presence/index.js",
|
|
20
|
+
"require": "./dist/presence/index.cjs"
|
|
21
|
+
},
|
|
22
|
+
"./dist/presence": {
|
|
23
|
+
"types": "./dist/presence/index.d.ts",
|
|
24
|
+
"import": "./dist/presence/index.js",
|
|
25
|
+
"require": "./dist/presence/index.cjs"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"typesVersions": {
|
|
29
|
+
"*": {
|
|
30
|
+
"hooks": [
|
|
31
|
+
"./dist/hooks/index.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"presence": [
|
|
34
|
+
"./dist/presence/index.d.ts"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"prepublishOnly": "npm run build"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/Rencar-dev/feature-modules-public.git"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"registry": "https://registry.npmjs.org",
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"rencar",
|
|
56
|
+
"hooks",
|
|
57
|
+
"utils",
|
|
58
|
+
"presence",
|
|
59
|
+
"feature-modules"
|
|
60
|
+
],
|
|
61
|
+
"author": "rencar-dev",
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/react": "^19.2.7",
|
|
65
|
+
"socket.io-client": "^4.8.3",
|
|
66
|
+
"tsup": "^8.0.0",
|
|
67
|
+
"typescript": "^5.0.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"react": ">=17.0.0",
|
|
71
|
+
"socket.io-client": "^4.0.0"
|
|
72
|
+
}
|
|
73
|
+
}
|