y-mxgraph 0.6.14 → 0.7.0

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 CHANGED
@@ -116,53 +116,209 @@ pnpm --filter @y-mxgraph/ws-demo dev # Start client on port 5174
116
116
  └─────────────┘
117
117
  ```
118
118
 
119
- By default, the iframe provider keeps its local awareness state in sync with the parent bridge using `awareness.setLocalState()`. The parent page is treated as authoritative for user info, and the bridge forwards awareness updates between the iframe and the network provider.
119
+ ### Complete Example
120
+
121
+ #### 1. Parent Page (Server)
122
+
123
+ ```html
124
+ <!DOCTYPE html>
125
+ <html>
126
+ <head>
127
+ <title>iframe Bridge Server</title>
128
+ </head>
129
+ <body>
130
+ <div>
131
+ <label>User: <input id="username" value="Alice" /></label>
132
+ <label>Color: <input id="usercolor" type="color" value="#2563eb" /></label>
133
+ <button id="undo-btn" disabled>Undo</button>
134
+ <button id="redo-btn" disabled>Redo</button>
135
+ <span id="status">Disconnected</span>
136
+ </div>
137
+ <iframe id="editor-iframe" src="./editor.html" style="width:100%;height:80vh;border:1px solid #ccc"></iframe>
138
+
139
+ <script type="module">
140
+ import * as Y from 'yjs';
141
+ import { WebrtcProvider } from 'y-webrtc';
142
+ import { LOCAL_ORIGIN } from 'y-mxgraph';
143
+ import { IFRAME_ORIGIN } from 'y-mxgraph/iframe-bridge';
144
+ import { createIframeBridgeServer } from 'y-mxgraph/iframe-bridge/server';
145
+
146
+ const iframe = document.getElementById('editor-iframe');
147
+ const statusEl = document.getElementById('status');
148
+ const undoBtn = document.getElementById('undo-btn');
149
+ const redoBtn = document.getElementById('redo-btn');
150
+
151
+ // 1. Create Y.Doc and network provider
152
+ const doc = new Y.Doc();
153
+ const provider = new WebrtcProvider('my-collab-room', doc);
154
+ const awareness = provider.awareness;
155
+
156
+ // 2. Set user info on parent awareness (synced to iframe automatically)
157
+ const updateUserInfo = () => {
158
+ awareness.setLocalState({
159
+ user: {
160
+ name: document.getElementById('username').value,
161
+ color: document.getElementById('usercolor').value,
162
+ }
163
+ });
164
+ };
165
+ updateUserInfo();
166
+ document.getElementById('username').onchange = updateUserInfo;
167
+ document.getElementById('usercolor').onchange = updateUserInfo;
168
+
169
+ // 3. Create UndoManager with cross-iframe support
170
+ const undoManager = new Y.UndoManager(doc, {
171
+ trackedOrigins: new Set([LOCAL_ORIGIN, IFRAME_ORIGIN]),
172
+ });
173
+
174
+ // 4. Create bridge server
175
+ const bridge = createIframeBridgeServer(iframe, doc, awareness, {
176
+ undoManager,
177
+ debug: true, // Enable console logging
178
+ });
179
+
180
+ // 5. Monitor connection status
181
+ bridge.onConnect(() => {
182
+ statusEl.textContent = 'Connected';
183
+ statusEl.style.color = 'green';
184
+ });
185
+ bridge.onDisconnect(() => {
186
+ statusEl.textContent = 'Disconnected';
187
+ statusEl.style.color = 'red';
188
+ });
189
+
190
+ // 6. Monitor peer count
191
+ awareness.on('update', () => {
192
+ const count = awareness.getStates().size;
193
+ statusEl.textContent = `Connected (${count} peers)`;
194
+ });
195
+
196
+ // 7. Undo/Redo from parent page
197
+ const updateUndoRedoButtons = () => {
198
+ undoBtn.disabled = !undoManager.canUndo();
199
+ redoBtn.disabled = !undoManager.canRedo();
200
+ };
201
+ undoManager.on('stack-item-added', updateUndoRedoButtons);
202
+ undoManager.on('stack-item-popped', updateUndoRedoButtons);
203
+ undoManager.on('stack-cleared', updateUndoRedoButtons);
204
+
205
+ undoBtn.onclick = () => undoManager.canUndo() && undoManager.undo();
206
+ redoBtn.onclick = () => undoManager.canRedo() && undoManager.redo();
207
+
208
+ // 8. Cleanup on page unload
209
+ window.addEventListener('beforeunload', () => {
210
+ bridge.destroy();
211
+ provider.disconnect();
212
+ provider.destroy();
213
+ undoManager.destroy();
214
+ });
215
+ </script>
216
+ </body>
217
+ </html>
218
+ ```
120
219
 
121
- ```ts
122
- // Server (parent page)
123
- import * as Y from 'yjs';
124
- import { WebrtcProvider } from 'y-webrtc';
125
- import { LOCAL_ORIGIN } from 'y-mxgraph';
126
- import { IFRAME_ORIGIN } from 'y-mxgraph/iframe-bridge';
127
- import { createIframeBridgeServer } from 'y-mxgraph/iframe-bridge/server';
220
+ #### 2. Iframe Child (Provider)
221
+
222
+ ```html
223
+ <!DOCTYPE html>
224
+ <html>
225
+ <head>
226
+ <title>draw.io Editor</title>
227
+ </head>
228
+ <body>
229
+ <div id="drawio-container"></div>
230
+
231
+ <script type="module">
232
+ import * as Y from 'yjs';
233
+ import { Binding, LOCAL_ORIGIN } from 'y-mxgraph';
234
+ import { createIframeBridgeProvider } from 'y-mxgraph/iframe-bridge/provider';
235
+
236
+ // 1. Create local Y.Doc (no network provider needed)
237
+ const doc = new Y.Doc();
238
+
239
+ // 2. Create iframe bridge provider
240
+ // - No external awareness needed, provider creates its own AwarenessLike
241
+ // - Automatically syncs with parent via postMessage
242
+ const bridge = createIframeBridgeProvider(doc, {
243
+ debug: true, // Enable console logging
244
+ });
245
+
246
+ // 3. Monitor connection to parent
247
+ bridge.onConnect(() => {
248
+ console.log('[iframe] Connected to parent bridge');
249
+ });
250
+ bridge.onDisconnect(() => {
251
+ console.log('[iframe] Disconnected from parent bridge');
252
+ });
253
+
254
+ // 4. Initialize draw.io
255
+ App.main((app) => {
256
+ const file = app.currentFile;
257
+
258
+ // 5. Create binding with bridge awareness
259
+ const binding = new Binding(file, {
260
+ doc,
261
+ awareness: bridge.awareness,
262
+ });
263
+
264
+ // 6. Takeover draw.io's undo manager to route through parent
265
+ const cleanupUndo = bridge.takeoverUndoManager(file);
266
+
267
+ // 7. Cleanup on page unload
268
+ window.addEventListener('beforeunload', () => {
269
+ binding.destroy();
270
+ cleanupUndo();
271
+ bridge.destroy();
272
+ });
273
+ });
274
+ </script>
275
+ </body>
276
+ </html>
277
+ ```
128
278
 
