diff --git a/global.d.ts b/global.d.ts index ce33a8f..bc193a9 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,5 +1,10 @@ +import type * as React from 'react'; +import type * as ReactDOM from 'react-dom'; +import type * as JSXRuntime from 'react/jsx-runtime'; declare global { interface Window { SP_REACT: typeof React; + SP_REACTDOM: typeof ReactDOM; + SP_JSX: typeof JSXRuntime; } } diff --git a/package.json b/package.json index 1ee6ced..2fc5867 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,8 @@ "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@types/jest": "^29.5.14", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", + "@types/react": "19.1.1", + "@types/react-dom": "19.1.1", "@types/react-router": "5.1.20", "commitizen": "^4.3.1", "husky": "^9.1.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd69b5f..2dfc538 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,11 +27,11 @@ importers: specifier: ^29.5.14 version: 29.5.14 '@types/react': - specifier: 18.3.3 - version: 18.3.3 + specifier: 19.1.1 + version: 19.1.1 '@types/react-dom': - specifier: 18.3.0 - version: 18.3.0 + specifier: 19.1.1 + version: 19.1.1(@types/react@19.1.1) '@types/react-router': specifier: 5.1.20 version: 5.1.20 @@ -606,17 +606,16 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/prop-types@15.7.15': - resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - - '@types/react-dom@18.3.0': - resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + '@types/react-dom@19.1.1': + resolution: {integrity: sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==} + peerDependencies: + '@types/react': ^19.0.0 '@types/react-router@5.1.20': resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} - '@types/react@18.3.3': - resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/react@19.1.1': + resolution: {integrity: sha512-ePapxDL7qrgqSF67s0h9m412d9DbXyC1n59O2st+9rjuuamWsZuD2w55rqY12CbzsZ7uVXb5Nw0gEp9Z8MMutQ==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -3622,20 +3621,17 @@ snapshots: '@types/normalize-package-data@2.4.4': {} - '@types/prop-types@15.7.15': {} - - '@types/react-dom@18.3.0': + '@types/react-dom@19.1.1(@types/react@19.1.1)': dependencies: - '@types/react': 18.3.3 + '@types/react': 19.1.1 '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 18.3.3 + '@types/react': 19.1.1 - '@types/react@18.3.3': + '@types/react@19.1.1': dependencies: - '@types/prop-types': 15.7.15 csstype: 3.1.3 '@types/stack-utils@2.0.3': {} diff --git a/src/components/Focusable.ts b/src/components/Focusable.ts index dc9a357..35cf213 100644 --- a/src/components/Focusable.ts +++ b/src/components/Focusable.ts @@ -17,5 +17,5 @@ export interface FocusableProps extends HTMLAttributes, FooterLe const focusableRegex = createPropListRegex(["flow-children", "onActivate", "onCancel", "focusClassName", "focusWithinClassName"]); export const Focusable = findModuleExport((e: Export) => - e?.render?.toString && focusableRegex.test(e.render.toString()) + (typeof e == 'function' && e?.toString && focusableRegex.test(e.toString())) || (e?.render?.toString && focusableRegex.test(e.render.toString())) ) as FC>; diff --git a/src/custom-components/ReorderableList.tsx b/src/custom-components/ReorderableList.tsx index 078e30e..781cd0c 100644 --- a/src/custom-components/ReorderableList.tsx +++ b/src/custom-components/ReorderableList.tsx @@ -108,7 +108,7 @@ export type ReorderableListEntryProps = { reorderEntryFunc: CallableFunction; reorderEnabled: boolean; animate: boolean; - children: ReactElement | null; + children: ReactElement | null; }; function ReorderableItem(props: ReorderableListEntryProps) { diff --git a/src/utils/react/fc.ts b/src/utils/react/fc.ts index 0acd9b5..6de16a1 100644 --- a/src/utils/react/fc.ts +++ b/src/utils/react/fc.ts @@ -69,71 +69,145 @@ export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoli let renderHookStep = 0; - // Accessed two times, once directly before class instantiation, and again in some extra logic we don't need to worry about that we hanlde below just in case. - Object.defineProperty(component, "contextType", { - configurable: true, - get: function () { - loggingEnabled && logger.debug("get contexttype", this, stubsApplied, renderHookStep); - loggingEnabled && console.trace("contextType trace"); - if (renderHookStep == 0) renderHookStep = 1; - else if (renderHookStep == 3) renderHookStep = 4; - return this._contextType; - }, - set: function (value) { - this._contextType = value; - } - }); - - // Always accessed directly after contextType for the path we want to catch. - Object.defineProperty(component, "contextTypes", { - configurable: true, - get: function () { - loggingEnabled && logger.debug("get contexttypes", this, stubsApplied, renderHookStep); - loggingEnabled && console.trace("contextTypes trace"); - if (renderHookStep == 1) { - renderHookStep = 2; - applyStubsIfNeeded(); - }; - return this._contextTypes; - }, - set: function (value) { - this._contextTypes = value; - } - }); - - // Set directly after class is instantiated - Object.defineProperty(component.prototype, "updater", { - configurable: true, - get: function () { - return this._updater; - }, - set: function (value) { - loggingEnabled && logger.debug("set updater", this, value, stubsApplied, renderHookStep); - loggingEnabled && console.trace("updater trace"); - if (renderHookStep == 2) { - renderHookStep = 0; - removeStubsIfNeeded(); + if (window.SP_REACTDOM.version.startsWith("19.")) { + // Accessed two times directly before class instantiation on path A and once on path B + Object.defineProperty(component, "contextType", { + configurable: true, + get: function () { + loggingEnabled && logger.debug("get contexttype", this, this._contextType, stubsApplied, renderHookStep); + loggingEnabled && console.trace("contextType trace"); + if (renderHookStep == 0) { + renderHookStep = 1; + } + if (this._contextType == null) { + this._contextType = {}; + } + if (!this._contextType.appliedCurrentValueHook) { + logger.debug("applied currentvalue hook"); + this._contextType.appliedCurrentValueHook = true; + Object.defineProperty(this._contextType, "_currentValue", { + configurable: true, + get: function () { + loggingEnabled && logger.debug("get currentValue", this, stubsApplied, renderHookStep); + loggingEnabled && console.trace("currentValue trace"); + if (renderHookStep == 1) { + renderHookStep = 2; + applyStubsIfNeeded(); + } + return this.__currentValue; + }, + set: function (value) { + return this.__currentValue = value; + } + }); + } + return this._contextType; + }, + set: function (value) { + this._contextType = value; } - return this._updater = value; - } - }); + }); - // Prevents the second contextType+contextTypes access from leaving its hooks around - Object.defineProperty(component, "getDerivedStateFromProps", { - configurable: true, - get: function () { - loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied, renderHookStep); - loggingEnabled && console.trace("getDerivedStateFromProps trace"); - if (renderHookStep == 2) { - renderHookStep = 0; - removeStubsIfNeeded(); + // Set directly after class is instantiated + Object.defineProperty(component.prototype, "updater", { + configurable: true, + get: function () { + return this._updater; + }, + set: function (value) { + loggingEnabled && logger.debug("set updater", this, value, stubsApplied, renderHookStep); + loggingEnabled && console.trace("updater trace"); + if (renderHookStep == 1 || renderHookStep == 2) { + renderHookStep = 0; + removeStubsIfNeeded(); + } + return this._updater = value; } - return this._getDerivedStateFromProps; - }, - set: function (value) { - this._getDerivedStateFromProps = value; - } - }); + }); + + // Prevents the second contextType access from leaving its hooks around + Object.defineProperty(component, "getDerivedStateFromProps", { + configurable: true, + get: function () { + loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied, renderHookStep); + loggingEnabled && console.trace("getDerivedStateFromProps trace"); + if (renderHookStep == 1|| renderHookStep == 2) { + renderHookStep = 0; + removeStubsIfNeeded(); + } + return this._getDerivedStateFromProps; + }, + set: function (value) { + this._getDerivedStateFromProps = value; + } + }); + } else if (window.SP_REACTDOM.version.startsWith("18.")) { + // Accessed two times, once directly before class instantiation, and again in some extra logic we don't need to worry about that we hanlde below just in case. + Object.defineProperty(component, "contextType", { + configurable: true, + get: function () { + loggingEnabled && logger.debug("get contexttype", this, stubsApplied, renderHookStep); + loggingEnabled && console.trace("contextType trace"); + if (renderHookStep == 0) renderHookStep = 1; + else if (renderHookStep == 3) renderHookStep = 4; + return this._contextType; + }, + set: function (value) { + this._contextType = value; + } + }); + + // Always accessed directly after contextType for the path we want to catch. + Object.defineProperty(component, "contextTypes", { + configurable: true, + get: function () { + loggingEnabled && logger.debug("get contexttypes", this, stubsApplied, renderHookStep); + loggingEnabled && console.trace("contextTypes trace"); + if (renderHookStep == 1) { + renderHookStep = 2; + applyStubsIfNeeded(); + }; + return this._contextTypes; + }, + set: function (value) { + this._contextTypes = value; + } + }); + + // Set directly after class is instantiated + Object.defineProperty(component.prototype, "updater", { + configurable: true, + get: function () { + return this._updater; + }, + set: function (value) { + loggingEnabled && logger.debug("set updater", this, value, stubsApplied, renderHookStep); + loggingEnabled && console.trace("updater trace"); + if (renderHookStep == 2) { + renderHookStep = 0; + removeStubsIfNeeded(); + } + return this._updater = value; + } + }); + + // Prevents the second contextType+contextTypes access from leaving its hooks around + Object.defineProperty(component, "getDerivedStateFromProps", { + configurable: true, + get: function () { + loggingEnabled && logger.debug("get getDerivedStateFromProps", this, stubsApplied, renderHookStep); + loggingEnabled && console.trace("getDerivedStateFromProps trace"); + if (renderHookStep == 2) { + renderHookStep = 0; + removeStubsIfNeeded(); + } + return this._getDerivedStateFromProps; + }, + set: function (value) { + this._getDerivedStateFromProps = value; + } + }); + } return userComponent; } \ No newline at end of file diff --git a/src/utils/react/react.ts b/src/utils/react/react.ts index d3e0ad8..3afac09 100644 --- a/src/utils/react/react.ts +++ b/src/utils/react/react.ts @@ -1,4 +1,6 @@ -import * as React from 'react'; +import type * as React from 'react'; +import type * as ReactDOM from 'react-dom'; +import type * as JSXRuntime from 'react/jsx-runtime'; import { Ref, useState } from 'react'; // this shouldn't need to be redeclared but it does for some reason @@ -6,6 +8,8 @@ import { Ref, useState } from 'react'; declare global { interface Window { SP_REACT: typeof React; + SP_REACTDOM: typeof ReactDOM; + SP_JSX: typeof JSXRuntime; } } @@ -33,9 +37,11 @@ export function createPropListRegex(propList: string[], fromStart: boolean = tru let oldHooks = {}; +export let INTERNAL_HOOKS = (window.SP_REACT as any)?.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?.ReactCurrentDispatcher + .current || Object.values((window.SP_REACT as any)?.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE).find((p: any) => p?.useEffect); + export function applyHookStubs(customHooks: any = {}): any { - const hooks = (window.SP_REACT as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher - .current; + const hooks = INTERNAL_HOOKS; // TODO: add more hooks @@ -50,7 +56,7 @@ export function applyHookStubs(customHooks: any = {}): any { }; hooks.useCallback = (cb: Function) => cb; - hooks.useContext = (cb: any) => cb._currentValue; + hooks.useContext = (cb: any) => cb?._currentValue; hooks.useLayoutEffect = (_: Function) => {}; //cb(); hooks.useMemo = (cb: Function, _: any[]) => cb; hooks.useEffect = (_: Function) => {}; //cb(); @@ -67,8 +73,7 @@ export function applyHookStubs(customHooks: any = {}): any { } export function removeHookStubs() { - const hooks = (window.SP_REACT as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher - .current; + const hooks = INTERNAL_HOOKS; Object.assign(hooks, oldHooks); oldHooks = {}; } diff --git a/tsconfig.json b/tsconfig.json index 5dc80d0..d328952 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,7 @@ "outDir": "dist", "module": "ESNext", "target": "ES2020", - "jsx": "react", - "jsxFactory": "window.SP_REACT.createElement", + "jsx": "react-jsx", "declaration": true, "moduleResolution": "node", "noUnusedLocals": true,