@wordpress/interactivity
Version:
Package that provides a standard and simple way to handle the frontend interactivity of Gutenberg blocks.
8 lines (7 loc) • 13.9 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/store.ts"],
"sourcesContent": ["/**\n * Internal dependencies\n */\nimport { proxifyState, proxifyStore, deepMerge, peek } from './proxies';\nimport { PENDING_GETTER } from './proxies/state';\nimport { getNamespace } from './namespaces';\nimport { isPlainObject, navigationSignal, deepClone } from './utils';\n\nexport const stores = new Map();\nconst rawStores = new Map();\nconst storeLocks = new Map();\nconst storeConfigs = new Map();\nconst serverStates = new Map();\n\n/**\n * Gets the defined config for the store with the passed namespace.\n *\n * @param namespace Store's namespace from which to retrieve the config.\n * @return Defined config for the given namespace.\n */\nexport const getConfig = ( namespace?: string ) =>\n\tstoreConfigs.get( namespace || getNamespace() ) || {};\n\n/**\n * Gets the state defined and updated from the server.\n *\n * The object returned is a deep clone of the state defined in PHP with\n * `wp_interactivity_state()`. When using `actions.navigate()`, this object is\n * updated to reflect the changes in its properties, without affecting the state\n * returned by `store()`. Directives can subscribe to those changes to update\n * the state if needed.\n *\n * @example\n * ```js\n * const { state } = store( 'myPlugin', {\n * callbacks: {\n * updateServerState() {\n * const serverState = getServerState();\n * // Override some property with the new value that came from the server.\n * state.overridableProp = serverState.overridableProp;\n * },\n * },\n * } );\n * ```\n *\n * @param namespace Store namespace. By default, it inherits the namespace of\n * the store where it is defined.\n * @return The server state for the given namespace.\n */\nexport function getServerState( namespace?: string ): Record< string, unknown >;\nexport function getServerState< T extends object >( namespace?: string ): T;\nexport function getServerState< T extends object >( namespace?: string ): T {\n\tconst ns = namespace || getNamespace();\n\tif ( ! serverStates.has( ns ) ) {\n\t\tserverStates.set( ns, {} );\n\t}\n\t// Accesses the navigation signal to make this reactive. It assigns it to an\n\t// arbitrary property (`subscribe`) to prevent the JavaScript minifier from\n\t// removing this line.\n\tgetServerState.subscribe = navigationSignal.value;\n\treturn deepClone( serverStates.get( ns ) ) as T;\n}\ngetServerState.subscribe = 0;\n\ninterface StoreOptions {\n\t/**\n\t * Property to block/unblock private store namespaces.\n\t *\n\t * If the passed value is `true`, it blocks the given namespace, making it\n\t * accessible only through the returned variables of the `store()` call. In\n\t * the case a lock string is passed, it also blocks the namespace, but can\n\t * be unblocked for other `store()` calls using the same lock string.\n\t *\n\t * @example\n\t * ```\n\t * // The store can only be accessed where the `state` const can.\n\t * const { state } = store( 'myblock/private', { ... }, { lock: true } );\n\t * ```\n\t *\n\t * @example\n\t * ```\n\t * // Other modules knowing `SECRET_LOCK_STRING` can access the namespace.\n\t * const { state } = store(\n\t * 'myblock/private',\n\t * { ... },\n\t * { lock: 'SECRET_LOCK_STRING' }\n\t * );\n\t * ```\n\t */\n\tlock?: boolean | string;\n}\n\nexport type AsyncAction< T > = Generator< any, T, unknown >;\nexport type TypeYield< T extends ( ...args: any[] ) => Promise< any > > =\n\tAwaited< ReturnType< T > >;\ntype Prettify< T > = { [ K in keyof T ]: T[ K ] } & {};\ntype DeepPartial< T > = T extends object\n\t? { [ P in keyof T ]?: DeepPartial< T[ P ] > }\n\t: T;\ntype DeepPartialState< T extends { state: object } > = Omit< T, 'state' > & {\n\tstate?: DeepPartial< T[ 'state' ] >;\n};\ntype ConvertGeneratorToPromise< T > = T extends (\n\t...args: infer A\n) => Generator< any, infer R, any >\n\t? ( ...args: A ) => Promise< R >\n\t: never;\ntype ConvertGeneratorsToPromises< T > = {\n\t[ K in keyof T ]: T[ K ] extends ( ...args: any[] ) => any\n\t\t? ConvertGeneratorToPromise< T[ K ] > extends never\n\t\t\t? T[ K ]\n\t\t\t: ConvertGeneratorToPromise< T[ K ] >\n\t\t: T[ K ] extends object\n\t\t? Prettify< ConvertGeneratorsToPromises< T[ K ] > >\n\t\t: T[ K ];\n};\ntype ConvertPromiseToGenerator< T > = T extends (\n\t...args: infer A\n) => Promise< infer R >\n\t? ( ...args: A ) => Generator< any, R, any >\n\t: never;\ntype ConvertPromisesToGenerators< T > = {\n\t[ K in keyof T ]: T[ K ] extends ( ...args: any[] ) => any\n\t\t? ConvertPromiseToGenerator< T[ K ] > extends never\n\t\t\t? T[ K ]\n\t\t\t: ConvertPromiseToGenerator< T[ K ] >\n\t\t: T[ K ] extends object\n\t\t? Prettify< ConvertPromisesToGenerators< T[ K ] > >\n\t\t: T[ K ];\n};\n\nexport const universalUnlock =\n\t'I acknowledge that using a private store means my plugin will inevitably break on the next store release.';\n\n/**\n * Extends the Interactivity API global store adding the passed properties to\n * the given namespace. It also returns stable references to the namespace\n * content.\n *\n * These props typically consist of `state`, which is the reactive part of the\n * store \u2015 which means that any directive referencing a state property will be\n * re-rendered anytime it changes \u2015 and function properties like `actions` and\n * `callbacks`, mostly used for event handlers. These props can then be\n * referenced by any directive to make the HTML interactive.\n *\n * @example\n * ```js\n * const { state } = store( 'counter', {\n * state: {\n * value: 0,\n * get double() { return state.value * 2; },\n * },\n * actions: {\n * increment() {\n * state.value += 1;\n * },\n * },\n * } );\n * ```\n *\n * The code from the example above allows blocks to subscribe and interact with\n * the store by using directives in the HTML, e.g.:\n *\n * ```html\n * <div data-wp-interactive=\"counter\">\n * <button\n * data-wp-text=\"state.double\"\n * data-wp-on--click=\"actions.increment\"\n * >\n * 0\n * </button>\n * </div>\n * ```\n * @param namespace The store namespace to interact with.\n * @param storePart Properties to add to the store namespace.\n * @param options Options for the given namespace.\n *\n * @return A reference to the namespace content.\n */\n\n// Overload for when the types are inferred.\nexport function store< T extends object >(\n\tnamespace: string,\n\tstorePart: T,\n\toptions?: StoreOptions\n): Prettify< ConvertGeneratorsToPromises< T > >;\n\n// Overload for when types are passed via generics and they contain state.\nexport function store< T extends { state: object } >(\n\tnamespace: string,\n\tstorePart?: ConvertPromisesToGenerators< DeepPartialState< T > >,\n\toptions?: StoreOptions\n): Prettify< ConvertGeneratorsToPromises< T > >;\n\n// Overload for when types are passed via generics and they don't contain state.\nexport function store< T extends object >(\n\tnamespace: string,\n\tstorePart?: ConvertPromisesToGenerators< T >,\n\toptions?: StoreOptions\n): Prettify< ConvertGeneratorsToPromises< T > >;\n\n// Overload for when types are divided into multiple parts.\nexport function store< T extends object >(\n\tnamespace: string,\n\tstorePart: ConvertPromisesToGenerators< DeepPartial< T > >,\n\toptions?: StoreOptions\n): Prettify< ConvertGeneratorsToPromises< T > >;\n\nexport function store(\n\tnamespace: string,\n\t{ state = {}, ...block }: any = {},\n\t{ lock = false }: StoreOptions = {}\n) {\n\tif ( ! stores.has( namespace ) ) {\n\t\t// Lock the store if the passed lock is different from the universal\n\t\t// unlock. Once the lock is set (either false, true, or a given string),\n\t\t// it cannot change.\n\t\tif ( lock !== universalUnlock ) {\n\t\t\tstoreLocks.set( namespace, lock );\n\t\t}\n\t\tconst rawStore = {\n\t\t\tstate: proxifyState(\n\t\t\t\tnamespace,\n\t\t\t\tisPlainObject( state ) ? state : {}\n\t\t\t),\n\t\t\t...block,\n\t\t};\n\t\tconst proxifiedStore = proxifyStore( namespace, rawStore );\n\t\trawStores.set( namespace, rawStore );\n\t\tstores.set( namespace, proxifiedStore );\n\t} else {\n\t\t// Lock the store if it wasn't locked yet and the passed lock is\n\t\t// different from the universal unlock. If no lock is given, the store\n\t\t// will be public and won't accept any lock from now on.\n\t\tif ( lock !== universalUnlock && ! storeLocks.has( namespace ) ) {\n\t\t\tstoreLocks.set( namespace, lock );\n\t\t} else {\n\t\t\tconst storeLock = storeLocks.get( namespace );\n\t\t\tconst isLockValid =\n\t\t\t\tlock === universalUnlock ||\n\t\t\t\t( lock !== true && lock === storeLock );\n\n\t\t\tif ( ! isLockValid ) {\n\t\t\t\tif ( ! storeLock ) {\n\t\t\t\t\tthrow Error( 'Cannot lock a public store' );\n\t\t\t\t} else {\n\t\t\t\t\tthrow Error(\n\t\t\t\t\t\t'Cannot unlock a private store with an invalid lock code'\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst target = rawStores.get( namespace );\n\t\tdeepMerge( target, block );\n\t\tdeepMerge( target.state, state );\n\t}\n\n\treturn stores.get( namespace );\n}\n\nexport const parseServerData = ( dom = document ) => {\n\tconst jsonDataScriptTag =\n\t\t// Preferred Script Module data passing form\n\t\tdom.getElementById(\n\t\t\t'wp-script-module-data-@wordpress/interactivity'\n\t\t) ??\n\t\t// Legacy form\n\t\tdom.getElementById( 'wp-interactivity-data' );\n\tif ( jsonDataScriptTag?.textContent ) {\n\t\ttry {\n\t\t\treturn JSON.parse( jsonDataScriptTag.textContent );\n\t\t} catch {}\n\t}\n\treturn {};\n};\n\nexport const populateServerData = ( data?: {\n\tstate?: Record< string, unknown >;\n\tconfig?: Record< string, unknown >;\n\tderivedStateClosures?: Record< string, string[] >;\n} ) => {\n\t// Resets all the previous server states and configs.\n\tserverStates.clear();\n\tstoreConfigs.clear();\n\n\tif ( isPlainObject( data?.state ) ) {\n\t\tObject.entries( data!.state ).forEach( ( [ namespace, state ] ) => {\n\t\t\tconst st = store< any >( namespace, {}, { lock: universalUnlock } );\n\t\t\tdeepMerge( st.state, state, false );\n\t\t\tserverStates.set( namespace, state! );\n\t\t} );\n\t}\n\tif ( isPlainObject( data?.config ) ) {\n\t\tObject.entries( data!.config ).forEach( ( [ namespace, config ] ) => {\n\t\t\tstoreConfigs.set( namespace, config );\n\t\t} );\n\t}\n\tif ( isPlainObject( data?.derivedStateClosures ) ) {\n\t\tObject.entries( data!.derivedStateClosures ).forEach(\n\t\t\t( [ namespace, paths ] ) => {\n\t\t\t\tconst st = store< any >(\n\t\t\t\t\tnamespace,\n\t\t\t\t\t{},\n\t\t\t\t\t{ lock: universalUnlock }\n\t\t\t\t);\n\t\t\t\tpaths.forEach( ( path ) => {\n\t\t\t\t\tconst pathParts = path.split( '.' );\n\t\t\t\t\tconst prop = pathParts.splice( -1, 1 )[ 0 ];\n\t\t\t\t\tconst parent = pathParts.reduce(\n\t\t\t\t\t\t( prev, key ) => peek( prev, key ),\n\t\t\t\t\t\tst\n\t\t\t\t\t);\n\n\t\t\t\t\t// Get the descriptor of the derived state prop.\n\t\t\t\t\tconst desc = Object.getOwnPropertyDescriptor(\n\t\t\t\t\t\tparent,\n\t\t\t\t\t\tprop\n\t\t\t\t\t);\n\n\t\t\t\t\t// The derived state prop is considered a pending getter\n\t\t\t\t\t// only if its value is a plain object, which is how\n\t\t\t\t\t// closures are serialized from PHP.\n\t\t\t\t\tif ( isPlainObject( desc?.value ) ) {\n\t\t\t\t\t\tparent[ prop ] = PENDING_GETTER;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t);\n\t}\n};\n"],
"mappings": "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",
"names": []
}