129
- const doc = new Y.Doc();
130
- const provider = new WebrtcProvider(roomName, doc, { signaling });
131
- const awareness = provider.awareness;
279
+ ### Key Features
132
280
 
133
- // Optional: enable cross-iframe undo/redo
134
- const undoManager = new Y.UndoManager(doc, {
135
- trackedOrigins: new Set([LOCAL_ORIGIN, IFRAME_ORIGIN]),
136
- });
281
+ - **Automatic sync**: Y.Doc and Awareness state automatically sync between parent and iframe
282
+ - **User info propagation**: Set user info on parent awareness, iframe receives it automatically
283
+ - **Cross-iframe undo/redo**: UndoManager in parent page controls undo/redo for all iframes
284
+ - **Connection lifecycle**: `onConnect`/`onDisconnect` callbacks for status monitoring
285
+ - **Debug mode**: Set `debug: true` to log all postMessage traffic
137
286
 
138
- // Create bridge server, bound directly to the target iframe
139
- // If the UndoManager implementation supports addTrackedOrigin/removeTrackedOrigin,
140
- // the bridge will automatically add/remove IFRAME_ORIGIN for you.
141
- // If not, keep IFRAME_ORIGIN in trackedOrigins manually.
142
- const bridge = createIframeBridgeServer(iframeElement, doc, awareness, { undoManager });
287
+ ### API Reference
143
288
 
