Skip to content

Commit 3c769d7

Browse files
Adding usePresence and bumping version
1 parent 6287790 commit 3c769d7

21 files changed

Lines changed: 2269 additions & 1 deletion

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Vue-reactive modules. Use inside `<script setup>` or `setup()`.
6767
| Data fetching | usePagination, useInfiniteScroll, useLiveData, useLazyLoad |
6868
| State | useQueryState |
6969
| Auth & Chat | useAuth, useChat |
70+
| Collaboration | usePresence |
7071
| Routing | useRouter |
7172

7273
## File Uploads
@@ -256,6 +257,67 @@ router.navigate('/users/123')
256257
// Reactive: router.currentPath, router.params
257258
```
258259

260+
### usePresence
261+
262+
Real-time collaborative presence: who's here, where their cursor is, what they're focused on.
263+
264+
```javascript
265+
import { usePresence } from 'stellify-framework'
266+
267+
const { users, cursor, focus } = usePresence({
268+
channel: 'customers',
269+
user: { id: currentUser.id, name: currentUser.name },
270+
})
271+
```
272+
273+
Wire `cursor` to a `@mousemove` handler. Wire `focus(key)` to row hover or field focus. Other connected users appear in `users` with their cursor positions, focus state, and metadata.
274+
275+
```vue
276+
<template>
277+
<div @mousemove="cursor" class="relative">
278+
<div v-for="row in rows" :key="row.id"
279+
@mouseenter="focus(row.id)"
280+
@mouseleave="focus(null)">
281+
{{ row.name }}
282+
<span v-if="users.find(u => u.focus === row.id)">
283+
{{ users.find(u => u.focus === row.id).name }} viewing
284+
</span>
285+
</div>
286+
<UserCursor v-for="user in users" :key="user.id" :user="user" />
287+
</div>
288+
</template>
289+
```
290+
291+
Pairs with Laravel Reverb / Pusher on the backend. Channel name should match a presence channel defined in `routes/channels.php`.
292+
293+
**Options:**
294+
- `channel` (required) - Laravel broadcast channel name
295+
- `user` (required) - `{ id, ...metadata }` current user's identity
296+
- `autoJoin` - Join on mount (default: true)
297+
- `throttleMs` - Cursor broadcast throttle (default: 50ms)
298+
299+
**Returns:**
300+
- `users` - `ComputedRef<PresenceUser[]>` other users present (excludes self)
301+
- `self` - `Ref<PresenceUser | null>` current user's presence record
302+
- `cursor` - `(event: MouseEvent) => void` call from @mousemove
303+
- `focus` - `(key: string | null) => void` broadcast focus state
304+
- `setMeta` - `(meta: Record<string, unknown>) => void` broadcast arbitrary metadata
305+
- `isConnected` - `Ref<boolean>` WebSocket connection state
306+
- `error` - `Ref<Error | null>` connection errors
307+
- `join` / `leave` - Manual channel control
308+
309+
**PresenceUser shape:**
310+
```typescript
311+
{
312+
id: string | number
313+
joinedAt: number
314+
cursor: { x: number; y: number } | null
315+
focus: string | null
316+
meta: Record<string, unknown>
317+
// ...additional fields from user config
318+
}
319+
```
320+
259321
## Design Principles
260322

261323
1. **Chainable APIs** - Fluent method chaining for readable code
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import type { PresenceUser, PresenceOptions, CursorMessage, FocusMessage, MetaMessage } from './types';
2+
/**
3+
* Channel event handlers interface
4+
*/
5+
export interface ChannelHandlers {
6+
onSubscribed: (members: Map<string | number, PresenceUser>, self: PresenceUser) => void;
7+
onMemberAdded: (user: PresenceUser) => void;
8+
onMemberRemoved: (userId: string | number) => void;
9+
onCursor: (message: CursorMessage) => void;
10+
onFocus: (message: FocusMessage) => void;
11+
onMeta: (message: MetaMessage) => void;
12+
onConnected: () => void;
13+
onDisconnected: () => void;
14+
onError: (error: Error) => void;
15+
}
16+
/**
17+
* Presence channel manager
18+
* Handles channel subscription, message routing, and client broadcasts
19+
*/
20+
export declare class PresenceChannel {
21+
private socket;
22+
private channelName;
23+
private user;
24+
private handlers;
25+
private socketUrl;
26+
private isSubscribed;
27+
constructor(options: PresenceOptions, handlers: ChannelHandlers);
28+
/**
29+
* Auto-detect Reverb WebSocket URL
30+
*/
31+
private getDefaultSocketUrl;
32+
/**
33+
* Connect to socket and subscribe to presence channel
34+
*/
35+
connect(): Promise<void>;
36+
/**
37+
* Disconnect and cleanup
38+
*/
39+
disconnect(): void;
40+
/**
41+
* Check if connected
42+
*/
43+
isConnected(): boolean;
44+
/**
45+
* Broadcast cursor position to channel
46+
*/
47+
whisperCursor(x: number, y: number): void;
48+
/**
49+
* Broadcast focus state to channel
50+
*/
51+
whisperFocus(focus: string | null): void;
52+
/**
53+
* Broadcast meta state to channel
54+
*/
55+
whisperMeta(meta: Record<string, unknown>): void;
56+
/**
57+
* Handle socket connection opened
58+
*/
59+
private handleOpen;
60+
/**
61+
* Handle socket connection closed
62+
*/
63+
private handleClose;
64+
/**
65+
* Handle socket error
66+
*/
67+
private handleError;
68+
/**
69+
* Handle successful channel subscription
70+
*/
71+
private handleSubscribed;
72+
/**
73+
* Handle member joining channel
74+
*/
75+
private handleMemberAdded;
76+
/**
77+
* Handle member leaving channel
78+
*/
79+
private handleMemberRemoved;
80+
/**
81+
* Handle cursor broadcast from another user
82+
*/
83+
private handleCursor;
84+
/**
85+
* Handle focus broadcast from another user
86+
*/
87+
private handleFocus;
88+
/**
89+
* Handle meta broadcast from another user
90+
*/
91+
private handleMeta;
92+
/**
93+
* Create a PresenceUser from user info
94+
*/
95+
private createPresenceUser;
96+
}

0 commit comments

Comments
 (0)