diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts index bb26e5ecd..f0675ca48 100644 --- a/packages/discord-types/src/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -35,17 +35,19 @@ export type Button = ComponentType & { // #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; "aria-label"?: string; allowOverflow?: boolean; @@ -62,7 +64,9 @@ export type Tooltip = ComponentType<{ tooltipClassName?: string; tooltipContentClassName?: string; -}> & { +} + +export type Tooltip = ComponentType & { Colors: Record<"BLACK" | "BRAND" | "CUSTOM" | "GREEN" | "GREY" | "PRIMARY" | "RED" | "YELLOW", string>; }; diff --git a/src/components/TooltipContainer.tsx b/src/components/TooltipContainer.tsx new file mode 100644 index 000000000..f37f20f4b --- /dev/null +++ b/src/components/TooltipContainer.tsx @@ -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 & { children: React.ReactNode; }) { + return ( + + {tooltipProps => +
+ {children} +
+ } +
+ ); +} diff --git a/src/components/TooltipFallback.tsx b/src/components/TooltipFallback.tsx new file mode 100644 index 000000000..145b43c7d --- /dev/null +++ b/src/components/TooltipFallback.tsx @@ -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; diff --git a/src/plugins/messageLogger/HistoryModal.tsx b/src/plugins/messageLogger/HistoryModal.tsx index 4da925a59..1dd7943fd 100644 --- a/src/plugins/messageLogger/HistoryModal.tsx +++ b/src/plugins/messageLogger/HistoryModal.tsx @@ -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 "."; diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index 5a0ca71f1..a30442459 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -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"; diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 09a1ea467..2091687f2 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -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"; diff --git a/src/plugins/showTimeoutDuration/index.tsx b/src/plugins/showTimeoutDuration/index.tsx index 472eb7854..99fa10df0 100644 --- a/src/plugins/showTimeoutDuration/index.tsx +++ b/src/plugins/showTimeoutDuration/index.tsx @@ -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}/); diff --git a/src/plugins/sortFriendRequests/index.tsx b/src/plugins/sortFriendRequests/index.tsx index 03fe42311..8d7b2df6e 100644 --- a/src/plugins/sortFriendRequests/index.tsx +++ b/src/plugins/sortFriendRequests/index.tsx @@ -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, { diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index 659cd8dfd..867da2991 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -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("Card", filters.componentByCode(".editable),", ".outline:")); export const Checkbox = waitForComponent("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("Tooltip", m => m.prototype?.shouldShowTooltip && m.prototype.render, TooltipFallback); +/** @deprecated import from @vencord/components */ +export const TooltipContainer = TooltipContainerComponent as never; export const TextInput = waitForComponent("TextInput", filters.componentByCode("#{intl::MAXIMUM_LENGTH_ERROR}", '"input"')); export const TextArea = waitForComponent("TextArea", filters.componentByCode("this.getPaddingRight()},id:")); diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx index 1aca50058..3da77e131 100644 --- a/src/webpack/common/internal.tsx +++ b/src/webpack/common/internal.tsx @@ -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 = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]) { +export function waitForComponent = ComponentType & Record>(name: string, filter: FilterFn | string | string[], fallbackValue: ComponentType | 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; - const lazyComponent = LazyComponent(() => myValue) as LazyComponentWrapper; waitFor(filter, (v: any) => { myValue = v; Object.assign(lazyComponent, v);