144
- // Undo/redo from parent page
145
- document.getElementById('undo-btn')!.onclick = () => {
146
- if (undoManager.canUndo()) undoManager.undo();
147
- };
289
+ #### `createIframeBridgeServer(iframe, ydoc, awareness, options?)`
148
290
 
149
- // Provider (iframe child)
150
- import * as Y from 'yjs';
151
- import { Awareness } from 'y-protocols/awareness';
152
- import { Binding } from 'y-mxgraph';
153
- import { createIframeBridgeProvider } from 'y-mxgraph/iframe-bridge/provider';
291
+ Creates a bridge server on the parent page.
154
292
 
155
- const doc = new Y.Doc();
156
- const awareness = new Awareness(doc);
157
- const bridge = createIframeBridgeProvider(doc, awareness);
293
+ **Parameters:**
294
+ - `iframe: HTMLIFrameElement` - Target iframe element
295
+ - `ydoc: Y.Doc` - Shared Yjs document
296
+ - `awareness: Awareness` - Awareness instance (usually from provider.awareness)
297
+ - `options.undoManager?: Y.UndoManager` - Optional UndoManager for cross-iframe undo
298
+ - `options.debug?: boolean` - Enable debug logging (default: false)
158
299
 
159
- App.main((app) => {
160
- const file = app.currentFile;
161
- const binding = new Binding(file, { doc, awareness });
162
- // Takeover draw.io's undo manager to route through Server
163
- bridge.takeoverUndoManager(file);
164
- });
165
- ```
300
+ **Returns:** `IframeBridgeServer` with:
301
+ - `connected: boolean` - Current connection status
302
+ - `onConnect(fn)` / `onDisconnect(fn)` - Connection lifecycle callbacks
303
+ - `destroy()` - Cleanup all listeners
304
+
305
+ #### `createIframeBridgeProvider(ydoc, options?)`
306
+
307
+ Creates a bridge provider inside the iframe.
308
+
309
+ **Parameters:**
310
+ - `ydoc: Y.Doc` - Local Yjs document
311
+ - `options.awareness?: Awareness` - Optional external Awareness (creates internal AwarenessLike if omitted)
312
+ - `options.debug?: boolean` - Enable debug logging (default: false)
313
+
314
+ **Returns:** `IframeBridgeProvider` with:
315
+ - `connected: boolean` - Connection status to parent
316
+ - `awareness: Awareness` - Awareness instance (use for Binding)
317
+ - `serverClientId: number | null` - Parent's client ID
318
+ - `setLocalFields(fields)` - Update local user fields
319
+ - `takeoverUndoManager(file)` - Route draw.io undo/redo through parent
320
+ - `onConnect(fn)` / `onDisconnect(fn)` - Connection lifecycle callbacks
321
+ - `destroy()` - Cleanup all listeners
166
322
 
167
323
  See [iframe Bridge documentation](https://mizuka-wu.github.io/y-mxgraph/en/guide/iframe-bridge) for details.
168
324
 
package/README.zh-CN.md CHANGED
@@ -78,52 +78,209 @@ App.main((app) => {
78
78
  └─────────────┘
79
79
  ```
