@rencar-dev/feature-modules-public 1.2.1 → 1.2.3
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 +48 -0
- package/dist/presence/index.cjs +114 -40
- package/dist/presence/index.cjs.map +1 -1
- package/dist/presence/index.d.cts +106 -32
- package/dist/presence/index.d.ts +106 -32
- package/dist/presence/index.js +116 -44
- package/dist/presence/index.js.map +1 -1
- package/package.json +2 -12
package/README.md
CHANGED
|
@@ -43,6 +43,54 @@ npm run dev
|
|
|
43
43
|
npm run build
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
### 다른 프로젝트에서 테스트 (npm link)
|
|
47
|
+
|
|
48
|
+
패키지를 npm에 배포하지 않고 다른 프로젝트에서 실시간으로 테스트하려면:
|
|
49
|
+
|
|
50
|
+
**Step 1: 이 패키지에서 글로벌 링크 생성**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm link
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Step 2: 다른 프로젝트에서 링크 연결**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cd your-project
|
|
60
|
+
npm link @rencar-dev/feature-modules-public
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Step 3: 연결 확인**
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm ls @rencar-dev/feature-modules-public
|
|
67
|
+
# 출력에 -> 심볼릭 링크 경로가 표시되면 성공
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Step 4: Next.js 프로젝트의 경우 추가 설정**
|
|
71
|
+
|
|
72
|
+
`next.config.js` (또는 `.mjs`)에 다음 설정 추가:
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
webpack: (config, { isServer }) => {
|
|
76
|
+
// 심볼릭 링크 해결을 위한 설정
|
|
77
|
+
config.resolve.symlinks = false;
|
|
78
|
+
|
|
79
|
+
// ... 기존 webpack 설정 ...
|
|
80
|
+
return config;
|
|
81
|
+
},
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
> **Note**: 설정 변경 후 `.next` 폴더를 삭제하고 dev 서버를 재시작하세요.
|
|
85
|
+
|
|
86
|
+
**테스트 완료 후 링크 해제:**
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cd your-project
|
|
90
|
+
npm unlink @rencar-dev/feature-modules-public
|
|
91
|
+
npm install # 원래 패키지로 복원
|
|
92
|
+
```
|
|
93
|
+
|
|
46
94
|
### 새 모듈 추가하기
|
|
47
95
|
|
|
48
96
|
1. **폴더 생성**: `src/[모듈명]/` 폴더 생성
|
package/dist/presence/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var presence_exports = {};
|
|
|
22
22
|
__export(presence_exports, {
|
|
23
23
|
PresenceAvatars: () => PresenceAvatars,
|
|
24
24
|
PresenceFloating: () => PresenceFloating,
|
|
25
|
+
PresenceProvider: () => PresenceProvider,
|
|
25
26
|
disconnectPresenceSocket: () => disconnectPresenceSocket,
|
|
26
27
|
getPresenceSocket: () => getPresenceSocket,
|
|
27
28
|
initPresenceSocket: () => initPresenceSocket,
|
|
@@ -29,6 +30,7 @@ __export(presence_exports, {
|
|
|
29
30
|
usePresence: () => usePresence,
|
|
30
31
|
usePresenceAvatarsState: () => usePresenceAvatarsState,
|
|
31
32
|
usePresenceFloatingState: () => usePresenceFloatingState,
|
|
33
|
+
usePresenceStatus: () => usePresenceStatus,
|
|
32
34
|
usePresenceWatch: () => usePresenceWatch
|
|
33
35
|
});
|
|
34
36
|
module.exports = __toCommonJS(presence_exports);
|
|
@@ -97,18 +99,29 @@ function usePresence(options) {
|
|
|
97
99
|
setUsers([]);
|
|
98
100
|
}
|
|
99
101
|
}, [enabled]);
|
|
102
|
+
const currentUserKey = JSON.stringify(currentUser);
|
|
100
103
|
(0, import_react.useEffect)(() => {
|
|
101
104
|
if (!enabled) {
|
|
102
105
|
return;
|
|
103
106
|
}
|
|
107
|
+
if (!isPresenceSocketInitialized()) {
|
|
108
|
+
console.warn(
|
|
109
|
+
"[presence] Socket not initialized. Presence features disabled."
|
|
110
|
+
);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
104
113
|
const socket = getPresenceSocket();
|
|
114
|
+
let hasJoined = false;
|
|
105
115
|
const joinRoom = () => {
|
|
116
|
+
if (hasJoined) return;
|
|
117
|
+
hasJoined = true;
|
|
106
118
|
socket.emit("presence:join", { roomId, user: currentUser });
|
|
107
119
|
};
|
|
108
120
|
if (socket.connected) {
|
|
109
121
|
joinRoom();
|
|
122
|
+
} else {
|
|
123
|
+
socket.once("connect", joinRoom);
|
|
110
124
|
}
|
|
111
|
-
socket.on("connect", joinRoom);
|
|
112
125
|
const heartbeat = () => {
|
|
113
126
|
socket.emit("presence:heartbeat", { roomId });
|
|
114
127
|
};
|
|
@@ -134,8 +147,7 @@ function usePresence(options) {
|
|
|
134
147
|
};
|
|
135
148
|
}, [
|
|
136
149
|
roomId,
|
|
137
|
-
|
|
138
|
-
currentUser.name,
|
|
150
|
+
currentUserKey,
|
|
139
151
|
enabled,
|
|
140
152
|
heartbeatInterval,
|
|
141
153
|
normalizeUsers2
|
|
@@ -157,10 +169,12 @@ function usePresenceWatch(options) {
|
|
|
157
169
|
const [roomsUsers, setRoomsUsers] = (0, import_react2.useState)(
|
|
158
170
|
{}
|
|
159
171
|
);
|
|
172
|
+
const roomIdsKey = JSON.stringify(roomIds ?? []);
|
|
160
173
|
const normalizedRoomIds = (0, import_react2.useMemo)(() => {
|
|
161
|
-
const
|
|
174
|
+
const parsed = JSON.parse(roomIdsKey);
|
|
175
|
+
const uniq = Array.from(new Set(parsed.filter(Boolean)));
|
|
162
176
|
return uniq;
|
|
163
|
-
}, [
|
|
177
|
+
}, [roomIdsKey]);
|
|
164
178
|
const watchedRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
165
179
|
(0, import_react2.useEffect)(() => {
|
|
166
180
|
if (!enabled) {
|
|
@@ -168,6 +182,12 @@ function usePresenceWatch(options) {
|
|
|
168
182
|
watchedRef.current = /* @__PURE__ */ new Set();
|
|
169
183
|
return;
|
|
170
184
|
}
|
|
185
|
+
if (!isPresenceSocketInitialized()) {
|
|
186
|
+
console.warn(
|
|
187
|
+
"[presence-watch] Socket not initialized. Watch features disabled."
|
|
188
|
+
);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
171
191
|
const socket = getPresenceSocket();
|
|
172
192
|
const toWatch = normalizedRoomIds.filter((r) => !watchedRef.current.has(r));
|
|
173
193
|
const toUnwatch = Array.from(watchedRef.current).filter(
|
|
@@ -186,10 +206,6 @@ function usePresenceWatch(options) {
|
|
|
186
206
|
return next;
|
|
187
207
|
});
|
|
188
208
|
}
|
|
189
|
-
}, [enabled, normalizedRoomIds]);
|
|
190
|
-
(0, import_react2.useEffect)(() => {
|
|
191
|
-
if (!enabled) return;
|
|
192
|
-
const socket = getPresenceSocket();
|
|
193
209
|
const handleSnapshot = (payload) => {
|
|
194
210
|
const rooms = payload?.rooms ?? {};
|
|
195
211
|
setRoomsUsers((prev) => {
|
|
@@ -207,33 +223,38 @@ function usePresenceWatch(options) {
|
|
|
207
223
|
if (!watchedRef.current.has(roomId)) return;
|
|
208
224
|
setRoomsUsers((prev) => ({ ...prev, [roomId]: normalizeUsers(users) }));
|
|
209
225
|
};
|
|
210
|
-
socket.on("presence:snapshot", handleSnapshot);
|
|
211
|
-
socket.on("presence:update", handleUpdate);
|
|
212
226
|
const handleConnect = () => {
|
|
213
227
|
const ids = Array.from(watchedRef.current);
|
|
214
228
|
if (ids.length > 0) socket.emit("presence:watch", { roomIds: ids });
|
|
215
229
|
};
|
|
230
|
+
socket.on("presence:snapshot", handleSnapshot);
|
|
231
|
+
socket.on("presence:update", handleUpdate);
|
|
216
232
|
socket.on("connect", handleConnect);
|
|
217
233
|
return () => {
|
|
218
234
|
socket.off("presence:snapshot", handleSnapshot);
|
|
219
235
|
socket.off("presence:update", handleUpdate);
|
|
220
236
|
socket.off("connect", handleConnect);
|
|
221
|
-
|
|
222
|
-
|
|
237
|
+
if (normalizedRoomIds.length > 0) {
|
|
238
|
+
socket.emit("presence:unwatch", { roomIds: normalizedRoomIds });
|
|
239
|
+
}
|
|
223
240
|
watchedRef.current = /* @__PURE__ */ new Set();
|
|
224
241
|
};
|
|
225
|
-
}, [enabled]);
|
|
242
|
+
}, [enabled, normalizedRoomIds]);
|
|
226
243
|
return roomsUsers;
|
|
227
244
|
}
|
|
228
245
|
|
|
229
246
|
// src/presence/headless/usePresenceAvatarsState.ts
|
|
230
247
|
var import_react3 = require("react");
|
|
231
|
-
|
|
248
|
+
|
|
249
|
+
// src/presence/core/utils.ts
|
|
250
|
+
function getInitial(user) {
|
|
232
251
|
const base = (user?.name || user?.userId || "").trim();
|
|
233
252
|
if (!base) return "?";
|
|
234
253
|
const ch = base.charAt(0);
|
|
235
254
|
return /[a-z]/i.test(ch) ? ch.toUpperCase() : ch;
|
|
236
255
|
}
|
|
256
|
+
|
|
257
|
+
// src/presence/headless/usePresenceAvatarsState.ts
|
|
237
258
|
function usePresenceAvatarsState(options) {
|
|
238
259
|
const { users, maxVisible = 3 } = options;
|
|
239
260
|
const safeUsers = Array.isArray(users) ? users : [];
|
|
@@ -245,9 +266,6 @@ function usePresenceAvatarsState(options) {
|
|
|
245
266
|
const [hoveredIndex, setHoveredIndex] = (0, import_react3.useState)(null);
|
|
246
267
|
const hoveredUser = hoveredIndex != null ? visibleUsers[hoveredIndex] : null;
|
|
247
268
|
const baseTopZ = visibleUsers.length + 1;
|
|
248
|
-
const getInitial2 = (0, import_react3.useCallback)((user) => {
|
|
249
|
-
return getInitialFromUser(user);
|
|
250
|
-
}, []);
|
|
251
269
|
const getZIndex = (0, import_react3.useCallback)(
|
|
252
270
|
(index, isHovered) => {
|
|
253
271
|
return isHovered ? 100 : baseTopZ - index;
|
|
@@ -260,7 +278,7 @@ function usePresenceAvatarsState(options) {
|
|
|
260
278
|
hoveredUser,
|
|
261
279
|
hoveredIndex,
|
|
262
280
|
setHoveredIndex,
|
|
263
|
-
getInitial
|
|
281
|
+
getInitial,
|
|
264
282
|
getZIndex
|
|
265
283
|
};
|
|
266
284
|
}
|
|
@@ -412,22 +430,19 @@ function usePresenceFloatingState(options) {
|
|
|
412
430
|
};
|
|
413
431
|
}
|
|
414
432
|
}, [position.top, position.left, hasDragged]);
|
|
415
|
-
const onAvatarEnter = (0, import_react4.useCallback)(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (!container) {
|
|
419
|
-
setHoveredUser(user);
|
|
420
|
-
setTooltipTop(0);
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
const containerRect = container.getBoundingClientRect();
|
|
424
|
-
const targetRect = e.currentTarget.getBoundingClientRect();
|
|
425
|
-
const topWithinContainer = targetRect.top - containerRect.top;
|
|
433
|
+
const onAvatarEnter = (0, import_react4.useCallback)((e, user) => {
|
|
434
|
+
const container = containerRef.current;
|
|
435
|
+
if (!container) {
|
|
426
436
|
setHoveredUser(user);
|
|
427
|
-
setTooltipTop(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
437
|
+
setTooltipTop(0);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const containerRect = container.getBoundingClientRect();
|
|
441
|
+
const targetRect = e.currentTarget.getBoundingClientRect();
|
|
442
|
+
const topWithinContainer = targetRect.top - containerRect.top;
|
|
443
|
+
setHoveredUser(user);
|
|
444
|
+
setTooltipTop(topWithinContainer);
|
|
445
|
+
}, []);
|
|
431
446
|
const onAvatarLeave = (0, import_react4.useCallback)(() => {
|
|
432
447
|
setHoveredUser(null);
|
|
433
448
|
}, []);
|
|
@@ -556,12 +571,6 @@ function PresenceAvatars({
|
|
|
556
571
|
// src/presence/components/PresenceFloating.tsx
|
|
557
572
|
var import_react6 = require("react");
|
|
558
573
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
559
|
-
function getInitial(user) {
|
|
560
|
-
const base = (user?.name || user?.userId || "").trim();
|
|
561
|
-
if (!base) return "?";
|
|
562
|
-
const ch = base.charAt(0);
|
|
563
|
-
return /[a-z]/i.test(ch) ? ch.toUpperCase() : ch;
|
|
564
|
-
}
|
|
565
574
|
function PresenceFloating({
|
|
566
575
|
users,
|
|
567
576
|
maxVisible = 8,
|
|
@@ -671,10 +680,74 @@ function PresenceFloating({
|
|
|
671
680
|
}
|
|
672
681
|
);
|
|
673
682
|
}
|
|
683
|
+
|
|
684
|
+
// src/presence/components/PresenceProvider.tsx
|
|
685
|
+
var import_react7 = require("react");
|
|
686
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
687
|
+
var PresenceContext = (0, import_react7.createContext)(null);
|
|
688
|
+
function PresenceProvider({
|
|
689
|
+
url,
|
|
690
|
+
transports,
|
|
691
|
+
debug = false,
|
|
692
|
+
children
|
|
693
|
+
}) {
|
|
694
|
+
const [status, setStatus] = (0, import_react7.useState)("disconnected");
|
|
695
|
+
const [isInitialized, setIsInitialized] = (0, import_react7.useState)(false);
|
|
696
|
+
(0, import_react7.useEffect)(() => {
|
|
697
|
+
initPresenceSocket({ url, transports });
|
|
698
|
+
setIsInitialized(true);
|
|
699
|
+
if (debug) {
|
|
700
|
+
console.log("[presence] Socket initialized with URL:", url);
|
|
701
|
+
}
|
|
702
|
+
const socket = getPresenceSocket();
|
|
703
|
+
const handleConnect = () => {
|
|
704
|
+
setStatus("connected");
|
|
705
|
+
if (debug) {
|
|
706
|
+
console.log("[presence] Connected");
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const handleDisconnect = () => {
|
|
710
|
+
setStatus("disconnected");
|
|
711
|
+
if (debug) {
|
|
712
|
+
console.log("[presence] Disconnected");
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
const handleConnecting = () => {
|
|
716
|
+
setStatus("connecting");
|
|
717
|
+
};
|
|
718
|
+
if (socket.connected) {
|
|
719
|
+
setStatus("connected");
|
|
720
|
+
} else {
|
|
721
|
+
setStatus("connecting");
|
|
722
|
+
}
|
|
723
|
+
socket.on("connect", handleConnect);
|
|
724
|
+
socket.on("disconnect", handleDisconnect);
|
|
725
|
+
socket.io.on("reconnect_attempt", handleConnecting);
|
|
726
|
+
return () => {
|
|
727
|
+
socket.off("connect", handleConnect);
|
|
728
|
+
socket.off("disconnect", handleDisconnect);
|
|
729
|
+
socket.io.off("reconnect_attempt", handleConnecting);
|
|
730
|
+
disconnectPresenceSocket();
|
|
731
|
+
setIsInitialized(false);
|
|
732
|
+
if (debug) {
|
|
733
|
+
console.log("[presence] Socket disconnected and cleaned up");
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}, [url, transports, debug]);
|
|
737
|
+
const contextValue = {
|
|
738
|
+
isInitialized,
|
|
739
|
+
status
|
|
740
|
+
};
|
|
741
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PresenceContext.Provider, { value: contextValue, children });
|
|
742
|
+
}
|
|
743
|
+
function usePresenceStatus() {
|
|
744
|
+
return (0, import_react7.useContext)(PresenceContext);
|
|
745
|
+
}
|
|
674
746
|
// Annotate the CommonJS export names for ESM import in node:
|
|
675
747
|
0 && (module.exports = {
|
|
676
748
|
PresenceAvatars,
|
|
677
749
|
PresenceFloating,
|
|
750
|
+
PresenceProvider,
|
|
678
751
|
disconnectPresenceSocket,
|
|
679
752
|
getPresenceSocket,
|
|
680
753
|
initPresenceSocket,
|
|
@@ -682,6 +755,7 @@ function PresenceFloating({
|
|
|
682
755
|
usePresence,
|
|
683
756
|
usePresenceAvatarsState,
|
|
684
757
|
usePresenceFloatingState,
|
|
758
|
+
usePresenceStatus,
|
|
685
759
|
usePresenceWatch
|
|
686
760
|
});
|
|
687
761
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/presence/index.ts","../../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","../../src/presence/components/PresenceAvatars.tsx","../../src/presence/components/PresenceFloating.tsx"],"sourcesContent":["// Core exports\nexport {\n initPresenceSocket,\n getPresenceSocket,\n disconnectPresenceSocket,\n isPresenceSocketInitialized,\n} from \"./core/socket\";\n\nexport type {\n PresenceUser,\n PresenceConfig,\n UsePresenceOptions,\n UsePresenceWatchOptions,\n UsePresenceAvatarsStateOptions,\n UsePresenceAvatarsStateReturn,\n UsePresenceFloatingStateOptions,\n UsePresenceFloatingStateReturn,\n // Component props types\n PresenceAvatarsProps,\n PresenceAvatarRenderProps,\n PresenceAvatarsTooltipRenderProps,\n PresenceFloatingProps,\n PresenceFloatingAvatarRenderProps,\n PresenceFloatingTooltipRenderProps,\n} from \"./core/types\";\n\n// Hooks exports\nexport { usePresence } from \"./hooks/usePresence\";\nexport { usePresenceWatch } from \"./hooks/usePresenceWatch\";\n\n// Headless state exports\nexport { usePresenceAvatarsState } from \"./headless/usePresenceAvatarsState\";\nexport { usePresenceFloatingState } from \"./headless/usePresenceFloatingState\";\n\n// Component exports\nexport { PresenceAvatars } from \"./components/PresenceAvatars\";\nexport { PresenceFloating } from \"./components/PresenceFloating\";\n","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","import React, { useState, useCallback } from \"react\";\nimport { usePresenceAvatarsState } from \"../headless/usePresenceAvatarsState\";\nimport type {\n PresenceUser,\n PresenceAvatarsProps,\n PresenceAvatarRenderProps,\n PresenceAvatarsTooltipRenderProps,\n} from \"../core/types\";\n\n/**\n * PresenceAvatars - Displays a stack of user avatars with hover tooltips\n *\n * @example\n * ```tsx\n * // Basic usage\n * <PresenceAvatars users={users} />\n *\n * // Custom avatar\n * <PresenceAvatars\n * users={users}\n * renderAvatar={({ user, initial }) => (\n * <img src={user.avatarUrl} alt={user.name} />\n * )}\n * />\n *\n * // Custom tooltip\n * <PresenceAvatars\n * users={users}\n * renderTooltip={({ user, position }) => (\n * <div style={{ top: position.top, left: position.left }}>\n * {user.name}\n * </div>\n * )}\n * />\n * ```\n */\nexport function PresenceAvatars({\n users,\n maxVisible = 3,\n className,\n renderAvatar,\n renderTooltip,\n renderMore,\n}: PresenceAvatarsProps) {\n const {\n visibleUsers,\n moreCount,\n hoveredUser,\n hoveredIndex,\n setHoveredIndex,\n getInitial,\n getZIndex,\n } = usePresenceAvatarsState({ users, maxVisible });\n\n const [tooltipPos, setTooltipPos] = useState<{\n top: number;\n left: number;\n } | null>(null);\n\n const updateTooltipPos = useCallback((el: HTMLElement) => {\n const rect = el.getBoundingClientRect();\n const tooltipWidth = 200;\n const half = tooltipWidth / 2;\n const centerX = rect.left + rect.width / 2;\n const left = Math.min(\n Math.max(centerX, half + 8),\n window.innerWidth - half - 8\n );\n const top = Math.min(rect.bottom + 8, window.innerHeight - 8);\n setTooltipPos({ top, left });\n }, []);\n\n const handleMouseEnter = useCallback(\n (e: React.MouseEvent, idx: number) => {\n setHoveredIndex(idx);\n updateTooltipPos(e.currentTarget as HTMLElement);\n },\n [setHoveredIndex, updateTooltipPos]\n );\n\n const handleMouseLeave = useCallback(() => {\n setHoveredIndex(null);\n }, [setHoveredIndex]);\n\n // Default tooltip renderer\n const defaultTooltipRenderer = useCallback(\n ({ user, position }: PresenceAvatarsTooltipRenderProps) => (\n <div\n className=\"presence-avatars__tooltip\"\n style={{ top: position.top, left: position.left }}\n >\n <div className=\"presence-avatars__tooltip-row\">\n <span className=\"presence-avatars__tooltip-key\">Name</span>\n <span className=\"presence-avatars__tooltip-val\">\n {user.name || \"-\"}\n </span>\n </div>\n <div className=\"presence-avatars__tooltip-row\">\n <span className=\"presence-avatars__tooltip-key\">ID</span>\n <span className=\"presence-avatars__tooltip-val\">\n {user.userId || \"-\"}\n </span>\n </div>\n </div>\n ),\n []\n );\n\n // Default avatar renderer\n const defaultAvatarRenderer = useCallback(\n ({ initial, isHovered }: PresenceAvatarRenderProps) => <>{initial}</>,\n []\n );\n\n return (\n <div className={`presence-avatars ${className ?? \"\"}`}>\n <div className=\"presence-avatars__stack\">\n {visibleUsers.map((user, idx) => {\n const isHovered = hoveredIndex === idx;\n const initial = getInitial(user);\n const zIndex = getZIndex(idx, isHovered);\n\n const renderProps: PresenceAvatarRenderProps = {\n user,\n index: idx,\n initial,\n isHovered,\n zIndex,\n };\n\n return (\n <span\n key={`${user.userId}-${idx}`}\n className={`presence-avatars__avatar ${\n isHovered ? \"is-hovered\" : \"\"\n }`}\n style={{ zIndex }}\n onMouseEnter={(e) => handleMouseEnter(e, idx)}\n onMouseLeave={handleMouseLeave}\n aria-label={user.name || user.userId || \"\"}\n >\n {renderAvatar\n ? renderAvatar(renderProps)\n : defaultAvatarRenderer(renderProps)}\n </span>\n );\n })}\n {moreCount > 0 &&\n (renderMore ? (\n renderMore(moreCount)\n ) : (\n <span className=\"presence-avatars__more\">+{moreCount}</span>\n ))}\n </div>\n\n {hoveredUser &&\n tooltipPos &&\n (renderTooltip\n ? renderTooltip({ user: hoveredUser, position: tooltipPos })\n : defaultTooltipRenderer({\n user: hoveredUser,\n position: tooltipPos,\n }))}\n </div>\n );\n}\n\nexport default PresenceAvatars;\n","import React, { useCallback } from \"react\";\nimport { usePresenceFloatingState } from \"../headless/usePresenceFloatingState\";\nimport type {\n PresenceUser,\n PresenceFloatingProps,\n PresenceFloatingAvatarRenderProps,\n PresenceFloatingTooltipRenderProps,\n} from \"../core/types\";\n\n/**\n * Extract initial character from user name/id\n */\nfunction getInitial(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 * PresenceFloating - Draggable floating panel showing presence users\n *\n * @example\n * ```tsx\n * // Basic usage\n * <PresenceFloating users={users} />\n *\n * // Custom text\n * <PresenceFloating\n * users={users}\n * title=\"Viewing\"\n * emptyText=\"No one\"\n * moreText={(n) => `+${n} more`}\n * />\n *\n * // Custom avatar\n * <PresenceFloating\n * users={users}\n * renderAvatar={({ user, initial }) => (\n * <img src={user.avatarUrl} alt={user.name} />\n * )}\n * />\n * ```\n */\nexport function PresenceFloating({\n users,\n maxVisible = 8,\n className,\n style,\n initialPosition,\n title = \"열람중\",\n emptyText = \"없음\",\n moreText = (n) => `+${n}명`,\n renderAvatar,\n renderTooltip,\n}: PresenceFloatingProps) {\n const {\n containerRef,\n inlineStyle,\n isDragging,\n visibleUsers,\n moreCount,\n hoveredUser,\n tooltipTop,\n onMouseDownHeader,\n onTouchStartHeader,\n onAvatarEnter,\n onAvatarLeave,\n } = usePresenceFloatingState({ users, maxVisible, initialPosition });\n\n // Default tooltip renderer\n const defaultTooltipRenderer = useCallback(\n ({ user, top }: PresenceFloatingTooltipRenderProps) => (\n <div\n className=\"presence-floating__tooltip\"\n style={{ top: Math.max(0, top - 6) }}\n onMouseLeave={onAvatarLeave}\n >\n <div className=\"presence-floating__tooltip-row\">\n <span className=\"presence-floating__tooltip-key\">Name</span>\n <span className=\"presence-floating__tooltip-val\">\n {user.name || \"-\"}\n </span>\n </div>\n <div className=\"presence-floating__tooltip-row\">\n <span className=\"presence-floating__tooltip-key\">ID</span>\n <span className=\"presence-floating__tooltip-val\">\n {user.userId || \"-\"}\n </span>\n </div>\n </div>\n ),\n [onAvatarLeave]\n );\n\n // Default avatar renderer\n const defaultAvatarRenderer = useCallback(\n ({ initial }: PresenceFloatingAvatarRenderProps) => <>{initial}</>,\n []\n );\n\n const combinedStyle: React.CSSProperties = {\n ...inlineStyle,\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n className={`presence-floating ${isDragging ? \"is-dragging\" : \"\"} ${\n className ?? \"\"\n }`}\n style={combinedStyle}\n >\n <div\n className=\"presence-floating__header\"\n onMouseDown={onMouseDownHeader}\n onTouchStart={onTouchStartHeader}\n >\n <span\n className=\"presence-floating__title\"\n title={title}\n aria-label={title}\n >\n {title}\n </span>\n <span className=\"presence-floating__count\">{users.length}</span>\n </div>\n\n {hoveredUser &&\n (renderTooltip\n ? renderTooltip({ user: hoveredUser, top: tooltipTop })\n : defaultTooltipRenderer({ user: hoveredUser, top: tooltipTop }))}\n\n <div className=\"presence-floating__body\">\n {visibleUsers.length === 0 ? (\n <span className=\"presence-floating__empty\">{emptyText}</span>\n ) : (\n visibleUsers.map((user, idx) => {\n const initial = getInitial(user);\n const label = user.name || user.userId || \"\";\n\n const renderProps: PresenceFloatingAvatarRenderProps = {\n user,\n initial,\n };\n\n return (\n <span\n key={`${user.userId}-${idx}`}\n className=\"presence-floating__avatar\"\n title={label}\n aria-label={label}\n onMouseEnter={(e) => onAvatarEnter(e, user)}\n onMouseLeave={onAvatarLeave}\n >\n {renderAvatar\n ? renderAvatar(renderProps)\n : defaultAvatarRenderer(renderProps)}\n </span>\n );\n })\n )}\n {moreCount > 0 && (\n <span className=\"presence-floating__more\">{moreText(moreCount)}</span>\n )}\n </div>\n </div>\n );\n}\n\nexport default PresenceFloating;\n"],"mappings":"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","names":["normalizeUsers","import_react","import_react","getInitial","import_react","import_react","getInitial","import_react","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../../src/presence/index.ts","../../src/presence/core/socket.ts","../../src/presence/hooks/usePresence.ts","../../src/presence/hooks/usePresenceWatch.ts","../../src/presence/headless/usePresenceAvatarsState.ts","../../src/presence/core/utils.ts","../../src/presence/headless/usePresenceFloatingState.ts","../../src/presence/components/PresenceAvatars.tsx","../../src/presence/components/PresenceFloating.tsx","../../src/presence/components/PresenceProvider.tsx"],"sourcesContent":["// Core exports\nexport {\n initPresenceSocket,\n getPresenceSocket,\n disconnectPresenceSocket,\n isPresenceSocketInitialized,\n} from \"./core/socket\";\n\nexport type {\n PresenceUser,\n PresenceConfig,\n PresenceProviderProps,\n UsePresenceOptions,\n UsePresenceWatchOptions,\n UsePresenceAvatarsStateOptions,\n UsePresenceAvatarsStateReturn,\n UsePresenceFloatingStateOptions,\n UsePresenceFloatingStateReturn,\n // Component props types\n PresenceAvatarsProps,\n PresenceAvatarRenderProps,\n PresenceAvatarsTooltipRenderProps,\n PresenceFloatingProps,\n PresenceFloatingAvatarRenderProps,\n PresenceFloatingTooltipRenderProps,\n} from \"./core/types\";\n\n// Hooks exports\nexport { usePresence } from \"./hooks/usePresence\";\nexport { usePresenceWatch } from \"./hooks/usePresenceWatch\";\n\n// Headless state exports\nexport { usePresenceAvatarsState } from \"./headless/usePresenceAvatarsState\";\nexport { usePresenceFloatingState } from \"./headless/usePresenceFloatingState\";\n\n// Component exports\nexport { PresenceAvatars } from \"./components/PresenceAvatars\";\nexport { PresenceFloating } from \"./components/PresenceFloating\";\nexport {\n PresenceProvider,\n usePresenceStatus,\n} from \"./components/PresenceProvider\";\n","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, useRef } from \"react\";\nimport { getPresenceSocket, isPresenceSocketInitialized } 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 // Serialize currentUser to detect deep changes without infinite loops\n const currentUserKey = JSON.stringify(currentUser);\n\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n if (!isPresenceSocketInitialized()) {\n console.warn(\n \"[presence] Socket not initialized. Presence features disabled.\",\n );\n return;\n }\n\n const socket = getPresenceSocket();\n let hasJoined = false;\n\n const joinRoom = () => {\n if (hasJoined) return; // 중복 join 방지\n hasJoined = true;\n socket.emit(\"presence:join\", { roomId, user: currentUser });\n };\n\n // Join immediately if connected, otherwise wait for connect event (once)\n if (socket.connected) {\n joinRoom();\n } else {\n socket.once(\"connect\", joinRoom); // on → once로 변경하여 중복 방지\n }\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 currentUserKey,\n enabled,\n heartbeatInterval,\n normalizeUsers,\n ]);\n\n return users;\n}\n","import { useEffect, useMemo, useRef, useState, useCallback } from \"react\";\nimport { getPresenceSocket, isPresenceSocketInitialized } 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 // Serialize roomIds to prevent infinite effect loops when array reference changes\n const roomIdsKey = JSON.stringify(roomIds ?? []);\n const normalizedRoomIds = useMemo(() => {\n const parsed = JSON.parse(roomIdsKey) as string[];\n const uniq = Array.from(new Set(parsed.filter(Boolean)));\n return uniq;\n }, [roomIdsKey]);\n\n const watchedRef = useRef<Set<string>>(new Set());\n\n // 통합된 useEffect: watch/unwatch + socket 이벤트 처리\n useEffect(() => {\n if (!enabled) {\n setRoomsUsers({});\n watchedRef.current = new Set();\n return;\n }\n\n if (!isPresenceSocketInitialized()) {\n console.warn(\n \"[presence-watch] Socket not initialized. Watch features disabled.\",\n );\n return;\n }\n\n const socket = getPresenceSocket();\n\n // Watch/unwatch 로직\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\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 // 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\n socket.on(\"presence:snapshot\", handleSnapshot);\n socket.on(\"presence:update\", handleUpdate);\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 // cleanup 시 현재 normalizedRoomIds 기준으로 unwatch\n if (normalizedRoomIds.length > 0) {\n socket.emit(\"presence:unwatch\", { roomIds: normalizedRoomIds });\n }\n watchedRef.current = new Set();\n };\n }, [enabled, normalizedRoomIds]);\n\n return roomsUsers;\n}\n","import { useMemo, useState, useCallback } from \"react\";\nimport type {\n PresenceUser,\n UsePresenceAvatarsStateOptions,\n UsePresenceAvatarsStateReturn,\n} from \"../core/types\";\nimport { getInitial } from \"../core/utils\";\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<T extends PresenceUser = PresenceUser>(\n options: UsePresenceAvatarsStateOptions<T>,\n): UsePresenceAvatarsStateReturn<T> {\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 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 type { PresenceUser } from \"./types\";\n\n/**\n * Extract initial character from user name/id\n * @param user - PresenceUser object\n * @returns Single character initial (uppercase for letters)\n */\nexport function getInitial(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","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<T extends PresenceUser = PresenceUser>(\n options: UsePresenceFloatingStateOptions<T>,\n): UsePresenceFloatingStateReturn<T> {\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<T | 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((e: React.MouseEvent, user: T) => {\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 = (e.currentTarget as HTMLElement).getBoundingClientRect();\n const topWithinContainer = targetRect.top - containerRect.top;\n setHoveredUser(user);\n setTooltipTop(topWithinContainer);\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","import React, { useState, useCallback } from \"react\";\nimport { usePresenceAvatarsState } from \"../headless/usePresenceAvatarsState\";\nimport type {\n PresenceUser,\n PresenceAvatarsProps,\n PresenceAvatarRenderProps,\n PresenceAvatarsTooltipRenderProps,\n} from \"../core/types\";\n\n/**\n * PresenceAvatars - Displays a stack of user avatars with hover tooltips\n *\n * @example\n * ```tsx\n * // Basic usage\n * <PresenceAvatars users={users} />\n *\n * // Custom avatar\n * <PresenceAvatars\n * users={users}\n * renderAvatar={({ user, initial }) => (\n * <img src={user.avatarUrl} alt={user.name} />\n * )}\n * />\n *\n * // Custom tooltip\n * <PresenceAvatars\n * users={users}\n * renderTooltip={({ user, position }) => (\n * <div style={{ top: position.top, left: position.left }}>\n * {user.name}\n * </div>\n * )}\n * />\n * ```\n */\nexport function PresenceAvatars<T extends PresenceUser = PresenceUser>({\n users,\n maxVisible = 3,\n className,\n renderAvatar,\n renderTooltip,\n renderMore,\n}: PresenceAvatarsProps<T>) {\n const {\n visibleUsers,\n moreCount,\n hoveredUser,\n hoveredIndex,\n setHoveredIndex,\n getInitial,\n getZIndex,\n } = usePresenceAvatarsState<T>({ users, maxVisible });\n\n const [tooltipPos, setTooltipPos] = useState<{\n top: number;\n left: number;\n } | null>(null);\n\n const updateTooltipPos = useCallback((el: HTMLElement) => {\n const rect = el.getBoundingClientRect();\n const tooltipWidth = 200;\n const half = tooltipWidth / 2;\n const centerX = rect.left + rect.width / 2;\n const left = Math.min(\n Math.max(centerX, half + 8),\n window.innerWidth - half - 8,\n );\n const top = Math.min(rect.bottom + 8, window.innerHeight - 8);\n setTooltipPos({ top, left });\n }, []);\n\n const handleMouseEnter = useCallback(\n (e: React.MouseEvent, idx: number) => {\n setHoveredIndex(idx);\n updateTooltipPos(e.currentTarget as HTMLElement);\n },\n [setHoveredIndex, updateTooltipPos],\n );\n\n const handleMouseLeave = useCallback(() => {\n setHoveredIndex(null);\n }, [setHoveredIndex]);\n\n // Default tooltip renderer\n const defaultTooltipRenderer = useCallback(\n ({ user, position }: PresenceAvatarsTooltipRenderProps) => (\n <div\n className=\"presence-avatars__tooltip\"\n style={{ top: position.top, left: position.left }}\n >\n <div className=\"presence-avatars__tooltip-row\">\n <span className=\"presence-avatars__tooltip-key\">Name</span>\n <span className=\"presence-avatars__tooltip-val\">\n {user.name || \"-\"}\n </span>\n </div>\n <div className=\"presence-avatars__tooltip-row\">\n <span className=\"presence-avatars__tooltip-key\">ID</span>\n <span className=\"presence-avatars__tooltip-val\">\n {user.userId || \"-\"}\n </span>\n </div>\n </div>\n ),\n [],\n );\n\n // Default avatar renderer\n const defaultAvatarRenderer = useCallback(\n ({ initial, isHovered }: PresenceAvatarRenderProps) => <>{initial}</>,\n [],\n );\n\n return (\n <div className={`presence-avatars ${className ?? \"\"}`}>\n <div className=\"presence-avatars__stack\">\n {visibleUsers.map((user, idx) => {\n const isHovered = hoveredIndex === idx;\n const initial = getInitial(user);\n const zIndex = getZIndex(idx, isHovered);\n\n const renderProps: PresenceAvatarRenderProps<T> = {\n user,\n index: idx,\n initial,\n isHovered,\n zIndex,\n };\n\n return (\n <span\n key={`${user.userId}-${idx}`}\n className={`presence-avatars__avatar ${\n isHovered ? \"is-hovered\" : \"\"\n }`}\n style={{ zIndex }}\n onMouseEnter={(e) => handleMouseEnter(e, idx)}\n onMouseLeave={handleMouseLeave}\n aria-label={user.name || user.userId || \"\"}\n >\n {renderAvatar\n ? renderAvatar(renderProps)\n : defaultAvatarRenderer(renderProps)}\n </span>\n );\n })}\n {moreCount > 0 &&\n (renderMore ? (\n renderMore(moreCount)\n ) : (\n <span className=\"presence-avatars__more\">+{moreCount}</span>\n ))}\n </div>\n\n {hoveredUser &&\n tooltipPos &&\n (renderTooltip\n ? renderTooltip({ user: hoveredUser, position: tooltipPos })\n : defaultTooltipRenderer({\n user: hoveredUser,\n position: tooltipPos,\n }))}\n </div>\n );\n}\n\nexport default PresenceAvatars;\n","import React, { useCallback } from \"react\";\nimport { usePresenceFloatingState } from \"../headless/usePresenceFloatingState\";\nimport type {\n PresenceUser,\n PresenceFloatingProps,\n PresenceFloatingAvatarRenderProps,\n PresenceFloatingTooltipRenderProps,\n} from \"../core/types\";\nimport { getInitial } from \"../core/utils\";\n\n/**\n * PresenceFloating - Draggable floating panel showing presence users\n *\n * @example\n * ```tsx\n * // Basic usage\n * <PresenceFloating users={users} />\n *\n * // Custom text\n * <PresenceFloating\n * users={users}\n * title=\"Viewing\"\n * emptyText=\"No one\"\n * moreText={(n) => `+${n} more`}\n * />\n *\n * // Custom avatar\n * <PresenceFloating\n * users={users}\n * renderAvatar={({ user, initial }) => (\n * <img src={user.avatarUrl} alt={user.name} />\n * )}\n * />\n * ```\n */\nexport function PresenceFloating<T extends PresenceUser = PresenceUser>({\n users,\n maxVisible = 8,\n className,\n style,\n initialPosition,\n title = \"열람중\",\n emptyText = \"없음\",\n moreText = (n) => `+${n}명`,\n renderAvatar,\n renderTooltip,\n}: PresenceFloatingProps<T>) {\n const {\n containerRef,\n inlineStyle,\n isDragging,\n visibleUsers,\n moreCount,\n hoveredUser,\n tooltipTop,\n onMouseDownHeader,\n onTouchStartHeader,\n onAvatarEnter,\n onAvatarLeave,\n } = usePresenceFloatingState<T>({ users, maxVisible, initialPosition });\n\n // Default tooltip renderer\n const defaultTooltipRenderer = useCallback(\n ({ user, top }: PresenceFloatingTooltipRenderProps) => (\n <div\n className=\"presence-floating__tooltip\"\n style={{ top: Math.max(0, top - 6) }}\n onMouseLeave={onAvatarLeave}\n >\n <div className=\"presence-floating__tooltip-row\">\n <span className=\"presence-floating__tooltip-key\">Name</span>\n <span className=\"presence-floating__tooltip-val\">\n {user.name || \"-\"}\n </span>\n </div>\n <div className=\"presence-floating__tooltip-row\">\n <span className=\"presence-floating__tooltip-key\">ID</span>\n <span className=\"presence-floating__tooltip-val\">\n {user.userId || \"-\"}\n </span>\n </div>\n </div>\n ),\n [onAvatarLeave],\n );\n\n // Default avatar renderer\n const defaultAvatarRenderer = useCallback(\n ({ initial }: PresenceFloatingAvatarRenderProps) => <>{initial}</>,\n [],\n );\n\n const combinedStyle: React.CSSProperties = {\n ...inlineStyle,\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n className={`presence-floating ${isDragging ? \"is-dragging\" : \"\"} ${\n className ?? \"\"\n }`}\n style={combinedStyle}\n >\n <div\n className=\"presence-floating__header\"\n onMouseDown={onMouseDownHeader}\n onTouchStart={onTouchStartHeader}\n >\n <span\n className=\"presence-floating__title\"\n title={title}\n aria-label={title}\n >\n {title}\n </span>\n <span className=\"presence-floating__count\">{users.length}</span>\n </div>\n\n {hoveredUser &&\n (renderTooltip\n ? renderTooltip({ user: hoveredUser, top: tooltipTop })\n : defaultTooltipRenderer({ user: hoveredUser, top: tooltipTop }))}\n\n <div className=\"presence-floating__body\">\n {visibleUsers.length === 0 ? (\n <span className=\"presence-floating__empty\">{emptyText}</span>\n ) : (\n visibleUsers.map((user, idx) => {\n const initial = getInitial(user);\n const label = user.name || user.userId || \"\";\n\n const renderProps: PresenceFloatingAvatarRenderProps<T> = {\n user,\n initial,\n };\n\n return (\n <span\n key={`${user.userId}-${idx}`}\n className=\"presence-floating__avatar\"\n title={label}\n aria-label={label}\n onMouseEnter={(e) => onAvatarEnter(e, user)}\n onMouseLeave={onAvatarLeave}\n >\n {renderAvatar\n ? renderAvatar(renderProps)\n : defaultAvatarRenderer(renderProps)}\n </span>\n );\n })\n )}\n {moreCount > 0 && (\n <span className=\"presence-floating__more\">{moreText(moreCount)}</span>\n )}\n </div>\n </div>\n );\n}\n\nexport default PresenceFloating;\n","import React, { createContext, useContext, useEffect, useState } from \"react\";\nimport {\n initPresenceSocket,\n disconnectPresenceSocket,\n isPresenceSocketInitialized,\n getPresenceSocket,\n} from \"../core/socket\";\nimport type { PresenceProviderProps } from \"../core/types\";\n\n/**\n * Presence Context value type\n */\ntype PresenceContextValue = {\n /** Whether the socket is initialized */\n isInitialized: boolean;\n /** Current connection status */\n status: \"connecting\" | \"connected\" | \"disconnected\";\n};\n\nconst PresenceContext = createContext<PresenceContextValue | null>(null);\n\n/**\n * PresenceProvider - Manages socket initialization lifecycle\n *\n * Wrap your app with this provider to automatically initialize\n * and cleanup the presence socket connection.\n *\n * @example\n * ```tsx\n * // In your app entry point\n * import { PresenceProvider } from '@rencar-dev/feature-modules-public';\n *\n * function App() {\n * return (\n * <PresenceProvider url=\"https://socket.example.com\">\n * <MyApp />\n * </PresenceProvider>\n * );\n * }\n *\n * // In child components, hooks work automatically\n * function MyComponent() {\n * const users = usePresence({ roomId: 'room-1', currentUser });\n * return <PresenceFloating users={users} />;\n * }\n * ```\n */\nexport function PresenceProvider({\n url,\n transports,\n debug = false,\n children,\n}: PresenceProviderProps) {\n const [status, setStatus] =\n useState<PresenceContextValue[\"status\"]>(\"disconnected\");\n const [isInitialized, setIsInitialized] = useState(false);\n\n useEffect(() => {\n // Initialize socket\n initPresenceSocket({ url, transports });\n setIsInitialized(true);\n\n if (debug) {\n console.log(\"[presence] Socket initialized with URL:\", url);\n }\n\n // Get socket and setup connection listeners\n const socket = getPresenceSocket();\n\n const handleConnect = () => {\n setStatus(\"connected\");\n if (debug) {\n console.log(\"[presence] Connected\");\n }\n };\n\n const handleDisconnect = () => {\n setStatus(\"disconnected\");\n if (debug) {\n console.log(\"[presence] Disconnected\");\n }\n };\n\n const handleConnecting = () => {\n setStatus(\"connecting\");\n };\n\n // Set initial status\n if (socket.connected) {\n setStatus(\"connected\");\n } else {\n setStatus(\"connecting\");\n }\n\n socket.on(\"connect\", handleConnect);\n socket.on(\"disconnect\", handleDisconnect);\n socket.io.on(\"reconnect_attempt\", handleConnecting);\n\n return () => {\n socket.off(\"connect\", handleConnect);\n socket.off(\"disconnect\", handleDisconnect);\n socket.io.off(\"reconnect_attempt\", handleConnecting);\n\n disconnectPresenceSocket();\n setIsInitialized(false);\n\n if (debug) {\n console.log(\"[presence] Socket disconnected and cleaned up\");\n }\n };\n }, [url, transports, debug]);\n\n const contextValue: PresenceContextValue = {\n isInitialized,\n status,\n };\n\n return (\n <PresenceContext.Provider value={contextValue}>\n {children}\n </PresenceContext.Provider>\n );\n}\n\n/**\n * Hook to access presence connection status\n *\n * @returns Connection status object or null if not inside PresenceProvider\n *\n * @example\n * ```tsx\n * function StatusIndicator() {\n * const presence = usePresenceStatus();\n *\n * if (!presence) {\n * return <span>Not initialized</span>;\n * }\n *\n * return (\n * <span>\n * Status: {presence.status}\n * {presence.status === 'connecting' && <Spinner />}\n * </span>\n * );\n * }\n * ```\n */\nexport function usePresenceStatus(): PresenceContextValue | null {\n return useContext(PresenceContext);\n}\n\nexport default PresenceProvider;\n"],"mappings":"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","names":["normalizeUsers","import_react","import_react","import_react","import_react","getInitial","import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
|