@decky/ui 3.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +504 -0
  2. package/README.md +30 -0
  3. package/dist/class-mapper.d.ts +7 -0
  4. package/dist/class-mapper.js +26 -0
  5. package/dist/components/Button.d.ts +5 -0
  6. package/dist/components/Button.js +2 -0
  7. package/dist/components/ButtonItem.d.ts +7 -0
  8. package/dist/components/ButtonItem.js +5 -0
  9. package/dist/components/Carousel.d.ts +19 -0
  10. package/dist/components/Carousel.js +2 -0
  11. package/dist/components/ControlsList.d.ts +7 -0
  12. package/dist/components/ControlsList.js +2 -0
  13. package/dist/components/Dialog.d.ts +33 -0
  14. package/dist/components/Dialog.js +24 -0
  15. package/dist/components/DialogCheckbox.d.ts +17 -0
  16. package/dist/components/DialogCheckbox.js +14 -0
  17. package/dist/components/Dropdown.d.ts +34 -0
  18. package/dist/components/Dropdown.js +5 -0
  19. package/dist/components/Field.d.ts +23 -0
  20. package/dist/components/Field.js +2 -0
  21. package/dist/components/FocusRing.d.ts +9 -0
  22. package/dist/components/FocusRing.js +2 -0
  23. package/dist/components/Focusable.d.ts +12 -0
  24. package/dist/components/Focusable.js +4 -0
  25. package/dist/components/FooterLegend.d.ts +66 -0
  26. package/dist/components/FooterLegend.js +32 -0
  27. package/dist/components/Item.d.ts +12 -0
  28. package/dist/components/Item.js +1 -0
  29. package/dist/components/Marquee.d.ts +14 -0
  30. package/dist/components/Marquee.js +2 -0
  31. package/dist/components/Menu.d.ts +29 -0
  32. package/dist/components/Menu.js +9 -0
  33. package/dist/components/Modal.d.ts +52 -0
  34. package/dist/components/Modal.js +29 -0
  35. package/dist/components/Panel.d.ts +11 -0
  36. package/dist/components/Panel.js +4 -0
  37. package/dist/components/ProgressBar.d.ts +21 -0
  38. package/dist/components/ProgressBar.js +6 -0
  39. package/dist/components/Scroll.d.ts +7 -0
  40. package/dist/components/Scroll.js +5 -0
  41. package/dist/components/SidebarNavigation.d.ts +21 -0
  42. package/dist/components/SidebarNavigation.js +4 -0
  43. package/dist/components/SliderField.d.ts +26 -0
  44. package/dist/components/SliderField.js +2 -0
  45. package/dist/components/Spinner.d.ts +2 -0
  46. package/dist/components/Spinner.js +2 -0
  47. package/dist/components/SteamSpinner.d.ts +2 -0
  48. package/dist/components/SteamSpinner.js +2 -0
  49. package/dist/components/Tabs.d.ts +16 -0
  50. package/dist/components/Tabs.js +48 -0
  51. package/dist/components/TextField.d.ts +22 -0
  52. package/dist/components/TextField.js +2 -0
  53. package/dist/components/Toggle.d.ts +8 -0
  54. package/dist/components/Toggle.js +2 -0
  55. package/dist/components/ToggleField.d.ts +9 -0
  56. package/dist/components/ToggleField.js +2 -0
  57. package/dist/components/index.d.ts +25 -0
  58. package/dist/components/index.js +25 -0
  59. package/dist/custom-components/ColorPickerModal.d.ts +12 -0
  60. package/dist/custom-components/ColorPickerModal.js +87 -0
  61. package/dist/custom-components/ReorderableList.d.ts +26 -0
  62. package/dist/custom-components/ReorderableList.js +84 -0
  63. package/dist/custom-components/SuspensefulImage.d.ts +7 -0
  64. package/dist/custom-components/SuspensefulImage.js +27 -0
  65. package/dist/custom-components/index.d.ts +3 -0
  66. package/dist/custom-components/index.js +3 -0
  67. package/dist/custom-hooks/index.d.ts +1 -0
  68. package/dist/custom-hooks/index.js +1 -0
  69. package/dist/custom-hooks/useQuickAccessVisible.d.ts +1 -0
  70. package/dist/custom-hooks/useQuickAccessVisible.js +22 -0
  71. package/dist/deck-hooks/index.d.ts +1 -0
  72. package/dist/deck-hooks/index.js +1 -0
  73. package/dist/deck-hooks/useParams.d.ts +1 -0
  74. package/dist/deck-hooks/useParams.js +2 -0
  75. package/dist/globals/SteamClient.d.ts +307 -0
  76. package/dist/globals/SteamClient.js +1 -0
  77. package/dist/globals/index.d.ts +2 -0
  78. package/dist/globals/index.js +2 -0
  79. package/dist/globals/stores.d.ts +50 -0
  80. package/dist/globals/stores.js +1 -0
  81. package/dist/index.d.ts +10 -0
  82. package/dist/index.js +14 -0
  83. package/dist/logger.d.ts +17 -0
  84. package/dist/logger.js +46 -0
  85. package/dist/modules/Router.d.ts +110 -0
  86. package/dist/modules/Router.js +102 -0
  87. package/dist/modules/index.d.ts +1 -0
  88. package/dist/modules/index.js +1 -0
  89. package/dist/utils/index.d.ts +12 -0
  90. package/dist/utils/index.js +23 -0
  91. package/dist/utils/patcher.d.ts +18 -0
  92. package/dist/utils/patcher.js +103 -0
  93. package/dist/utils/react.d.ts +19 -0
  94. package/dist/utils/react.js +89 -0
  95. package/dist/utils/static-classes.d.ts +52 -0
  96. package/dist/utils/static-classes.js +28 -0
  97. package/dist/webpack.d.ts +21 -0
  98. package/dist/webpack.js +102 -0
  99. package/package.json +89 -0
  100. package/src/class-mapper.ts +34 -0
  101. package/src/components/Button.ts +8 -0
  102. package/src/components/ButtonItem.ts +16 -0
  103. package/src/components/Carousel.ts +25 -0
  104. package/src/components/ControlsList.ts +14 -0
  105. package/src/components/Dialog.ts +90 -0
  106. package/src/components/DialogCheckbox.ts +36 -0
  107. package/src/components/Dropdown.ts +51 -0
  108. package/src/components/Field.ts +29 -0
  109. package/src/components/FocusRing.ts +15 -0
  110. package/src/components/Focusable.ts +21 -0
  111. package/src/components/FooterLegend.ts +67 -0
  112. package/src/components/Item.ts +13 -0
  113. package/src/components/Marquee.ts +20 -0
  114. package/src/components/Menu.ts +58 -0
  115. package/src/components/Modal.ts +119 -0
  116. package/src/components/Panel.ts +26 -0
  117. package/src/components/ProgressBar.ts +37 -0
  118. package/src/components/Scroll.ts +15 -0
  119. package/src/components/SidebarNavigation.ts +30 -0
  120. package/src/components/SliderField.ts +33 -0
  121. package/src/components/Spinner.ts +8 -0
  122. package/src/components/SteamSpinner.ts +7 -0
  123. package/src/components/Tabs.tsx +127 -0
  124. package/src/components/TextField.ts +28 -0
  125. package/src/components/Toggle.ts +14 -0
  126. package/src/components/ToggleField.ts +15 -0
  127. package/src/components/index.ts +25 -0
  128. package/src/custom-components/ColorPickerModal.tsx +132 -0
  129. package/src/custom-components/ReorderableList.tsx +183 -0
  130. package/src/custom-components/SuspensefulImage.tsx +44 -0
  131. package/src/custom-components/index.ts +3 -0
  132. package/src/custom-hooks/index.ts +1 -0
  133. package/src/custom-hooks/useQuickAccessVisible.ts +63 -0
  134. package/src/deck-hooks/index.ts +1 -0
  135. package/src/deck-hooks/useParams.ts +15 -0
  136. package/src/globals/SteamClient.ts +321 -0
  137. package/src/globals/index.ts +2 -0
  138. package/src/globals/stores.ts +50 -0
  139. package/src/index.ts +20 -0
  140. package/src/logger.ts +77 -0
  141. package/src/modules/Router.ts +174 -0
  142. package/src/modules/index.ts +1 -0
  143. package/src/utils/index.ts +43 -0
  144. package/src/utils/patcher.ts +160 -0
  145. package/src/utils/react.ts +141 -0
  146. package/src/utils/static-classes.ts +1104 -0
  147. package/src/webpack.ts +136 -0
