UNPKG

@wordpress/interactivity

Version:

Package that provides a standard and simple way to handle the frontend interactivity of Gutenberg blocks.

204 lines (192 loc) 6.78 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.populateServerData = exports.parseServerData = exports.getServerState = exports.getConfig = void 0; exports.store = store; exports.universalUnlock = exports.stores = void 0; var _proxies = require("./proxies"); var _namespaces = require("./namespaces"); var _utils = require("./utils"); /** * Internal dependencies */ /** * External dependencies */ const stores = exports.stores = new Map(); const rawStores = new Map(); const storeLocks = new Map(); const storeConfigs = new Map(); const serverStates = new Map(); /** * Gets the defined config for the store with the passed namespace. * * @param namespace Store's namespace from which to retrieve the config. * @return Defined config for the given namespace. */ const getConfig = namespace => storeConfigs.get(namespace || (0, _namespaces.getNamespace)()) || {}; /** * Gets the part of the state defined and updated from the server. * * The object returned is read-only, and includes the state defined in PHP with * `wp_interactivity_state()`. When using `actions.navigate()`, this object is * updated to reflect the changes in its properties, without affecting the state * returned by `store()`. Directives can subscribe to those changes to update * the state if needed. * * @example * ```js * const { state } = store('myStore', { * callbacks: { * updateServerState() { * const serverState = getServerState(); * // Override some property with the new value that came from the server. * state.overridableProp = serverState.overridableProp; * }, * }, * }); * ``` * * @param namespace Store's namespace from which to retrieve the server state. * @return The server state for the given namespace. */ exports.getConfig = getConfig; const getServerState = namespace => { const ns = namespace || (0, _namespaces.getNamespace)(); if (!serverStates.has(ns)) { serverStates.set(ns, (0, _proxies.proxifyState)(ns, {}, { readOnly: true })); } return serverStates.get(ns); }; exports.getServerState = getServerState; const universalUnlock = exports.universalUnlock = 'I acknowledge that using a private store means my plugin will inevitably break on the next store release.'; /** * Extends the Interactivity API global store adding the passed properties to * the given namespace. It also returns stable references to the namespace * content. * * These props typically consist of `state`, which is the reactive part of the * store ― which means that any directive referencing a state property will be * re-rendered anytime it changes ― and function properties like `actions` and * `callbacks`, mostly used for event handlers. These props can then be * referenced by any directive to make the HTML interactive. * * @example * ```js * const { state } = store( 'counter', { * state: { * value: 0, * get double() { return state.value * 2; }, * }, * actions: { * increment() { * state.value += 1; * }, * }, * } ); * ``` * * The code from the example above allows blocks to subscribe and interact with * the store by using directives in the HTML, e.g.: * * ```html * <div data-wp-interactive="counter"> * <button * data-wp-text="state.double" * data-wp-on--click="actions.increment" * > * 0 * </button> * </div> * ``` * @param namespace The store namespace to interact with. * @param storePart Properties to add to the store namespace. * @param options Options for the given namespace. * * @return A reference to the namespace content. */ // Overload for when the types are inferred. // Overload for when types are passed via generics and they contain state. // Overload for when types are passed via generics and they don't contain state. // Overload for when types are divided into multiple parts. function store(namespace, { state = {}, ...block } = {}, { lock = false } = {}) { if (!stores.has(namespace)) { // Lock the store if the passed lock is different from the universal // unlock. Once the lock is set (either false, true, or a given string), // it cannot change. if (lock !== universalUnlock) { storeLocks.set(namespace, lock); } const rawStore = { state: (0, _proxies.proxifyState)(namespace, (0, _utils.isPlainObject)(state) ? state : {}), ...block }; const proxifiedStore = (0, _proxies.proxifyStore)(namespace, rawStore); rawStores.set(namespace, rawStore); stores.set(namespace, proxifiedStore); } else { // Lock the store if it wasn't locked yet and the passed lock is // different from the universal unlock. If no lock is given, the store // will be public and won't accept any lock from now on. if (lock !== universalUnlock && !storeLocks.has(namespace)) { storeLocks.set(namespace, lock); } else { const storeLock = storeLocks.get(namespace); const isLockValid = lock === universalUnlock || lock !== true && lock === storeLock; if (!isLockValid) { if (!storeLock) { throw Error('Cannot lock a public store'); } else { throw Error('Cannot unlock a private store with an invalid lock code'); } } } const target = rawStores.get(namespace); (0, _proxies.deepMerge)(target, block); (0, _proxies.deepMerge)(target.state, state); } return stores.get(namespace); } const parseServerData = (dom = document) => { var _dom$getElementById; const jsonDataScriptTag = // Preferred Script Module data passing form (_dom$getElementById = dom.getElementById('wp-script-module-data-@wordpress/interactivity')) !== null && _dom$getElementById !== void 0 ? _dom$getElementById : // Legacy form dom.getElementById('wp-interactivity-data'); if (jsonDataScriptTag?.textContent) { try { return JSON.parse(jsonDataScriptTag.textContent); } catch {} } return {}; }; exports.parseServerData = parseServerData; const populateServerData = data => { if ((0, _utils.isPlainObject)(data?.state)) { Object.entries(data.state).forEach(([namespace, state]) => { const st = store(namespace, {}, { lock: universalUnlock }); (0, _proxies.deepMerge)(st.state, state, false); (0, _proxies.deepMerge)(getServerState(namespace), state); }); } if ((0, _utils.isPlainObject)(data?.config)) { Object.entries(data.config).forEach(([namespace, config]) => { storeConfigs.set(namespace, config); }); } }; // Parse and populate the initial state and config. exports.populateServerData = populateServerData; const data = parseServerData(); populateServerData(data); //# sourceMappingURL=store.js.map