import * as ReactImport from 'react';
import * as ReactDOMImport from 'react-dom';
type TReact = typeof ReactImport;
type TReactDOM = typeof ReactDOMImport;
const HIGHLIGHT_CLASS = 'react-update-highlight';
const MATCHES_HIGHLIGHT_CLASS = /\s*\breact-update-highlight\b\s*/gi;
const STYLE = document.createElement('style');
STYLE.type = 'text/css';
export const highlightUpdates = (React: TReact, ReactDOM: TReactDOM, color: string = 'rgba(255, 0, 0, 0.5)') => {
STYLE.innerHTML =
`.react-update-highlight {
animation-name: react-update-highlight;
animation-duration: 2s;
animation-iteration-count: 1;
}
@keyframes react-update-highlight {
from {
background-color: ${color};
}
to {
background-color: transparent;
}
}`;
document.head.appendChild(STYLE);
const originalCreateElement = React.createElement;
(React as any).createElement = function () {
const args = Array.prototype.slice.call(arguments);
const component = args[0];
if (
typeof component === 'function' &&
component.prototype instanceof React.Component &&
!component.prototype.hasReactUpdateHighlight
) {
const originalComponentDidUpdate = (component as any).prototype.componentDidUpdate;
component.prototype.hasReactUpdateHighlight = true;
(component as any).prototype.componentDidUpdate = function () {
const updateArgs = Array.prototype.slice.call(arguments);
let node: Element;
try {
node = ReactDOM.findDOMNode(this);
} catch (error) {
return;
}
if (MATCHES_HIGHLIGHT_CLASS.test(node.className)) {
node.className = node.className.replace(MATCHES_HIGHLIGHT_CLASS, '');
}
void (node as any).offsetWidth; // tslint:disable-line:no-unused-expression
const needsSpace = node.className || node.className.lastIndexOf(' ') !== node.className.length - 1;
node.className = `${node.className}${needsSpace ? ' ' + HIGHLIGHT_CLASS : HIGHLIGHT_CLASS}`;
if (typeof originalComponentDidUpdate === 'function') {
originalComponentDidUpdate.apply(this, updateArgs);
}
};
}
return originalCreateElement.apply(this, args);
};
};
export default highlightUpdates;
|