@@ -0,0 +1,174 @@
1
+ import { sleep } from '../utils';
2
+ import { Export, findModuleExport } from '../webpack';
3
+
4
+ export enum SideMenu {
5
+ None,
6
+ Main,
7
+ QuickAccess,
8
+ }
9
+
10
+ export enum QuickAccessTab {
11
+ Notifications,
12
+ RemotePlayTogetherControls,
13
+ VoiceChat,
14
+ Friends,
15
+ Settings,
16
+ Perf,
17
+ Help,
18
+ Music,
19
+ Decky = 999,
20
+ }
21
+
22
+ export enum DisplayStatus {
23
+ Invalid = 0,
24
+ Launching = 1,
25
+ Uninstalling = 2,
26
+ Installing = 3,
27
+ Running = 4,
28
+ Validating = 5,
29
+ Updating = 6,
30
+ Downloading = 7,
31
+ Synchronizing = 8,
32
+ ReadyToInstall = 9,
33
+ ReadyToPreload = 10,
34
+ ReadyToLaunch = 11,
35
+ RegionRestricted = 12,
36
+ PresaleOnly = 13,
37
+ InvalidPlatform = 14,
38
+ PreloadComplete = 16,
39
+ BorrowerLocked = 17,
40
+ UpdatePaused = 18,
41
+ UpdateQueued = 19,
42
+ UpdateRequired = 20,
43
+ UpdateDisabled = 21,
44
+ DownloadPaused = 22,
45
+ DownloadQueued = 23,
46
+ DownloadRequired = 24,
47
+ DownloadDisabled = 25,
48
+ LicensePending = 26,
49
+ LicenseExpired = 27,
50
+ AvailForFree = 28,
51
+ AvailToBorrow = 29,
52
+ AvailGuestPass = 30,
53
+ Purchase = 31,
54
+ Unavailable = 32,
55
+ NotLaunchable = 33,
56
+ CloudError = 34,
57
+ CloudOutOfDate = 35,
58
+ Terminating = 36,
59
+ }
60
+
61
+ export type AppOverview = {
62
+ appid: string;
63
+ display_name: string;
64
+ display_status: DisplayStatus;
65
+ sort_as: string;
66
+ };
67
+
68
+ export interface MenuStore {
69
+ OpenSideMenu(sideMenu: SideMenu): void;
70
+ OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void;
71
+ OpenMainMenu(): void;
72
+ }
73
+
74
+ export interface WindowRouter {
75
+ BrowserWindow: Window;
76
+ MenuStore: MenuStore;
77
+ Navigate(path: string): void;
78
+ NavigateToChat(): void;
79
+ NavigateToSteamWeb(url: string): void;
80
+ NavigateBack(): void;
81
+ }
82
+
83
+ export interface WindowStore {
84
+ GamepadUIMainWindowInstance?: WindowRouter; // Current
85
+ SteamUIWindows: WindowRouter[];
86
+ OverlayWindows: WindowRouter[]; // Used by desktop GamepadUI
87
+ }
88
+
89
+ export interface Router {
90
+ WindowStore?: WindowStore;
91
+ CloseSideMenus(): void;
92
+ Navigate(path: string): void;
93
+ NavigateToAppProperties(): void;
94
+ NavigateToExternalWeb(url: string): void;
95
+ NavigateToInvites(): void;
96
+ NavigateToChat(): void;
97
+ NavigateToLibraryTab(): void;
98
+ NavigateToLayoutPreview(e: unknown): void;
99
+ OpenPowerMenu(unknown?: any): void;
100
+ get RunningApps(): AppOverview[];
101
+ get MainRunningApp(): AppOverview | undefined;
102
+ }
103
+
104
+ export const Router = findModuleExport((e: Export) => e.Navigate && e.NavigationManager) as Router;
105
+
106
+ export interface Navigation {
107
+ Navigate(path: string): void;
108
+ NavigateBack(): void;
109
+ NavigateToAppProperties(): void;
110
+ NavigateToExternalWeb(url: string): void;
111
+ NavigateToInvites(): void;
112
+ NavigateToChat(): void;
113
+ NavigateToLibraryTab(): void;
114
+ NavigateToLayoutPreview(e: unknown): void;
115
+ NavigateToSteamWeb(url: string): void;
116
+ OpenSideMenu(sideMenu: SideMenu): void;
117
+ OpenQuickAccessMenu(quickAccessTab?: QuickAccessTab): void;
118
+ OpenMainMenu(): void;
119
+ OpenPowerMenu(unknown?: any): void;
120
+ CloseSideMenus(): void;
121
+ }
122
+
123
+ export let Navigation = {} as Navigation;
124
+
125
+ try {
126
+ (async () => {
127
+ let InternalNavigators: any = {};
128
+ if (!Router.NavigateToAppProperties || (Router as unknown as any).deckyShim) {
129
+ function initInternalNavigators() {
130
+ try {
131
+ InternalNavigators = findModuleExport((e: Export) => e.GetNavigator && e.SetNavigator)?.GetNavigator();
132
+ } catch (e) {
133
+ console.error('[DFL:Router]: Failed to init internal navigators, trying again');
134
+ }
135
+ }
136
+ initInternalNavigators();
137
+ while (!InternalNavigators?.AppProperties) {
138
+ console.log('[DFL:Router]: Trying to init internal navigators again');
139
+ await sleep(2000);
140
+ initInternalNavigators();
141
+ }
142
+ }
143
+ const newNavigation = {
144
+ Navigate: Router.Navigate?.bind(Router),
145
+ NavigateBack: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateBack?.bind(
146
+ Router.WindowStore.GamepadUIMainWindowInstance,
147
+ ),
148
+ NavigateToAppProperties: InternalNavigators?.AppProperties || Router.NavigateToAppProperties?.bind(Router),
149
+ NavigateToExternalWeb: InternalNavigators?.ExternalWeb || Router.NavigateToExternalWeb?.bind(Router),
150
+ NavigateToInvites: InternalNavigators?.Invites || Router.NavigateToInvites?.bind(Router),
151
+ NavigateToChat: InternalNavigators?.Chat || Router.NavigateToChat?.bind(Router),
152
+ NavigateToLibraryTab: InternalNavigators?.LibraryTab || Router.NavigateToLibraryTab?.bind(Router),
153
+ NavigateToLayoutPreview: Router.NavigateToLayoutPreview?.bind(Router),
154
+ NavigateToSteamWeb: Router.WindowStore?.GamepadUIMainWindowInstance?.NavigateToSteamWeb?.bind(
155
+ Router.WindowStore.GamepadUIMainWindowInstance,
156
+ ),
157
+ OpenSideMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenSideMenu?.bind(
158
+ Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
159
+ ),
160
+ OpenQuickAccessMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenQuickAccessMenu?.bind(
161
+ Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
162
+ ),
163
+ OpenMainMenu: Router.WindowStore?.GamepadUIMainWindowInstance?.MenuStore.OpenMainMenu?.bind(
164
+ Router.WindowStore.GamepadUIMainWindowInstance.MenuStore,
165
+ ),
166
+ CloseSideMenus: Router.CloseSideMenus?.bind(Router),
167
+ OpenPowerMenu: Router.OpenPowerMenu?.bind(Router),
168
+ } as Navigation;
169
+
170
+ Object.assign(Navigation, newNavigation);
171
+ })();
172
+ } catch (e) {
173
+ console.error('[DFL:Router]: Error initializing Navigation interface', e);
174
+ }
@@ -0,0 +1 @@
1
+ export * from './Router';
@@ -0,0 +1,43 @@
1
+ export * from './patcher';
2
+ export * from './react';
3
+ export * from './static-classes';
4
+
5
+ declare global {
6
+ var FocusNavController: any;
7
+ var GamepadNavTree: any;
8
+ }
9
+
10
+ export function joinClassNames(...classes: string[]): string {
11
+ return classes.join(' ');
12
+ }
13
+
14
+ export function sleep(ms: number) {
15
+ return new Promise((res) => setTimeout(res, ms));
16
+ }
17
+
18
+ /**
19
+ * Finds the SP window, since it is a render target as of 10-19-2022's beta
20
+ */
21
+ export function findSP(): Window {
22
+ // old (SP as host)
23
+ if (document.title == 'SP') return window;
24
+ // new (SP as popup)
25
+ const navTrees = getGamepadNavigationTrees();
26
+ return navTrees?.find((x: any) => x.m_ID == 'root_1_').Root.Element.ownerDocument.defaultView;
27
+ }
28
+
29
+ /**
30
+ * Gets the correct FocusNavController, as the Feb 22 2023 beta has two for some reason.
31
+ */
32
+ export function getFocusNavController(): any {
33
+ return window.GamepadNavTree?.m_context?.m_controller || window.FocusNavController;
34
+ }
35
+
36
+ /**
37
+ * Gets the gamepad navigation trees as Valve seems to be moving them.
38
+ */
39
+ export function getGamepadNavigationTrees(): any {
40
+ const focusNav = getFocusNavController();
41
+ const context = focusNav.m_ActiveContext || focusNav.m_LastActiveContext;
42
+ return context?.m_rgGamepadNavigationTrees;
43
+ }
@@ -0,0 +1,160 @@
1
+ // TODO: implement storing patches as an option so we can offer unpatchAll selectively
2
+ // Return this in a replacePatch to call the original method (can still modify args).
3
+ export let callOriginal = Symbol('DECKY_CALL_ORIGINAL');
4
+
5
+ export interface PatchOptions {
6
+ singleShot?: boolean;
7
+ }
8
+
9
+ type GenericPatchHandler = (args: any[], ret?: any) => any;
10
+
11
+ export interface Patch {
12
+ original: Function;
13
+ property: string;
14
+ object: any;
15
+ patchedFunction: any;
16
+ hasUnpatched: boolean;
17
+ handler: GenericPatchHandler;
18
+
19
+ unpatch: () => void;
20
+ }
21
+
22
+ // let patches = new Set<Patch>();
23
+
24
+ export function beforePatch(
25
+ object: any,
26
+ property: string,
27
+ handler: (args: any[]) => any,
28
+ options: PatchOptions = {},
29
+ ): Patch {
30
+ const orig = object[property];
31
+ object[property] = function (...args: any[]) {
32
+ handler.call(this, args);
33
+ const ret = patch.original.call(this, ...args);
34
+ if (options.singleShot) {
35
+ patch.unpatch();
36
+ }
37
+ return ret;
38
+ };
39
+ const patch = processPatch(object, property, handler, object[property], orig);
40
+ return patch;
41
+ }
42
+
43
+ export function afterPatch(
44
+ object: any,
45
+ property: string,
46
+ handler: (args: any[], ret: any) => any,
47
+ options: PatchOptions = {},
48
+ ): Patch {
49
+ const orig = object[property];
50
+ object[property] = function (...args: any[]) {
51
+ let ret = patch.original.call(this, ...args);
52
+ ret = handler.call(this, args, ret);
53
+ if (options.singleShot) {
54
+ patch.unpatch();
55
+ }
56
+ return ret;
57
+ };
58
+ const patch = processPatch(object, property, handler, object[property], orig);
59
+ return patch;
60
+ }
61
+
62
+ export function replacePatch(
63
+ object: any,
64
+ property: string,
65
+ handler: (args: any[]) => any,
66
+ options: PatchOptions = {},
67
+ ): Patch {
68
+ const orig = object[property];
69
+ object[property] = function (...args: any[]) {
70
+ const ret = handler.call(this, args);
71
+ if (ret == callOriginal) return patch.original.call(this, ...args);
72
+ if (options.singleShot) {
73
+ patch.unpatch();
74
+ }
75
+ return ret;
76
+ };
77
+ const patch = processPatch(object, property, handler, object[property], orig);
78
+ return patch;
79
+ }
80
+
81
+ function processPatch(
82
+ object: any,
83
+ property: any,
84
+ handler: GenericPatchHandler,
85
+ patchedFunction: any,
86
+ original: any,
87
+ ): Patch {
88
+ // Assign all props of original function to new one
89
+ Object.assign(object[property], original);
90
+ // Allow toString webpack filters to continue to work
91
+ object[property].toString = () => original.toString();
92
+
93
+ // HACK: for compatibility, remove when all plugins are using new patcher
94
+ Object.defineProperty(object[property], '__deckyOrig', {
95
+ get: () => patch.original,
96
+ set: (val: any) => (patch.original = val),
97
+ });
98
+
99
+ // Build a Patch object of this patch
100
+ const patch: Patch = {
101
+ object,
102
+ property,
103
+ handler,
104
+ patchedFunction,
105
+ original,
106
+ hasUnpatched: false,
107
+ unpatch: () => unpatch(patch),
108
+ };
109
+
110
+ object[property].__deckyPatch = patch;
111
+
112
+ return patch;
113
+ }
114
+
115
+ function unpatch(patch: Patch): void {
116
+ const { object, property, handler, patchedFunction, original } = patch;
117
+ if (patch.hasUnpatched) throw new Error('Function is already unpatched.');
118
+ let realProp = property;
119
+ let realObject = object;
120
+ console.debug('[Patcher] unpatching', {
121
+ realObject,
122
+ realProp,
123
+ object,
124
+ property,
125
+ handler,
126
+ patchedFunction,
127
+ original,
128
+ isEqual: realObject[realProp] === patchedFunction,
129
+ });
130
+
131
+ // If another patch has been applied to this function after this one, move down until we find the correct patch
132
+ while (realObject[realProp] && realObject[realProp] !== patchedFunction) {
133
+ realObject = realObject[realProp].__deckyPatch;
134
+ realProp = 'original';
135
+ console.debug('[Patcher] moved to next', {
136
+ realObject,
137
+ realProp,
138
+ object,
139
+ property,
140
+ handler,
141
+ patchedFunction,
142
+ original,
143
+ isEqual: realObject[realProp] === patchedFunction,
144
+ });
145
+ }
146
+
147
+ realObject[realProp] = realObject[realProp].__deckyPatch.original;
148
+
149
+ patch.hasUnpatched = true;
150
+ console.debug('[Patcher] unpatched', {
151
+ realObject,
152
+ realProp,
153
+ object,
154
+ property,
155
+ handler,
156
+ patchedFunction,
157
+ original,
158
+ isEqual: realObject[realProp] === patchedFunction,
159
+ });
160
+ }
@@ -0,0 +1,141 @@
1
+ import * as React from 'react';
2
+
3
+ // this shouldn't need to be redeclared but it does for some reason
4
+
5
+ declare global {
6
+ interface Window {
7
+ SP_REACT: typeof React;
8
+ }
9
+ }
10
+
11
+ /**
12
+ * Create a Regular Expression to search for a React component that uses certain props in order.
13
+ *
14
+ * @export
15
+ * @param {string[]} propList Ordererd list of properties to search for
16
+ * @returns {RegExp} RegEx to call .test(component.toString()) on
17
+ */
18
+ export function createPropListRegex(propList: string[], fromStart: boolean = true): RegExp {
19
+ let regexString = fromStart ? "const\{" : "";
20
+ propList.forEach((prop: any, propIdx) => {
21
+ regexString += `"?${prop}"?:[a-zA-Z_$]{1,2}`;
22
+ if (propIdx < propList.length - 1) {
23
+ regexString += ",";
24
+ }
25
+ });
26
+
27
+ // TODO provide a way to enable this
28
+ // console.debug(`[DFL:Utils] createPropListRegex generated regex "${regexString}" for props`, propList);
29
+
30
+ return new RegExp(regexString);
31
+ }
32
+
33
+ export function fakeRenderComponent(fun: Function, customHooks: any = {}): any {
34
+ const hooks = (window.SP_REACT as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher
35
+ .current;
36
+
37
+ // TODO: add more hooks
38
+
39
+ let oldHooks = {
40
+ useContext: hooks.useContext,
41
+ useCallback: hooks.useCallback,
42
+ useLayoutEffect: hooks.useLayoutEffect,
43
+ useEffect: hooks.useEffect,
44
+ useMemo: hooks.useMemo,
45
+ useRef: hooks.useRef,
46
+ useState: hooks.useState,
47
+ };
48
+
49
+ hooks.useCallback = (cb: Function) => cb;
50
+ hooks.useContext = (cb: any) => cb._currentValue;
51
+ hooks.useLayoutEffect = (_: Function) => {}; //cb();
52
+ hooks.useMemo = (cb: Function, _: any[]) => cb;
53
+ hooks.useEffect = (_: Function) => {}; //cb();
54
+ hooks.useRef = (val: any) => ({ current: val || {} });
55
+ hooks.useState = (v: any) => {
56
+ let val = v;
57
+
58
+ return [val, (n: any) => (val = n)];
59
+ };
60
+
61
+ Object.assign(hooks, customHooks);
62
+
63
+ const res = fun(hooks);
64
+
65
+ Object.assign(hooks, oldHooks);
66
+
67
+ return res;
68
+ }
69
+
70
+ export function wrapReactType(node: any, prop: any = 'type') {
71
+ if (node[prop]?.__DECKY_WRAPPED) {
72
+ return node[prop];
73
+ } else {
74
+ return (node[prop] = { ...node[prop], __DECKY_WRAPPED: true });
75
+ }
76
+ }
77
+
78
+ export function wrapReactClass(node: any, prop: any = 'type') {
79
+ if (node[prop]?.__DECKY_WRAPPED) {
80
+ return node[prop];
81
+ } else {
82
+ const cls = node[prop];
83
+ const wrappedCls = class extends cls {
84
+ static __DECKY_WRAPPED = true;
85
+ };
86
+ return (node[prop] = wrappedCls);
87
+ }
88
+ }
89
+
90
+ export function getReactRoot(o: HTMLElement | Element | Node) {
91
+ return (
92
+ // @ts-expect-error 7053
93
+ o[Object.keys(o).find((k) => k.startsWith('__reactContainer$')) as string] ||
94
+ // @ts-expect-error 7053
95
+ o['_reactRootContainer']?._internalRoot?.current
96
+ );
97
+ }
98
+
99
+ export function getReactInstance(o: HTMLElement | Element | Node) {
100
+ return (
101
+ // @ts-expect-error 7053
102
+ o[Object.keys(o).find((k) => k.startsWith('__reactFiber')) as string] ||
103
+ // @ts-expect-error 7053
104
+ o[Object.keys(o).find((k) => k.startsWith('__reactInternalInstance')) as string]
105
+ );
106
+ }
107
+
108
+ // Based on https://github.com/GooseMod/GooseMod/blob/9ef146515a9e59ed4e25665ed365fd72fc0dcf23/src/util/react.js#L20
109
+ export interface findInTreeOpts {
110
+ walkable?: string[];
111
+ ignore?: string[];
112
+ }
113
+
114
+ export declare type findInTreeFilter = (element: any) => boolean;
115
+
116
+ export const findInTree = (parent: any, filter: findInTreeFilter, opts: findInTreeOpts): any => {
117
+ const { walkable = null, ignore = [] } = opts ?? {};
118
+
119
+ if (!parent || typeof parent !== 'object') {
120
+ // Parent is invalid to search through
121
+ return null;
122
+ }
123
+
124
+ if (filter(parent)) return parent; // Parent matches, just return
125
+
126
+ if (Array.isArray(parent)) {
127
+ // Parent is an array, go through values
128
+ return parent.map((x) => findInTree(x, filter, opts)).find((x) => x);
129
+ }
130
+
131
+ // Parent is an object, go through values (or option to only use certain keys)
132
+ return (walkable || Object.keys(parent))
133
+ .map((x) => !ignore.includes(x) && findInTree(parent[x], filter, opts))
134
+ .find((x: any) => x);
135
+ };
136
+
137
+ export const findInReactTree = (node: any, filter: findInTreeFilter) =>
138
+ findInTree(node, filter, {
139
+ // Specialised findInTree for React nodes
140
+ walkable: ['props', 'children', 'child', 'sibling'],
141
+ });