@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
JavaScript
;
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