80
80
 
81
- 默认情况下,iframe provider 会通过 `awareness.setLocalState()` 将本地 awareness state 同步到父容器桥接层。父页面的 awareness 信息被视为权威来源,桥接器会在 iframe 和网络 provider 之间转发 awareness 更新。
81
+ ### 完整示例
82
+
83
+ #### 1. 父页面(Server)
84
+
85
+ ```html
86
+ <!DOCTYPE html>
87
+ <html>
88
+ <head>
89
+ <title>iframe Bridge Server</title>
90
+ </head>
91
+ <body>
92
+ <div>
93
+ <label>用户名: <input id="username" value="Alice" /></label>
94
+ <label>颜色: <input id="usercolor" type="color" value="#2563eb" /></label>
95
+ <button id="undo-btn" disabled>撤销</button>
96
+ <button id="redo-btn" disabled>重做</button>
97
+ <span id="status">未连接</span>
98
+ </div>
99
+ <iframe id="editor-iframe" src="./editor.html" style="width:100%;height:80vh;border:1px solid #ccc"></iframe>
100
+
101
+ <script type="module">
102
+ import * as Y from 'yjs';
103
+ import { WebrtcProvider } from 'y-webrtc';
104
+ import { LOCAL_ORIGIN } from 'y-mxgraph';
105
+ import { IFRAME_ORIGIN } from 'y-mxgraph/iframe-bridge';
106
+ import { createIframeBridgeServer } from 'y-mxgraph/iframe-bridge/server';
107
+
108
+ const iframe = document.getElementById('editor-iframe');
109
+ const statusEl = document.getElementById('status');
110
+ const undoBtn = document.getElementById('undo-btn');
111
+ const redoBtn = document.getElementById('redo-btn');
112
+
113
+ // 1. 创建 Y.Doc 和网络 Provider
114
+ const doc = new Y.Doc();
115
+ const provider = new WebrtcProvider('my-collab-room', doc);
116
+ const awareness = provider.awareness;
117
+
118
+ // 2. 在父容器 awareness 上设置用户信息(自动同步到 iframe)
119
+ const updateUserInfo = () => {
120
+ awareness.setLocalState({
121
+ user: {
122
+ name: document.getElementById('username').value,
123
+ color: document.getElementById('usercolor').value,
124
+ }
125
+ });
126
+ };
127
+ updateUserInfo();
128
+ document.getElementById('username').onchange = updateUserInfo;
129
+ document.getElementById('usercolor').onchange = updateUserInfo;
130
+
131
+ // 3. 创建支持跨 iframe 的 UndoManager
132
+ const undoManager = new Y.UndoManager(doc, {
133
+ trackedOrigins: new Set([LOCAL_ORIGIN, IFRAME_ORIGIN]),
134
+ });
135
+
136
+ // 4. 创建 bridge server
137
+ const bridge = createIframeBridgeServer(iframe, doc, awareness, {
138
+ undoManager,
139
+ debug: true, // 启用控制台日志
140
+ });
141
+
142
+ // 5. 监听连接状态
143
+ bridge.onConnect(() => {
144
+ statusEl.textContent = '已连接';
145
+ statusEl.style.color = 'green';
146
+ });
147
+ bridge.onDisconnect(() => {
148
+ statusEl.textContent = '未连接';
149
+ statusEl.style.color = 'red';
150
+ });
151
+
152
+ // 6. 监听在线人数
153
+ awareness.on('update', () => {
154
+ const count = awareness.getStates().size;
155
+ statusEl.textContent = `已连接 (${count} 人在线)`;
156
+ });
157
+
158
+ // 7. 从父页面执行撤销/重做
159
+ const updateUndoRedoButtons = () => {
160
+ undoBtn.disabled = !undoManager.canUndo();
161
+ redoBtn.disabled = !undoManager.canRedo();
162
+ };
163
+ undoManager.on('stack-item-added', updateUndoRedoButtons);
164
+ undoManager.on('stack-item-popped', updateUndoRedoButtons);
165
+ undoManager.on('stack-cleared', updateUndoRedoButtons);
166
+
167
+ undoBtn.onclick = () => undoManager.canUndo() && undoManager.undo();
168
+ redoBtn.onclick = () => undoManager.canRedo() && undoManager.redo();
169
+
170
+ // 8. 页面卸载时清理
171
+ window.addEventListener('beforeunload', () => {
172
+ bridge.destroy();
173
+ provider.disconnect();
174
+ provider.destroy();
175
+ undoManager.destroy();
176
+ });
177
+ </script>
178
+ </body>
179
+ </html>
180
+ ```
82
181
 
