ckeditor5-blazor 1.9.0 → 1.10.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/dist/elements/editable.d.ts +3 -11
- package/dist/elements/editable.d.ts.map +1 -1
- package/dist/elements/editor/editor.d.ts.map +1 -1
- package/dist/elements/editor/typings.d.ts +2 -1
- package/dist/elements/editor/typings.d.ts.map +1 -1
- package/dist/elements/editor/utils/cleanup-orphan-editor-elements.d.ts +8 -0
- package/dist/elements/editor/utils/cleanup-orphan-editor-elements.d.ts.map +1 -0
- package/dist/elements/editor/utils/create-editor-in-context.d.ts +6 -1
- package/dist/elements/editor/utils/create-editor-in-context.d.ts.map +1 -1
- package/dist/elements/editor/utils/index.d.ts +1 -0
- package/dist/elements/editor/utils/index.d.ts.map +1 -1
- package/dist/elements/editor/utils/wrap-with-watchdog.d.ts +7 -16
- package/dist/elements/editor/utils/wrap-with-watchdog.d.ts.map +1 -1
- package/dist/elements/ui-part.d.ts +3 -3
- package/dist/elements/ui-part.d.ts.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +459 -394
- package/dist/index.mjs.map +1 -1
- package/dist/interop/create-editable-blazor-interop.d.ts.map +1 -1
- package/dist/interop/create-editor-blazor-interop.d.ts.map +1 -1
- package/dist/shared/are-maps-equal.d.ts +11 -0
- package/dist/shared/are-maps-equal.d.ts.map +1 -0
- package/dist/shared/async-registry.d.ts +44 -16
- package/dist/shared/async-registry.d.ts.map +1 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/elements/editable.ts +38 -58
- package/src/elements/editor/editor.ts +122 -101
- package/src/elements/editor/typings.ts +3 -1
- package/src/elements/editor/utils/cleanup-orphan-editor-elements.test.ts +285 -0
- package/src/elements/editor/utils/cleanup-orphan-editor-elements.ts +60 -0
- package/src/elements/editor/utils/create-editor-in-context.ts +8 -2
- package/src/elements/editor/utils/index.ts +1 -0
- package/src/elements/editor/utils/wrap-with-watchdog.test.ts +34 -14
- package/src/elements/editor/utils/wrap-with-watchdog.ts +15 -25
- package/src/elements/ui-part.test.ts +1 -1
- package/src/elements/ui-part.ts +12 -11
- package/src/interop/create-editable-blazor-interop.ts +19 -16
- package/src/interop/create-editor-blazor-interop.ts +15 -18
- package/src/shared/are-maps-equal.test.ts +56 -0
- package/src/shared/are-maps-equal.ts +22 -0
- package/src/shared/async-registry.test.ts +190 -88
- package/src/shared/async-registry.ts +179 -107
- package/src/shared/index.ts +1 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { areMapsEqual } from './are-maps-equal';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Generic async registry for objects with an async destroy method.
|
|
3
5
|
* Provides a way to register, unregister, and execute callbacks on objects by ID.
|
|
@@ -23,6 +25,18 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
23
25
|
*/
|
|
24
26
|
private readonly watchers = new Set<RegistryWatcher<T>>();
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Batch nesting depth. When > 0, watcher notifications are deferred.
|
|
30
|
+
*/
|
|
31
|
+
private batchDepth = 0;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Snapshot of the last state dispatched to watchers, used for change detection.
|
|
35
|
+
*/
|
|
36
|
+
private lastNotifiedItems: Map<any, any> | null = null;
|
|
37
|
+
|
|
38
|
+
private lastNotifiedErrors: Map<any, any> | null = null;
|
|
39
|
+
|
|
26
40
|
/**
|
|
27
41
|
* Executes a function on an item.
|
|
28
42
|
* If the item is not yet registered, it will wait for it to be registered.
|
|
@@ -68,6 +82,66 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
68
82
|
});
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Reactively binds a mount/unmount lifecycle to a single registry item.
|
|
87
|
+
*
|
|
88
|
+
* @param id The ID of the item to observe.
|
|
89
|
+
* @param onMount Function executed when the item mounts.
|
|
90
|
+
* @returns A function that stops observing and immediately runs any pending cleanup.
|
|
91
|
+
*/
|
|
92
|
+
mountEffect<E extends T = T>(
|
|
93
|
+
id: RegistryId | null,
|
|
94
|
+
onMount: (item: E) => (() => void) | void,
|
|
95
|
+
): () => void {
|
|
96
|
+
let cleanup: VoidFunction | void;
|
|
97
|
+
let mountedItem: T | undefined;
|
|
98
|
+
let unmounted = false;
|
|
99
|
+
|
|
100
|
+
const unwatch = this.watch((items) => {
|
|
101
|
+
const item = items.get(id);
|
|
102
|
+
|
|
103
|
+
if (item === mountedItem) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
cleanup?.();
|
|
108
|
+
cleanup = undefined;
|
|
109
|
+
mountedItem = item;
|
|
110
|
+
|
|
111
|
+
if (!item) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const newCleanup = onMount(item as E);
|
|
117
|
+
|
|
118
|
+
if (unmounted) {
|
|
119
|
+
newCleanup?.();
|
|
120
|
+
unwatch();
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
cleanup = newCleanup;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
/* v8 ignore start -- @preserve */
|
|
128
|
+
console.error(err);
|
|
129
|
+
throw err;
|
|
130
|
+
/* v8 ignore end */
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return () => {
|
|
135
|
+
unmounted = true;
|
|
136
|
+
|
|
137
|
+
if (mountedItem) {
|
|
138
|
+
unwatch();
|
|
139
|
+
cleanup?.();
|
|
140
|
+
cleanup = undefined;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
71
145
|
/**
|
|
72
146
|
* Registers an item.
|
|
73
147
|
*
|
|
@@ -75,24 +149,27 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
75
149
|
* @param item The item instance.
|
|
76
150
|
*/
|
|
77
151
|
register(id: RegistryId | null, item: T): void {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
152
|
+
this.batch(() => {
|
|
153
|
+
if (this.items.has(id)) {
|
|
154
|
+
throw new Error(`Item with ID "${id}" is already registered.`);
|
|
155
|
+
}
|
|
81
156
|
|
|
82
|
-
|
|
83
|
-
|
|
157
|
+
this.resetErrors(id);
|
|
158
|
+
this.items.set(id, item);
|
|
84
159
|
|
|
85
|
-
|
|
86
|
-
|
|
160
|
+
// Execute all pending callbacks for this item (synchronously).
|
|
161
|
+
const pending = this.pendingCallbacks.get(id);
|
|
87
162
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
163
|
+
if (pending) {
|
|
164
|
+
pending.success.forEach(callback => callback(item));
|
|
165
|
+
this.pendingCallbacks.delete(id);
|
|
166
|
+
}
|
|
92
167
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
168
|
+
// Register the first item as the default item (null ID).
|
|
169
|
+
if (this.items.size === 1 && id !== null) {
|
|
170
|
+
this.register(null, item);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
96
173
|
}
|
|
97
174
|
|
|
98
175
|
/**
|
|
@@ -102,24 +179,23 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
102
179
|
* @param error The error to register.
|
|
103
180
|
*/
|
|
104
181
|
error(id: RegistryId | null, error: any): void {
|
|
105
|
-
this.
|
|
106
|
-
|
|
182
|
+
this.batch(() => {
|
|
183
|
+
this.items.delete(id);
|
|
184
|
+
this.initializationErrors.set(id, error);
|
|
107
185
|
|
|
108
|
-
|
|
109
|
-
|
|
186
|
+
// Execute all pending error callbacks for this item.
|
|
187
|
+
const pending = this.pendingCallbacks.get(id);
|
|
110
188
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// Set as default error if this is the first error and no items exist.
|
|
117
|
-
if (this.initializationErrors.size === 1 && !this.items.size) {
|
|
118
|
-
this.error(null, error);
|
|
119
|
-
}
|
|
189
|
+
if (pending) {
|
|
190
|
+
pending.error.forEach(callback => callback(error));
|
|
191
|
+
this.pendingCallbacks.delete(id);
|
|
192
|
+
}
|
|
120
193
|
|
|
121
|
-
|
|
122
|
-
|
|
194
|
+
// Set as default error if this is the first error and no items exist.
|
|
195
|
+
if (this.initializationErrors.size === 1 && !this.items.size) {
|
|
196
|
+
this.error(null, error);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
123
199
|
}
|
|
124
200
|
|
|
125
201
|
/**
|
|
@@ -142,21 +218,23 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
142
218
|
* Un-registers an item.
|
|
143
219
|
*
|
|
144
220
|
* @param id The ID of the item.
|
|
221
|
+
* @param resetPendingCallbacks If true resets pending callbacks.
|
|
145
222
|
*/
|
|
146
|
-
unregister(id: RegistryId | null): void {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
223
|
+
unregister(id: RegistryId | null, resetPendingCallbacks: boolean = true): void {
|
|
224
|
+
this.batch(() => {
|
|
225
|
+
// If unregistering the default item, clear it.
|
|
226
|
+
if (id && this.items.get(null) === this.items.get(id)) {
|
|
227
|
+
this.unregister(null, false);
|
|
228
|
+
}
|
|
150
229
|
|
|
151
|
-
|
|
152
|
-
if (id && this.items.get(null) === this.items.get(id)) {
|
|
153
|
-
this.unregister(null);
|
|
154
|
-
}
|
|
230
|
+
this.items.delete(id);
|
|
155
231
|
|
|
156
|
-
|
|
157
|
-
|
|
232
|
+
if (resetPendingCallbacks) {
|
|
233
|
+
this.pendingCallbacks.delete(id);
|
|
234
|
+
}
|
|
158
235
|
|
|
159
|
-
|
|
236
|
+
this.resetErrors(id);
|
|
237
|
+
});
|
|
160
238
|
}
|
|
161
239
|
|
|
162
240
|
/**
|
|
@@ -168,6 +246,15 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
168
246
|
return Array.from(this.items.values());
|
|
169
247
|
}
|
|
170
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Returns single registered item.
|
|
251
|
+
*
|
|
252
|
+
* @returns Registered item.
|
|
253
|
+
*/
|
|
254
|
+
getItem(id: RegistryId | null): T | undefined {
|
|
255
|
+
return this.items.get(id);
|
|
256
|
+
}
|
|
257
|
+
|
|
171
258
|
/**
|
|
172
259
|
* Checks if an item with the given ID is registered.
|
|
173
260
|
*
|
|
@@ -183,46 +270,11 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
183
270
|
* If the item is not registered yet, it will wait for it to be registered.
|
|
184
271
|
*
|
|
185
272
|
* @param id The ID of the item.
|
|
186
|
-
* @param timeout Optional timeout in milliseconds.
|
|
187
273
|
* @returns A promise that resolves with the item instance.
|
|
188
274
|
*/
|
|
189
|
-
waitFor<E extends T = T>(id: RegistryId | null
|
|
275
|
+
waitFor<E extends T = T>(id: RegistryId | null): Promise<E> {
|
|
190
276
|
return new Promise<E>((resolve, reject) => {
|
|
191
|
-
|
|
192
|
-
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
193
|
-
|
|
194
|
-
void this.execute(
|
|
195
|
-
id,
|
|
196
|
-
(value: E) => {
|
|
197
|
-
if (exceedTimeout) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (timer !== null) {
|
|
202
|
-
clearTimeout(timer!);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
(resolve as (value: E) => void)(value);
|
|
206
|
-
},
|
|
207
|
-
(error: any) => {
|
|
208
|
-
if (exceedTimeout) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (timer !== null) {
|
|
213
|
-
clearTimeout(timer!);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
reject(error);
|
|
217
|
-
},
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
if (timeout) {
|
|
221
|
-
timer = setTimeout(() => {
|
|
222
|
-
exceedTimeout = true;
|
|
223
|
-
reject(new Error(`Timeout waiting for item with ID "${id}" to be registered.`));
|
|
224
|
-
}, timeout);
|
|
225
|
-
}
|
|
277
|
+
void this.execute(id, resolve as (value: E) => void, reject);
|
|
226
278
|
});
|
|
227
279
|
}
|
|
228
280
|
|
|
@@ -242,7 +294,41 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
242
294
|
|
|
243
295
|
await Promise.all(promises);
|
|
244
296
|
|
|
245
|
-
this.
|
|
297
|
+
this.flushWatchers();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Destroys all registered editors and removes all watchers.
|
|
302
|
+
*/
|
|
303
|
+
async reset() {
|
|
304
|
+
await this.destroyAll();
|
|
305
|
+
this.watchers.clear();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Executes a callback while deferring all watcher notifications.
|
|
310
|
+
* A single notification is fired synchronously after the callback returns,
|
|
311
|
+
* but only if the registry actually changed.
|
|
312
|
+
*
|
|
313
|
+
* Batches can be nested — watchers are notified only when the outermost
|
|
314
|
+
* batch completes.
|
|
315
|
+
*
|
|
316
|
+
* @param fn The callback to execute.
|
|
317
|
+
* @returns The return value of the callback.
|
|
318
|
+
*/
|
|
319
|
+
batch<R>(fn: () => R): R {
|
|
320
|
+
this.batchDepth++;
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
return fn();
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
this.batchDepth--;
|
|
327
|
+
|
|
328
|
+
if (this.batchDepth === 0) {
|
|
329
|
+
this.flushWatchers();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
246
332
|
}
|
|
247
333
|
|
|
248
334
|
/**
|
|
@@ -273,25 +359,23 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
273
359
|
}
|
|
274
360
|
|
|
275
361
|
/**
|
|
276
|
-
*
|
|
362
|
+
* Immediately dispatches the current state to all watchers if it changed.
|
|
277
363
|
*/
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
364
|
+
private flushWatchers(): void {
|
|
365
|
+
if (
|
|
366
|
+
areMapsEqual(this.lastNotifiedItems, this.items)
|
|
367
|
+
&& areMapsEqual(this.lastNotifiedErrors, this.initializationErrors)
|
|
368
|
+
) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
284
371
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
new Map(this.initializationErrors),
|
|
293
|
-
),
|
|
294
|
-
);
|
|
372
|
+
this.lastNotifiedItems = new Map(this.items);
|
|
373
|
+
this.lastNotifiedErrors = new Map(this.initializationErrors);
|
|
374
|
+
|
|
375
|
+
this.watchers.forEach(watcher => watcher(
|
|
376
|
+
new Map(this.items),
|
|
377
|
+
new Map(this.initializationErrors),
|
|
378
|
+
));
|
|
295
379
|
}
|
|
296
380
|
|
|
297
381
|
/**
|
|
@@ -310,18 +394,6 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
310
394
|
|
|
311
395
|
return pending;
|
|
312
396
|
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Registers an item as the default (null ID) item if it's the first one.
|
|
316
|
-
*
|
|
317
|
-
* @param id The ID of the item being registered.
|
|
318
|
-
* @param item The item instance.
|
|
319
|
-
*/
|
|
320
|
-
private registerAsDefault(id: RegistryId | null, item: T): void {
|
|
321
|
-
if (this.items.size === 1 && id !== null) {
|
|
322
|
-
this.register(null, item);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
397
|
}
|
|
326
398
|
|
|
327
399
|
/**
|
package/src/shared/index.ts
CHANGED