@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.
@@ -0,0 +1,459 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/presence/index.ts
21
+ var presence_exports = {};
22
+ __export(presence_exports, {
23
+ disconnectPresenceSocket: () => disconnectPresenceSocket,
24
+ getPresenceSocket: () => getPresenceSocket,
25
+ initPresenceSocket: () => initPresenceSocket,
26
+ isPresenceSocketInitialized: () => isPresenceSocketInitialized,
27
+ usePresence: () => usePresence,
28
+ usePresenceAvatarsState: () => usePresenceAvatarsState,
29
+ usePresenceFloatingState: () => usePresenceFloatingState,
30
+ usePresenceWatch: () => usePresenceWatch
31
+ });
32
+ module.exports = __toCommonJS(presence_exports);
33
+
34
+ // src/presence/core/socket.ts
35
+ var import_socket = require("socket.io-client");
36
+ var config = null;
37
+ var sharedSocket = null;
38
+ function initPresenceSocket(presenceConfig) {
39
+ config = presenceConfig;
40
+ if (sharedSocket) {
41
+ sharedSocket.disconnect();
42
+ sharedSocket = null;
43
+ }
44
+ }
45
+ function getPresenceSocket() {
46
+ if (!config) {
47
+ throw new Error(
48
+ "[presence-module] Socket not initialized. Call initPresenceSocket() first."
49
+ );
50
+ }
51
+ if (sharedSocket && sharedSocket.connected) {
52
+ return sharedSocket;
53
+ }
54
+ if (!sharedSocket) {
55
+ sharedSocket = (0, import_socket.io)(config.url, {
56
+ transports: config.transports ?? ["websocket"]
57
+ });
58
+ } else if (!sharedSocket.connected) {
59
+ sharedSocket.connect();
60
+ }
61
+ return sharedSocket;
62
+ }
63
+ function disconnectPresenceSocket() {
64
+ if (sharedSocket) {
65
+ sharedSocket.disconnect();
66
+ sharedSocket = null;
67
+ }
68
+ }
69
+ function isPresenceSocketInitialized() {
70
+ return config !== null;
71
+ }
72
+
73
+ // src/presence/hooks/usePresence.ts
74
+ var import_react = require("react");
75
+ var DEFAULT_HEARTBEAT_INTERVAL = 10 * 60 * 1e3;
76
+ function usePresence(options) {
77
+ const {
78
+ roomId,
79
+ currentUser,
80
+ enabled = true,
81
+ heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL
82
+ } = options;
83
+ const [users, setUsers] = (0, import_react.useState)([]);
84
+ const normalizeUsers2 = (0, import_react.useCallback)((incoming) => {
85
+ if (Array.isArray(incoming)) {
86
+ return incoming;
87
+ }
88
+ if (incoming && typeof incoming === "object" && Array.isArray(incoming.users)) {
89
+ return incoming.users;
90
+ }
91
+ return [];
92
+ }, []);
93
+ (0, import_react.useEffect)(() => {
94
+ if (!enabled) {
95
+ setUsers([]);
96
+ }
97
+ }, [enabled]);
98
+ (0, import_react.useEffect)(() => {
99
+ if (!enabled) {
100
+ return;
101
+ }
102
+ const socket = getPresenceSocket();
103
+ const joinRoom = () => {
104
+ socket.emit("presence:join", { roomId, user: currentUser });
105
+ };
106
+ if (socket.connected) {
107
+ joinRoom();
108
+ }
109
+ socket.on("connect", joinRoom);
110
+ const heartbeat = () => {
111
+ socket.emit("presence:heartbeat", { roomId });
112
+ };
113
+ heartbeat();
114
+ const heartbeatIntervalId = window.setInterval(
115
+ heartbeat,
116
+ heartbeatInterval
117
+ );
118
+ const handlePresenceUpdate = (incoming) => {
119
+ setUsers(normalizeUsers2(incoming));
120
+ };
121
+ socket.on("presence:update", handlePresenceUpdate);
122
+ const handleBeforeUnload = () => {
123
+ socket.emit("presence:leave", { roomId, userId: currentUser.userId });
124
+ };
125
+ window.addEventListener("beforeunload", handleBeforeUnload);
126
+ return () => {
127
+ socket.off("connect", joinRoom);
128
+ socket.off("presence:update", handlePresenceUpdate);
129
+ window.removeEventListener("beforeunload", handleBeforeUnload);
130
+ window.clearInterval(heartbeatIntervalId);
131
+ socket.emit("presence:leave", { roomId, userId: currentUser.userId });
132
+ };
133
+ }, [
134
+ roomId,
135
+ currentUser.userId,
136
+ currentUser.name,
137
+ enabled,
138
+ heartbeatInterval,
139
+ normalizeUsers2
140
+ ]);
141
+ return users;
142
+ }
143
+
144
+ // src/presence/hooks/usePresenceWatch.ts
145
+ var import_react2 = require("react");
146
+ function normalizeUsers(value) {
147
+ if (Array.isArray(value)) return value;
148
+ if (value && typeof value === "object" && Array.isArray(value.users)) {
149
+ return value.users;
150
+ }
151
+ return [];
152
+ }
153
+ function usePresenceWatch(options) {
154
+ const { roomIds, enabled = true } = options;
155
+ const [roomsUsers, setRoomsUsers] = (0, import_react2.useState)(
156
+ {}
157
+ );
158
+ const normalizedRoomIds = (0, import_react2.useMemo)(() => {
159
+ const uniq = Array.from(new Set((roomIds ?? []).filter(Boolean)));
160
+ return uniq;
161
+ }, [roomIds]);
162
+ const watchedRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
163
+ (0, import_react2.useEffect)(() => {
164
+ if (!enabled) {
165
+ setRoomsUsers({});
166
+ watchedRef.current = /* @__PURE__ */ new Set();
167
+ return;
168
+ }
169
+ const socket = getPresenceSocket();
170
+ const toWatch = normalizedRoomIds.filter((r) => !watchedRef.current.has(r));
171
+ const toUnwatch = Array.from(watchedRef.current).filter(
172
+ (r) => !normalizedRoomIds.includes(r)
173
+ );
174
+ if (toWatch.length > 0) {
175
+ socket.emit("presence:watch", { roomIds: toWatch });
176
+ for (const r of toWatch) watchedRef.current.add(r);
177
+ }
178
+ if (toUnwatch.length > 0) {
179
+ socket.emit("presence:unwatch", { roomIds: toUnwatch });
180
+ for (const r of toUnwatch) watchedRef.current.delete(r);
181
+ setRoomsUsers((prev) => {
182
+ const next = { ...prev };
183
+ for (const r of toUnwatch) delete next[r];
184
+ return next;
185
+ });
186
+ }
187
+ }, [enabled, normalizedRoomIds]);
188
+ (0, import_react2.useEffect)(() => {
189
+ if (!enabled) return;
190
+ const socket = getPresenceSocket();
191
+ const handleSnapshot = (payload) => {
192
+ const rooms = payload?.rooms ?? {};
193
+ setRoomsUsers((prev) => {
194
+ const next = { ...prev };
195
+ for (const [roomId, users] of Object.entries(rooms)) {
196
+ next[roomId] = normalizeUsers(users);
197
+ }
198
+ return next;
199
+ });
200
+ };
201
+ const handleUpdate = (payload) => {
202
+ if (!payload || typeof payload !== "object") return;
203
+ const { roomId, users } = payload;
204
+ if (!roomId) return;
205
+ if (!watchedRef.current.has(roomId)) return;
206
+ setRoomsUsers((prev) => ({ ...prev, [roomId]: normalizeUsers(users) }));
207
+ };
208
+ socket.on("presence:snapshot", handleSnapshot);
209
+ socket.on("presence:update", handleUpdate);
210
+ const handleConnect = () => {
211
+ const ids = Array.from(watchedRef.current);
212
+ if (ids.length > 0) socket.emit("presence:watch", { roomIds: ids });
213
+ };
214
+ socket.on("connect", handleConnect);
215
+ return () => {
216
+ socket.off("presence:snapshot", handleSnapshot);
217
+ socket.off("presence:update", handleUpdate);
218
+ socket.off("connect", handleConnect);
219
+ const ids = Array.from(watchedRef.current);
220
+ if (ids.length > 0) socket.emit("presence:unwatch", { roomIds: ids });
221
+ watchedRef.current = /* @__PURE__ */ new Set();
222
+ };
223
+ }, [enabled]);
224
+ return roomsUsers;
225
+ }
226
+
227
+ // src/presence/headless/usePresenceAvatarsState.ts
228
+ var import_react3 = require("react");
229
+ function getInitialFromUser(user) {
230
+ const base = (user?.name || user?.userId || "").trim();
231
+ if (!base) return "?";
232
+ const ch = base.charAt(0);
233
+ return /[a-z]/i.test(ch) ? ch.toUpperCase() : ch;
234
+ }
235
+ function usePresenceAvatarsState(options) {
236
+ const { users, maxVisible = 3 } = options;
237
+ const safeUsers = Array.isArray(users) ? users : [];
238
+ const visibleUsers = (0, import_react3.useMemo)(
239
+ () => safeUsers.slice(0, maxVisible),
240
+ [safeUsers, maxVisible]
241
+ );
242
+ const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);
243
+ const [hoveredIndex, setHoveredIndex] = (0, import_react3.useState)(null);
244
+ const hoveredUser = hoveredIndex != null ? visibleUsers[hoveredIndex] : null;
245
+ const baseTopZ = visibleUsers.length + 1;
246
+ const getInitial = (0, import_react3.useCallback)((user) => {
247
+ return getInitialFromUser(user);
248
+ }, []);
249
+ const getZIndex = (0, import_react3.useCallback)(
250
+ (index, isHovered) => {
251
+ return isHovered ? 100 : baseTopZ - index;
252
+ },
253
+ [baseTopZ]
254
+ );
255
+ return {
256
+ visibleUsers,
257
+ moreCount,
258
+ hoveredUser,
259
+ hoveredIndex,
260
+ setHoveredIndex,
261
+ getInitial,
262
+ getZIndex
263
+ };
264
+ }
265
+
266
+ // src/presence/headless/usePresenceFloatingState.ts
267
+ var import_react4 = require("react");
268
+ function usePresenceFloatingState(options) {
269
+ const { users, maxVisible = 8, initialPosition = { top: 166 } } = options;
270
+ const containerRef = (0, import_react4.useRef)(null);
271
+ const [dragging, setDragging] = (0, import_react4.useState)(false);
272
+ const [hasDragged, setHasDragged] = (0, import_react4.useState)(false);
273
+ const [dragOffset, setDragOffset] = (0, import_react4.useState)({
274
+ x: 0,
275
+ y: 0
276
+ });
277
+ const [position, setPosition] = (0, import_react4.useState)({
278
+ top: initialPosition.top,
279
+ left: 0
280
+ });
281
+ const [hoveredUser, setHoveredUser] = (0, import_react4.useState)(null);
282
+ const [tooltipTop, setTooltipTop] = (0, import_react4.useState)(0);
283
+ const safeUsers = Array.isArray(users) ? users : [];
284
+ const visibleUsers = safeUsers.slice(0, maxVisible);
285
+ const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);
286
+ (0, import_react4.useEffect)(() => {
287
+ if (!hasDragged) return;
288
+ const el = containerRef.current;
289
+ if (!el) return;
290
+ const adjustPosition = () => {
291
+ const rect = el.getBoundingClientRect();
292
+ const maxLeft = window.innerWidth - rect.width;
293
+ const maxTop = window.innerHeight - rect.height - 8;
294
+ setPosition((prev) => ({
295
+ left: Math.min(Math.max(0, prev.left), Math.max(0, maxLeft)),
296
+ top: Math.min(Math.max(8, prev.top), Math.max(8, maxTop))
297
+ }));
298
+ };
299
+ const resizeObserver = new ResizeObserver(() => {
300
+ adjustPosition();
301
+ });
302
+ resizeObserver.observe(el);
303
+ window.addEventListener("resize", adjustPosition);
304
+ return () => {
305
+ resizeObserver.disconnect();
306
+ window.removeEventListener("resize", adjustPosition);
307
+ };
308
+ }, [hasDragged]);
309
+ const getEventCoordinates = (0, import_react4.useCallback)(
310
+ (e) => {
311
+ if ("touches" in e && e.touches.length > 0) {
312
+ return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY };
313
+ }
314
+ if ("changedTouches" in e && e.changedTouches.length > 0) {
315
+ return {
316
+ clientX: e.changedTouches[0].clientX,
317
+ clientY: e.changedTouches[0].clientY
318
+ };
319
+ }
320
+ if ("clientX" in e) {
321
+ return { clientX: e.clientX, clientY: e.clientY };
322
+ }
323
+ return { clientX: 0, clientY: 0 };
324
+ },
325
+ []
326
+ );
327
+ const onMouseDownHeader = (0, import_react4.useCallback)(
328
+ (e) => {
329
+ const el = containerRef.current;
330
+ if (!el) return;
331
+ const rect = el.getBoundingClientRect();
332
+ if (!hasDragged) {
333
+ setHasDragged(true);
334
+ setPosition({
335
+ top: rect.top,
336
+ left: rect.left
337
+ });
338
+ }
339
+ setDragOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top });
340
+ setDragging(true);
341
+ e.preventDefault();
342
+ },
343
+ [hasDragged]
344
+ );
345
+ const onTouchStartHeader = (0, import_react4.useCallback)(
346
+ (e) => {
347
+ const el = containerRef.current;
348
+ if (!el) return;
349
+ const rect = el.getBoundingClientRect();
350
+ const { clientX, clientY } = getEventCoordinates(e);
351
+ if (!hasDragged) {
352
+ setHasDragged(true);
353
+ setPosition({
354
+ top: rect.top,
355
+ left: rect.left
356
+ });
357
+ }
358
+ setDragOffset({ x: clientX - rect.left, y: clientY - rect.top });
359
+ setDragging(true);
360
+ },
361
+ [hasDragged, getEventCoordinates]
362
+ );
363
+ (0, import_react4.useEffect)(() => {
364
+ if (!dragging) return;
365
+ const el = containerRef.current;
366
+ const onMove = (e) => {
367
+ if (!el) return;
368
+ const rect = el.getBoundingClientRect();
369
+ const { clientX, clientY } = getEventCoordinates(e);
370
+ const newLeft = clientX - dragOffset.x;
371
+ const newTop = clientY - dragOffset.y;
372
+ const maxLeft = window.innerWidth - rect.width;
373
+ const maxTop = window.innerHeight - rect.height - 8;
374
+ setPosition({
375
+ left: Math.min(Math.max(0, newLeft), Math.max(0, maxLeft)),
376
+ top: Math.min(Math.max(8, newTop), Math.max(8, maxTop))
377
+ });
378
+ if ("touches" in e) {
379
+ e.preventDefault();
380
+ }
381
+ };
382
+ const onUp = () => setDragging(false);
383
+ window.addEventListener("mousemove", onMove);
384
+ window.addEventListener("mouseup", onUp, { once: true });
385
+ window.addEventListener("touchmove", onMove, { passive: false });
386
+ window.addEventListener("touchend", onUp, { once: true });
387
+ window.addEventListener("touchcancel", onUp, { once: true });
388
+ return () => {
389
+ window.removeEventListener("mousemove", onMove);
390
+ window.removeEventListener("mouseup", onUp);
391
+ window.removeEventListener("touchmove", onMove);
392
+ window.removeEventListener("touchend", onUp);
393
+ window.removeEventListener("touchcancel", onUp);
394
+ };
395
+ }, [dragging, dragOffset.x, dragOffset.y, getEventCoordinates]);
396
+ const inlineStyle = (0, import_react4.useMemo)(() => {
397
+ if (hasDragged) {
398
+ return {
399
+ position: "fixed",
400
+ top: position.top,
401
+ left: position.left,
402
+ right: "auto"
403
+ };
404
+ } else {
405
+ return {
406
+ position: "fixed",
407
+ top: position.top,
408
+ right: 0,
409
+ left: "auto"
410
+ };
411
+ }
412
+ }, [position.top, position.left, hasDragged]);
413
+ const onAvatarEnter = (0, import_react4.useCallback)(
414
+ (e, user) => {
415
+ const container = containerRef.current;
416
+ if (!container) {
417
+ setHoveredUser(user);
418
+ setTooltipTop(0);
419
+ return;
420
+ }
421
+ const containerRect = container.getBoundingClientRect();
422
+ const targetRect = e.currentTarget.getBoundingClientRect();
423
+ const topWithinContainer = targetRect.top - containerRect.top;
424
+ setHoveredUser(user);
425
+ setTooltipTop(topWithinContainer);
426
+ },
427
+ []
428
+ );
429
+ const onAvatarLeave = (0, import_react4.useCallback)(() => {
430
+ setHoveredUser(null);
431
+ }, []);
432
+ return {
433
+ containerRef,
434
+ position,
435
+ isDragging: dragging,
436
+ hasDragged,
437
+ inlineStyle,
438
+ visibleUsers,
439
+ moreCount,
440
+ hoveredUser,
441
+ tooltipTop,
442
+ onMouseDownHeader,
443
+ onTouchStartHeader,
444
+ onAvatarEnter,
445
+ onAvatarLeave
446
+ };
447
+ }
448
+ // Annotate the CommonJS export names for ESM import in node:
449
+ 0 && (module.exports = {
450
+ disconnectPresenceSocket,
451
+ getPresenceSocket,
452
+ initPresenceSocket,
453
+ isPresenceSocketInitialized,
454
+ usePresence,
455
+ usePresenceAvatarsState,
456
+ usePresenceFloatingState,
457
+ usePresenceWatch
458
+ });
459
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"],"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} 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","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","import_react","import_react","import_react"]}