ckeditor5-livewire 1.9.0 → 1.11.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/hooks/editable.d.ts +3 -5
- package/dist/hooks/editable.d.ts.map +1 -1
- package/dist/hooks/editor/editor.d.ts +0 -4
- package/dist/hooks/editor/editor.d.ts.map +1 -1
- package/dist/hooks/editor/plugins/livewire-sync.d.ts.map +1 -1
- package/dist/hooks/editor/utils/cleanup-orphan-editor-elements.d.ts +8 -0
- package/dist/hooks/editor/utils/cleanup-orphan-editor-elements.d.ts.map +1 -0
- package/dist/hooks/editor/utils/create-editor-in-context.d.ts +6 -1
- package/dist/hooks/editor/utils/create-editor-in-context.d.ts.map +1 -1
- package/dist/hooks/editor/utils/index.d.ts +2 -0
- package/dist/hooks/editor/utils/index.d.ts.map +1 -1
- package/dist/hooks/editor/utils/is-multiroot-editor-instance.d.ts +6 -0
- package/dist/hooks/editor/utils/is-multiroot-editor-instance.d.ts.map +1 -0
- package/dist/hooks/editor/utils/wrap-with-watchdog.d.ts +7 -16
- package/dist/hooks/editor/utils/wrap-with-watchdog.d.ts.map +1 -1
- package/dist/hooks/ui-part.d.ts +2 -6
- package/dist/hooks/ui-part.d.ts.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +297 -220
- package/dist/index.mjs.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 +43 -10
- 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/hooks/context/context.test.ts +3 -1
- package/src/hooks/editable.ts +73 -46
- package/src/hooks/editor/editor.test.ts +44 -9
- package/src/hooks/editor/editor.ts +159 -149
- package/src/hooks/editor/plugins/livewire-sync.ts +17 -8
- package/src/hooks/editor/utils/cleanup-orphan-editor-elements.test.ts +285 -0
- package/src/hooks/editor/utils/cleanup-orphan-editor-elements.ts +60 -0
- package/src/hooks/editor/utils/create-editor-in-context.ts +6 -2
- package/src/hooks/editor/utils/index.ts +2 -0
- package/src/hooks/editor/utils/is-multiroot-editor-instance.ts +8 -0
- package/src/hooks/editor/utils/wrap-with-watchdog.test.ts +34 -14
- package/src/hooks/editor/utils/wrap-with-watchdog.ts +16 -26
- package/src/hooks/ui-part.ts +10 -16
- 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 +212 -31
- package/src/shared/async-registry.ts +178 -61
- package/src/shared/index.ts +1 -0
|
@@ -96,30 +96,6 @@ describe('async registry', () => {
|
|
|
96
96
|
registry.unregister('item1');
|
|
97
97
|
expect(registry.getItems()).not.toContain(item);
|
|
98
98
|
});
|
|
99
|
-
|
|
100
|
-
it('should throw an error if trying to unregister an item that is not registered', () => {
|
|
101
|
-
expect(() => registry.unregister('nonexistent')).toThrow(
|
|
102
|
-
'Item with ID "nonexistent" is not registered.',
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should also unregister the default item if the unregistered item was the default one', async () => {
|
|
107
|
-
const item1 = createMockItem('item1');
|
|
108
|
-
|
|
109
|
-
registry.register('item1', item1); // This also registers it as default
|
|
110
|
-
|
|
111
|
-
// Check it is the default
|
|
112
|
-
const promise = registry.execute(null, item => item);
|
|
113
|
-
|
|
114
|
-
await expect(promise).resolves.toBe(item1);
|
|
115
|
-
|
|
116
|
-
registry.unregister('item1');
|
|
117
|
-
|
|
118
|
-
// Now check that the default is also gone
|
|
119
|
-
expect(() => registry.unregister(null)).toThrow(
|
|
120
|
-
'Item with ID "null" is not registered.',
|
|
121
|
-
);
|
|
122
|
-
});
|
|
123
99
|
});
|
|
124
100
|
|
|
125
101
|
describe('execute', () => {
|
|
@@ -234,7 +210,7 @@ describe('async registry', () => {
|
|
|
234
210
|
});
|
|
235
211
|
});
|
|
236
212
|
|
|
237
|
-
describe('
|
|
213
|
+
describe('getItems', () => {
|
|
238
214
|
it('should return all registered items', () => {
|
|
239
215
|
const item1 = createMockItem('item1');
|
|
240
216
|
const item2 = createMockItem('item2');
|
|
@@ -244,7 +220,7 @@ describe('async registry', () => {
|
|
|
244
220
|
|
|
245
221
|
const items = registry.getItems();
|
|
246
222
|
|
|
247
|
-
expect(items).toHaveLength(3);
|
|
223
|
+
expect(items).toHaveLength(3);
|
|
248
224
|
expect(items).toContain(item1);
|
|
249
225
|
expect(items).toContain(item2);
|
|
250
226
|
});
|
|
@@ -252,16 +228,30 @@ describe('async registry', () => {
|
|
|
252
228
|
it('should return unique items if some point to the same instance', () => {
|
|
253
229
|
const item1 = createMockItem('item1');
|
|
254
230
|
|
|
255
|
-
registry.register('item1', item1);
|
|
231
|
+
registry.register('item1', item1);
|
|
256
232
|
|
|
257
233
|
const items = registry.getItems();
|
|
258
234
|
|
|
259
|
-
expect(items).toHaveLength(2);
|
|
235
|
+
expect(items).toHaveLength(2);
|
|
260
236
|
expect(items.filter(e => e === item1)).toHaveLength(2);
|
|
261
237
|
});
|
|
262
238
|
});
|
|
263
239
|
|
|
264
|
-
describe('
|
|
240
|
+
describe('getItem', () => {
|
|
241
|
+
it('should return registered item', () => {
|
|
242
|
+
const item = createMockItem('item1');
|
|
243
|
+
|
|
244
|
+
registry.register('item1', item);
|
|
245
|
+
|
|
246
|
+
expect(registry.getItem('item1')).toBe(item);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should return undefined if item doesn\'t exist', () => {
|
|
250
|
+
expect(registry.getItem('item1')).toBeUndefined();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('hasItem', () => {
|
|
265
255
|
it('should return true if an item with the given ID is registered', () => {
|
|
266
256
|
const item = createMockItem('item1');
|
|
267
257
|
|
|
@@ -402,6 +392,197 @@ describe('async registry', () => {
|
|
|
402
392
|
});
|
|
403
393
|
});
|
|
404
394
|
|
|
395
|
+
describe('reset', () => {
|
|
396
|
+
it('should destroy all registered items', async () => {
|
|
397
|
+
const item1 = createMockItem('item1');
|
|
398
|
+
const item2 = createMockItem('item2');
|
|
399
|
+
|
|
400
|
+
registry.register('item1', item1);
|
|
401
|
+
registry.register('item2', item2);
|
|
402
|
+
|
|
403
|
+
await registry.reset();
|
|
404
|
+
|
|
405
|
+
expect(registry.getItems()).toHaveLength(0);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('should call destroy on each unique item', async () => {
|
|
409
|
+
const destroyMock1 = vi.fn().mockResolvedValue(undefined);
|
|
410
|
+
const destroyMock2 = vi.fn().mockResolvedValue(undefined);
|
|
411
|
+
|
|
412
|
+
const item1 = { name: 'item1', destroy: destroyMock1 } as unknown as Mockitem;
|
|
413
|
+
const item2 = { name: 'item2', destroy: destroyMock2 } as unknown as Mockitem;
|
|
414
|
+
|
|
415
|
+
registry.register('item1', item1);
|
|
416
|
+
registry.register('item2', item2);
|
|
417
|
+
|
|
418
|
+
await registry.reset();
|
|
419
|
+
|
|
420
|
+
expect(destroyMock1).toHaveBeenCalledOnce();
|
|
421
|
+
expect(destroyMock2).toHaveBeenCalledOnce();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('should clear watchers so they are no longer called after reset', async () => {
|
|
425
|
+
const watcher = vi.fn();
|
|
426
|
+
registry.watch(watcher);
|
|
427
|
+
|
|
428
|
+
await registry.reset();
|
|
429
|
+
watcher.mockClear();
|
|
430
|
+
|
|
431
|
+
const item = createMockItem('item1');
|
|
432
|
+
registry.register('item1', item);
|
|
433
|
+
|
|
434
|
+
expect(watcher).not.toHaveBeenCalled();
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
describe('mountEffect', () => {
|
|
439
|
+
it('should call onMount immediately when item is already registered', () => {
|
|
440
|
+
const item = createMockItem('item1');
|
|
441
|
+
registry.register('item1', item);
|
|
442
|
+
|
|
443
|
+
const onMount = vi.fn();
|
|
444
|
+
registry.mountEffect('item1', onMount);
|
|
445
|
+
|
|
446
|
+
expect(onMount).toHaveBeenCalledOnce();
|
|
447
|
+
expect(onMount).toHaveBeenCalledWith(item);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should call onMount when item registers later', () => {
|
|
451
|
+
const onMount = vi.fn();
|
|
452
|
+
registry.mountEffect('item1', onMount);
|
|
453
|
+
|
|
454
|
+
expect(onMount).not.toHaveBeenCalled();
|
|
455
|
+
|
|
456
|
+
const item = createMockItem('item1');
|
|
457
|
+
registry.register('item1', item);
|
|
458
|
+
|
|
459
|
+
expect(onMount).toHaveBeenCalledOnce();
|
|
460
|
+
expect(onMount).toHaveBeenCalledWith(item);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('should call cleanup returned by onMount when item is unregistered', () => {
|
|
464
|
+
const item = createMockItem('item1');
|
|
465
|
+
registry.register('item1', item);
|
|
466
|
+
|
|
467
|
+
const cleanup = vi.fn();
|
|
468
|
+
registry.mountEffect('item1', () => cleanup);
|
|
469
|
+
|
|
470
|
+
registry.unregister('item1');
|
|
471
|
+
|
|
472
|
+
expect(cleanup).toHaveBeenCalledOnce();
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('should call cleanup and re-call onMount on re-registration', () => {
|
|
476
|
+
const item1a = createMockItem('item1');
|
|
477
|
+
registry.register('item1', item1a);
|
|
478
|
+
|
|
479
|
+
const cleanup = vi.fn();
|
|
480
|
+
const onMount = vi.fn(() => cleanup);
|
|
481
|
+
registry.mountEffect('item1', onMount);
|
|
482
|
+
|
|
483
|
+
expect(onMount).toHaveBeenCalledOnce();
|
|
484
|
+
|
|
485
|
+
registry.unregister('item1');
|
|
486
|
+
expect(cleanup).toHaveBeenCalledOnce();
|
|
487
|
+
|
|
488
|
+
const item1b = createMockItem('item1');
|
|
489
|
+
registry.register('item1', item1b);
|
|
490
|
+
|
|
491
|
+
expect(onMount).toHaveBeenCalledTimes(2);
|
|
492
|
+
expect(onMount).toHaveBeenLastCalledWith(item1b);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('should not call onMount if item never registers', () => {
|
|
496
|
+
const onMount = vi.fn();
|
|
497
|
+
const stop = registry.mountEffect('item1', onMount);
|
|
498
|
+
|
|
499
|
+
stop();
|
|
500
|
+
|
|
501
|
+
expect(onMount).not.toHaveBeenCalled();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should run cleanup and stop watching when stop is called with item mounted', () => {
|
|
505
|
+
const item = createMockItem('item1');
|
|
506
|
+
registry.register('item1', item);
|
|
507
|
+
|
|
508
|
+
const cleanup = vi.fn();
|
|
509
|
+
const stop = registry.mountEffect('item1', () => cleanup);
|
|
510
|
+
|
|
511
|
+
stop();
|
|
512
|
+
|
|
513
|
+
expect(cleanup).toHaveBeenCalledOnce();
|
|
514
|
+
|
|
515
|
+
// Watcher should be gone — unregistering should not trigger cleanup again.
|
|
516
|
+
registry.unregister('item1');
|
|
517
|
+
|
|
518
|
+
expect(cleanup).toHaveBeenCalledOnce();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('should not call cleanup when stop is called before item registers', () => {
|
|
522
|
+
const cleanup = vi.fn();
|
|
523
|
+
const onMount = vi.fn(() => cleanup);
|
|
524
|
+
const stop = registry.mountEffect('item1', onMount);
|
|
525
|
+
|
|
526
|
+
stop();
|
|
527
|
+
|
|
528
|
+
expect(onMount).not.toHaveBeenCalled();
|
|
529
|
+
expect(cleanup).not.toHaveBeenCalled();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('should call onMount and immediately run cleanup when item registers after stop', () => {
|
|
533
|
+
const cleanup = vi.fn();
|
|
534
|
+
const onMount = vi.fn(() => cleanup);
|
|
535
|
+
const stop = registry.mountEffect('item1', onMount);
|
|
536
|
+
|
|
537
|
+
stop();
|
|
538
|
+
|
|
539
|
+
const item = createMockItem('item1');
|
|
540
|
+
registry.register('item1', item);
|
|
541
|
+
|
|
542
|
+
expect(onMount).toHaveBeenCalledOnce();
|
|
543
|
+
expect(onMount).toHaveBeenCalledWith(item);
|
|
544
|
+
expect(cleanup).toHaveBeenCalledOnce();
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('should stop watching after late cleanup fires', () => {
|
|
548
|
+
const onMount = vi.fn();
|
|
549
|
+
const stop = registry.mountEffect('item1', onMount);
|
|
550
|
+
|
|
551
|
+
stop();
|
|
552
|
+
|
|
553
|
+
const item1 = createMockItem('item1');
|
|
554
|
+
registry.register('item1', item1);
|
|
555
|
+
|
|
556
|
+
expect(onMount).toHaveBeenCalledOnce();
|
|
557
|
+
|
|
558
|
+
// Watcher should have been removed after late cleanup — further changes are ignored.
|
|
559
|
+
registry.unregister('item1');
|
|
560
|
+
registry.register('item1', createMockItem('item1'));
|
|
561
|
+
|
|
562
|
+
expect(onMount).toHaveBeenCalledOnce();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('should not throw when onMount returns void and stop is called', () => {
|
|
566
|
+
const item = createMockItem('item1');
|
|
567
|
+
registry.register('item1', item);
|
|
568
|
+
|
|
569
|
+
const stop = registry.mountEffect('item1', () => {});
|
|
570
|
+
|
|
571
|
+
expect(() => stop()).not.toThrow();
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('should work with the default null ID', () => {
|
|
575
|
+
const item = createMockItem('item1');
|
|
576
|
+
registry.register('item1', item); // also registered as default
|
|
577
|
+
|
|
578
|
+
const onMount = vi.fn();
|
|
579
|
+
registry.mountEffect(null, onMount);
|
|
580
|
+
|
|
581
|
+
expect(onMount).toHaveBeenCalledOnce();
|
|
582
|
+
expect(onMount).toHaveBeenCalledWith(item);
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
|
|
405
586
|
describe('waitFor', () => {
|
|
406
587
|
it('should return a promise that resolves with the item instance', async () => {
|
|
407
588
|
const item1 = createMockItem('item1');
|
|
@@ -518,7 +699,7 @@ describe('async registry', () => {
|
|
|
518
699
|
const item1 = createMockItem('item1');
|
|
519
700
|
registry.register('item1', item1);
|
|
520
701
|
|
|
521
|
-
expect(watcher).toHaveBeenCalledTimes(
|
|
702
|
+
expect(watcher).toHaveBeenCalledTimes(2);
|
|
522
703
|
});
|
|
523
704
|
|
|
524
705
|
it('should call watcher when item is unregistered', () => {
|
|
@@ -531,7 +712,7 @@ describe('async registry', () => {
|
|
|
531
712
|
watcher.mockClear();
|
|
532
713
|
registry.unregister('item1');
|
|
533
714
|
|
|
534
|
-
expect(watcher).toHaveBeenCalledTimes(
|
|
715
|
+
expect(watcher).toHaveBeenCalledTimes(1);
|
|
535
716
|
});
|
|
536
717
|
|
|
537
718
|
it('should call watcher when all items are destroyed', async () => {
|
|
@@ -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
|
*
|
|
@@ -207,7 +294,41 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
207
294
|
|
|
208
295
|
await Promise.all(promises);
|
|
209
296
|
|
|
210
|
-
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
|
+
}
|
|
211
332
|
}
|
|
212
333
|
|
|
213
334
|
/**
|
|
@@ -238,15 +359,23 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
238
359
|
}
|
|
239
360
|
|
|
240
361
|
/**
|
|
241
|
-
*
|
|
362
|
+
* Immediately dispatches the current state to all watchers if it changed.
|
|
242
363
|
*/
|
|
243
|
-
private
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
364
|
+
private flushWatchers(): void {
|
|
365
|
+
if (
|
|
366
|
+
areMapsEqual(this.lastNotifiedItems, this.items)
|
|
367
|
+
&& areMapsEqual(this.lastNotifiedErrors, this.initializationErrors)
|
|
368
|
+
) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
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
|
+
));
|
|
250
379
|
}
|
|
251
380
|
|
|
252
381
|
/**
|
|
@@ -265,18 +394,6 @@ export class AsyncRegistry<T extends Destructible> {
|
|
|
265
394
|
|
|
266
395
|
return pending;
|
|
267
396
|
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Registers an item as the default (null ID) item if it's the first one.
|
|
271
|
-
*
|
|
272
|
-
* @param id The ID of the item being registered.
|
|
273
|
-
* @param item The item instance.
|
|
274
|
-
*/
|
|
275
|
-
private registerAsDefault(id: RegistryId | null, item: T): void {
|
|
276
|
-
if (this.items.size === 1 && id !== null) {
|
|
277
|
-
this.register(null, item);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
397
|
}
|
|
281
398
|
|
|
282
399
|
/**
|
package/src/shared/index.ts
CHANGED