@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,8 @@
1
+ import { FC } from 'react';
2
+
3
+ import { DialogButton, DialogButtonProps } from './Dialog';
4
+
5
+ export interface ButtonProps extends DialogButtonProps {}
6
+
7
+ // Button isn't exported, so call DialogButton to grab it
8
+ export const Button = (DialogButton as any)?.render({}).type as FC<ButtonProps>;
@@ -0,0 +1,16 @@
1
+ import { FC } from 'react';
2
+
3
+ import { CommonUIModule } from '../webpack';
4
+ import { ItemProps } from './Item';
5
+ import { createPropListRegex } from '../utils';
6
+
7
+ export interface ButtonItemProps extends ItemProps {
8
+ onClick?(e: MouseEvent): void;
9
+ disabled?: boolean;
10
+ }
11
+ const buttonItemRegex = createPropListRegex(["highlightOnFocus", "childrenContainerWidth"], false);
12
+ export const ButtonItem = Object.values(CommonUIModule).find(
13
+ (mod: any) =>
14
+ (mod?.render?.toString && buttonItemRegex.test(mod.render.toString())) ||
15
+ mod?.render?.toString?.().includes('childrenContainerWidth:"min"'),
16
+ ) as FC<ButtonItemProps>;
@@ -0,0 +1,25 @@
1
+ import { HTMLAttributes, ReactNode, RefAttributes, FC } from 'react';
2
+
3
+ import { Export, findModuleExport } from '../webpack';
4
+
5
+ export interface CarouselProps extends HTMLAttributes<HTMLDivElement> {
6
+ autoFocus?: boolean;
7
+ enableBumperPaging?: boolean;
8
+ fnDoesItemTakeFocus?: (...unknown: any[]) => boolean;
9
+ fnGetColumnWidth?: (...unknown: any[]) => number;
10
+ fnGetId?: (id: number) => number;
11
+ fnItemRenderer?: (id: number, ...unknown: any[]) => ReactNode;
12
+ fnUpdateArrows?: (...unknown: any[]) => any;
13
+ initialColumn?: number;
14
+ nHeight?: number;
15
+ nIndexLeftmost?: number;
16
+ nItemHeight?: number;
17
+ nItemMarginX?: number;
18
+ nNumItems?: number;
19
+ name?: string;
20
+ scrollToAlignment?: 'center';
21
+ }
22
+
23
+ export const Carousel = findModuleExport((e: Export) => e.render?.toString().includes('setFocusedColumn:')) as FC<
24
+ CarouselProps & RefAttributes<HTMLDivElement>
25
+ >;
@@ -0,0 +1,14 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ import { Export, findModuleExport } from '../webpack';
4
+
5
+ export interface ControlsListProps {
6
+ alignItems?: 'left' | 'right' | 'center';
7
+ spacing?: 'standard' | 'extra';
8
+ children?: ReactNode;
9
+ }
10
+
11
+ export const ControlsList: FC<ControlsListProps> = findModuleExport(
12
+ (e: Export) =>
13
+ e?.toString && e.toString().includes('().ControlsListChild') && e.toString().includes('().ControlsListOuterPanel'),
14
+ );
@@ -0,0 +1,90 @@
1
+ import { CSSProperties, FC, ReactNode, RefAttributes } from 'react';
2
+
3
+ import { CommonUIModule } from '../webpack';
4
+ import { FooterLegendProps } from './FooterLegend';
5
+
6
+ export interface DialogCommonProps extends RefAttributes<HTMLDivElement> {
7
+ style?: CSSProperties;
8
+ className?: string;
9
+ children?: ReactNode;
10
+ }
11
+
12
+ export interface DialogButtonProps extends DialogCommonProps, FooterLegendProps {
13
+ /**
14
+ * Enables/disables the focus around the button.
15
+ *
16
+ * @note
17
+ * Default value depends on context, so setting it to `false` will enable it.
18
+ */
19
+ noFocusRing?: boolean;
20
+
21
+ /**
22
+ * Disables the button - assigned `on*` methods will not be invoked if clicked.
23
+ *
24
+ * @note
25
+ * Depending on where it is, it might still get focus. In such case it can be
26
+ * partially disabled separately.
27
+ *
28
+ * @see focusable.
29
+ */
30
+ disabled?: boolean;
31
+
32
+ /**
33
+ * Enables/disables the navigation based focus on button - you won't be able to navigate to
34
+ * it via the gamepad or keyboard.
35
+ *
36
+ * @note
37
+ * If set to `false`, it still can be clicked and **WILL** become focused until navigated away.
38
+ * Depending on the context of where the button is, even a disabled button can focused.
39
+ */
40
+ focusable?: boolean;
41
+
42
+ onClick?(e: MouseEvent): void;
43
+ onPointerDown?(e: PointerEvent): void;
44
+ onPointerUp?(e: PointerEvent): void;
45
+ onPointerCancel?(e: PointerEvent): void;
46
+ onMouseDown?(e: MouseEvent): void;
47
+ onMouseUp?(e: MouseEvent): void;
48
+ onTouchStart?(e: TouchEvent): void;
49
+ onTouchEnd?(e: TouchEvent): void;
50
+ onTouchCancel?(e: TouchEvent): void;
51
+ onSubmit?(e: SubmitEvent): void;
52
+ }
53
+
54
+ const CommonDialogDivs = Object.values(CommonUIModule).filter(
55
+ (m: any) => typeof m === 'object' && m?.render?.toString().includes('createElement("div",{...') ||
56
+ m?.render?.toString().includes('createElement("div",Object.assign({},'),
57
+ );
58
+ const MappedDialogDivs = new Map(
59
+ Object.values(CommonDialogDivs).map((m: any) => {
60
+ try {
61
+ const renderedDiv = m.render({});
62
+ // Take only the first class name segment as it identifies the element we want
63
+ return [renderedDiv.props.className.split(' ')[0], m];
64
+ } catch (e) {
65
+ console.error("[DFL:Dialog]: failed to render common dialog component", e);
66
+ return [null, null];
67
+ }
68
+ }),
69
+ );
70
+
71
+ export const DialogHeader = MappedDialogDivs.get('DialogHeader') as FC<DialogCommonProps>;
72
+ export const DialogSubHeader = MappedDialogDivs.get('DialogSubHeader') as FC<DialogCommonProps>;
73
+ export const DialogFooter = MappedDialogDivs.get('DialogFooter') as FC<DialogCommonProps>;
74
+ export const DialogLabel = MappedDialogDivs.get('DialogLabel') as FC<DialogCommonProps>;
75
+ export const DialogBodyText = MappedDialogDivs.get('DialogBodyText') as FC<DialogCommonProps>;
76
+ export const DialogBody = MappedDialogDivs.get('DialogBody') as FC<DialogCommonProps>;
77
+ export const DialogControlsSection = MappedDialogDivs.get('DialogControlsSection') as FC<DialogCommonProps>;
78
+ export const DialogControlsSectionHeader = MappedDialogDivs.get('DialogControlsSectionHeader') as FC<DialogCommonProps>;
79
+
80
+ export const DialogButtonPrimary = Object.values(CommonUIModule).find(
81
+ (mod: any) => mod?.render?.toString()?.includes('"DialogButton","_DialogLayout","Primary"'),
82
+ ) as FC<DialogButtonProps>;
83
+
84
+ export const DialogButtonSecondary = Object.values(CommonUIModule).find(
85
+ (mod: any) => mod?.render?.toString()?.includes('"DialogButton","_DialogLayout","Secondary"')
86
+ ) as FC<DialogButtonProps>;
87
+
88
+ // This is the "main" button. The Primary can act as a submit button,
89
+ // therefore secondary is chosen (also for backwards comp. reasons)
90
+ export const DialogButton = DialogButtonSecondary;
@@ -0,0 +1,36 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ import { findModule } from '../webpack';
4
+ import { DialogCommonProps } from './Dialog';
5
+ import { FooterLegendProps } from './FooterLegend';
6
+
7
+ export interface DialogCheckboxProps extends DialogCommonProps, FooterLegendProps {
8
+ onChange?(checked: boolean): void;
9
+ label?: ReactNode;
10
+ description?: ReactNode;
11
+ disabled?: boolean;
12
+ tooltip?: string;
13
+ color?: string;
14
+ highlightColor?: string;
15
+ bottomSeparator?: 'standard' | 'thick' | 'none';
16
+ controlled?: boolean;
17
+ checked?: boolean;
18
+ onClick?(evt: Event): void;
19
+ }
20
+
21
+ export const DialogCheckbox = Object.values(
22
+ findModule((m: any) => {
23
+ if (typeof m !== 'object') return false;
24
+ for (const prop in m) {
25
+ if (m[prop]?.prototype?.GetPanelElementProps) return true;
26
+ }
27
+ return false;
28
+ }),
29
+ ).find(
30
+ (m: any) =>
31
+ m.contextType &&
32
+ m.prototype?.render.toString().includes('fallback:') &&
33
+ m?.prototype?.SetChecked &&
34
+ m?.prototype?.Toggle &&
35
+ m?.prototype?.GetPanelElementProps,
36
+ ) as FC<DialogCheckboxProps>;
@@ -0,0 +1,51 @@
1
+ import { ReactNode, FC } from 'react';
2
+
3
+ import { CommonUIModule } from '../webpack';
4
+ import { ItemProps } from './Item';
5
+ import { createPropListRegex } from '../utils';
6
+
7
+ export interface SingleDropdownOption {
8
+ data: any;
9
+ label: ReactNode;
10
+
11
+ options?: never;
12
+ }
13
+
14
+ export interface MultiDropdownOption {
15
+ label: ReactNode;
16
+ options: DropdownOption[];
17
+
18
+ data?: never;
19
+ }
20
+
21
+ export type DropdownOption = SingleDropdownOption | MultiDropdownOption;
22
+
23
+ export interface DropdownMenuPositionOptions {
24
+ [_: string]: unknown
25
+ bMatchWidth?: boolean
26
+ }
27
+
28
+ export interface DropdownProps {
29
+ rgOptions: DropdownOption[];
30
+ selectedOption: any;
31
+ disabled?: boolean;
32
+ onMenuWillOpen?(showMenu: () => void): void;
33
+ onMenuOpened?(): void;
34
+ onChange?(data: SingleDropdownOption): void;
35
+ contextMenuPositionOptions?: DropdownMenuPositionOptions;
36
+ menuLabel?: string;
37
+ strDefaultLabel?: string;
38
+ renderButtonValue?(element: ReactNode): ReactNode;
39
+ focusable?: boolean;
40
+ }
41
+
42
+ export const Dropdown = Object.values(CommonUIModule).find(
43
+ (mod: any) => mod?.prototype?.SetSelectedOption && mod?.prototype?.BuildMenu,
44
+ ) as FC<DropdownProps>;
45
+
46
+ export interface DropdownItemProps extends DropdownProps, ItemProps {}
47
+
48
+ const dropdownItemRegex = createPropListRegex(["dropDownControlRef", "description"], false);
49
+ export const DropdownItem = Object.values(CommonUIModule).find((mod: any) =>
50
+ mod?.toString && dropdownItemRegex.test(mod.toString()),
51
+ ) as FC<DropdownItemProps>;
@@ -0,0 +1,29 @@
1
+ import { FC, ReactNode, RefAttributes } from 'react';
2
+
3
+ import { Export, findModuleExport } from '../webpack';
4
+ import { FooterLegendProps } from './FooterLegend';
5
+
6
+ export interface FieldProps extends FooterLegendProps {
7
+ children?: ReactNode;
8
+ label?: ReactNode;
9
+ bottomSeparator?: 'standard' | 'thick' | 'none';
10
+ description?: ReactNode;
11
+ disabled?: boolean;
12
+ icon?: ReactNode;
13
+ inlineWrap?: 'keep-inline' | 'shift-children-below'; // If label is too long it will move shildren below before starting to wrap label
14
+ childrenLayout?: 'below' | 'inline';
15
+ childrenContainerWidth?: 'min' | 'max' | 'fixed'; // Does not work with childrenLayout==='below'
16
+ spacingBetweenLabelAndChild?: 'none'; // This applies only when childrenLayout==='below'
17
+ padding?: 'none' | 'standard' | 'compact';
18
+ className?: string;
19
+ highlightOnFocus?: boolean;
20
+ indentLevel?: number;
21
+ verticalAlignment?: 'center' | 'none'; // Alligns inline label with children
22
+ focusable?: boolean; // Allows to get focus without any focusable children or on* callbacks
23
+ onActivate?: (e: CustomEvent | MouseEvent) => void;
24
+ onClick?: (e: CustomEvent | MouseEvent) => void;
25
+ }
26
+
27
+ export const Field = findModuleExport((e: Export) => e?.render?.toString().includes('"shift-children-below"')) as FC<
28
+ FieldProps & RefAttributes<HTMLDivElement>
29
+ >;
@@ -0,0 +1,15 @@
1
+ import { ElementType, FC, ReactNode } from 'react';
2
+
3
+ import { Export, findModuleExport } from '../webpack';
4
+
5
+ export interface FocusRingProps {
6
+ className?: string;
7
+ rootClassName?: string;
8
+ render?: ElementType;
9
+ children?: ReactNode;
10
+ NavigationManager?: any;
11
+ }
12
+
13
+ export const FocusRing = findModuleExport((e: Export) =>
14
+ e?.toString()?.includes('.GetShowDebugFocusRing())'),
15
+ ) as FC<FocusRingProps>;
@@ -0,0 +1,21 @@
1
+ import { HTMLAttributes, ReactNode, RefAttributes, FC } from 'react';
2
+
3
+ import { Export, findModuleExport } from '../webpack';
4
+ import { FooterLegendProps } from './FooterLegend';
5
+ import { createPropListRegex } from '../utils';
6
+
7
+ export interface FocusableProps extends HTMLAttributes<HTMLDivElement>, FooterLegendProps {
8
+ children: ReactNode;
9
+ 'flow-children'?: string;
10
+ focusClassName?: string;
11
+ focusWithinClassName?: string;
12
+ noFocusRing?: boolean;
13
+ onActivate?: (e: CustomEvent) => void;
14
+ onCancel?: (e: CustomEvent) => void;
15
+ }
16
+
17
+ const focusableRegex = createPropListRegex(["flow-children", "onActivate", "onCancel", "focusClassName", "focusWithinClassName"]);
18
+
19
+ export const Focusable = findModuleExport((e: Export) =>
20
+ e?.render?.toString && focusableRegex.test(e.render.toString())
21
+ ) as FC<FocusableProps & RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,67 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ export enum GamepadButton {
4
+ INVALID,
5
+ OK,
6
+ CANCEL,
7
+ SECONDARY,
8
+ OPTIONS,
9
+ BUMPER_LEFT,
10
+ BUMPER_RIGHT,
11
+ TRIGGER_LEFT,
12
+ TRIGGER_RIGHT,
13
+ DIR_UP,
14
+ DIR_DOWN,
15
+ DIR_LEFT,
16
+ DIR_RIGHT,
17
+ SELECT,
18
+ START,
19
+ LSTICK_CLICK,
20
+ RSTICK_CLICK,
21
+ LSTICK_TOUCH,
22
+ RSTICK_TOUCH,
23
+ LPAD_TOUCH,
24
+ LPAD_CLICK,
25
+ RPAD_TOUCH,
26
+ RPAD_CLICK,
27
+ REAR_LEFT_UPPER,
28
+ REAR_LEFT_LOWER,
29
+ REAR_RIGHT_UPPER,
30
+ REAR_RIGHT_LOWER,
31
+ STEAM_GUIDE,
32
+ STEAM_QUICK_MENU,
33
+ }
34
+ export declare enum NavEntryPositionPreferences {
35
+ FIRST,
36
+ LAST,
37
+ MAINTAIN_X,
38
+ MAINTAIN_Y,
39
+ PREFERRED_CHILD,
40
+ }
41
+ export interface GamepadEventDetail {
42
+ button: number;
43
+ is_repeat?: boolean;
44
+ source: number;
45
+ }
46
+ export declare type ActionDescriptionMap = {
47
+ [key in GamepadButton]?: ReactNode;
48
+ };
49
+ export declare type GamepadEvent = CustomEvent<GamepadEventDetail>;
50
+ export interface FooterLegendProps {
51
+ actionDescriptionMap?: ActionDescriptionMap;
52
+ onOKActionDescription?: ReactNode;
53
+ onCancelActionDescription?: ReactNode;
54
+ onSecondaryActionDescription?: ReactNode;
55
+ onOptionsActionDescription?: ReactNode;
56
+ onMenuActionDescription?: ReactNode;
57
+ onButtonDown?: (evt: GamepadEvent) => void;
58
+ onButtonUp?: (evt: GamepadEvent) => void;
59
+ onOKButton?: (evt: GamepadEvent) => void;
60
+ onCancelButton?: (evt: GamepadEvent) => void;
61
+ onSecondaryButton?: (evt: GamepadEvent) => void;
62
+ onOptionsButton?: (evt: GamepadEvent) => void;
63
+ onGamepadDirection?: (evt: GamepadEvent) => void;
64
+ onGamepadFocus?: (evt: GamepadEvent) => void;
65
+ onGamepadBlur?: (evt: GamepadEvent) => void;
66
+ onMenuButton?: (evt: GamepadEvent) => void;
67
+ }
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ export interface ItemProps {
4
+ label?: ReactNode;
5
+ description?: ReactNode;
6
+ children?: ReactNode;
7
+ layout?: 'below' | 'inline';
8
+ icon?: ReactNode;
9
+ bottomSeparator?: 'standard' | 'thick' | 'none';
10
+ indentLevel?: number;
11
+ tooltip?: string;
12
+ highlightOnFocus?: boolean;
13
+ }
@@ -0,0 +1,20 @@
1
+ import { CSSProperties, FC } from 'react';
2
+
3
+ import { Export, findModuleExport } from '../webpack';
4
+
5
+ export interface MarqueeProps {
6
+ play?: boolean;
7
+ direction?: 'left' | 'right';
8
+ speed?: number;
9
+ delay?: number;
10
+ fadeLength?: number;
11
+ center?: boolean;
12
+ resetOnPause?: boolean;
13
+ style?: CSSProperties;
14
+ className?: string;
15
+ children: React.ReactNode;
16
+ }
17
+
18
+ export const Marquee: FC<MarqueeProps> = findModuleExport(
19
+ (e: Export) => e?.toString && e.toString().includes('.Marquee') && e.toString().includes('--fade-length'),
20
+ );
@@ -0,0 +1,58 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ import { fakeRenderComponent } from '../utils';
4
+ import { Export, findModuleExport } from '../webpack';
5
+ import { FooterLegendProps } from './FooterLegend';
6
+
7
+ export const showContextMenu: (children: ReactNode, parent?: EventTarget) => void = findModuleExport(
8
+ (e: Export) => typeof e === 'function' && e.toString().includes('GetContextMenuManagerFromWindow(')
9
+ && e.toString().includes('.CreateContextMenuInstance('),
10
+ );
11
+
12
+ export interface MenuProps extends FooterLegendProps {
13
+ label: string;
14
+ onCancel?(): void;
15
+ cancelText?: string;
16
+ children?: ReactNode;
17
+ }
18
+
19
+ export const Menu: FC<MenuProps> = findModuleExport(
20
+ (e: Export) => e?.prototype?.HideIfSubmenu && e?.prototype?.HideMenu,
21
+ );
22
+
23
+ export interface MenuGroupProps {
24
+ label: string;
25
+ disabled?: boolean;
26
+ children?: ReactNode;
27
+ }
28
+
29
+ export const MenuGroup: FC<MenuGroupProps> = findModuleExport(
30
+ (e: Export) =>
31
+ (e?.toString()?.includes?.('bInGamepadUI:') &&
32
+ fakeRenderComponent(() => e({ overview: { appid: 7 } }), {useContext: () => ({IN_GAMEPADUI: true})})?.type?.prototype?.RenderSubMenu) ||
33
+ (e?.prototype?.RenderSubMenu && e?.prototype?.ShowSubMenu)
34
+ );
35
+ export interface MenuItemProps extends FooterLegendProps {
36
+ bInteractableItem?: boolean;
37
+ onClick?(evt: Event): void;
38
+ onSelected?(evt: Event): void;
39
+ onMouseEnter?(evt: MouseEvent): void;
40
+ onMoveRight?(): void;
41
+ selected?: boolean;
42
+ disabled?: boolean;
43
+ bPlayAudio?: boolean;
44
+ tone?: 'positive' | 'emphasis' | 'destructive';
45
+ children?: ReactNode;
46
+ }
47
+
48
+ export const MenuItem: FC<MenuItemProps> = findModuleExport(
49
+ (e: Export) =>
50
+ e?.render?.toString()?.includes('bPlayAudio:') || (e?.prototype?.OnOKButton && e?.prototype?.OnMouseEnter),
51
+ );
52
+
53
+ /*
54
+ all().map(m => {
55
+ if (typeof m !== "object") return undefined;
56
+ for (let prop in m) { if (m[prop]?.prototype?.OK && m[prop]?.prototype?.Cancel && m[prop]?.prototype?.render) return m[prop]}
57
+ }).find(x => x)
58
+ */
@@ -0,0 +1,119 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ import { findSP } from '../utils';
4
+ import { Export, findModule, findModuleByExport, findModuleExport } from '../webpack';
5
+
6
+ // All of the popout options + strTitle are related. Proper usage is not yet known...
7
+ export interface ShowModalProps {
8
+ browserContext?: unknown;
9
+ bForcePopOut?: boolean;
10
+ bHideActionIcons?: boolean;
11
+ bHideMainWindowForPopouts?: boolean;
12
+ bNeverPopOut?: boolean;
13
+ fnOnClose?: () => void; // Seems to be the same as "closeModal" callback, but only when the modal is a popout. Will no longer work after "Update" invocation!
14
+ popupHeight?: number;
15
+ popupWidth?: number;
16
+ promiseRenderComplete?: Promise<void>; // Invoked once the render is complete. Currently, it seems to be used as image loading success/error callback...
17
+ strTitle?: string;
18
+ }
19
+
20
+ export interface ShowModalResult {
21
+ // This method will not invoke any of the variations of "closeModal" callbacks!
22
+ Close: () => void;
23
+
24
+ // This method will replace the modal element completely and will not update the callback chains,
25
+ // meaning that "closeModal" and etc. will not automatically close the modal anymore (also "fnOnClose"
26
+ // will not be even called upon close anymore)! You have to manually call the "Close" method when, for example,
27
+ // the "closeModal" is invoked in the newly updated modal:
28
+ // <ModalRoot closeModal={() => { console.log("ABOUT TO CLOSE"); showModalRes.Close(); }} />
29
+ Update: (modal: ReactNode) => void;
30
+ }
31
+
32
+ const showModalRaw: (
33
+ modal: ReactNode,
34
+ parent?: EventTarget,
35
+ title?: string,
36
+ props?: ShowModalProps,
37
+ unknown1?: unknown,
38
+ hideActions?: { bHideActions?: boolean },
39
+ modalManager?: unknown,
40
+ ) => ShowModalResult = findModuleExport(
41
+ (e: Export) =>
42
+ typeof e === 'function' && e.toString().includes('props.bDisableBackgroundDismiss') && !e?.prototype?.Cancel,
43
+ );
44
+
45
+ export const showModal = (
46
+ modal: ReactNode,
47
+ parent?: EventTarget,
48
+ props: ShowModalProps = {
49
+ strTitle: 'Decky Dialog',
50
+ bHideMainWindowForPopouts: false,
51
+ },
52
+ ): ShowModalResult => {
53
+ return showModalRaw(modal, parent || findSP(), props.strTitle, props, undefined, {
54
+ bHideActions: props.bHideActionIcons,
55
+ });
56
+ };
57
+
58
+ export interface ModalRootProps {
59
+ children?: ReactNode;
60
+ onCancel?(): void;
61
+ closeModal?(): void;
62
+ onOK?(): void;
63
+ onEscKeypress?(): void;
64
+ className?: string;
65
+ modalClassName?: string;
66
+ bAllowFullSize?: boolean;
67
+ bDestructiveWarning?: boolean;
68
+ bDisableBackgroundDismiss?: boolean;
69
+ bHideCloseIcon?: boolean;
70
+ bOKDisabled?: boolean;
71
+ bCancelDisabled?: boolean;
72
+ }
73
+
74
+ export interface ConfirmModalProps extends ModalRootProps {
75
+ onMiddleButton?(): void; // setting this prop will enable the middle button
76
+ strTitle?: ReactNode;
77
+ strDescription?: ReactNode;
78
+ strOKButtonText?: ReactNode;
79
+ strCancelButtonText?: ReactNode;
80
+ strMiddleButtonText?: ReactNode;
81
+ bAlertDialog?: boolean; // This will open a modal with only OK button enabled
82
+ bMiddleDisabled?: boolean;
83
+ }
84
+
85
+ export const ConfirmModal = findModuleExport(
86
+ (e: Export) => !e?.prototype?.OK && e?.prototype?.Cancel && e?.prototype?.render,
87
+ ) as FC<ConfirmModalProps>;
88
+
89
+ export const ModalRoot = Object.values(
90
+ findModule((m: any) => {
91
+ if (typeof m !== 'object') return false;
92
+
93
+ for (let prop in m) {
94
+ if (m[prop]?.m_mapModalManager && Object.values(m)?.find((x: any) => x?.type)) {
95
+ return true;
96
+ }
97
+ }
98
+
99
+ return false;
100
+ }) || {},
101
+ )?.find((x: any) => x?.type?.toString()?.includes('((function(){')) as FC<ModalRootProps>;
102
+
103
+ interface SimpleModalProps {
104
+ active?: boolean;
105
+ children: ReactNode;
106
+ }
107
+
108
+ const ModalModule = findModuleByExport((e: Export) => e?.toString().includes('.ModalPosition,fallback:'), 5);
109
+
110
+ const ModalModuleProps = ModalModule ? Object.values(ModalModule) : [];
111
+
112
+ export const SimpleModal = ModalModuleProps.find((prop) => {
113
+ const string = prop?.toString();
114
+ return string?.includes('.ShowPortalModal()') && string?.includes('.OnElementReadyCallbacks.Register(');
115
+ }) as FC<SimpleModalProps>;
116
+
117
+ export const ModalPosition = ModalModuleProps.find((prop) =>
118
+ prop?.toString().includes('.ModalPosition,fallback:'),
119
+ ) as FC<SimpleModalProps>;
@@ -0,0 +1,26 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ import { Export, findModuleDetailsByExport } from '../webpack';
4
+
5
+ // TODO where did this go?
6
+ // export const Panel: FC<{ children?: ReactNode; }> = findModuleExport((e: Export) => {
7
+ // if (typeof mod !== 'object' || !mod.__esModule) return undefined;
8
+ // return mod.Panel;
9
+ // });
10
+
11
+ export interface PanelSectionProps {
12
+ title?: string;
13
+ spinner?: boolean;
14
+ children?: ReactNode;
15
+ }
16
+
17
+ const [mod, panelSection] = findModuleDetailsByExport((e: Export) => e.toString()?.includes('.PanelSection'));
18
+
19
+ export const PanelSection = panelSection as FC<PanelSectionProps>;
20
+
21
+ export interface PanelSectionRowProps {
22
+ children?: ReactNode;
23
+ }
24
+ export const PanelSectionRow = Object.values(mod).filter(
25
+ (exp: any) => !exp?.toString()?.includes('.PanelSection'),
26
+ )[0] as FC<PanelSectionRowProps>;
@@ -0,0 +1,37 @@
1
+ import { ReactNode, FC } from 'react';
2
+
3
+ import { Export, findModuleExport } from '../webpack';
4
+ import { ItemProps } from './Item';
5
+ import { createPropListRegex } from '../utils';
6
+
7
+ export interface ProgressBarItemProps extends ItemProps {
8
+ indeterminate?: boolean;
9
+ nTransitionSec?: number;
10
+ nProgress?: number;
11
+ focusable?: boolean;
12
+ }
13
+
14
+ export interface ProgressBarProps {
15
+ indeterminate?: boolean;
16
+ nTransitionSec?: number;
17
+ nProgress?: number;
18
+ focusable?: boolean;
19
+ }
20
+
21
+ export interface ProgressBarWithInfoProps extends ProgressBarItemProps {
22
+ sTimeRemaining?: ReactNode;
23
+ sOperationText?: ReactNode;
24
+ }
25
+
26
+ export const ProgressBar = findModuleExport((e: Export) =>
27
+ e?.toString()?.includes('.ProgressBar,"standard"=='),
28
+ ) as FC<ProgressBarProps>;
29
+
30
+ export const ProgressBarWithInfo = findModuleExport((e: Export) =>
31
+ e?.toString()?.includes('.ProgressBarFieldStatus},'),
32
+ ) as FC<ProgressBarWithInfoProps>;
33
+
34
+ const progressBarItemRegex = createPropListRegex(["indeterminate", "nTransitionSec", "nProgress"]);
35
+ export const ProgressBarItem = findModuleExport((e: Export) =>
36
+ e?.toString && progressBarItemRegex.test(e.toString()),
37
+ ) as FC<ProgressBarItemProps>;
@@ -0,0 +1,15 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ import { Export, findModuleByExport, findModuleExport } from '../webpack';
4
+
5
+ const ScrollingModule = findModuleByExport((e: Export) => e?.render?.toString?.().includes('{case"x":'));
6
+
7
+ const ScrollingModuleProps = ScrollingModule ? Object.values(ScrollingModule) : [];
8
+
9
+ export const ScrollPanel = ScrollingModuleProps.find((prop: any) =>
10
+ prop?.render?.toString?.().includes('{case"x":'),
11
+ ) as FC<{ children?: ReactNode }>;
12
+
13
+ export const ScrollPanelGroup: FC<{ children?: ReactNode }> = findModuleExport((e: Export) =>
14
+ e?.render?.toString().includes('.FocusVisibleChild()),[])'),
15
+ );