Compare commits

..

20 Commits

Author SHA1 Message Date
semantic-release-bot
b151bdce8b chore(release): 4.11.4 [CI SKIP] 2026-05-15 03:58:28 +00:00
AAGaming
e44664c970 fix(dropdown): hack to fix styling in dropdownitem 2026-05-14 23:57:56 -04:00
AAGaming
ac8da8e9b6 fix(Field): fix filter for field on beta 2026-05-14 23:57:06 -04:00
semantic-release-bot
b688bc3544 chore(release): 4.11.3 [CI SKIP] 2026-03-22 00:34:53 +00:00
Jozen Blue Martinez
3126dd3e04 fix(Tabs): update for latest beta (#129) 2026-03-21 20:34:27 -04:00
semantic-release-bot
8596294667 chore(release): 4.11.2 [CI SKIP] 2026-03-21 01:29:42 +00:00
AAGaming
772a85523b fix(ci): update semantic release 2026-03-20 21:29:12 -04:00
AAGaming
aabc522740 fix(ci): microsoft 2026-03-20 21:26:44 -04:00
AAGaming
66e0afccae fix(ci): microsoft stinks 2026-03-20 21:23:44 -04:00
AAGaming
77e6acd828 fix(ci): use new npm publishing auth 2026-03-20 21:22:23 -04:00
AAGaming
261162c8bc fix(components): update for latest beta 2026-03-20 20:27:59 -04:00
semantic-release-bot
f8fda380f1 chore(release): 4.11.1 [CI SKIP] 2025-11-27 02:56:17 +00:00
AAGaming
7242c69758 fix(webpack): ignore window module (lol), ignore filter errors 2025-11-26 21:55:32 -05:00
semantic-release-bot
fd651eb989 chore(release): 4.11.0 [CI SKIP] 2025-10-15 04:05:49 +00:00
AAGaming
6e443c06d3 feat(*): react 19 support update 2025-10-15 00:04:31 -04:00
ynhhoJ
25b4b60e34 types(input): Add GamepadButton enum & set correct type for RegisterForControllerInputMessage (#125) 2025-09-22 10:13:29 -04:00
semantic-release-bot
cf6572cd7b chore(release): 4.10.6 [CI SKIP] 2025-09-01 00:41:34 +00:00
TrainDoctor
779b1ae0de fix(ci): update to resolve deprecation in sem. rel. due to octokit 2025-08-31 17:40:34 -07:00
semantic-release-bot
366357d9a3 chore(release): 4.10.5 [CI SKIP] 2025-08-20 19:36:27 +00:00
AAGaming
37dd88513e fix(DialogHeader): valve rewrote component on beta, update filter to search for both versions 2025-08-20 15:35:17 -04:00
21 changed files with 1786 additions and 1873 deletions

View File

@@ -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

View File

@@ -1,4 +1 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit "${1}"

View File

@@ -1,3 +1,57 @@
## [4.11.4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v4.11.3...v4.11.4) (2026-05-15)
### Bug Fixes
* **dropdown:** hack to fix styling in dropdownitem ([e44664c](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/e44664c9704b3b284284619bc26cf6a910890136))
* **Field:** fix filter for field on beta ([ac8da8e](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/ac8da8e9b650cfcabcd3e5752e1475e91e7edf7a))
## [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)

5
global.d.ts vendored
View File

@@ -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;
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@decky/ui",
"version": "4.10.4",
"version": "4.11.4",
"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

File diff suppressed because it is too large Load Diff

View File

@@ -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>;

View File

@@ -42,6 +42,8 @@ export const Dropdown = Object.values(CommonUIModule).find(
export interface DropdownItemProps extends DropdownProps, ItemProps {}
const dropdownItemRegex = createPropListRegex(["dropDownControlRef", "description"], false);
export const DropdownItem = Object.values(CommonUIModule).find((mod: any) =>
export const DropdownItemInternal = Object.values(CommonUIModule).find((mod: any) =>
mod?.toString && dropdownItemRegex.test(mod.toString()),
) as FC<DropdownItemProps>;
export const DropdownItem = ((args: DropdownItemProps) => <DropdownItemInternal childrenContainerWidth="min" {...args}/>) as FC<DropdownItemProps>;

View File

@@ -24,6 +24,7 @@ export interface FieldProps extends FooterLegendProps {
onClick?: (e: CustomEvent | MouseEvent) => void;
}
export const Field = findModuleExport((e: Export) => e?.render?.toString().includes('"shift-children-below"')) as FC<
// new || old
export const Field = findModuleExport((e: Export) => (e?.toString()?.includes('().Field') && e?.toString()?.includes('"shift-children-below"')) || e?.render?.toString()?.includes('"shift-children-below"')) as FC<
FieldProps & RefAttributes<HTMLDivElement>
>;

View File

@@ -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>>;

View File

@@ -7,6 +7,7 @@ export interface ItemProps {
layout?: 'below' | 'inline';
icon?: ReactNode;
bottomSeparator?: 'standard' | 'thick' | 'none';
childrenContainerWidth?: 'min' | 'max' | 'fixed'; // Does not work with layout==='below'
indentLevel?: number;
tooltip?: string;
highlightOnFocus?: boolean;

View File

@@ -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"]);

View File

@@ -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()),[])'),
);

View File

@@ -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>>;

View File

@@ -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>;

View File

@@ -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>) {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 = {};
}

View File

@@ -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);

View File

@@ -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,