@wordpress/data 6.0.1 → 6.1.3
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/CHANGELOG.md +8 -0
- package/README.md +31 -2
- package/build/components/registry-provider/use-registry.js +1 -1
- package/build/components/registry-provider/use-registry.js.map +1 -1
- package/build/controls.js +23 -15
- package/build/controls.js.map +1 -1
- package/build/plugins/persistence/index.js +43 -50
- package/build/plugins/persistence/index.js.map +1 -1
- package/build/redux-store/index.js +15 -3
- package/build/redux-store/index.js.map +1 -1
- package/build/registry.js +40 -7
- package/build/registry.js.map +1 -1
- package/build/types.js +2 -0
- package/build/{types.d.js.map → types.js.map} +0 -0
- package/build/utils/emitter.js +57 -0
- package/build/utils/emitter.js.map +1 -0
- package/build-module/components/registry-provider/use-registry.js +1 -1
- package/build-module/components/registry-provider/use-registry.js.map +1 -1
- package/build-module/controls.js +22 -15
- package/build-module/controls.js.map +1 -1
- package/build-module/plugins/persistence/index.js +42 -51
- package/build-module/plugins/persistence/index.js.map +1 -1
- package/build-module/redux-store/index.js +15 -3
- package/build-module/redux-store/index.js.map +1 -1
- package/build-module/registry.js +40 -8
- package/build-module/registry.js.map +1 -1
- package/build-module/types.js +2 -0
- package/build-module/{types.d.js.map → types.js.map} +0 -0
- package/build-module/utils/emitter.js +50 -0
- package/build-module/utils/emitter.js.map +1 -0
- package/build-types/redux-store/metadata/selectors.d.ts +1 -3
- package/build-types/redux-store/metadata/selectors.d.ts.map +1 -1
- package/build-types/types.d.ts +35 -0
- package/build-types/types.d.ts.map +1 -0
- package/build-types/utils/emitter.d.ts +7 -0
- package/build-types/utils/emitter.d.ts.map +1 -0
- package/package.json +6 -6
- package/src/components/registry-provider/use-registry.js +1 -1
- package/src/controls.js +43 -15
- package/src/plugins/persistence/index.js +56 -62
- package/src/plugins/persistence/test/index.js +117 -1
- package/src/redux-store/index.js +17 -4
- package/src/registry.js +38 -8
- package/src/test/registry.js +34 -0
- package/src/{types.d.ts → types.ts} +8 -0
- package/src/utils/emitter.js +46 -0
- package/tsconfig.json +2 -0
- package/tsconfig.tsbuildinfo +1 -826
- package/build/types.d.js +0 -2
- package/build-module/types.d.js +0 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { merge, isPlainObject
|
|
4
|
+
import { merge, isPlainObject } from 'lodash';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
@@ -223,75 +223,69 @@ function persistencePlugin( registry, pluginOptions ) {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
226
|
+
* Move the 'features' object in local storage from the sourceStoreName to the
|
|
227
|
+
* interface store.
|
|
228
|
+
*
|
|
229
|
+
* @param {Object} persistence The persistence interface.
|
|
230
|
+
* @param {string} sourceStoreName The name of the store that has persisted
|
|
231
|
+
* preferences to migrate to the interface
|
|
232
|
+
* package.
|
|
228
233
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
export function migrateFeaturePreferencesToInterfaceStore(
|
|
235
|
+
persistence,
|
|
236
|
+
sourceStoreName
|
|
237
|
+
) {
|
|
238
|
+
const interfaceStoreName = 'core/interface';
|
|
233
239
|
const state = persistence.get();
|
|
240
|
+
const sourcePreferences = state[ sourceStoreName ]?.preferences;
|
|
241
|
+
const sourceFeatures = sourcePreferences?.features;
|
|
242
|
+
|
|
243
|
+
if ( sourceFeatures ) {
|
|
244
|
+
const targetFeatures =
|
|
245
|
+
state[ interfaceStoreName ]?.preferences?.features;
|
|
246
|
+
|
|
247
|
+
// Avoid migrating features again if they've previously been migrated.
|
|
248
|
+
if ( ! targetFeatures?.[ sourceStoreName ] ) {
|
|
249
|
+
// Set the feature values in the interface store, the features
|
|
250
|
+
// object is keyed by 'scope', which matches the store name for
|
|
251
|
+
// the source.
|
|
252
|
+
persistence.set( interfaceStoreName, {
|
|
253
|
+
preferences: {
|
|
254
|
+
features: {
|
|
255
|
+
...targetFeatures,
|
|
256
|
+
[ sourceStoreName ]: sourceFeatures,
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
} );
|
|
234
260
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
persistence.set( 'core/block-editor', {
|
|
241
|
-
preferences: {
|
|
242
|
-
insertUsage: {
|
|
243
|
-
...editorInsertUsage,
|
|
244
|
-
...blockEditorInsertUsage,
|
|
261
|
+
// Remove feature preferences from the source.
|
|
262
|
+
persistence.set( sourceStoreName, {
|
|
263
|
+
preferences: {
|
|
264
|
+
...sourcePreferences,
|
|
265
|
+
features: undefined,
|
|
245
266
|
},
|
|
246
|
-
}
|
|
247
|
-
}
|
|
267
|
+
} );
|
|
268
|
+
}
|
|
248
269
|
}
|
|
270
|
+
}
|
|
249
271
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// is needed since `fullscreenMode` previously did not have a default value
|
|
255
|
-
// and was implicitly false by its absence. It is now `true` by default, but
|
|
256
|
-
// this change is not intended to affect upgrades from earlier versions.
|
|
257
|
-
const hadPersistedState = Object.keys( state ).length > 0;
|
|
258
|
-
const hadFullscreenModePreference = has( state, [
|
|
259
|
-
'core/edit-post',
|
|
260
|
-
'preferences',
|
|
261
|
-
'features',
|
|
262
|
-
'fullscreenMode',
|
|
263
|
-
] );
|
|
264
|
-
if ( hadPersistedState && ! hadFullscreenModePreference ) {
|
|
265
|
-
editPostState = merge( {}, editPostState, {
|
|
266
|
-
preferences: { features: { fullscreenMode: false } },
|
|
267
|
-
} );
|
|
268
|
-
}
|
|
272
|
+
/**
|
|
273
|
+
* Deprecated: Remove this function and the code in WordPress Core that calls
|
|
274
|
+
* it once WordPress 6.0 is released.
|
|
275
|
+
*/
|
|
269
276
|
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
'core/nux',
|
|
273
|
-
'preferences',
|
|
274
|
-
'areTipsEnabled',
|
|
275
|
-
] );
|
|
276
|
-
const hasWelcomeGuide = has( state, [
|
|
277
|
-
'core/edit-post',
|
|
278
|
-
'preferences',
|
|
279
|
-
'features',
|
|
280
|
-
'welcomeGuide',
|
|
281
|
-
] );
|
|
282
|
-
if ( areTipsEnabled !== undefined && ! hasWelcomeGuide ) {
|
|
283
|
-
editPostState = merge( {}, editPostState, {
|
|
284
|
-
preferences: {
|
|
285
|
-
features: {
|
|
286
|
-
welcomeGuide: areTipsEnabled,
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
} );
|
|
290
|
-
}
|
|
277
|
+
persistencePlugin.__unstableMigrate = ( pluginOptions ) => {
|
|
278
|
+
const persistence = createPersistenceInterface( pluginOptions );
|
|
291
279
|
|
|
292
|
-
|
|
293
|
-
persistence
|
|
294
|
-
|
|
280
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
281
|
+
persistence,
|
|
282
|
+
'core/edit-widgets'
|
|
283
|
+
);
|
|
284
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
285
|
+
persistence,
|
|
286
|
+
'core/customize-widgets'
|
|
287
|
+
);
|
|
288
|
+
migrateFeaturePreferencesToInterfaceStore( persistence, 'core/edit-post' );
|
|
295
289
|
};
|
|
296
290
|
|
|
297
291
|
export default persistencePlugin;
|
|
@@ -6,7 +6,11 @@ import deepFreeze from 'deep-freeze';
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
|
-
import plugin, {
|
|
9
|
+
import plugin, {
|
|
10
|
+
createPersistenceInterface,
|
|
11
|
+
withLazySameState,
|
|
12
|
+
migrateFeaturePreferencesToInterfaceStore,
|
|
13
|
+
} from '../';
|
|
10
14
|
import objectStorage from '../storage/object';
|
|
11
15
|
import { createRegistry } from '../../../';
|
|
12
16
|
|
|
@@ -377,3 +381,115 @@ describe( 'persistence', () => {
|
|
|
377
381
|
} );
|
|
378
382
|
} );
|
|
379
383
|
} );
|
|
384
|
+
|
|
385
|
+
describe( 'migrateFeaturePreferencesToInterfaceStore', () => {
|
|
386
|
+
it( 'migrates preferences from the source to the interface store', () => {
|
|
387
|
+
const persistenceInterface = createPersistenceInterface( {
|
|
388
|
+
storageKey: 'test-username',
|
|
389
|
+
} );
|
|
390
|
+
|
|
391
|
+
const initialState = {
|
|
392
|
+
preferences: {
|
|
393
|
+
features: {
|
|
394
|
+
featureA: true,
|
|
395
|
+
featureB: false,
|
|
396
|
+
featureC: true,
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
persistenceInterface.set( 'core/test', initialState );
|
|
402
|
+
|
|
403
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
404
|
+
persistenceInterface,
|
|
405
|
+
'core/test'
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
expect( persistenceInterface.get() ).toEqual( {
|
|
409
|
+
'core/interface': {
|
|
410
|
+
preferences: {
|
|
411
|
+
features: {
|
|
412
|
+
'core/test': {
|
|
413
|
+
featureA: true,
|
|
414
|
+
featureB: false,
|
|
415
|
+
featureC: true,
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
'core/test': {
|
|
421
|
+
preferences: {
|
|
422
|
+
features: undefined,
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
} );
|
|
426
|
+
} );
|
|
427
|
+
|
|
428
|
+
it( 'handles multiple preferences from different stores to be migrated', () => {
|
|
429
|
+
const persistenceInterface = createPersistenceInterface( {
|
|
430
|
+
storageKey: 'test-username',
|
|
431
|
+
} );
|
|
432
|
+
|
|
433
|
+
const initialStateA = {
|
|
434
|
+
preferences: {
|
|
435
|
+
features: {
|
|
436
|
+
featureA: true,
|
|
437
|
+
featureB: false,
|
|
438
|
+
featureC: true,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const initialStateB = {
|
|
444
|
+
preferences: {
|
|
445
|
+
features: {
|
|
446
|
+
featureD: true,
|
|
447
|
+
featureE: false,
|
|
448
|
+
featureF: true,
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
persistenceInterface.set( 'core/test-a', initialStateA );
|
|
454
|
+
persistenceInterface.set( 'core/test-b', initialStateB );
|
|
455
|
+
|
|
456
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
457
|
+
persistenceInterface,
|
|
458
|
+
'core/test-a'
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
migrateFeaturePreferencesToInterfaceStore(
|
|
462
|
+
persistenceInterface,
|
|
463
|
+
'core/test-b'
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
expect( persistenceInterface.get() ).toEqual( {
|
|
467
|
+
'core/interface': {
|
|
468
|
+
preferences: {
|
|
469
|
+
features: {
|
|
470
|
+
'core/test-a': {
|
|
471
|
+
featureA: true,
|
|
472
|
+
featureB: false,
|
|
473
|
+
featureC: true,
|
|
474
|
+
},
|
|
475
|
+
'core/test-b': {
|
|
476
|
+
featureD: true,
|
|
477
|
+
featureE: false,
|
|
478
|
+
featureF: true,
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
'core/test-a': {
|
|
484
|
+
preferences: {
|
|
485
|
+
features: undefined,
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
'core/test-b': {
|
|
489
|
+
preferences: {
|
|
490
|
+
features: undefined,
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
} );
|
|
494
|
+
} );
|
|
495
|
+
} );
|
package/src/redux-store/index.js
CHANGED
|
@@ -26,6 +26,16 @@ import * as metadataActions from './metadata/actions';
|
|
|
26
26
|
/** @typedef {import('../types').WPDataStore} WPDataStore */
|
|
27
27
|
/** @typedef {import('../types').WPDataReduxStoreConfig} WPDataReduxStoreConfig */
|
|
28
28
|
|
|
29
|
+
const trimUndefinedValues = ( array ) => {
|
|
30
|
+
const result = [ ...array ];
|
|
31
|
+
for ( let i = result.length - 1; i >= 0; i-- ) {
|
|
32
|
+
if ( result[ i ] === undefined ) {
|
|
33
|
+
result.splice( i, 1 );
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
|
|
29
39
|
/**
|
|
30
40
|
* Create a cache to track whether resolvers started running or not.
|
|
31
41
|
*
|
|
@@ -35,12 +45,15 @@ function createResolversCache() {
|
|
|
35
45
|
const cache = {};
|
|
36
46
|
return {
|
|
37
47
|
isRunning( selectorName, args ) {
|
|
38
|
-
return
|
|
48
|
+
return (
|
|
49
|
+
cache[ selectorName ] &&
|
|
50
|
+
cache[ selectorName ].get( trimUndefinedValues( args ) )
|
|
51
|
+
);
|
|
39
52
|
},
|
|
40
53
|
|
|
41
54
|
clear( selectorName, args ) {
|
|
42
55
|
if ( cache[ selectorName ] ) {
|
|
43
|
-
cache[ selectorName ].delete( args );
|
|
56
|
+
cache[ selectorName ].delete( trimUndefinedValues( args ) );
|
|
44
57
|
}
|
|
45
58
|
},
|
|
46
59
|
|
|
@@ -49,7 +62,7 @@ function createResolversCache() {
|
|
|
49
62
|
cache[ selectorName ] = new EquivalentKeyMap();
|
|
50
63
|
}
|
|
51
64
|
|
|
52
|
-
cache[ selectorName ].set( args, true );
|
|
65
|
+
cache[ selectorName ].set( trimUndefinedValues( args ), true );
|
|
53
66
|
},
|
|
54
67
|
};
|
|
55
68
|
}
|
|
@@ -324,7 +337,6 @@ function mapResolveSelectors( selectors, store ) {
|
|
|
324
337
|
const hasFinished = () =>
|
|
325
338
|
selectors.hasFinishedResolution( selectorName, args );
|
|
326
339
|
const getResult = () => selector.apply( null, args );
|
|
327
|
-
|
|
328
340
|
// trigger the selector (to trigger the resolver)
|
|
329
341
|
const result = getResult();
|
|
330
342
|
if ( hasFinished() ) {
|
|
@@ -376,6 +388,7 @@ function mapResolvers( resolvers, selectors, store, resolversCache ) {
|
|
|
376
388
|
const selectorResolver = ( ...args ) => {
|
|
377
389
|
async function fulfillSelector() {
|
|
378
390
|
const state = store.getState();
|
|
391
|
+
|
|
379
392
|
if (
|
|
380
393
|
resolversCache.isRunning( selectorName, args ) ||
|
|
381
394
|
( typeof resolver.isFulfilled === 'function' &&
|
package/src/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { mapValues, isObject, forEach } from 'lodash';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
@@ -9,6 +9,7 @@ import { without, mapValues, isObject } from 'lodash';
|
|
|
9
9
|
import createReduxStore from './redux-store';
|
|
10
10
|
import createCoreDataStore from './store';
|
|
11
11
|
import { STORE_NAME } from './store/name';
|
|
12
|
+
import { createEmitter } from './utils/emitter';
|
|
12
13
|
|
|
13
14
|
/** @typedef {import('./types').WPDataStore} WPDataStore */
|
|
14
15
|
|
|
@@ -49,14 +50,14 @@ import { STORE_NAME } from './store/name';
|
|
|
49
50
|
*/
|
|
50
51
|
export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
51
52
|
const stores = {};
|
|
52
|
-
|
|
53
|
+
const emitter = createEmitter();
|
|
53
54
|
const __experimentalListeningStores = new Set();
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
57
|
* Global listener called for each store's update.
|
|
57
58
|
*/
|
|
58
59
|
function globalListener() {
|
|
59
|
-
|
|
60
|
+
emitter.emit();
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
@@ -67,11 +68,7 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
|
67
68
|
* @return {Function} Unsubscribe function.
|
|
68
69
|
*/
|
|
69
70
|
const subscribe = ( listener ) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return () => {
|
|
73
|
-
listeners = without( listeners, listener );
|
|
74
|
-
};
|
|
71
|
+
return emitter.subscribe( listener );
|
|
75
72
|
};
|
|
76
73
|
|
|
77
74
|
/**
|
|
@@ -177,6 +174,30 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
|
177
174
|
if ( typeof config.subscribe !== 'function' ) {
|
|
178
175
|
throw new TypeError( 'config.subscribe must be a function' );
|
|
179
176
|
}
|
|
177
|
+
// Thi emitter is used to keep track of active listeners when the registry
|
|
178
|
+
// get paused, that way, when resumed we should be able to call all these
|
|
179
|
+
// pending listeners.
|
|
180
|
+
config.emitter = createEmitter();
|
|
181
|
+
const currentSubscribe = config.subscribe;
|
|
182
|
+
config.subscribe = ( listener ) => {
|
|
183
|
+
const unsubscribeFromStoreEmitter = config.emitter.subscribe(
|
|
184
|
+
listener
|
|
185
|
+
);
|
|
186
|
+
const unsubscribeFromRootStore = currentSubscribe( () => {
|
|
187
|
+
if ( config.emitter.isPaused ) {
|
|
188
|
+
config.emitter.emit();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
listener();
|
|
192
|
+
} );
|
|
193
|
+
|
|
194
|
+
return () => {
|
|
195
|
+
if ( unsubscribeFromRootStore ) {
|
|
196
|
+
unsubscribeFromRootStore();
|
|
197
|
+
}
|
|
198
|
+
unsubscribeFromStoreEmitter();
|
|
199
|
+
};
|
|
200
|
+
};
|
|
180
201
|
stores[ key ] = config;
|
|
181
202
|
config.subscribe( globalListener );
|
|
182
203
|
}
|
|
@@ -213,7 +234,16 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
|
|
|
213
234
|
return parent.__experimentalSubscribeStore( storeName, handler );
|
|
214
235
|
}
|
|
215
236
|
|
|
237
|
+
function batch( callback ) {
|
|
238
|
+
emitter.pause();
|
|
239
|
+
forEach( stores, ( store ) => store.emitter.pause() );
|
|
240
|
+
callback();
|
|
241
|
+
emitter.resume();
|
|
242
|
+
forEach( stores, ( store ) => store.emitter.resume() );
|
|
243
|
+
}
|
|
244
|
+
|
|
216
245
|
let registry = {
|
|
246
|
+
batch,
|
|
217
247
|
registerGenericStore,
|
|
218
248
|
stores,
|
|
219
249
|
namespaces: stores, // TODO: Deprecate/remove this.
|
package/src/test/registry.js
CHANGED
|
@@ -722,6 +722,40 @@ describe( 'createRegistry', () => {
|
|
|
722
722
|
} );
|
|
723
723
|
} );
|
|
724
724
|
|
|
725
|
+
describe( 'batch', () => {
|
|
726
|
+
it( 'should batch callbacks and only run the subscriber once', () => {
|
|
727
|
+
const store = registry.registerStore( 'myAwesomeReducer', {
|
|
728
|
+
reducer: ( state = 0 ) => state + 1,
|
|
729
|
+
} );
|
|
730
|
+
const listener = jest.fn();
|
|
731
|
+
subscribeWithUnsubscribe( listener );
|
|
732
|
+
|
|
733
|
+
registry.batch( () => {} );
|
|
734
|
+
expect( listener ).not.toHaveBeenCalled();
|
|
735
|
+
|
|
736
|
+
registry.batch( () => {
|
|
737
|
+
store.dispatch( { type: 'dummy' } );
|
|
738
|
+
store.dispatch( { type: 'dummy' } );
|
|
739
|
+
} );
|
|
740
|
+
expect( listener ).toHaveBeenCalledTimes( 1 );
|
|
741
|
+
|
|
742
|
+
const listener2 = jest.fn();
|
|
743
|
+
// useSelect subscribes to the stores differently,
|
|
744
|
+
// This test ensures batching works in this case as well.
|
|
745
|
+
const unsubscribe = registry.__experimentalSubscribeStore(
|
|
746
|
+
'myAwesomeReducer',
|
|
747
|
+
listener2
|
|
748
|
+
);
|
|
749
|
+
registry.batch( () => {
|
|
750
|
+
store.dispatch( { type: 'dummy' } );
|
|
751
|
+
store.dispatch( { type: 'dummy' } );
|
|
752
|
+
} );
|
|
753
|
+
unsubscribe();
|
|
754
|
+
|
|
755
|
+
expect( listener2 ).toHaveBeenCalledTimes( 1 );
|
|
756
|
+
} );
|
|
757
|
+
} );
|
|
758
|
+
|
|
725
759
|
describe( 'use', () => {
|
|
726
760
|
it( 'should pass through options object to plugin', () => {
|
|
727
761
|
const expectedOptions = {};
|
|
@@ -30,3 +30,11 @@ export interface WPDataReduxStoreConfig {
|
|
|
30
30
|
export interface WPDataRegistry {
|
|
31
31
|
register: ( store: WPDataStore ) => void;
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
export interface WPDataEmitter {
|
|
35
|
+
emit: () => void;
|
|
36
|
+
subscribe: ( listener: () => void ) => () => void;
|
|
37
|
+
pause: () => void;
|
|
38
|
+
resume: () => void;
|
|
39
|
+
isPaused: boolean;
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create an event emitter.
|
|
3
|
+
*
|
|
4
|
+
* @return {import("../types").WPDataEmitter} Emitter.
|
|
5
|
+
*/
|
|
6
|
+
export function createEmitter() {
|
|
7
|
+
let isPaused = false;
|
|
8
|
+
let isPending = false;
|
|
9
|
+
const listeners = new Set();
|
|
10
|
+
const notifyListeners = () =>
|
|
11
|
+
// We use Array.from to clone the listeners Set
|
|
12
|
+
// This ensures that we don't run a listener
|
|
13
|
+
// that was added as a response to another listener.
|
|
14
|
+
Array.from( listeners ).forEach( ( listener ) => listener() );
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
get isPaused() {
|
|
18
|
+
return isPaused;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
subscribe( listener ) {
|
|
22
|
+
listeners.add( listener );
|
|
23
|
+
return () => listeners.delete( listener );
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
pause() {
|
|
27
|
+
isPaused = true;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
resume() {
|
|
31
|
+
isPaused = false;
|
|
32
|
+
if ( isPending ) {
|
|
33
|
+
isPending = false;
|
|
34
|
+
notifyListeners();
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
emit() {
|
|
39
|
+
if ( isPaused ) {
|
|
40
|
+
isPending = true;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
notifyListeners();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|