Add lint rule to protect against this access on unbound methods (#32578)

* Add Actions to ViewModel utility types and specify `this: void` signature

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add https://typescript-eslint.io/rules/unbound-method/ linter to shared-components

also fix stray lint config which doesn't apply to SC

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add https://typescript-eslint.io/rules/unbound-method/ linter to element-web

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix genuine issues identified by the linter

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Specify this:void on i18napi

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update Module API

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add comment for MapToVoidThis

Added utility type to map VM actions to unbound functions.

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2026-02-23 15:37:58 +00:00 committed by GitHub
parent 87b28b725c
commit 77670eb369
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
135 changed files with 285 additions and 263 deletions

View File

@ -199,6 +199,7 @@ module.exports = {
files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}", "playwright/**/*.ts", "*.ts"],
extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"],
rules: {
"@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }],
"@typescript-eslint/explicit-function-return-type": [
"error",
{
@ -238,6 +239,7 @@ module.exports = {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/no-empty-object-type": "off",
"@typescript-eslint/unbound-method": "off",
// Jest/Playwright specific

View File

@ -47,7 +47,7 @@ interface FooViewActions {
// ViewModel is an object (usually a class) that implements both the interfaces listed above.
// https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/ViewModel.ts
export type FooViewModel = ViewModel<FooViewSnapshot> & FooViewActions;
export type FooViewModel = ViewModel<FooViewSnapshot, FooViewActions>;
interface FooViewProps {
// Ideally the view only depends on the view model i.e you don't expect any other props here.

View File

@ -9,9 +9,10 @@ module.exports = {
root: true,
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
extends: [
"plugin:matrix-org/babel",
"plugin:matrix-org/react",
"plugin:matrix-org/a11y",
"plugin:matrix-org/typescript",
"plugin:matrix-org/react",
"plugin:storybook/recommended",
],
parserOptions: {
@ -42,37 +43,35 @@ module.exports = {
],
},
],
"@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }],
"@typescript-eslint/explicit-function-return-type": [
"error",
{
allowExpressions: true,
},
],
// We're okay being explicit at the moment
// "@typescript-eslint/no-empty-interface": "off",
// We'd rather not do this but we do
// "@typescript-eslint/ban-ts-comment": "off",
// We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-empty-object-type": [
"error",
{
// We do this sometimes to brand interfaces
allowInterfaces: "with-single-extends",
},
],
},
overrides: [
{
files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}"],
extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"],
files: ["src/**/*.test.{ts,tsx}"],
rules: {
"@typescript-eslint/explicit-function-return-type": [
"error",
{
allowExpressions: true,
},
],
// Remove Babel things manually due to override limitations
"@babel/no-invalid-this": ["off"],
// We're okay being explicit at the moment
"@typescript-eslint/no-empty-interface": "off",
// We disable this while we're transitioning
"@typescript-eslint/unbound-method": "off",
"@typescript-eslint/no-explicit-any": "off",
// We'd rather not do this but we do
"@typescript-eslint/ban-ts-comment": "off",
// We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-empty-object-type": [
"error",
{
// We do this sometimes to brand interfaces
allowInterfaces: "with-single-extends",
},
],
},
},
],

View File

@ -70,7 +70,7 @@ export interface AudioPlayerViewActions {
/**
* The view model for the audio player.
*/
export type AudioPlayerViewModel = ViewModel<AudioPlayerViewSnapshot> & AudioPlayerViewActions;
export type AudioPlayerViewModel = ViewModel<AudioPlayerViewSnapshot, AudioPlayerViewActions>;
interface AudioPlayerViewProps {
/**

View File

@ -84,7 +84,7 @@ interface DecryptionFailureBodyViewProps {
/**
* React ref to attach to any React components returned
*/
ref?: React.RefObject<any>;
ref?: React.RefObject<HTMLDivElement>;
}
/**

View File

@ -57,8 +57,10 @@ export interface DisambiguatedProfileViewActions {
/**
* The view model for DisambiguatedProfileView.
*/
export type DisambiguatedProfileViewModel = ViewModel<DisambiguatedProfileViewSnapshot> &
DisambiguatedProfileViewActions;
export type DisambiguatedProfileViewModel = ViewModel<
DisambiguatedProfileViewSnapshot,
DisambiguatedProfileViewActions
>;
interface DisambiguatedProfileViewProps {
/**

View File

@ -89,7 +89,7 @@ export interface WidgetContextMenuAction {
onMoveButton: (direction: number) => void;
}
export type WidgetContextMenuViewModel = ViewModel<WidgetContextMenuSnapshot> & WidgetContextMenuAction;
export type WidgetContextMenuViewModel = ViewModel<WidgetContextMenuSnapshot, WidgetContextMenuAction>;
interface WidgetContextMenuViewProps {
vm: WidgetContextMenuViewModel;

View File

@ -103,7 +103,7 @@ export interface RoomListHeaderViewActions {
/**
* The view model for the room list header component.
*/
export type RoomListHeaderViewModel = ViewModel<RoomListHeaderViewSnapshot> & RoomListHeaderViewActions;
export type RoomListHeaderViewModel = ViewModel<RoomListHeaderViewSnapshot, RoomListHeaderViewActions>;
interface RoomListHeaderViewProps {
/**

View File

@ -25,7 +25,7 @@ import type { RoomListItemSnapshot, RoomListItemActions } from "./RoomListItemVi
/**
* View model type for room list item
*/
export type RoomItemViewModel = ViewModel<RoomListItemSnapshot> & RoomListItemActions;
export type RoomItemViewModel = ViewModel<RoomListItemSnapshot, RoomListItemActions>;
/**
* Props for RoomListItemMoreOptionsMenu component

View File

@ -21,7 +21,7 @@ import type { RoomListItemSnapshot, RoomListItemActions } from "./RoomListItemVi
/**
* View model type for room list item
*/
export type RoomItemViewModel = ViewModel<RoomListItemSnapshot> & RoomListItemActions;
export type RoomItemViewModel = ViewModel<RoomListItemSnapshot, RoomListItemActions>;
/**
* Props for RoomListItemNotificationMenu component

View File

@ -105,7 +105,7 @@ export interface RoomListItemActions {
/**
* The view model type for a room list item
*/
export type RoomItemViewModel = ViewModel<RoomListItemSnapshot> & RoomListItemActions;
export type RoomItemViewModel = ViewModel<RoomListItemSnapshot, RoomListItemActions>;
/**
* Props for RoomListItemView component

View File

@ -50,7 +50,7 @@ export interface RoomListSearchViewActions {
/**
* The view model for the room list search component.
*/
export type RoomListSearchViewModel = ViewModel<RoomListSearchViewSnapshot> & RoomListSearchViewActions;
export type RoomListSearchViewModel = ViewModel<RoomListSearchViewSnapshot, RoomListSearchViewActions>;
interface RoomListSearchViewProps {
/**

View File

@ -12,7 +12,7 @@ import { RoomListPrimaryFilters, type FilterId } from "../RoomListPrimaryFilters
import { RoomListLoadingSkeleton } from "./RoomListLoadingSkeleton";
import { RoomListEmptyStateView } from "./RoomListEmptyStateView";
import { VirtualizedRoomListView, type RoomListViewState } from "../VirtualizedRoomListView";
import { type Room } from "../RoomListItemView";
import { type Room, type RoomItemViewModel } from "../RoomListItemView";
/**
* Snapshot for the room list view
@ -49,7 +49,7 @@ export interface RoomListViewActions {
/** Called to create a new room */
createRoom: () => void;
/** Get view model for a specific room (virtualization API) */
getRoomItemViewModel: (roomId: string) => any;
getRoomItemViewModel: (roomId: string) => RoomItemViewModel;
/** Called when the visible range changes (virtualization API) */
updateVisibleRooms: (startIndex: number, endIndex: number) => void;
}
@ -57,7 +57,7 @@ export interface RoomListViewActions {
/**
* The view model type for the room list view
*/
export type RoomListViewModel = ViewModel<RoomListSnapshot> & RoomListViewActions;
export type RoomListViewModel = ViewModel<RoomListSnapshot, RoomListViewActions>;
/**
* Props for RoomListView component

View File

@ -8,7 +8,7 @@
import React from "react";
import { fn } from "storybook/test";
import { type Room, type RoomListItemSnapshot, RoomNotifState } from "./RoomListItemView";
import { type Room, type RoomItemViewModel, type RoomListItemSnapshot, RoomNotifState } from "./RoomListItemView";
/**
* Mock avatar component for stories
@ -39,6 +39,7 @@ export const mockAvatar = (name: string): React.ReactElement => (
*/
export const renderAvatar = (room: Room): React.ReactElement => {
// Cast to any to access properties - in real usage, the room object from the SDK will have these
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return mockAvatar((room as any)?.name || "Room");
};
@ -102,8 +103,8 @@ export const createMockRoomSnapshot = (id: string, name: string, index: number):
/**
* Create a mock getRoomItemViewModel function for stories
*/
export const createGetRoomItemViewModel = (roomIds: string[]): ((roomId: string) => any) => {
const viewModels = new Map();
export const createGetRoomItemViewModel = (roomIds: string[]): ((roomId: string) => RoomItemViewModel) => {
const viewModels = new Map<string, RoomItemViewModel>();
roomIds.forEach((roomId, index) => {
const name = roomNames[index % roomNames.length];
const snapshot = createMockRoomSnapshot(roomId, name, index);
@ -125,7 +126,7 @@ export const createGetRoomItemViewModel = (roomIds: string[]): ((roomId: string)
viewModels.set(roomId, mockViewModel);
});
return (roomId: string) => viewModels.get(roomId);
return (roomId: string) => viewModels.get(roomId)!;
};
/**

View File

@ -100,7 +100,7 @@ export type RoomStatusBarViewSnapshot =
/**
* The view model for RoomStatusBarView.
*/
export type RoomStatusBarViewModel = ViewModel<RoomStatusBarViewSnapshot> & RoomStatusBarViewActions;
export type RoomStatusBarViewModel = ViewModel<RoomStatusBarViewSnapshot, RoomStatusBarViewActions>;
interface RoomStatusBarViewProps {
/**

View File

@ -55,7 +55,7 @@ export function Box({
...props
}: React.PropsWithChildren<BoxProps>): JSX.Element {
const style = useMemo(() => {
const style: Record<string, any> = {};
const style: Record<string, string> = {};
if (flex) style["--mx-box-flex"] = flex;
if (shrink) style["--mx-box-shrink"] = shrink;
if (grow) style["--mx-box-grow"] = grow;

View File

@ -11,6 +11,7 @@ import React, { type JSX, type ComponentProps, type JSXElementConstructor, useMe
import styles from "./Flex.module.css";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type FlexProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> = {
/**
* The type of the HTML element
@ -60,6 +61,7 @@ type FlexProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any
/**
* A flexbox container helper
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function Flex<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any> = "div">({
as = "div",
display = "flex",

View File

@ -21,7 +21,7 @@ export class I18nApi implements II18nApi {
/**
* Register translations for the module, may override app's existing translations
*/
public register(translations: Partial<Translations>): void {
public register(this: void, translations: Partial<Translations>): void {
const langs: Record<string, Record<string, string>> = {};
for (const key in translations) {
@ -42,11 +42,9 @@ export class I18nApi implements II18nApi {
* @param key - The key to translate
* @param variables - Optional variables to interpolate into the translation
*/
public translate(key: TranslationKey, variables?: Variables): string {
public translate(this: void, key: TranslationKey, variables?: Variables): string {
return _t(key, variables);
}
public humanizeTime(timeMillis: number): string {
return humanizeTime(timeMillis, this);
}
public humanizeTime = (timeMillis: number): string => humanizeTime(timeMillis, this);
}

View File

@ -113,7 +113,7 @@ export interface IVirtualizedListProps<Item, Context> extends Omit<
/**
* Utility type for the prop scrollIntoViewOnChange allowing it to be memoised by a caller without repeating types
*/
export type ScrollIntoViewOnChange<Item, Context = any> = NonNullable<
export type ScrollIntoViewOnChange<Item, Context> = NonNullable<
VirtuosoProps<Item, VirtualizedListContext<Context>>["scrollIntoViewOnChange"]
>;
@ -124,7 +124,7 @@ export type ScrollIntoViewOnChange<Item, Context = any> = NonNullable<
* @template Item - The type of data items in the list
* @template Context - The type of additional context data passed to items
*/
export function VirtualizedList<Item, Context = any>(props: IVirtualizedListProps<Item, Context>): React.ReactElement {
export function VirtualizedList<Item, Context>(props: IVirtualizedListProps<Item, Context>): React.ReactElement {
// Extract our custom props to avoid conflicts with Virtuoso props
const {
items,

View File

@ -9,15 +9,22 @@ Please see LICENSE files in the repository root for full details.
* The interface for a generic View Model passed to the shared components.
* The snapshot is of type T which is a type specifying a snapshot for the view in question.
*/
export interface ViewModel<T> {
// Utility type to map all VM actions to unbound functions so that they do not have
// to be called with the correct 'this' context. This prevents "cannot read X of undefined" bugs.
type MapToVoidThis<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (this: void, ...args: A) => R : T[K];
};
export type ViewModel<Snapshot, Actions = unknown> = {
/**
* The current snapshot of the view model.
*/
getSnapshot: () => T;
getSnapshot: () => Snapshot;
/**
* Subscribes to changes in the view model.
* The listener will be called whenever the snapshot changes.
*/
subscribe: (listener: () => void) => () => void;
}
} & MapToVoidThis<Actions>;

View File

@ -14,7 +14,7 @@ import { type ViewModel } from "./ViewModel";
* @param vm The view model to use
* @returns The current snapshot
*/
export function useViewModel<T>(vm: ViewModel<T>): T {
export function useViewModel<T>(vm: ViewModel<T, unknown>): T {
// We need to pass the same getSnapshot function as getServerSnapshot as this
// is used when making the HTML chat export.
return useSyncExternalStore(vm.subscribe, vm.getSnapshot, vm.getSnapshot);

20
pnpm-lock.yaml generated
View File

@ -7,8 +7,8 @@ settings:
catalogs:
default:
'@element-hq/element-web-module-api':
specifier: 1.9.0
version: 1.9.0
specifier: 1.9.1
version: 1.9.1
'@element-hq/element-web-playwright-common':
specifier: 2.2.7
version: 2.2.7
@ -91,7 +91,7 @@ importers:
version: 7.28.6
'@element-hq/element-web-module-api':
specifier: 'catalog:'
version: 1.9.0(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4)
version: 1.9.1(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4)
'@element-hq/web-shared-components':
specifier: workspace:*
version: link:packages/shared-components
@ -371,7 +371,7 @@ importers:
version: 0.16.3
'@element-hq/element-web-playwright-common':
specifier: 'catalog:'
version: 2.2.7(@element-hq/element-web-module-api@1.9.0(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4))(@playwright/test@1.58.2)(playwright-core@1.58.2)
version: 2.2.7(@element-hq/element-web-module-api@1.9.1(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4))(@playwright/test@1.58.2)(playwright-core@1.58.2)
'@element-hq/element-web-playwright-common-local':
specifier: workspace:*
version: link:packages/playwright-common
@ -743,7 +743,7 @@ importers:
dependencies:
'@element-hq/element-web-module-api':
specifier: 'catalog:'
version: 1.9.0(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4)
version: 1.9.1(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4)
'@matrix-org/spec':
specifier: ^1.7.0
version: 1.16.0
@ -2021,8 +2021,8 @@ packages:
'@element-hq/element-call-embedded@0.16.3':
resolution: {integrity: sha512-OViKJonDaDNVBUW9WdV9mk78/Ruh34C7XsEgt3O8D9z+64C39elbIgllHSoH5S12IRlv9RYrrV37FZLo6QWsDQ==}
'@element-hq/element-web-module-api@1.9.0':
resolution: {integrity: sha512-Ao/V9w+wysZK4bh61LlKlznF10n2ZbD6KcUI46/zUMttXbmJn3ahvbzhEpwYcD+Cjy3ag5ycxLIIGkKV/fncXg==}
'@element-hq/element-web-module-api@1.9.1':
resolution: {integrity: sha512-eCHHBkaDWc7Ai10b2VmOAkWIQAsur+YZ2kpFrPFVG41wMXn0PbFL+n6wvpbN+mU5Mg7uVIqXhQn4jflHESBUrA==}
engines: {node: '>=20.0.0'}
peerDependencies:
'@matrix-org/react-sdk-module-api': '*'
@ -12085,7 +12085,7 @@ snapshots:
'@element-hq/element-call-embedded@0.16.3': {}
'@element-hq/element-web-module-api@1.9.0(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4)':
'@element-hq/element-web-module-api@1.9.1(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4)':
dependencies:
'@types/react': 19.2.10
'@types/react-dom': 19.2.3(@types/react@19.2.10)
@ -12094,10 +12094,10 @@ snapshots:
'@matrix-org/react-sdk-module-api': 2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4)
matrix-web-i18n: 3.6.0
'@element-hq/element-web-playwright-common@2.2.7(@element-hq/element-web-module-api@1.9.0(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4))(@playwright/test@1.58.2)(playwright-core@1.58.2)':
'@element-hq/element-web-playwright-common@2.2.7(@element-hq/element-web-module-api@1.9.1(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4))(@playwright/test@1.58.2)(playwright-core@1.58.2)':
dependencies:
'@axe-core/playwright': 4.11.1(playwright-core@1.58.2)
'@element-hq/element-web-module-api': 1.9.0(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4)
'@element-hq/element-web-module-api': 1.9.1(@matrix-org/react-sdk-module-api@2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.4))(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(matrix-web-i18n@3.6.0)(react@19.2.4)
'@playwright/test': 1.58.2
'@testcontainers/postgresql': 11.11.0
glob: 13.0.6

View File

@ -16,7 +16,7 @@ catalog:
"@element-hq/element-web-playwright-common": 2.2.7
"@playwright/test": 1.58.2
# Module API
"@element-hq/element-web-module-api": 1.9.0
"@element-hq/element-web-module-api": 1.9.1
# Compound
"@vector-im/compound-design-tokens": 6.9.0
"@vector-im/compound-web": 8.3.6

View File

@ -23,8 +23,8 @@ export const DAY_MS = HOUR_MS * 24;
*/
export function getDaysArray(weekday: Intl.DateTimeFormatOptions["weekday"] = "short"): string[] {
const sunday = 1672574400000; // 2023-01-01 12:00 UTC
const { format } = new Intl.DateTimeFormat(getUserLanguage(), { weekday, timeZone: "UTC" });
return [...Array(7).keys()].map((day) => format(sunday + day * DAY_MS));
const dateTimeFormat = new Intl.DateTimeFormat(getUserLanguage(), { weekday, timeZone: "UTC" });
return [...Array(7).keys()].map((day) => dateTimeFormat.format(sunday + day * DAY_MS));
}
/**
@ -32,8 +32,8 @@ export function getDaysArray(weekday: Intl.DateTimeFormatOptions["weekday"] = "s
* @param month - format desired "numeric" | "2-digit" | "long" | "short" | "narrow"
*/
export function getMonthsArray(month: Intl.DateTimeFormatOptions["month"] = "short"): string[] {
const { format } = new Intl.DateTimeFormat(getUserLanguage(), { month, timeZone: "UTC" });
return [...Array(12).keys()].map((m) => format(Date.UTC(2021, m)));
const dateTimeFormat = new Intl.DateTimeFormat(getUserLanguage(), { month, timeZone: "UTC" });
return [...Array(12).keys()].map((m) => dateTimeFormat.format(Date.UTC(2021, m)));
}
// XXX: Ideally we could just specify `hour12: boolean` but it has issues on Chrome in the `en` locale

View File

@ -93,7 +93,7 @@ export interface IHandle<C extends ComponentType> {
*
* @param args - Arguments to return to {@link finished}.
*/
close(...args: OnFinishedParams<C>): void;
close(this: void, ...args: OnFinishedParams<C>): void;
}
interface IOptions<C extends ComponentType> {

View File

@ -171,8 +171,14 @@ interface IProps {
handleLeftRight?: boolean;
handleInputFields?: boolean;
scrollIntoView?: boolean | ScrollIntoViewOptions;
children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent): void; onDragEndHandler(): void }): ReactNode;
onKeyDown?(ev: React.KeyboardEvent, state: IState, dispatch: Dispatch<IAction>): void;
children(
this: void,
renderProps: {
onKeyDownHandler(this: void, ev: React.KeyboardEvent): void;
onDragEndHandler(this: void): void;
},
): ReactNode;
onKeyDown?(this: void, ev: React.KeyboardEvent, state: IState, dispatch: Dispatch<IAction>): void;
}
export const findSiblingElement = (

View File

@ -16,8 +16,8 @@ import { KeyBindingAction } from "../KeyboardShortcuts";
import { getKeyBindingsManager } from "../../KeyBindingsManager";
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
onChange(this: void): void; // we handle keyup/down ourselves so lose the ChangeEvent
onClose(this: void): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
}
// Semantic component for representing a styled role=menuitemcheckbox

View File

@ -17,8 +17,8 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
label?: string;
onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
onClose(): void; // gets called after onChange on KeyBindingAction.Enter
onChange(this: void): void; // we handle keyup/down ourselves so lose the ChangeEvent
onClose(this: void): void; // gets called after onChange on KeyBindingAction.Enter
}
// Semantic component for representing a styled role=menuitemradio

View File

@ -14,11 +14,14 @@ import { type FocusHandler } from "./types";
interface IProps {
inputRef?: RefObject<HTMLElement | null>;
children(renderProps: {
onFocus: FocusHandler;
isActive: boolean;
ref: RefCallback<HTMLElement>;
}): ReactElement<any, any>;
children(
this: void,
renderProps: {
onFocus: FocusHandler;
isActive: boolean;
ref: RefCallback<HTMLElement>;
},
): ReactElement<any, any>;
}
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components.

View File

@ -116,7 +116,7 @@ const linkFactory =
);
export const UnsupportedBrowserView: React.FC<{
onAccept?(): void;
onAccept?(this: void): void;
}> = ({ onAccept }) => {
const config = SdkConfig.get();
const brand = config.brand ?? "Element";

View File

@ -26,7 +26,7 @@ interface NewRecoveryMethodDialogProps {
/**
* Callback when the dialog is dismissed.
*/
onFinished(): void;
onFinished(this: void): void;
}
// Export as default instead of a named export so that it can be dynamically imported with React lazy

View File

@ -92,9 +92,9 @@ export interface IProps extends MenuProps {
closeOnInteraction?: boolean;
// Function to be called on menu close
onFinished(): void;
onFinished(this: void): void;
// on resize callback
windowResize?(): void;
windowResize?(this: void): void;
}
interface IState {

View File

@ -16,7 +16,7 @@ import { useRoomState } from "../../hooks/useRoomState.ts";
interface IProps {
room: Room;
parent: HTMLElement | null;
onFileDrop(dataTransfer: DataTransfer): void;
onFileDrop(this: void, dataTransfer: DataTransfer): void;
}
interface IState {

View File

@ -87,7 +87,7 @@ interface IProps {
matrixClient: MatrixClient;
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: (credentials: IMatrixClientCreds) => Promise<MatrixClient>;
onRegistered: (this: void, credentials: IMatrixClientCreds) => Promise<MatrixClient>;
hideToSRUsers: boolean;
// eslint-disable-next-line camelcase
page_type?: string;

View File

@ -2023,7 +2023,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.setPageSubtitle();
}
private onLogoutClick(event: ButtonEvent): void {
private onLogoutClick(this: void, event: ButtonEvent): void {
dis.dispatch({
action: "logout",
});
@ -2047,7 +2047,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.stores.resizeNotifier.notifyWindowResized();
};
private dispatchTimelineResize(): void {
private dispatchTimelineResize(this: void): void {
dis.dispatch({ action: "timeline_resize" });
}
@ -2068,7 +2068,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
};
// returns a promise which resolves to the new MatrixClient
private onRegistered(credentials: IMatrixClientCreds): Promise<MatrixClient> {
private onRegistered(this: void, credentials: IMatrixClientCreds): Promise<MatrixClient> {
return Lifecycle.setLoggedIn(credentials);
}

View File

@ -22,7 +22,7 @@ interface IProps {
}
export default class RoomSearch extends React.PureComponent<IProps> {
private openSpotlight(): void {
private openSpotlight(this: void): void {
defaultDispatcher.fire(Action.OpenSpotlight);
}

View File

@ -42,7 +42,7 @@ interface Props {
promise: Promise<ISearchResults>;
abortController?: AbortController;
className: string;
onUpdate(inProgress: boolean, results: ISearchResults | null, error: Error | null): void;
onUpdate(this: void, inProgress: boolean, results: ISearchResults | null, error: Error | null): void;
ref?: Ref<ScrollPanel>;
}

View File

@ -78,7 +78,7 @@ interface IProps {
space: Room;
initialText?: string;
additionalButtons?: ReactNode;
showRoom(cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string, roomType?: RoomType): void;
showRoom(this: void, cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string, roomType?: RoomType): void;
}
interface ITileProps {
@ -88,9 +88,9 @@ interface ITileProps {
numChildRooms?: number;
hasPermissions?: boolean;
children?: ReactNode;
onViewRoomClick(): void;
onJoinRoomClick(): Promise<unknown>;
onToggleClick?(): void;
onViewRoomClick(this: void): void;
onJoinRoomClick(this: void): Promise<unknown>;
onToggleClick?(this: void): void;
}
const Tile: React.FC<ITileProps> = ({
@ -468,9 +468,9 @@ interface IHierarchyLevelProps {
hierarchy: RoomHierarchy;
parents: Set<string>;
selectedMap?: Map<string, Set<string>>;
onViewRoomClick(roomId: string, roomType?: RoomType): void;
onJoinRoomClick(roomId: string, parents: Set<string>): Promise<unknown>;
onToggleClick?(parentId: string, childId: string): void;
onViewRoomClick(this: void, roomId: string, roomType?: RoomType): void;
onJoinRoomClick(this: void, roomId: string, parents: Set<string>): Promise<unknown>;
onToggleClick?(this: void, parentId: string, childId: string): void;
}
export const toLocalRoom = (cli: MatrixClient, room: HierarchyRoom, hierarchy: RoomHierarchy): HierarchyRoom => {
@ -600,7 +600,7 @@ export const useRoomHierarchy = (
rooms?: HierarchyRoom[];
hierarchy?: RoomHierarchy;
error?: Error;
loadMore(pageSize?: number): Promise<void>;
loadMore(this: void, pageSize?: number): Promise<void>;
} => {
const [rooms, setRooms] = useState<HierarchyRoom[]>([]);
const [hierarchy, setHierarchy] = useState<RoomHierarchy>();

View File

@ -13,7 +13,7 @@ const SpacePillButton: React.FC<{
title: string;
icon: JSX.Element;
description: string;
onClick(): void;
onClick(this: void): void;
}> = ({ title, icon, description, onClick }) => {
return (
<AccessibleButton className="mx_SpacePillButton" onClick={onClick}>

View File

@ -304,7 +304,7 @@ const SpaceSetupFirstRooms: React.FC<{
space: Room;
title: string;
description: JSX.Element;
onFinished(firstRoomId?: string): void;
onFinished(this: void, firstRoomId?: string): void;
}> = ({ space, title, description, onFinished }) => {
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
@ -399,7 +399,7 @@ const SpaceSetupFirstRooms: React.FC<{
const SpaceAddExistingRooms: React.FC<{
space: Room;
onFinished(): void;
onFinished(this: void): void;
}> = ({ space, onFinished }) => {
return (
<div>
@ -423,7 +423,7 @@ const SpaceAddExistingRooms: React.FC<{
};
interface ISpaceSetupPublicShareProps extends Pick<IProps & IState, "justCreatedOpts" | "space" | "firstRoomId"> {
onFinished(): void;
onFinished(this: void): void;
}
const SpaceSetupPublicShare: React.FC<ISpaceSetupPublicShareProps> = ({
@ -455,7 +455,7 @@ const SpaceSetupPublicShare: React.FC<ISpaceSetupPublicShareProps> = ({
const SpaceSetupPrivateScope: React.FC<{
space: Room;
justCreatedOpts?: IOpts;
onFinished(createRooms: boolean): void;
onFinished(this: void, createRooms: boolean): void;
}> = ({ space, justCreatedOpts, onFinished }) => {
return (
<div className="mx_SpaceRoomView_privateScope">
@ -498,7 +498,7 @@ const validateEmailRules = withValidation({
const SpaceSetupPrivateInvite: React.FC<{
space: Room;
onFinished(): void;
onFinished(this: void): void;
}> = ({ space, onFinished }) => {
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");

View File

@ -27,7 +27,7 @@ interface Props {
*
* @param event - The click event
*/
onLogoutClick: (event: ButtonEvent) => void;
onLogoutClick: (this: void, event: ButtonEvent) => void;
/**
* Error that caused `/sync` to fail. If set, an error message will be shown on the splash screen.

View File

@ -33,7 +33,7 @@ interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idNam
roomId?: string;
};
viewAvatarOnClick?: boolean;
onClick?(): void;
onClick?(this: void): void;
}
const RoomAvatar: React.FC<IProps> = ({ room, viewAvatarOnClick, onClick, oobData, size = "36px", ...otherProps }) => {

View File

@ -33,7 +33,7 @@ export interface IProps {
matrixClient: MatrixClient;
// open the map centered on this beacon's location
initialFocusedBeacon?: Beacon;
onFinished(): void;
onFinished(this: void): void;
}
// track the 'focused time' as ts

View File

@ -36,9 +36,9 @@ interface IProps extends Omit<ComponentProps<typeof IconizedContextMenu>, "child
userWidget?: boolean;
showUnpin?: boolean;
// override delete handler
onDeleteClick?(): void;
onDeleteClick?(this: void): void;
// override edit handler
onEditClick?(): void;
onEditClick?(this: void): void;
}
const showStreamAudioStreamButton = (app: IWidget): boolean => {

View File

@ -17,8 +17,8 @@ import { AddExistingToSpace, defaultSpacesRenderer, SubspaceSelector } from "./A
interface IProps {
space: Room;
onCreateSubspaceClick(): void;
onFinished(added?: boolean): void;
onCreateSubspaceClick(this: void): void;
onFinished(this: void, added?: boolean): void;
}
const AddExistingSubspaceDialog: React.FC<IProps> = ({ space, onCreateSubspaceClick, onFinished }) => {

View File

@ -43,15 +43,15 @@ const GROUP_MARGIN = 24;
interface IProps {
space: Room;
onCreateRoomClick(ev: ButtonEvent): void;
onAddSubspaceClick(): void;
onFinished(added?: boolean): void;
onCreateRoomClick(this: void, ev: ButtonEvent): void;
onAddSubspaceClick(this: void): void;
onFinished(this: void, added?: boolean): void;
}
export const Entry: React.FC<{
room: Room;
checked: boolean;
onChange?(value: boolean): void;
onChange?(this: void, value: boolean): void;
}> = ({ room, checked, onChange }) => {
const id = useId();
return (
@ -86,7 +86,7 @@ interface IAddExistingToSpaceProps {
footerPrompt?: ReactNode;
filterPlaceholder: string;
emptySelectionButton?: ReactNode;
onFinished(added: boolean): void;
onFinished(this: void, added: boolean): void;
roomsRenderer?: Renderer;
spacesRenderer?: Renderer;
dmsRenderer?: Renderer;
@ -391,7 +391,7 @@ interface ISubspaceSelectorProps {
title: string;
space: Room;
value: Room;
onChange(space: Room): void;
onChange(this: void, space: Room): void;
}
export const SubspaceSelector: React.FC<ISubspaceSelectorProps> = ({ title, space, value, onChange }) => {

View File

@ -23,7 +23,7 @@ export enum ButtonClicked {
}
interface IProps {
onFinished(buttonClicked?: ButtonClicked): void;
onFinished(this: void, buttonClicked?: ButtonClicked): void;
analyticsOwner: string;
privacyPolicyUrl?: string;
primaryButton?: string;

View File

@ -21,7 +21,7 @@ import { type SettingKey } from "../../../settings/Settings.tsx";
interface IProps {
featureId: SettingKey;
onFinished(sendFeedback?: boolean): void;
onFinished(this: void, sendFeedback?: boolean): void;
}
const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {

View File

@ -29,7 +29,7 @@ interface Props {
matrixClient: MatrixClient;
room: Room;
member: RoomMember;
onFinished(redact?: boolean): void;
onFinished(this: void, redact?: boolean): void;
}
const BulkRedactDialog: React.FC<Props> = (props) => {

View File

@ -88,7 +88,7 @@ export default class ChangelogDialog extends React.Component<IProps, State> {
}
}
private elementsForCommit(commit: Commit): JSX.Element {
private elementsForCommit(this: void, commit: Commit): JSX.Element {
return (
<li key={commit.sha} className="mx_ChangelogDialog_li">
<a href={commit.html_url} target="_blank" rel="noreferrer noopener">

View File

@ -21,8 +21,8 @@ interface IProps extends Omit<BaseProps, "matrixClient" | "children" | "onFinish
specificLabel: string;
noneLabel?: string;
warningMessage?: string;
onFinished(success?: boolean, reason?: string, rooms?: Room[]): void;
spaceChildFilter?(child: Room): boolean;
onFinished(this: void, success?: boolean, reason?: string, rooms?: Room[]): void;
spaceChildFilter?(this: void, child: Room): boolean;
}
const ConfirmSpaceUserActionDialog: React.FC<IProps> = ({

View File

@ -23,8 +23,8 @@ import JoinRuleDropdown from "../elements/JoinRuleDropdown";
interface IProps {
space: Room;
onAddExistingSpaceClick(): void;
onFinished(added?: boolean): void;
onAddExistingSpaceClick(this: void): void;
onFinished(this: void, added?: boolean): void;
}
const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick, onFinished }) => {

View File

@ -61,7 +61,7 @@ const Tools: Record<Category, [label: TranslationKey, tool: Tool][]> = {
interface IProps {
roomId: string;
threadRootId?: string | null;
onFinished(finished?: boolean): void;
onFinished(this: void, finished?: boolean): void;
}
type ToolInfo = [label: TranslationKey, tool: Tool];

View File

@ -37,7 +37,7 @@ import { validateNumberInRange } from "../../../utils/validate";
interface IProps {
room: Room;
onFinished(doExport?: boolean): void;
onFinished(this: void, doExport?: boolean): void;
}
interface ExportConfig {

View File

@ -73,7 +73,7 @@ interface IProps {
// We need a permalink creator for the source room to pass through to EventTile
// in case the event is a reply (even though the user can't get at the link)
permalinkCreator: RoomPermalinkCreator;
onFinished(): void;
onFinished(this: void): void;
}
interface IEntryProps<K extends keyof TimelineEvents> {
@ -81,7 +81,7 @@ interface IEntryProps<K extends keyof TimelineEvents> {
type: K;
content: TimelineEvents[K];
matrixClient: MatrixClient;
onFinished(success: boolean): void;
onFinished(this: void, success: boolean): void;
}
enum SendState {

View File

@ -22,7 +22,7 @@ interface IProps {
rageshakeLabel?: string;
rageshakeData?: Record<string, any>;
children?: ReactNode;
onFinished(sendFeedback?: boolean): void;
onFinished(this: void, sendFeedback?: boolean): void;
}
const GenericFeatureFeedbackDialog: React.FC<IProps> = ({

View File

@ -16,7 +16,7 @@ import DialogButtons from "../elements/DialogButtons";
import { UserTab } from "./UserTab";
interface IProps {
onFinished(): void;
onFinished(this: void): void;
}
export const IntegrationsDisabledDialog: React.FC<IProps> = ({ onFinished }) => {

View File

@ -1124,7 +1124,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.setState({ currentTabId: tabId });
};
private async onLinkClick(e: React.MouseEvent<HTMLAnchorElement>): Promise<void> {
private async onLinkClick(this: void, e: React.MouseEvent<HTMLAnchorElement>): Promise<void> {
e.preventDefault();
selectText(e.currentTarget);
}

View File

@ -20,7 +20,7 @@ import { isOnlyAdmin } from "../../../utils/membership";
interface IProps {
space: Room;
onFinished(leave: boolean, rooms?: Room[]): void;
onFinished(this: void, leave: boolean, rooms?: Room[]): void;
}
const LeaveSpaceDialog: React.FC<IProps> = ({ space, onFinished }) => {

View File

@ -25,13 +25,13 @@ import { filterBoolean } from "../../../utils/arrays";
interface IProps {
room: Room;
selected?: string[];
onFinished(rooms?: string[]): void;
onFinished(this: void, rooms?: string[]): void;
}
const Entry: React.FC<{
room: Room;
checked: boolean;
onChange(value: boolean): void;
onChange(this: void, value: boolean): void;
}> = ({ room, checked, onChange }) => {
const localRoom = room instanceof Room;

View File

@ -17,7 +17,7 @@ type PollHistoryDialogProps = {
room: Room;
matrixClient: MatrixClient;
permalinkCreator: RoomPermalinkCreator;
onFinished(): void;
onFinished(this: void): void;
};
export const PollHistoryDialog: React.FC<PollHistoryDialogProps> = ({

View File

@ -15,8 +15,8 @@ import DialogButtons from "../elements/DialogButtons";
import EmailField from "../auth/EmailField";
interface IProps {
onFinished(continued: false, email?: undefined): void;
onFinished(continued: true, email: string): void;
onFinished(this: void, continued: false, email?: undefined): void;
onFinished(this: void, continued: true, email: string): void;
}
const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => {

View File

@ -25,7 +25,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
interface IProps {
roomId: string;
onFinished(leave: boolean): void;
onFinished(this: void, leave: boolean): void;
}
/*

View File

@ -56,7 +56,7 @@ interface BaseProps {
/**
* A function that is called when the dialog is dismissed
*/
onFinished(): void;
onFinished(this: void): void;
/**
* An optional string to use as the dialog title.
* If not provided, an appropriate title for the target type will be used.

View File

@ -20,7 +20,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
*/
interface IProps {
roomId: string;
onFinished(): void;
onFinished(this: void): void;
}
const SlashCommandHelpDialog: React.FC<IProps> = ({ roomId, onFinished }) => {

View File

@ -26,7 +26,7 @@ import { SettingsSubsection, SettingsSubsectionText } from "../settings/shared/S
interface IProps {
space: Room;
onFinished(): void;
onFinished(this: void): void;
}
const SpacePreferencesAppearanceTab: React.FC<Pick<IProps, "space">> = ({ space }) => {

View File

@ -39,7 +39,7 @@ export enum SpaceSettingsTab {
interface IProps {
matrixClient: MatrixClient;
space: Room;
onFinished(): void;
onFinished(this: void): void;
}
const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFinished }) => {

View File

@ -31,7 +31,7 @@ interface IProps {
* If mode is "sas", the user wants to verify the device with SAS. Otherwise, the dialog was dismissed normally.
* @param mode The mode of dismissal.
*/
onFinished(mode?: "sas"): void;
onFinished(this: void, mode?: "sas"): void;
}
const UntrustedDeviceDialog: React.FC<IProps> = ({ device, user, onFinished }) => {

View File

@ -58,7 +58,7 @@ interface IProps {
*/
initialEncryptionState?: State;
sdkContext: SdkContextClass;
onFinished(): void;
onFinished(this: void): void;
}
function titleForTabID(tabId: UserTab): React.ReactNode {

View File

@ -16,8 +16,8 @@ import { type XOR } from "../../../../@types/common";
import { type Tool } from "../DevtoolsDialog";
export interface IDevtoolsProps {
onBack(): void;
setTool(label: TranslationKey, tool: Tool): void;
onBack(this: void): void;
setTool(this: void, label: TranslationKey, tool: Tool): void;
}
interface IMinProps extends Pick<IDevtoolsProps, "onBack"> {
@ -28,7 +28,7 @@ interface IMinProps extends Pick<IDevtoolsProps, "onBack"> {
interface IProps extends IMinProps {
actionLabel: TranslationKey;
onAction(): Promise<string | void>;
onAction(this: void): Promise<string | void>;
}
const BaseTool: React.FC<XOR<IMinProps, IProps>> = ({

View File

@ -20,7 +20,7 @@ interface KeyBackupProps {
/**
* Callback to invoke when the back button is clicked.
*/
onBack(): void;
onBack(this: void): void;
}
/**

View File

@ -24,7 +24,7 @@ export const stringify = (object: object): string => {
interface IEventEditorProps extends Pick<IDevtoolsProps, "onBack"> {
fieldDefs: IFieldDef[]; // immutable
defaultContent?: string;
onSend(fields: string[], content: IContent): Promise<unknown>;
onSend(this: void, fields: string[], content: IContent): Promise<unknown>;
}
interface IFieldDef {

View File

@ -19,7 +19,7 @@ const LOAD_TILES_STEP_SIZE = 50;
interface IProps {
children: React.ReactElement[];
query: string;
onChange(value: string): void;
onChange(this: void, value: string): void;
}
const FilteredList: React.FC<IProps> = ({ children, query, onChange }) => {

View File

@ -40,12 +40,12 @@ export const StateEventEditor: React.FC<IEditorProps> = ({ mxEvent, onBack }) =>
interface StateEventButtonProps {
label: string;
onClick(): void;
onClick(this: void): void;
}
const RoomStateHistory: React.FC<{
mxEvent: MatrixEvent;
onBack(): void;
onBack(this: void): void;
}> = ({ mxEvent, onBack }) => {
const cli = useContext(MatrixClientContext);
const events = useAsyncMemo(

View File

@ -192,7 +192,7 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
interface IViewSettingProps extends Pick<IDevtoolsProps, "onBack"> {
setting: SettingKey;
onEdit(): Promise<void>;
onEdit(this: void): Promise<void>;
}
const ViewSetting: React.FC<IViewSettingProps> = ({ setting, onEdit, onBack }) => {
@ -249,8 +249,8 @@ function renderSettingValue(val: any): string {
}
interface ISettingsListProps extends Pick<IDevtoolsProps, "onBack"> {
onView(setting: string): void;
onEdit(setting: string): void;
onView(this: void, setting: string): void;
onEdit(this: void, setting: string): void;
}
const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit }) => {

View File

@ -78,7 +78,7 @@ export const UserList: React.FC<Pick<IDevtoolsProps, "onBack">> = ({ onBack }) =
interface UserButtonProps {
member: RoomMember;
onClick(): void;
onClick(this: void): void;
}
/**
@ -216,7 +216,7 @@ const UserView: React.FC<UserProps> = ({ member, onBack }) => {
interface DeviceButtonProps {
crypto: CryptoApi;
device: Device;
onClick(): void;
onClick(this: void): void;
}
/**

View File

@ -106,7 +106,7 @@ const AVATAR_SIZE = "24px";
interface IProps {
initialText?: string;
initialFilter?: Filter;
onFinished(): void;
onFinished(this: void): void;
}
function nodeIsForRecentlyViewed(node?: HTMLElement): boolean {
@ -191,7 +191,7 @@ interface IResult extends IBaseResult {
avatar: JSX.Element;
name: string;
description?: string;
onClick?(): void;
onClick?(this: void): void;
}
type Result = IRoomResult | IPublicRoomResult | IMemberResult | IResult;

View File

@ -306,7 +306,7 @@ export default class Dropdown extends React.Component<DropdownProps, IState> {
return keys[index <= 0 ? keys.length - 1 : (index - 1) % keys.length];
}
private scrollIntoView(node: Element | null): void {
private scrollIntoView(this: void, node: Element | null): void {
node?.scrollIntoView({
block: "nearest",
behavior: "auto",

View File

@ -31,7 +31,7 @@ interface IProps {
// An array of EventTiles to render when expanded
"children": ReactNode[] | null;
// Called when the event list expansion is toggled
onToggle?(): void;
onToggle?(this: void): void;
// The layout currently used
"layout"?: Layout;
"data-testid"?: string;

View File

@ -26,7 +26,7 @@ interface IProps {
labelKnock?: string;
labelPublic?: string;
labelRestricted?: string; // if omitted then this option will be hidden, e.g if unsupported
onChange(value: JoinRule): void;
onChange(this: void, value: JoinRule): void;
}
const JoinRuleDropdown: React.FC<IProps> = ({

View File

@ -21,7 +21,7 @@ interface IProps {
// Whether or not to disable the checkbox
disabled?: boolean;
// The function to call when the value changes
onChange(checked: boolean): void;
onChange(this: void, checked: boolean): void;
// Optional additional CSS class to apply to the label
className?: string;
// The id for the checkbox

View File

@ -24,9 +24,9 @@ interface IProps {
hasAvatar: boolean;
noAvatarLabel?: string;
hasAvatarLabel?: string;
setAvatarUrl(url: string): Promise<unknown>;
setAvatarUrl(this: void, url: string): Promise<unknown>;
isUserAvatar?: boolean;
onClick?(ev: MouseEvent<HTMLInputElement>): void;
onClick?(this: void, ev: MouseEvent<HTMLInputElement>): void;
children?: ReactNode;
}

View File

@ -13,7 +13,7 @@ import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
interface IProps {
room?: Room;
children?(name: string): JSX.Element;
children?(this: void, name: string): JSX.Element;
}
/**

View File

@ -23,7 +23,7 @@ interface IProps {
dialogTitle?: string;
serverConfig: ValidatedServerConfig;
disabled?: boolean;
onServerConfigChange?(config: ValidatedServerConfig): void;
onServerConfigChange?(this: void, config: ValidatedServerConfig): void;
}
const showPickerDialog = (

View File

@ -21,7 +21,7 @@ interface Props {
label?: string;
isExplicit?: boolean;
hideIfCannotSet?: boolean;
onChange?(option: string): void;
onChange?(this: void, option: string): void;
}
const SettingsDropdown = ({

View File

@ -19,7 +19,7 @@ interface Props {
roomId?: string; // for per-room settings
label?: string;
isExplicit?: boolean;
onChange?(value: string): void;
onChange?(this: void, value: string): void;
}
const SettingsField = ({ settingKey, level, roomId, isExplicit, label, onChange: _onSave }: Props): JSX.Element => {

View File

@ -81,9 +81,9 @@ export default class SpellCheckLanguagesDropdown extends React.Component<
}
}
private onSearchChange(searchQuery: string): void {
private onSearchChange = (searchQuery: string): void => {
this.setState({ searchQuery });
}
};
public render(): React.ReactNode {
if (!this.state.languages) {

View File

@ -27,7 +27,7 @@ interface IProps<T extends string> {
value?: T; // if not provided no options will be selected
outlined?: boolean;
disabled?: boolean;
onChange(newValue: T): void;
onChange(this: void, newValue: T): void;
}
function StyledRadioGroup<T extends string>({

View File

@ -25,7 +25,7 @@ interface IProps {
tooltip?: string;
// Called when the checked state changes. First argument will be the new state.
onChange(checked: boolean): void;
onChange(this: void, checked: boolean): void;
// id to bind with other elements
id?: string;

View File

@ -33,7 +33,7 @@ interface IArgs<T, D = void> {
rules: IRule<T, D>[];
description?(this: T, derivedData: D, results: IResult[]): ReactNode;
hideDescriptionIfValid?: boolean;
deriveData?(data: Data): Promise<D>;
deriveData?(this: T, data: Data): Promise<D>;
memoize?: boolean;
}
@ -78,8 +78,10 @@ export interface IValidationResult {
* the overall validity and a feedback UI that can be rendered for more detail.
*/
export default function withValidation<T = void, D = void>({
// eslint-disable-next-line @typescript-eslint/unbound-method
description,
hideDescriptionIfValid,
// eslint-disable-next-line @typescript-eslint/unbound-method
deriveData,
rules,
memoize,

View File

@ -32,7 +32,7 @@ const useMapWithStyle = ({
}: {
id: string;
centerGeoUri?: string;
onError?(error: Error): void;
onError?(this: void, error: Error): void;
interactive?: boolean;
bounds?: Bounds;
allowGeolocate?: boolean;

View File

@ -23,7 +23,7 @@ interface Props {
const ExpandCollapseButton: React.FC<{
expanded: boolean;
onClick(): void;
onClick(this: void): void;
}> = ({ expanded, onClick }) => {
return (
<span className="mx_EventTile_button" onClick={onClick}>

View File

@ -16,7 +16,7 @@ import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile";
interface IProps {
mxEvent: MatrixEvent;
onClick?(): void;
onClick?(this: void): void;
withTooltip?: boolean;
}

View File

@ -23,7 +23,7 @@ type PollHistoryProps = {
room: Room;
matrixClient: MatrixClient;
permalinkCreator: RoomPermalinkCreator;
onFinished(): void;
onFinished(this: void): void;
};
const sortEventsByLatest = (left: MatrixEvent, right: MatrixEvent): number => right.getTs() - left.getTs();

View File

@ -28,9 +28,9 @@ interface IProps {
ariaLabelledBy?: string;
withoutScrollContainer?: boolean;
closeLabel?: string;
onClose?(ev: MouseEvent<HTMLButtonElement>): void;
onBack?(ev: MouseEvent<HTMLButtonElement>): void;
onKeyDown?(ev: KeyboardEvent): void;
onClose?(this: void, ev: MouseEvent<HTMLButtonElement>): void;
onBack?(this: void, ev: MouseEvent<HTMLButtonElement>): void;
onKeyDown?(this: void, ev: KeyboardEvent): void;
cardState?: any;
ref?: Ref<HTMLDivElement>;
// Ref for the 'close' button the card

View File

@ -35,7 +35,7 @@ import { WidgetContextMenu } from "../../../viewmodels/right-panel/WidgetContext
interface Props {
room: Room;
onClose(): void;
onClose(this: void): void;
}
interface IAppRowProps {

View File

@ -44,7 +44,7 @@ interface PinnedMessagesCardProps {
/**
* Callback for when the card is closed.
*/
onClose(): void;
onClose(this: void): void;
}
export function PinnedMessagesCard({ room, onClose, permalinkCreator }: PinnedMessagesCardProps): JSX.Element {

View File

@ -192,7 +192,7 @@ interface IProps {
user: Member;
room?: Room;
phase: RightPanelPhases.MemberInfo | RightPanelPhases.EncryptionPanel;
onClose(): void;
onClose(this: void): void;
verificationRequest?: VerificationRequest;
verificationRequestPromise?: Promise<VerificationRequest>;
}

View File

@ -23,7 +23,7 @@ import { WidgetContextMenu } from "../../../viewmodels/right-panel/WidgetContext
interface IProps {
room: Room;
widgetId: string;
onClose(): void;
onClose(this: void): void;
}
const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {

View File

@ -33,8 +33,8 @@ const Container: React.FC<{
interface IBaseProps {
member: RoomMember;
isUpdating: boolean;
startUpdating(): void;
stopUpdating(): void;
startUpdating(this: void): void;
stopUpdating(this: void): void;
}
export const RoomKickButton = ({

Some files were not shown because too many files have changed in this diff Show More