83
- ```ts
84
- // Server(父页面)
85
- import * as Y from 'yjs';
86
- import { WebrtcProvider } from 'y-webrtc';
87
- import { LOCAL_ORIGIN } from 'y-mxgraph';
88
- import { IFRAME_ORIGIN } from 'y-mxgraph/iframe-bridge';
89
- import { createIframeBridgeServer } from 'y-mxgraph/iframe-bridge/server';
182
+ #### 2. iframe 子页面(Provider)
183
+
184
+ ```html
185
+ <!DOCTYPE html>
186
+ <html>
187
+ <head>
188
+ <title>draw.io 编辑器</title>
189
+ </head>
190
+ <body>
191
+ <div id="drawio-container"></div>
192
+
193
+ <script type="module">
194
+ import * as Y from 'yjs';
195
+ import { Binding, LOCAL_ORIGIN } from 'y-mxgraph';
196
+ import { createIframeBridgeProvider } from 'y-mxgraph/iframe-bridge/provider';
197
+
198
+ // 1. 创建本地 Y.Doc(不需要网络 Provider)
199
+ const doc = new Y.Doc();
200
+
201
+ // 2. 创建 iframe bridge provider
202
+ // - 不需要外部 awareness,provider 会创建自己的 AwarenessLike
203
+ // - 自动通过 postMessage 与父页面同步
204
+ const bridge = createIframeBridgeProvider(doc, {
205
+ debug: true, // 启用控制台日志
206
+ });
207
+
208
+ // 3. 监听与父页面的连接状态
209
+ bridge.onConnect(() => {
210
+ console.log('[iframe] 已连接到父页面 bridge');
211
+ });
212
+ bridge.onDisconnect(() => {
213
+ console.log('[iframe] 已断开与父页面 bridge 的连接');
214
+ });
215
+
216
+ // 4. 初始化 draw.io
217
+ App.main((app) => {
218
+ const file = app.currentFile;
219
+
220
+ // 5. 使用 bridge awareness 创建 binding
221
+ const binding = new Binding(file, {
222
+ doc,
223
+ awareness: bridge.awareness,
224
+ });
225
+
226
+ // 6. 接管 draw.io 的 undo manager,使其通过父页面执行
227
+ const cleanupUndo = bridge.takeoverUndoManager(file);
228
+
229
+ // 7. 页面卸载时清理
230
+ window.addEventListener('beforeunload', () => {
231
+ binding.destroy();
232
+ cleanupUndo();
233
+ bridge.destroy();
234
+ });
235
+ });
236
+ </script>
237
+ </body>
238
+ </html>
239
+ ```
90
240
 
91
- const doc = new Y.Doc();
92
- const provider = new WebrtcProvider(roomName, doc, { signaling });
93
- const awareness = provider.awareness;
241
+ ### 核心特性
94
242
 
95
- // 可选:启用跨 iframe 撤销/重做
96
- const undoManager = new Y.UndoManager(doc, {
97
- trackedOrigins: new Set([LOCAL_ORIGIN, IFRAME_ORIGIN]),
98
- });
243
+ - **自动同步**: Y.Doc 和 Awareness 状态在父页面和 iframe 之间自动同步
244
+ - **用户信息传播**: 在父页面 awareness 上设置用户信息,iframe 自动接收
245
+ - **跨 iframe 撤销/重做**: 父页面的 UndoManager 控制所有 iframe 的撤销/重做
246
+ - **连接生命周期**: `onConnect`/`onDisconnect` 回调用于状态监控
247
+ - **调试模式**: 设置 `debug: true` 记录所有 postMessage 通信
99
248
 
100
- // 创建 bridge server,直接绑定到目标 iframe。
101
- // 如果 UndoManager 支持 addTrackedOrigin/removeTrackedOrigin,桥接会自动管理 IFRAME_ORIGIN。
102
- // 如果不支持,请继续在 trackedOrigins 中保留 IFRAME_ORIGIN。
103
- const bridge = createIframeBridgeServer(iframeElement, doc, awareness, { undoManager });
249
+ ### API 参考
104
250
 
105
- // 从父页面执行撤销/重做
106
- document.getElementById('undo-btn')!.onclick = () => {
107
- if (undoManager.canUndo()) undoManager.undo();
108
- };
251
+ #### `createIframeBridgeServer(iframe, ydoc, awareness, options?)`
109
252
 
110
- // Provider(iframe 子页面)
111
- import * as Y from 'yjs';
112
- import { Awareness } from 'y-protocols/awareness';
113
- import { Binding } from 'y-mxgraph';
114
- import { createIframeBridgeProvider } from 'y-mxgraph/iframe-bridge/provider';
253
+ 在父页面创建 bridge server。
115
254
 
116
- const doc = new Y.Doc();
117
- const awareness = new Awareness(doc);
118
- const bridge = createIframeBridgeProvider(doc, awareness);
255
+ **参数:**
256
+ - `iframe: HTMLIFrameElement` - 目标 iframe 元素
257
+ - `ydoc: Y.Doc` - 共享的 Yjs 文档
258
+ - `awareness: Awareness` - Awareness 实例(通常来自 provider.awareness)
259
+ - `options.undoManager?: Y.UndoManager` - 可选的 UndoManager,用于跨 iframe 撤销
260
+ - `options.debug?: boolean` - 启用调试日志(默认:false)
119
261
 
120
- App.main((app) => {
121
- const file = app.currentFile;
122
- const binding = new Binding(file, { doc, awareness });
123
- // 接管 draw.io 的 undo manager,使其通过 Server 执行
124
- bridge.takeoverUndoManager(file);
125
- });
126
- ```
262
+ **返回:** `IframeBridgeServer`,包含:
263
+ - `connected: boolean` - 当前连接状态
264
+ - `onConnect(fn)` / `onDisconnect(fn)` - 连接生命周期回调
265
+ - `destroy()` - 清理所有监听器
266
+
267
+ #### `createIframeBridgeProvider(ydoc, options?)`
268
+
269
+ 在 iframe 内创建 bridge provider。
270
+
271
+ **参数:**
272
+ - `ydoc: Y.Doc` - 本地 Yjs 文档
273
+ - `options.awareness?: Awareness` - 可选的外部 Awareness(省略则创建内部 AwarenessLike)
274
+ - `options.debug?: boolean` - 启用调试日志(默认:false)
275
+
276
+ **返回:** `IframeBridgeProvider`,包含:
277
+ - `connected: boolean` - 与父页面的连接状态
278
+ - `awareness: Awareness` - Awareness 实例(用于 Binding)
279
+ - `serverClientId: number | null` - 父页面的 client ID
280
+ - `setLocalFields(fields)` - 更新本地用户字段
281
+ - `takeoverUndoManager(file)` - 接管 draw.io 的 undo/redo 使其通过父页面执行
282
+ - `onConnect(fn)` / `onDisconnect(fn)` - 连接生命周期回调
283
+ - `destroy()` - 清理所有监听器
127
284
 
128
285
  详见 [iframe Bridge 文档](https://mizuka-wu.github.io/y-mxgraph/guide/iframe-bridge)。
129
286