mirror of
https://github.com/SteamDeckHomebrew/decky-frontend-lib.git
synced 2026-05-20 10:00:08 +02:00
Compare commits
21 Commits
globals-wo
...
v4.11.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b688bc3544 | ||
|
|
3126dd3e04 | ||
|
|
8596294667 | ||
|
|
772a85523b | ||
|
|
aabc522740 | ||
|
|
66e0afccae | ||
|
|
77e6acd828 | ||
|
|
261162c8bc | ||
|
|
f8fda380f1 | ||
|
|
7242c69758 | ||
|
|
fd651eb989 | ||
|
|
6e443c06d3 | ||
|
|
25b4b60e34 | ||
|
|
cf6572cd7b | ||
|
|
779b1ae0de | ||
|
|
366357d9a3 | ||
|
|
37dd88513e | ||
|
|
ed87f73602 | ||
|
|
3d5de65077 | ||
|
|
33d224bc3f | ||
|
|
aa0678c857 |
7
.github/workflows/release.yaml
vendored
7
.github/workflows/release.yaml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
@@ -18,7 +22,7 @@ jobs:
|
||||
- name: Setup | Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
- name: Setup | Dependencies
|
||||
run: npm i -g pnpm && pnpm i --frozen-lockfile
|
||||
- name: Build
|
||||
@@ -29,5 +33,4 @@ jobs:
|
||||
if: github.event_name != 'pull_request'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN_DECKY_ORG }}
|
||||
run: pnpm exec semantic-release
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no -- commitlint --edit "${1}"
|
||||
|
||||
60
CHANGELOG.md
60
CHANGELOG.md
@@ -1,3 +1,63 @@
|
||||
## [4.11.3](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.11.2...v4.11.3) (2026-03-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Tabs:** update for latest beta ([#129](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/129)) ([3126dd3](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/3126dd3e040eaef00eb0362b69efb143d7e01030))
|
||||
|
||||
## [4.11.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.11.1...v4.11.2) (2026-03-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** microsoft ([aabc522](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/aabc5227400f1660ddcb7917fa8f305ad2fce3d7))
|
||||
* **ci:** microsoft stinks ([66e0afc](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/66e0afccae6803ee57d817621340d09a12ba75d8))
|
||||
* **ci:** update semantic release ([772a855](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/772a85523b8c4bbf80ccd4827a89801e3a51cfd6))
|
||||
* **ci:** use new npm publishing auth ([77e6acd](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/77e6acd828b1d0f0978356292b1c46bfc3b8e422))
|
||||
* **components:** update for latest beta ([261162c](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/261162c8bceecc50afad07d57a78b85b98936fe1))
|
||||
|
||||
## [4.11.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.11.0...v4.11.1) (2025-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **webpack:** ignore window module (lol), ignore filter errors ([7242c69](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/7242c697580f3a6a6d373eaacc4fb83ccff9bd2a))
|
||||
|
||||
# [4.11.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.10.6...v4.11.0) (2025-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* react 19 support update ([6e443c0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/6e443c06d3b7f2790da8b44a5c907517fcf12152))
|
||||
|
||||
## [4.10.6](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.10.5...v4.10.6) (2025-09-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** update to resolve deprecation in sem. rel. due to octokit ([779b1ae](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/779b1ae0defac84dd0d93517858d5aa5e51a2328))
|
||||
|
||||
## [4.10.5](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.10.4...v4.10.5) (2025-08-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **DialogHeader:** valve rewrote component on beta, update filter to search for both versions ([37dd885](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/37dd88513ed7360d3bcdc99630d6cb0c8d31db3e))
|
||||
|
||||
## [4.10.4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.10.3...v4.10.4) (2025-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modals:** fix ModalRoot on beta ([3d5de65](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/3d5de65077098ff3fb5192cb55fb1534336411f5))
|
||||
|
||||
## [4.10.3](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.10.2...v4.10.3) (2025-07-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modals:** fix `<ConfirmModal />` for new Steam beta ([#123](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/123)) ([aa0678c](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/aa0678c857e07f58de58e5d20565bf2718fff6dc))
|
||||
|
||||
## [4.10.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.10.1...v4.10.2) (2025-06-28)
|
||||
|
||||
|
||||
|
||||
5
global.d.ts
vendored
5
global.d.ts
vendored
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
28
package.json
28
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@decky/ui",
|
||||
"version": "4.10.2",
|
||||
"version": "4.11.3",
|
||||
"description": "A library for interacting with the Steam frontend in Decky plugins and elsewhere.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -41,30 +41,30 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.3.0",
|
||||
"@commitlint/config-conventional": "^19.2.2",
|
||||
"@commitlint/cz-commitlint": "^19.2.0",
|
||||
"@commitlint/cli": "^19.8.1",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@commitlint/cz-commitlint": "^19.8.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/react": "19.1.1",
|
||||
"@types/react-dom": "19.1.1",
|
||||
"@types/react-router": "5.1.20",
|
||||
"commitizen": "^4.3.0",
|
||||
"husky": "^9.0.11",
|
||||
"commitizen": "^4.3.1",
|
||||
"husky": "^9.1.7",
|
||||
"import-sort-style-module": "^6.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"minimist": "^1.2.8",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-import-sort": "^0.0.7",
|
||||
"semantic-release": "^24.0.0",
|
||||
"semantic-release": "^25.0.3",
|
||||
"shx": "^0.3.4",
|
||||
"ts-jest": "^29.1.4",
|
||||
"ts-jest": "^29.4.1",
|
||||
"typedoc": "^0.25.13",
|
||||
"typedoc-plugin-mdn-links": "^3.1.29",
|
||||
"typedoc-plugin-mdn-links": "^3.3.8",
|
||||
"typedoc-plugin-missing-exports": "^2.3.0",
|
||||
"typedoc-wikijs-theme": "^1.0.5",
|
||||
"typescript": "^5.4.5"
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
|
||||
3194
pnpm-lock.yaml
generated
3194
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import { CSSProperties, FC, ReactNode, RefAttributes } from 'react';
|
||||
|
||||
import { CommonUIModule } from '../webpack';
|
||||
import { CommonUIModule, Module } from '../webpack';
|
||||
import { FooterLegendProps } from './FooterLegend';
|
||||
|
||||
export interface DialogCommonProps extends RefAttributes<HTMLDivElement> {
|
||||
@@ -52,8 +52,17 @@ export interface DialogButtonProps extends DialogCommonProps, FooterLegendProps
|
||||
}
|
||||
|
||||
const CommonDialogDivs = Object.values(CommonUIModule).filter(
|
||||
(m: any) => typeof m === 'object' && m?.render?.toString().includes('createElement("div",{...') ||
|
||||
m?.render?.toString().includes('createElement("div",Object.assign({},'),
|
||||
(m: any) => typeof m === 'object' &&
|
||||
// New
|
||||
(
|
||||
m?.render?.toString().includes('jsx)("div",{...') ||
|
||||
m?.render?.toString().includes('jsx)("div",Object.assign({},')
|
||||
) ||
|
||||
// Old
|
||||
(
|
||||
m?.render?.toString().includes('createElement("div",{...') ||
|
||||
m?.render?.toString().includes('createElement("div",Object.assign({},')
|
||||
)
|
||||
);
|
||||
const MappedDialogDivs = new Map(
|
||||
Object.values(CommonDialogDivs).map((m: any) => {
|
||||
@@ -68,7 +77,12 @@ const MappedDialogDivs = new Map(
|
||||
}),
|
||||
);
|
||||
|
||||
export const DialogHeader = MappedDialogDivs.get('DialogHeader') as FC<DialogCommonProps>;
|
||||
// Old | New
|
||||
export const DialogHeader = (MappedDialogDivs.get('DialogHeader') || Object.values(CommonUIModule).find((component: Module) => {
|
||||
const str = component?.render?.toString?.();
|
||||
return str?.includes("role:\"heading\"") && str.includes(")(\"DialogHeader\",");
|
||||
})) as FC<DialogCommonProps>;
|
||||
|
||||
export const DialogSubHeader = MappedDialogDivs.get('DialogSubHeader') as FC<DialogCommonProps>;
|
||||
export const DialogFooter = MappedDialogDivs.get('DialogFooter') as FC<DialogCommonProps>;
|
||||
export const DialogLabel = MappedDialogDivs.get('DialogLabel') as FC<DialogCommonProps>;
|
||||
|
||||
@@ -17,5 +17,5 @@ export interface FocusableProps extends HTMLAttributes<HTMLDivElement>, 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<FocusableProps & RefAttributes<HTMLDivElement>>;
|
||||
|
||||
@@ -83,22 +83,26 @@ export interface ConfirmModalProps extends ModalRootProps {
|
||||
}
|
||||
|
||||
export const ConfirmModal = findModuleExport(
|
||||
(e: Export) => !e?.prototype?.OK && e?.prototype?.Cancel && e?.prototype?.render,
|
||||
(e: Export) => e?.toString()?.includes('bUpdateDisabled') && e?.toString()?.includes('closeModal') && e?.toString()?.includes('onGamepadCancel'),
|
||||
) as FC<ConfirmModalProps>;
|
||||
|
||||
export const ModalRoot = Object.values(
|
||||
findModule((m: any) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
export const ModalRoot =
|
||||
// new
|
||||
findModuleExport((e: Export) => typeof e === 'function' && e.toString().includes('Either closeModal or onCancel should be passed to GenericDialog. Classes: ')) ||
|
||||
// old
|
||||
Object.values(
|
||||
findModule((m: any) => {
|
||||
if (typeof m !== 'object') return false;
|
||||
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.m_mapModalManager && Object.values(m)?.find((x: any) => x?.type)) {
|
||||
return true;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.m_mapModalManager && Object.values(m)?.find((x: any) => x?.type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}) || {},
|
||||
)?.find((x: any) => x?.type?.toString?.()?.includes('((function(){')) as FC<ModalRootProps>;
|
||||
return false;
|
||||
}) || {},
|
||||
)?.find((x: any) => x?.type?.toString?.()?.includes('((function(){')) as FC<ModalRootProps>;
|
||||
|
||||
interface SimpleModalProps {
|
||||
active?: boolean;
|
||||
|
||||
@@ -28,7 +28,8 @@ export const ProgressBar = findModuleExport((e: Export) =>
|
||||
) as FC<ProgressBarProps>;
|
||||
|
||||
export const ProgressBarWithInfo = findModuleExport((e: Export) =>
|
||||
e?.toString?.()?.includes('.ProgressBarFieldStatus},'),
|
||||
// new || old
|
||||
e?.toString?.()?.includes('.ProgressBarFieldStatus,children') || e?.toString?.()?.includes('.ProgressBarFieldStatus},'),
|
||||
) as FC<ProgressBarWithInfoProps>;
|
||||
|
||||
const progressBarItemRegex = createPropListRegex(["indeterminate", "nTransitionSec", "nProgress"]);
|
||||
|
||||
@@ -11,5 +11,6 @@ export const ScrollPanel = ScrollingModuleProps.find((prop: any) =>
|
||||
) as FC<{ children?: ReactNode }>;
|
||||
|
||||
export const ScrollPanelGroup: FC<{ children?: ReactNode }> = findModuleExport((e: Export) =>
|
||||
e?.render?.toString().includes('.FocusVisibleChild()),[])'),
|
||||
// new || old
|
||||
e?.render?.toString().includes('.FocusVisibleChild(),[])') || e?.render?.toString().includes('.FocusVisibleChild()),[])'),
|
||||
);
|
||||
|
||||
@@ -3,6 +3,6 @@ import { FC, SVGAttributes } from 'react';
|
||||
import { IconsModule } from '../webpack';
|
||||
|
||||
// TODO type this and other icons?
|
||||
export const Spinner = Object.values(IconsModule)?.find(
|
||||
(mod: any) => mod?.toString && /Spinner\)}\)?,.\.createElement\(\"path\",{d:\"M18 /.test(mod.toString()),
|
||||
export const Spinner = IconsModule && Object.values(IconsModule)?.find(
|
||||
(mod: any) => mod?.toString && /Spinner\),children:\[\(0,\w+\.jsx\)\("path",\{d:"M18 /.test(mod.toString()) || /Spinner\)}\)?,.\.createElement\(\"path\",{d:\"M18 /.test(mod.toString()),
|
||||
) as FC<SVGAttributes<SVGElement>>;
|
||||
|
||||
@@ -67,4 +67,4 @@ const tabsModule = findModuleByExport(e => e?.toString?.()?.includes(".TabRowTab
|
||||
/**
|
||||
* Tabs component as used in the library and media tabs. See {@link TabsProps}.
|
||||
*/
|
||||
export const Tabs = tabsModule && Object.values(tabsModule).find((e: any) => e?.type?.toString?.()?.includes("((function()")) as FC<TabsProps>;
|
||||
export const Tabs = tabsModule && Object.values(tabsModule).find((e: any) => e?.type?.toString?.()?.includes("(function()")) as FC<TabsProps>;
|
||||
@@ -108,7 +108,7 @@ export type ReorderableListEntryProps<T> = {
|
||||
reorderEntryFunc: CallableFunction;
|
||||
reorderEnabled: boolean;
|
||||
animate: boolean;
|
||||
children: ReactElement | null;
|
||||
children: ReactElement<any> | null;
|
||||
};
|
||||
|
||||
function ReorderableItem<T>(props: ReorderableListEntryProps<T>) {
|
||||
|
||||
@@ -130,7 +130,7 @@ export interface Input {
|
||||
* @returns an object that can be used to unregister the callback.
|
||||
*/
|
||||
RegisterForControllerInputMessages(
|
||||
callback: (msgs: ControllerInputMessage[]) => void,
|
||||
callback: (controllerIndex: number, gamepadButton: ControllerInputGamepadButton, isButtonPressed: boolean) => void,
|
||||
): Unregisterable;
|
||||
|
||||
RegisterForControllerListChanges(callback: (controllerListChanges: ControllerInfo[]) => void): Unregisterable;
|
||||
@@ -560,6 +560,60 @@ export enum EControllerRumbleSetting {
|
||||
On,
|
||||
}
|
||||
|
||||
export enum ControllerInputGamepadButton {
|
||||
GAMEPAD_BUTTON_A = 0,
|
||||
GAMEPAD_BUTTON_B = 1,
|
||||
GAMEPAD_BUTTON_X = 2,
|
||||
GAMEPAD_BUTTON_Y = 3,
|
||||
GAMEPAD_BUTTON_DPAD_UP = 4,
|
||||
GAMEPAD_BUTTON_DPAD_RIGHT = 5,
|
||||
GAMEPAD_BUTTON_DPAD_DOWN = 6,
|
||||
GAMEPAD_BUTTON_DPAD_LEFT = 7,
|
||||
GAMEPAD_BUTTON_MENU = 8,
|
||||
GAMEPAD_BUTTON_VIEW = 9,
|
||||
GAMEPAD_LEFTPAD_UP = 10,
|
||||
GAMEPAD_LEFTPAD_DOWN = 11,
|
||||
GAMEPAD_LEFTPAD_LEFT = 12,
|
||||
GAMEPAD_LEFTPAD_RIGHT = 13,
|
||||
GAMEPAD_LEFTPAD_ANALOG = 14,
|
||||
GAMEPAD_RIGHTPAD_UP = 15,
|
||||
GAMEPAD_RIGHTPAD_DOWN = 16,
|
||||
GAMEPAD_RIGHTPAD_LEFT = 17,
|
||||
GAMEPAD_RIGHTPAD_RIGHT = 18,
|
||||
GAMEPAD_RIGHTPAD_ANALOG = 19,
|
||||
GAMEPAD_LEFTSTICK_UP = 20,
|
||||
GAMEPAD_LEFTSTICK_DOWN = 21,
|
||||
GAMEPAD_LEFTSTICK_LEFT = 22,
|
||||
GAMEPAD_LEFTSTICK_RIGHT = 23,
|
||||
GAMEPAD_LEFTSTICK_ANALOG = 24,
|
||||
GAMEPAD_LEFTSTICK_CLICK = 25,
|
||||
GAMEPAD_LTRIGGER_ANALOG = 26,
|
||||
GAMEPAD_RTRIGGER_ANALOG = 27,
|
||||
GAMEPAD_BUTTON_LTRIGGER = 28,
|
||||
GAMEPAD_BUTTON_RTRIGGER = 29,
|
||||
GAMEPAD_BUTTON_LSHOULDER = 30,
|
||||
GAMEPAD_BUTTON_RSHOULDER = 31,
|
||||
GAMEPAD_BUTTON_LBACK = 32,
|
||||
GAMEPAD_BUTTON_RBACK = 33,
|
||||
GAMEPAD_BUTTON_GUIDE = 34,
|
||||
GAMEPAD_BUTTON_SELECT = 35,
|
||||
GAMEPAD_BUTTON_START = 36,
|
||||
GAMEPAD_BUTTON_LPAD_CLICKED = 37,
|
||||
GAMEPAD_BUTTON_LPAD_TOUCH = 38,
|
||||
GAMEPAD_BUTTON_RPAD_CLICKED = 39,
|
||||
GAMEPAD_BUTTON_RPAD_TOUCH = 40,
|
||||
GAMEPAD_RIGHTSTICK_CLICK = 41,
|
||||
GAMEPAD_RIGHTSTICK_TOUCH = 42,
|
||||
GAMEPAD_LEFTSTICK_TOUCH = 43,
|
||||
GAMEPAD_BUTTON_LBACK_UPPER = 44,
|
||||
GAMEPAD_BUTTON_RBACK_UPPER = 45,
|
||||
GAMEPAD_BUTTON_LAST = 46,
|
||||
GAMEPAD_ANALOG_SCROLL = 47,
|
||||
GAMEPAD_ANALOG_LEFT_KEYBOARD_CURSOR = 48,
|
||||
GAMEPAD_ANALOG_RIGHT_KEYBOARD_CURSOR = 49,
|
||||
GAMEPAD_ANALOG_LAST = 50
|
||||
}
|
||||
|
||||
// TODO: Not the actual name, but the enum is only represented in a dropdown
|
||||
// options vector, ty valve
|
||||
export enum EThirdPartyControllerConfiguration {
|
||||
@@ -568,12 +622,6 @@ export enum EThirdPartyControllerConfiguration {
|
||||
On,
|
||||
}
|
||||
|
||||
export interface ControllerInputMessage {
|
||||
nA: number;
|
||||
bS: boolean;
|
||||
nC: number;
|
||||
}
|
||||
|
||||
export interface ActiveAccount {
|
||||
strActiveAccountID: string;
|
||||
strName: string;
|
||||
|
||||
@@ -42,6 +42,9 @@ export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoli
|
||||
};
|
||||
component.prototype.isReactComponent = true;
|
||||
let stubsApplied = false;
|
||||
const patchJsx = window.SP_REACTDOM.version.startsWith("19.");
|
||||
let oldJsx = window.SP_JSX?.jsx;
|
||||
let oldJsxs = window.SP_JSX?.jsxs;
|
||||
let oldCreateElement = window.SP_REACT.createElement;
|
||||
|
||||
const applyStubsIfNeeded = () => {
|
||||
@@ -55,6 +58,18 @@ export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoli
|
||||
loggingEnabled && console.trace("createElement trace");
|
||||
return Object.create(component.prototype);
|
||||
};
|
||||
if (patchJsx) {
|
||||
window.SP_JSX.jsx = () => {
|
||||
loggingEnabled && logger.debug("jsx hook called");
|
||||
loggingEnabled && console.trace("jsx trace");
|
||||
return Object.create(component.prototype);
|
||||
}
|
||||
window.SP_JSX.jsxs = () => {
|
||||
loggingEnabled && logger.debug("jsxs hook called");
|
||||
loggingEnabled && console.trace("jsxs trace");
|
||||
return Object.create(component.prototype);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,76 +79,154 @@ export function injectFCTrampoline(component: FC, customHooks?: any): FCTrampoli
|
||||
stubsApplied = false;
|
||||
removeHookStubs();
|
||||
window.SP_REACT.createElement = oldCreateElement;
|
||||
if (patchJsx) {
|
||||
window.SP_JSX.jsx = oldJsx;
|
||||
window.SP_JSX.jsxs = oldJsxs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 = {};
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export let modules = new Map<ModuleID, Module>();
|
||||
function initModuleCache() {
|
||||
const startTime = performance.now();
|
||||
logger.group('Webpack Module Init');
|
||||
// Webpack 5, currently on beta
|
||||
// Webpack 5
|
||||
// Generate a fake module ID
|
||||
const id = Symbol("@decky/ui");
|
||||
let webpackRequire!: ((id: any) => Module) & { m: object };
|
||||
@@ -70,15 +70,22 @@ export const findModuleDetailsByExport = (
|
||||
for (const [id, m] of modules) {
|
||||
if (!m) continue;
|
||||
for (const mod of [m.default, m]) {
|
||||
// special cases
|
||||
if (typeof mod !== 'object') continue;
|
||||
if (mod == window) continue; // wtf
|
||||
if (minExports && Object.keys(mod).length < minExports) continue;
|
||||
|
||||
for (let exportName in mod) {
|
||||
if (mod?.[exportName]) {
|
||||
const filterRes = filter(mod[exportName], exportName);
|
||||
if (filterRes) {
|
||||
return [mod, mod[exportName], exportName, id];
|
||||
} else {
|
||||
continue;
|
||||
try {
|
||||
const filterRes = filter(mod[exportName], exportName);
|
||||
if (filterRes) {
|
||||
return [mod, mod[exportName], exportName, id];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn("Webpack filter threw exception: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,6 +106,7 @@ export const findModuleExport = (filter: ExportFilterFn, minExports?: number) =>
|
||||
* @deprecated use findModuleExport instead
|
||||
*/
|
||||
export const findModuleChild = (filter: FindFn) => {
|
||||
logger.warn("findModuleChild is deprecated and will be removed soon. Use findModuleExport instead. Used in:", new Error().stack?.substring(5))
|
||||
for (const m of modules.values()) {
|
||||
for (const mod of [m.default, m]) {
|
||||
const filterRes = filter(mod);
|
||||
@@ -115,6 +123,7 @@ export const findModuleChild = (filter: FindFn) => {
|
||||
* @deprecated use createModuleMapping instead
|
||||
*/
|
||||
export const findAllModules = (filter: FilterFn) => {
|
||||
logger.warn("findAllModules is deprecated and will be removed soon. Use createModuleMapping instead. Used in:", new Error().stack?.substring(5))
|
||||
const out = [];
|
||||
|
||||
for (const m of modules.values()) {
|
||||
@@ -145,7 +154,7 @@ export const CommonUIModule = findModule((m: Module) => {
|
||||
});
|
||||
|
||||
export const IconsModule = findModuleByExport(
|
||||
(e) => e?.toString && /Spinner\)}\)?,.\.createElement\(\"path\",{d:\"M18 /.test(e.toString()),
|
||||
(e) => e?.toString && /Spinner\),children:\[\(0,\w+\.jsx\)\("path",\{d:"M18 /.test(e.toString()) || /Spinner\)}\)?,.\.createElement\(\"path\",{d:\"M18 /.test(e.toString()),
|
||||
);
|
||||
|
||||
export const ReactRouter = findModuleByExport((e) => e.computeRootMatch);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user