UNPKG

@domx/dataelement

Version:

A DataElement base class for handling data state changes

169 lines 6.45 kB
import { RootState } from "./RootState"; import { DataElement } from "./DataElement"; import { StateChange } from "@domx/statechange"; export { applyDataElementRdtLogging }; /** * Redux Dev Tools Middleware * * Docs: * https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Methods.md * * Set logChangeEvents to false if using StateChange and * do not want the duplicate state entry. * @param options {RdtLoggingOptions} */ const applyDataElementRdtLogging = (options = { logChangeEvents: true, exclude: [] }) => { if (isApplied || hasDevTools() === false) { return; } isApplied = true; logChangeEvents = options.logChangeEvents ? true : false; excludeActions = options.exclude ? excludeActions.concat(options.exclude) : excludeActions; DataElement.applyMiddlware(connectedCallback, disconnectedCallback); StateChange.applyNextMiddleware(stateChangeNext); window.addEventListener("rootstate-snapshot", sendSnapshot); }; let isApplied = false; let logChangeEvents = true; let excludeActions = ["domx-"]; ; const sendSnapshot = (event) => { getDevToolsInstance().send(event.detail.name, event.detail.state); }; const connectedElements = {}; const connectedCallback = (metaData) => (next) => () => { const el = metaData.element; Object.keys(metaData.dataProperties).forEach((propertyName) => { const dp = metaData.dataProperties[propertyName]; const { statePath, changeEvent } = dp; // update the connected elements connectedElements[statePath] = connectedElements[statePath] || []; connectedElements[statePath].push({ element: el, changeEvent, property: propertyName }); sendStateToDevTools(el, propertyName, statePath, changeEvent); const rdtListener = ((event) => { var _a; !((_a = event.detail) === null || _a === void 0 ? void 0 : _a.isFromDevTools) && sendStateToDevTools(el, propertyName, statePath, changeEvent); }); logChangeEvents && el.addEventListener(changeEvent, rdtListener); //@ts-ignore TS2339 dynamic property dp.rdtListener = rdtListener; }); next(); }; const sendStateToDevTools = (el, propertyName, statePath, changeEvent) => { // @ts-ignore TS7053 getting indexed property const nextState = el[propertyName]; const action = `${el.constructor.__elementName}@${changeEvent}`; if (!excludeActions.find(a => action.indexOf(a) === 0)) { getDevToolsInstance().send(action, RootState.draft(statePath, nextState)); } }; const disconnectedCallback = (metaData) => (next) => () => { const el = metaData.element; Object.keys(metaData.dataProperties).forEach((propertyName) => { // update the connected elements const dp = metaData.dataProperties[propertyName]; const { statePath, changeEvent } = dp; const elIndex = connectedElements[statePath].findIndex(cde => cde.element === el); connectedElements[statePath].splice(elIndex, 1); //@ts-ignore TS2339 dynamic property el.removeEventListener(changeEvent, dp.rdtLitener); //@ts-ignore TS2339 dynamic property delete dp.rdtListener; }); next(); }; const stateChangeNext = (stateChange) => (next) => (state) => { const result = next(state); const meta = stateChange.meta; const dpmd = meta.el.__dataPropertyMetaData; const statePath = dpmd[meta.property].statePath; getDevToolsInstance().send(getFnName(meta), RootState.draft(statePath, result)); return result; }; const getFnName = ({ className, tapName, nextName }) => `${className}.${tapName ? `${tapName}(${nextName})` : `${nextName}()`}`; /** * True if the redux dev tools extension is installed * @returns {boolean} */ const hasDevTools = () => window.__REDUX_DEVTOOLS_EXTENSION__ !== undefined; /** * Returns the redux dev tools extension * @returns {DevToolsExtension} */ const getDevTools = () => window.__REDUX_DEVTOOLS_EXTENSION__; /** * Pulls the connected dev tools instance from the HTML Element. * Creates it if it does not exist. * @param stateChange {StateChange} * @returns {DevToolsInstance} */ let __rdt = null; const getDevToolsInstance = () => { __rdt = __rdt || setupDevToolsInstance(); return __rdt; }; /** * Creates the dev tools istance and sets up the * listener for dev tools interactions. * @returns DevToolsInstance */ const setupDevToolsInstance = () => { const dt = getDevTools().connect(); dt.init(RootState.current); dt.subscribe(updateFromDevTools(dt)); return dt; }; const updateFromDevTools = (dt) => (data) => { if (isInit(data)) { return; } if (canHandleUpdate(data)) { RootState.init(JSON.parse(data.state)); updateConnectedElements(); } else { dt.error(`DataElement RDT logging does not support payload type: ${data.type}:${data.payload.type}`); } }; /** * Loops through connected elements and updates * their state properties and dispatches the sync change */ const updateConnectedElements = () => { Object.keys(connectedElements).forEach((statePath) => { const stateAtPath = RootState.get(statePath); connectedElements[statePath].forEach(({ property, changeEvent, element }) => { // @ts-ignore TS7053 setting indexed property element[property] = stateAtPath; element.dispatchEvent(new CustomEvent(changeEvent, { detail: { isSyncUpdate: true, isFromDevTools: true } })); }); }); }; /** * Returns true if the listener data is for initializing dev tools state. * @param data {DevToolsEventData} * @returns {boolean} */ const isInit = (data) => data.type === "START" || data.payload.type === "IMPORT_STATE"; /** * Returns true if this middleware can handle the listener data. * @param data {DevToolsEventData} * @returns {boolean} */ const canHandleUpdate = (data) => data.type === "DISPATCH" && (data.payload.type === "JUMP_TO_ACTION" || data.payload.type === "JUMP_TO_STATE"); /** * Exposed for testing */ applyDataElementRdtLogging.reset = () => { isApplied = false; }; //# sourceMappingURL=applyDataElementRdtLogging.js.map