0
# Event System
1
2
Comprehensive event handling for observing changes to shared types with detailed change information. The event system provides fine-grained notifications about all modifications to shared data structures.
3
4
## Capabilities
5
6
### Base YEvent Class
7
8
Foundation class for all Yjs events providing common change information.
9
10
```typescript { .api }
11
/**
12
* Base event class for all Yjs type changes
13
*/
14
abstract class YEvent<T> {
15
/** The type that was changed */
16
readonly target: T;
17
18
/** Current event target (may differ from target in event bubbling) */
19
readonly currentTarget: AbstractType<any>;
20
21
/** Transaction that triggered this event */
22
readonly transaction: Transaction;
23
24
/** Path from document root to the changed type */
25
readonly path: Array<string | number>;
26
27
/** Detailed information about all changes in this event */
28
readonly changes: {
29
added: Set<Item>;
30
deleted: Set<Item>;
31
keys: Map<string, { action: 'add' | 'update' | 'delete'; oldValue: any }>;
32
delta: Array<any>;
33
};
34
35
/** Changes in Quill Delta format */
36
readonly delta: Array<any>;
37
38
/** Map of changed keys with their change types and old values */
39
readonly keys: Map<string, { action: 'add' | 'update' | 'delete'; oldValue: any }>;
40
}
41
```
42
43
**Event Methods:**
44
45
```typescript { .api }
46
/**
47
* Check if a struct was added in this event
48
* @param struct - Struct to check
49
* @returns True if struct was added
50
*/
51
adds(struct: AbstractStruct): boolean;
52
53
/**
54
* Check if a struct was deleted in this event
55
* @param struct - Struct to check
56
* @returns True if struct was deleted
57
*/
58
deletes(struct: AbstractStruct): boolean;
59
```
60
61
### AbstractType Observer Methods
62
63
All shared types inherit observer methods for event handling.
64
65
```typescript { .api }
66
/**
67
* Observer methods available on all AbstractType instances
68
*/
69
interface AbstractType<EventType> {
70
/**
71
* Add an observer for direct changes to this type
72
* @param f - Observer function to call on changes
73
*/
74
observe(f: (event: EventType, transaction: Transaction) => void): void;
75
76
/**
77
* Remove an observer for direct changes
78
* @param f - Observer function to remove
79
*/
80
unobserve(f: (event: EventType, transaction: Transaction) => void): void;
81
82
/**
83
* Add an observer for changes to this type or any nested types
84
* @param f - Observer function to call on deep changes
85
*/
86
observeDeep(f: (events: Array<YEvent<any>>, transaction: Transaction) => void): void;
87
88
/**
89
* Remove a deep observer
90
* @param f - Deep observer function to remove
91
*/
92
unobserveDeep(f: (events: Array<YEvent<any>>, transaction: Transaction) => void): void;
93
}
94
```
95
96
### YArrayEvent
97
98
Array-specific event providing detailed information about array changes.
99
100
```typescript { .api }
101
/**
102
* Event fired when YArray changes
103
*/
104
class YArrayEvent extends YEvent<YArray<any>> {
105
/** The YArray that changed */
106
readonly target: YArray<any>;
107
108
/** Changes in Delta format showing insertions and deletions */
109
readonly changes: {
110
added: Set<Item>;
111
deleted: Set<Item>;
112
delta: Array<{ retain?: number; insert?: any[]; delete?: number }>;
113
};
114
}
115
```
116
117
**Usage Examples:**
118
119
```typescript
120
import * as Y from "yjs";
121
122
const doc = new Y.Doc();
123
const yarray = doc.getArray<string>("fruits");
124
125
// Observe array changes
126
yarray.observe((event, transaction) => {
127
console.log("Array changed by:", transaction.origin);
128
129
// Process delta changes
130
event.changes.delta.forEach((change) => {
131
if (change.insert) {
132
console.log("Inserted:", change.insert);
133
}
134
if (change.delete) {
135
console.log("Deleted:", change.delete, "items");
136
}
137
if (change.retain) {
138
console.log("Retained:", change.retain, "items");
139
}
140
});
141
142
// Check specific items
143
event.changes.added.forEach((item) => {
144
console.log("Added item at index:", item.index);
145
});
146
});
147
148
// Trigger changes
149
yarray.push(["apple", "banana"]);
150
yarray.delete(0, 1);
151
```
152
153
### YMapEvent
154
155
Map-specific event providing information about key-value changes.
156
157
```typescript { .api }
158
/**
159
* Event fired when YMap changes
160
*/
161
class YMapEvent extends YEvent<YMap<any>> {
162
/** The YMap that changed */
163
readonly target: YMap<any>;
164
165
/** Set of keys that changed in this event */
166
readonly keysChanged: Set<string>;
167
168
/** Detailed key changes with actions and old values */
169
readonly keys: Map<string, { action: 'add' | 'update' | 'delete'; oldValue: any }>;
170
}
171
```
172
173
**Usage Examples:**
174
175
```typescript
176
import * as Y from "yjs";
177
178
const doc = new Y.Doc();
179
const ymap = doc.getMap<any>("settings");
180
181
// Observe map changes
182
ymap.observe((event, transaction) => {
183
console.log("Map changed, keys affected:", Array.from(event.keysChanged));
184
185
// Process each changed key
186
event.keys.forEach((change, key) => {
187
switch (change.action) {
188
case 'add':
189
console.log(`Added key "${key}" with value:`, ymap.get(key));
190
break;
191
case 'update':
192
console.log(`Updated key "${key}" from:`, change.oldValue, "to:", ymap.get(key));
193
break;
194
case 'delete':
195
console.log(`Deleted key "${key}" with old value:`, change.oldValue);
196
break;
197
}
198
});
199
});
200
201
// Trigger changes
202
ymap.set("theme", "dark");
203
ymap.set("theme", "light"); // Update
204
ymap.delete("theme"); // Delete
205
```
206
207
### YTextEvent
208
209
Text-specific event providing information about text and formatting changes.
210
211
```typescript { .api }
212
/**
213
* Event fired when YText changes
214
*/
215
class YTextEvent extends YEvent<YText> {
216
/** The YText that changed */
217
readonly target: YText;
218
219
/** Whether child list (content) changed */
220
readonly childListChanged: boolean;
221
222
/** Set of attribute keys that changed */
223
readonly keysChanged: Set<string>;
224
225
/** Changes in Delta format for text operations */
226
readonly changes: {
227
added: Set<Item>;
228
deleted: Set<Item>;
229
keys: Map<string, { action: 'add' | 'update' | 'delete'; oldValue: any }>;
230
delta: Array<any>;
231
};
232
}
233
```
234
235
**Usage Examples:**
236
237
```typescript
238
import * as Y from "yjs";
239
240
const doc = new Y.Doc();
241
const ytext = doc.getText("document");
242
243
// Observe text changes
244
ytext.observe((event, transaction) => {
245
if (event.childListChanged) {
246
console.log("Text content changed");
247
248
// Process delta for text changes
249
event.changes.delta.forEach((op) => {
250
if (op.retain) {
251
console.log(`Retained ${op.retain} characters`);
252
}
253
if (op.insert) {
254
console.log(`Inserted: "${op.insert}"`);
255
if (op.attributes) {
256
console.log("With attributes:", op.attributes);
257
}
258
}
259
if (op.delete) {
260
console.log(`Deleted ${op.delete} characters`);
261
}
262
});
263
}
264
265
if (event.keysChanged.size > 0) {
266
console.log("Text attributes changed:", Array.from(event.keysChanged));
267
}
268
});
269
270
// Trigger changes
271
ytext.insert(0, "Hello");
272
ytext.insert(5, " World", { bold: true });
273
ytext.format(0, 5, { italic: true });
274
```
275
276
### YXmlEvent
277
278
XML-specific event providing information about XML structure and attribute changes.
279
280
```typescript { .api }
281
/**
282
* Event fired when XML types change
283
*/
284
class YXmlEvent extends YEvent<YXmlElement | YXmlText | YXmlFragment> {
285
/** The XML type that changed */
286
readonly target: YXmlElement | YXmlText | YXmlFragment;
287
288
/** Whether child elements changed */
289
readonly childListChanged: boolean;
290
291
/** Set of attributes that changed (for YXmlElement) */
292
readonly attributesChanged: Set<string>;
293
}
294
```
295
296
**Usage Examples:**
297
298
```typescript
299
import * as Y from "yjs";
300
301
const doc = new Y.Doc();
302
const xmlFragment = doc.getXmlFragment("document");
303
304
// Observe XML changes
305
xmlFragment.observeDeep((events, transaction) => {
306
events.forEach((event) => {
307
if (event instanceof Y.YXmlEvent) {
308
console.log("XML changed:", event.target.constructor.name);
309
310
if (event.childListChanged) {
311
console.log("Child elements changed");
312
}
313
314
if (event.attributesChanged.size > 0) {
315
console.log("Attributes changed:", Array.from(event.attributesChanged));
316
}
317
}
318
});
319
});
320
321
// Create XML structure
322
const element = new Y.XmlElement("div");
323
element.setAttribute("class", "container");
324
xmlFragment.push([element]);
325
```
326
327
### Deep Observation
328
329
Deep observers receive events from nested types and provide comprehensive change tracking.
330
331
```typescript { .api }
332
/**
333
* Deep observer function signature
334
*/
335
type DeepObserver = (events: Array<YEvent<any>>, transaction: Transaction) => void;
336
```
337
338
**Usage Examples:**
339
340
```typescript
341
import * as Y from "yjs";
342
343
const doc = new Y.Doc();
344
const rootMap = doc.getMap("root");
345
346
// Set up nested structure
347
const nestedArray = new Y.Array();
348
const nestedMap = new Y.Map();
349
rootMap.set("array", nestedArray);
350
rootMap.set("map", nestedMap);
351
352
// Deep observer catches all nested changes
353
rootMap.observeDeep((events, transaction) => {
354
console.log(`Received ${events.length} events in transaction`);
355
356
events.forEach((event, index) => {
357
console.log(`Event ${index}:`);
358
console.log(" Type:", event.target.constructor.name);
359
console.log(" Path:", event.path);
360
361
if (event instanceof Y.YArrayEvent) {
362
console.log(" Array delta:", event.changes.delta);
363
} else if (event instanceof Y.YMapEvent) {
364
console.log(" Map keys changed:", Array.from(event.keysChanged));
365
}
366
});
367
});
368
369
// Changes to nested types trigger deep observer
370
nestedArray.push(["item1", "item2"]);
371
nestedMap.set("key", "value");
372
```
373
374
### Event Performance Patterns
375
376
**Efficient Event Handling:**
377
378
```typescript
379
import * as Y from "yjs";
380
381
const doc = new Y.Doc();
382
const yarray = doc.getArray("items");
383
384
// Efficient: Process changes in batches
385
yarray.observe((event) => {
386
// Single event handler processes all changes at once
387
const insertedItems = [];
388
const deletedCount = event.changes.delta.reduce((count, op) => {
389
if (op.insert) insertedItems.push(...op.insert);
390
if (op.delete) count += op.delete;
391
return count;
392
}, 0);
393
394
console.log(`Batch: +${insertedItems.length}, -${deletedCount}`);
395
});
396
397
// Multiple operations in single transaction = single event
398
doc.transact(() => {
399
yarray.push(["a", "b", "c"]);
400
yarray.delete(0, 1);
401
yarray.push(["d"]);
402
});
403
```
404
405
**Event Filtering:**
406
407
```typescript
408
import * as Y from "yjs";
409
410
const doc = new Y.Doc();
411
const ytext = doc.getText("document");
412
413
// Filter events by origin
414
ytext.observe((event, transaction) => {
415
// Only process user-initiated changes
416
if (transaction.origin === "user-input") {
417
console.log("Processing user input:", event.changes.delta);
418
}
419
});
420
421
// Filter by change type
422
ytext.observe((event) => {
423
const hasTextChanges = event.changes.delta.some(op => op.insert || op.delete);
424
const hasFormatChanges = event.changes.delta.some(op => op.attributes);
425
426
if (hasTextChanges) {
427
console.log("Text content changed");
428
}
429
if (hasFormatChanges) {
430
console.log("Formatting changed");
431
}
432
});
433
```