@steambrew/client
Version:
A support library for creating plugins with Millennium.
132 lines (131 loc) • 4.75 kB
JavaScript
import { useState } from 'react';
/**
* Create a Regular Expression to search for a React component that uses certain props in order.
*
* @param {string[]} propList Ordererd list of properties to search for
* @returns {RegExp} RegEx to call .test(component.toString()) on
*/
export function createPropListRegex(propList, fromStart = true) {
let regexString = fromStart ? 'const{' : '';
propList.forEach((prop, propIdx) => {
regexString += `"?${prop}"?:[a-zA-Z_$]{1,2}`;
if (propIdx < propList.length - 1) {
regexString += ',';
}
});
// TODO provide a way to enable this
// console.debug(`[DFL:Utils] createPropListRegex generated regex "${regexString}" for props`, propList);
return new RegExp(regexString);
}
let oldHooks = {};
function getReactInternals() {
const react = window.SP_REACT;
let internals =
/** react <18 */
react?.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ??
/** react >18 */
react?.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
if (internals?.ReactCurrentDispatcher?.current) {
return internals.ReactCurrentDispatcher.current;
}
return Object.values(internals).find((module) => module?.useEffect && module?.useContext && module?.useRef && module?.useState);
}
export function applyHookStubs(customHooks = {}) {
const hooks = getReactInternals();
// TODO: add more hooks
oldHooks = {
useContext: hooks.useContext,
useCallback: hooks.useCallback,
useLayoutEffect: hooks.useLayoutEffect,
useEffect: hooks.useEffect,
useMemo: hooks.useMemo,
useRef: hooks.useRef,
useState: hooks.useState,
};
hooks.useCallback = (cb) => cb;
hooks.useContext = (cb) => cb._currentValue;
hooks.useLayoutEffect = (_) => { }; //cb();
hooks.useMemo = (cb, _) => cb;
hooks.useEffect = (_) => { }; //cb();
hooks.useRef = (val) => ({ current: val || {} });
hooks.useState = (v) => {
let val = v;
return [val, (n) => (val = n)];
};
Object.assign(hooks, customHooks);
return hooks;
}
export function removeHookStubs() {
const hooks = getReactInternals();
Object.assign(hooks, oldHooks);
oldHooks = {};
}
export function fakeRenderComponent(fun, customHooks) {
const hooks = applyHookStubs(customHooks);
const res = fun(hooks); // TODO why'd we do this?
removeHookStubs();
return res;
}
export function wrapReactType(node, prop = 'type') {
if (node[prop]?.__MILLENNIUM_WRAPPED) {
return node[prop];
}
else {
return (node[prop] = { ...node[prop], __MILLENNIUM_WRAPPED: true });
}
}
export function wrapReactClass(node, prop = 'type') {
var _a;
if (node[prop]?.__MILLENNIUM_WRAPPED) {
return node[prop];
}
else {
const cls = node[prop];
const wrappedCls = (_a = class extends cls {
},
_a.__MILLENNIUM_WRAPPED = true,
_a);
return (node[prop] = wrappedCls);
}
}
export function getReactRoot(o) {
return o[Object.keys(o).find((k) => k.startsWith('__reactContainer$'))] || o['_reactRootContainer']?._internalRoot?.current;
}
export function getReactInstance(o) {
return (o[Object.keys(o).find((k) => k.startsWith('__reactFiber'))] ||
o[Object.keys(o).find((k) => k.startsWith('__reactInternalInstance'))]);
}
export const findInTree = (parent, filter, opts) => {
const { walkable = null, ignore = [] } = opts ?? {};
if (!parent || typeof parent !== 'object') {
// Parent is invalid to search through
return null;
}
if (filter(parent))
return parent; // Parent matches, just return
if (Array.isArray(parent)) {
// Parent is an array, go through values
return parent.map((x) => findInTree(x, filter, opts)).find((x) => x);
}
// Parent is an object, go through values (or option to only use certain keys)
return (walkable || Object.keys(parent)).map((x) => !ignore.includes(x) && findInTree(parent[x], filter, opts)).find((x) => x);
};
export const findInReactTree = (node, filter) => findInTree(node, filter, {
// Specialised findInTree for React nodes
walkable: ['props', 'children', 'child', 'sibling'],
});
/**
* Finds the parent window of a DOM element
*/
export function getParentWindow(elem) {
return elem?.ownerDocument?.defaultView;
}
/**
* React hook to find the host window of a component
* Pass the returned ref into a React element and window will be its host window.
* @returns [ref, window]
*/
export function useWindowRef() {
const [win, setWin] = useState(null);
return [(elem) => setWin(getParentWindow(elem)), win];
}