fix & future proof tooltips

This commit is contained in:
Vendicated 2025-12-28 02:28:22 +01:00
parent 58ef6585af
commit c123efd659
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
10 changed files with 88 additions and 32 deletions

View file

@ -35,17 +35,19 @@ export type Button = ComponentType<ButtonProps> & {
// #endregion
export type Tooltip = ComponentType<{
export interface TooltipChildrenProps {
onClick(): void;
onMouseEnter(): void;
onMouseLeave(): void;
onContextMenu(): void;
onFocus(): void;
onBlur(): void;
"aria-label"?: string;
}
export interface TooltipProps {
text: ReactNode | ComponentType;
children: FunctionComponent<{
onClick(): void;
onMouseEnter(): void;
onMouseLeave(): void;
onContextMenu(): void;
onFocus(): void;
onBlur(): void;
"aria-label"?: string;
}>;
children: FunctionComponent<TooltipChildrenProps>;
"aria-label"?: string;
allowOverflow?: boolean;
@ -62,7 +64,9 @@ export type Tooltip = ComponentType<{
tooltipClassName?: string;
tooltipContentClassName?: string;
}> & {
}
export type Tooltip = ComponentType<TooltipProps> & {
Colors: Record<"BLACK" | "BRAND" | "CUSTOM" | "GREEN" | "GREY" | "PRIMARY" | "RED" | "YELLOW", string>;
};

View file

@ -0,0 +1,20 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { TooltipProps } from "@vencord/discord-types";
import { Tooltip } from "@webpack/common";
export function TooltipContainer({ children, ...props }: Omit<TooltipProps, "children"> & { children: React.ReactNode; }) {
return (
<Tooltip {...props}>
{tooltipProps =>
<div {...tooltipProps}>
{children}
</div>
}
</Tooltip>
);
}

View file

@ -0,0 +1,29 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Tooltip } from "@vencord/discord-types";
const NOOP = () => { };
/** Don't use this */
export const TooltipFallback: Tooltip = ({ children }) => {
if (typeof children !== "function") {
return null;
}
const node = children({
onBlur: NOOP,
onFocus: NOOP,
onMouseEnter: NOOP,
onMouseLeave: NOOP,
onClick: NOOP,
onContextMenu: NOOP
});
return <>{node}</>;
};
TooltipFallback.Colors = {} as any;

View file

@ -5,12 +5,13 @@
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { TooltipContainer } from "@components/TooltipContainer";
import { classNameFactory } from "@utils/css";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { findByPropsLazy } from "@webpack";
import { TabBar, Text, Timestamp, TooltipContainer, useState } from "@webpack/common";
import { TabBar, Text, Timestamp, useState } from "@webpack/common";
import { parseEditContent } from ".";

View file

@ -22,12 +22,13 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { SafetyIcon } from "@components/Icons";
import { TooltipContainer } from "@components/TooltipContainer";
import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import type { Guild, GuildMember } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, useRef, UserStore } from "@webpack/common";
import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildStore, match, Menu, PermissionsBits, Popout, useRef, UserStore } from "@webpack/common";
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions";
import UserPermissions from "./components/UserPermissions";

View file

@ -21,12 +21,13 @@ import "./style.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import ErrorBoundary from "@components/ErrorBoundary";
import { NotesIcon, OpenExternalIcon } from "@components/Icons";
import { TooltipContainer } from "@components/TooltipContainer";
import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin from "@utils/types";
import { Guild, User } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
import { Alerts, Clickable, Menu, Parser, TooltipContainer } from "@webpack/common";
import { Alerts, Clickable, Menu, Parser } from "@webpack/common";
import { Auth, initAuth, updateAuth } from "./auth";
import { openReviewsModal } from "./components/ReviewModal";

View file

@ -8,13 +8,14 @@ import "./styles.css";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { TooltipContainer } from "@components/TooltipContainer";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
import { Message } from "@vencord/discord-types";
import { findComponentLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, Text, TooltipContainer } from "@webpack/common";
import { ChannelStore, GuildMemberStore, Text } from "@webpack/common";
import { ReactNode } from "react";
const countDownFilter = canonicalizeMatch(/#{intl::MAX_AGE_NEVER}/);

View file

@ -20,11 +20,12 @@ import "./styles.css";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { TooltipContainer } from "@components/TooltipContainer";
import { Devs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { DateUtils, RelationshipStore, Text, TooltipContainer } from "@webpack/common";
import { DateUtils, RelationshipStore, Text } from "@webpack/common";
import { PropsWithChildren } from "react";
const formatter = new Intl.DateTimeFormat(undefined, {

View file

@ -22,6 +22,8 @@ import { Divider } from "@components/Divider";
import { FormSwitchCompat } from "@components/FormSwitch";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { TooltipContainer as TooltipContainerComponent } from "@components/TooltipContainer";
import { TooltipFallback } from "@components/TooltipFallback";
import { LazyComponent } from "@utils/lazyReact";
import * as t from "@vencord/discord-types";
import { filters, mapMangledModuleLazy, waitFor } from "@webpack";
@ -52,17 +54,9 @@ export const Switch = FormSwitchCompat as never;
export const Card = waitForComponent<never>("Card", filters.componentByCode(".editable),", ".outline:"));
export const Checkbox = waitForComponent<t.Checkbox>("Checkbox", filters.componentByCode(".checkboxWrapperDisabled:"));
const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", {
Tooltip: filters.componentByCode("this.renderTooltip()]"),
TooltipContainer: filters.componentByCode('="div"')
}) as {
Tooltip: t.Tooltip,
TooltipContainer: t.TooltipContainer;
};
// TODO: if these finds break, they should just return their children
export const Tooltip = LazyComponent(() => Tooltips.Tooltip);
export const TooltipContainer = LazyComponent(() => Tooltips.TooltipContainer);
export const Tooltip = waitForComponent<t.Tooltip>("Tooltip", m => m.prototype?.shouldShowTooltip && m.prototype.render, TooltipFallback);
/** @deprecated import from @vencord/components */
export const TooltipContainer = TooltipContainerComponent as never;
export const TextInput = waitForComponent<t.TextInput>("TextInput", filters.componentByCode("#{intl::MAXIMUM_LENGTH_ERROR}", '"input"'));
export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.componentByCode("this.getPaddingRight()},id:"));

View file

@ -19,22 +19,26 @@
import { Logger } from "@utils/Logger";
import { LazyComponent, LazyComponentWrapper } from "@utils/react";
import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "@webpack";
import { ComponentType } from "react";
const logger = new Logger("Webpack");
export function waitForComponent<T extends React.ComponentType<any> = React.ComponentType<any> & Record<string, any>>(name: string, filter: FilterFn | string | string[]) {
export function waitForComponent<T extends ComponentType<any> = ComponentType<any> & Record<string, any>>(name: string, filter: FilterFn | string | string[], fallbackValue: ComponentType<any> | null = null) {
if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]);
let myValue: T = function () {
let myValue: T | null = null;
const lazyComponent = LazyComponent(() => {
if (myValue) return myValue;
const error = new Error(`Vencord could not find the ${name} Component`);
logger.error(error);
if (IS_DEV) throw error;
return null;
} as any;
return fallbackValue!;
}) as LazyComponentWrapper<T>;
const lazyComponent = LazyComponent(() => myValue) as LazyComponentWrapper<T>;
waitFor(filter, (v: any) => {
myValue = v;
Object.assign(lazyComponent, v);