@steambrew/client
Version:
A support library for creating plugins with Millennium.
163 lines (130 loc) • 4.51 kB
text/typescript
import { FC, ReactNode } from 'react';
import { Export, findModuleByExport, findModuleDetailsByExport, findModuleExport } from '../webpack';
import { FooterLegendProps } from './FooterLegend';
interface PopupCreationOptions {
/**
* Initially hidden, make it appear with {@link ContextMenuInstance.Show}.
*/
bCreateHidden?: boolean;
bModal?: boolean;
/**
* Document title.
*/
title?: string;
}
// Separate interface, since one of webpack module exports uses this exact object,
// so maybe it could be reused elsewhere.
interface MonitorOptions {
targetMonitor: {
flMonitorScale: number;
nScreenLeft: number;
nScreenTop: number;
nScreenWidth: number;
nScreenHeight: number;
};
flGamepadScale: number;
}
export interface ContextMenuPositionOptions extends PopupCreationOptions, Partial<MonitorOptions> {
/**
* When {@link bForcePopup} is true, makes the window appear above everything else.
*/
bAlwaysOnTop?: boolean;
/**
* Disables the mouse overlay, granting the ability to click anywhere while
* the menu's active.
*/
bDisableMouseOverlay?: boolean;
/**
* Disables the {@link bPreferPopTop} behavior.
*/
bDisablePopTop?: boolean;
bFitToWindow?: boolean;
/**
* Forces the menu to open in a separate window instead of inside the parent.
*/
bForcePopup?: boolean;
/**
* Like {@link bMatchWidth}, but don't shrink below the menu's minimum width.
*/
bGrowToElementWidth?: boolean;
/**
* Match the parent's exact height.
*/
bMatchHeight?: boolean;
/**
* Match the parent's exact width.
*/
bMatchWidth?: boolean;
bNoFocusWhenShown?: boolean;
/**
* Makes the menu **invisible**, instead of getting removed from the DOM.
*/
bRetainOnHide?: boolean;
bScreenCoordinates?: boolean;
/**
* Set to `true` to not account for the parent's width.
*/
bOverlapHorizontal?: boolean;
/**
* Set to `true` to not account for the parent's height.
*/
bOverlapVertical?: boolean;
/**
* Set to `true` to make the entire menu try to appear on the left side from
* the parent.
*/
bPreferPopLeft?: boolean;
/**
* Set to `true` to make the entire menu try to appear above the parent.
*/
bPreferPopTop?: boolean;
bShiftToFitWindow?: boolean;
// different window creation flags (StandaloneContextMenu vs PopupContextMenu)
bStandalone?: boolean;
/**
* Class name **replacement** for the container element, i.e. it replaces the
* default class.
*/
strClassName?: string;
}
export interface ContextMenuInstance {
Hide(): void;
Show(): void;
}
export const showContextMenu: (children: ReactNode, parent?: EventTarget, options?: ContextMenuPositionOptions) => ContextMenuInstance = findModuleExport(
(e: Export) => typeof e === 'function' && e.toString().includes('GetContextMenuManagerFromWindow(') && e.toString().includes('.CreateContextMenuInstance('),
);
export interface MenuProps extends FooterLegendProps {
label: string;
onCancel?(): void;
cancelText?: string;
children?: ReactNode;
}
const MenuModule = findModuleDetailsByExport((e: Export) => e?.render?.toString()?.includes('bPlayAudio:') || (e?.prototype?.OnOKButton && e?.prototype?.OnMouseEnter));
export const Menu: FC<MenuProps> =
findModuleExport((e: Export) => e?.prototype?.HideIfSubmenu && e?.prototype?.HideMenu) || // Legacy Menu
(Object.values(MenuModule?.[0] ?? {}).find((e) => e?.toString()?.includes?.(`useId`) && e?.toString()?.includes?.(`labelId`)) as FC<MenuProps>); // New Menu 6/15/2025
export interface MenuGroupProps {
label: string;
disabled?: boolean;
children?: ReactNode;
}
const MenuGoupModule = findModuleByExport(
(e) => e?.prototype?.Focus && e?.prototype?.OnOKButton && e?.prototype?.render?.toString().includes?.(`"emphasis"==this.props.tone`),
);
export const MenuGroup: FC<MenuGroupProps> =
MenuGoupModule && Object.values(MenuGoupModule).find((e: Export) => typeof e == 'function' && e?.toString?.()?.includes('bInGamepadUI:'));
export interface MenuItemProps extends FooterLegendProps {
bInteractableItem?: boolean;
onClick?(evt: Event): void;
onSelected?(evt: Event): void;
onMouseEnter?(evt: MouseEvent): void;
onMoveRight?(): void;
selected?: boolean;
disabled?: boolean;
bPlayAudio?: boolean;
tone?: 'positive' | 'emphasis' | 'destructive';
children?: ReactNode;
}
export const MenuItem: FC<MenuItemProps> = MenuModule?.[1];
export const MenuSeparator: FC = findModuleExport((e: Export) => typeof e === 'function' && /className:.+?\.ContextMenuSeparator/.test(e.toString()));