Add moduleSuffixes to tsconfig (#11146)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-07-16 22:47:53 +03:00
committed by GitHub
parent a8b963b357
commit 007c21fa8e
82 changed files with 628 additions and 201 deletions
@@ -0,0 +1,10 @@
import {type ComposerImage} from '#/state/gallery'
import type * as Dialog from '#/components/Dialog'
export type EditImageDialogProps = {
control: Dialog.DialogOuterProps['control']
image?: ComposerImage
onChange: (next: ComposerImage) => void
aspectRatio?: number
circularCrop?: boolean
}
@@ -1,13 +1,6 @@
import {type ComposerImage} from '#/state/gallery'
import type * as Dialog from '#/components/Dialog'
import {type EditImageDialogProps} from './EditImageDialog.shared'
export type EditImageDialogProps = {
control: Dialog.DialogOuterProps['control']
image?: ComposerImage
onChange: (next: ComposerImage) => void
aspectRatio?: number
circularCrop?: boolean
}
export type {EditImageDialogProps} from './EditImageDialog.shared'
export const EditImageDialog = ({}: EditImageDialogProps): React.ReactNode => {
return null
@@ -16,7 +16,7 @@ import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {Loader} from '#/components/Loader'
import {type EditImageDialogProps} from './EditImageDialog'
import {type EditImageDialogProps} from './EditImageDialog.shared'
export function EditImageDialog(props: EditImageDialogProps) {
return (
@@ -0,0 +1,6 @@
import {type ComposerImage} from '#/state/gallery'
export type OpenCameraBtnProps = {
disabled?: boolean
onAdd: (next: ComposerImage[]) => void
}
@@ -6,18 +6,14 @@ import {useLingui} from '@lingui/react'
import {useCameraPermission} from '#/lib/hooks/usePermissions'
import {openCamera} from '#/lib/media/picker'
import {logger} from '#/logger'
import {type ComposerImage, createComposerImage} from '#/state/gallery'
import {createComposerImage} from '#/state/gallery'
import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera'
import {IS_NATIVE, IS_WEB_MOBILE} from '#/env'
import {type OpenCameraBtnProps} from './OpenCameraBtn.shared'
type Props = {
disabled?: boolean
onAdd: (next: ComposerImage[]) => void
}
export function OpenCameraBtn({disabled, onAdd}: Props) {
export function OpenCameraBtn({disabled, onAdd}: OpenCameraBtnProps) {
const {_} = useLingui()
const {requestCameraAccessIfNeeded} = useCameraPermission()
const [mediaPermissionRes, requestMediaPermission] =
@@ -1,3 +1,5 @@
export function OpenCameraBtn() {
import {type OpenCameraBtnProps} from './OpenCameraBtn.shared'
export function OpenCameraBtn(_props: OpenCameraBtnProps) {
return null
}
@@ -1,3 +1,5 @@
export function SubtitleFilePicker() {
import {type SubtitleFilePickerProps} from './SubtitleFilePicker.shared'
export function SubtitleFilePicker(_props: SubtitleFilePickerProps): never {
throw new Error('SubtitleFilePicker is a web-only component')
}
@@ -0,0 +1,4 @@
export type SubtitleFilePickerProps = {
onSelectFile: (file: File) => void
disabled?: boolean
}
@@ -9,14 +9,12 @@ import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {CC_Stroke2_Corner0_Rounded as CCIcon} from '#/components/icons/CC'
import * as Toast from '#/components/Toast'
import {type SubtitleFilePickerProps} from './SubtitleFilePicker.shared'
export function SubtitleFilePicker({
onSelectFile,
disabled,
}: {
onSelectFile: (file: File) => void
disabled?: boolean
}) {
}: SubtitleFilePickerProps) {
const {_} = useLingui()
const ref = useRef<HTMLInputElement>(null)
@@ -1,6 +1,8 @@
import {type QueryClient} from '@tanstack/react-query'
import {atoms as a, flatten} from '#/alf'
export function clearThumbnailCache() {
export function clearThumbnailCache(_queryClient: QueryClient) {
// no-op on web
}
+2 -2
View File
@@ -6,7 +6,6 @@ import {
useState,
} from 'react'
import {
findNodeHandle,
type ListRenderItemInfo,
type StyleProp,
useWindowDimensions,
@@ -26,6 +25,7 @@ import {useSession} from '#/state/session'
import {EmptyState} from '#/view/com/util/EmptyState'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import {List, type ListRef} from '#/view/com/util/List'
import {findListNativeTag} from '#/view/com/util/listNativeTag'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
import {atoms as a, ios, useTheme} from '#/alf'
@@ -219,7 +219,7 @@ export function ProfileFeedgens({
useEffect(() => {
if (IS_IOS && enabled && scrollElRef.current) {
const nativeTag = findNodeHandle(scrollElRef.current)
const nativeTag = findListNativeTag(scrollElRef.current)
setScrollViewTag(nativeTag)
}
}, [enabled, scrollElRef, setScrollViewTag])
+2 -2
View File
@@ -6,7 +6,6 @@ import {
useState,
} from 'react'
import {
findNodeHandle,
type ListRenderItemInfo,
type StyleProp,
useWindowDimensions,
@@ -26,6 +25,7 @@ import {useSession} from '#/state/session'
import {EmptyState} from '#/view/com/util/EmptyState'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import {List, type ListRef} from '#/view/com/util/List'
import {findListNativeTag} from '#/view/com/util/listNativeTag'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
import {atoms as a, ios, useTheme} from '#/alf'
@@ -218,7 +218,7 @@ export function ProfileLists({
useEffect(() => {
if (IS_IOS && enabled && scrollElRef.current) {
const nativeTag = findNodeHandle(scrollElRef.current)
const nativeTag = findListNativeTag(scrollElRef.current)
setScrollViewTag(nativeTag)
}
}, [enabled, scrollElRef, setScrollViewTag])
+29 -3
View File
@@ -7,6 +7,7 @@ import {
useState,
} from 'react'
import {View} from 'react-native'
import {type SharedValue, useSharedValue} from 'react-native-reanimated'
import {flushSync} from 'react-dom'
import {s} from '#/lib/styles'
@@ -19,7 +20,9 @@ export interface PagerRef {
export interface RenderTabBarFnProps {
selectedPage: number
onSelect?: (index: number) => void
tabBarAnchor?: JSX.Element
tabBarAnchor?: JSX.Element | null | undefined // Ignored on native.
dragProgress: SharedValue<number> // Ignored on web.
dragState: SharedValue<'idle' | 'dragging' | 'settling'> // Ignored on web.
}
export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
@@ -27,7 +30,17 @@ interface Props {
ref?: React.Ref<PagerRef>
initialPage?: number
renderTabBar: RenderTabBarFn
// tab pressed, yet to scroll to page
onTabPressed?: (index: number) => void
// scroll settled
onPageSelected?: (index: number) => void
/**
* Never fires on web - pages switch instantly, there is no drag gesture.
*/
onPageScrollStateChanged?: (
scrollState: 'idle' | 'dragging' | 'settling',
) => void
testID?: string
}
export function Pager({
@@ -35,12 +48,22 @@ export function Pager({
children,
initialPage = 0,
renderTabBar,
onTabPressed,
onPageSelected,
onPageScrollStateChanged: _onPageScrollStateChanged,
testID,
}: React.PropsWithChildren<Props>) {
const [selectedPage, setSelectedPage] = useState(initialPage)
const scrollYs = useRef<Array<number | null>>([])
const anchorRef = useRef(null)
/*
* There is no drag gesture on web; these exist to satisfy the shared
* RenderTabBarFnProps contract and never change.
*/
const dragProgress = useSharedValue(selectedPage)
const dragState = useSharedValue<'idle' | 'dragging' | 'settling'>('idle')
useImperativeHandle(ref, () => ({
setPage: (index: number) => {
onTabBarSelect(index)
@@ -59,6 +82,7 @@ export function Pager({
: -scrollY // If there's no anchor, treat the top of the page as one.
const isSticking = anchorTop <= 5 // This would be 0 if browser scrollTo() was reliable.
onTabPressed?.(index)
if (isSticking) {
scrollYs.current[selectedPage] = window.scrollY
} else {
@@ -77,15 +101,17 @@ export function Pager({
}
}
},
[selectedPage, setSelectedPage, onPageSelected],
[selectedPage, setSelectedPage, onPageSelected, onTabPressed],
)
return (
<View style={s.hContentRegion}>
<View testID={testID} style={s.hContentRegion}>
{renderTabBar({
selectedPage,
tabBarAnchor: <View ref={anchorRef} />,
onSelect: e => onTabBarSelect(e),
dragProgress,
dragState,
})}
{Children.map(children, (child, i) => (
<View
+3 -2
View File
@@ -29,11 +29,12 @@ export interface PagerWithHeaderProps {
renderHeader?: ({
setMinimumHeight,
}: {
setMinimumHeight: () => void
setMinimumHeight: (height: number) => void
}) => JSX.Element
initialPage?: number
onPageSelected?: (index: number) => void
onCurrentPageSelected?: (index: number) => void
allowHeaderOverScroll?: boolean // Ignored on web.
}
export const PagerWithHeader = forwardRef<PagerRef, PagerWithHeaderProps>(
function PageWithHeaderImpl(
@@ -127,7 +128,7 @@ let PagerTabBar = ({
renderHeader?: ({
setMinimumHeight,
}: {
setMinimumHeight: () => void
setMinimumHeight: (height: number) => void
}) => JSX.Element
isHeaderReady: boolean
onCurrentPageSelected?: (index: number) => void
+9
View File
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useRef} from 'react'
import {type ScrollView, StyleSheet, View} from 'react-native'
import {type SharedValue} from 'react-native-reanimated'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {Text} from '#/components/Typography'
@@ -15,6 +16,14 @@ export interface TabBarProps {
onSelect?: (index: number) => void
onPressSelected?: (index: number) => void
/**
* The drag-following indicator and transparent background only exist in
* the native tab bar - accepted here so shared callers typecheck.
*/
dragProgress?: SharedValue<number> // Ignored on web.
dragState?: SharedValue<'idle' | 'dragging' | 'settling'> // Ignored on web.
transparent?: boolean // Ignored on web.
}
// How much of the previous/next item we're showing
+8 -1
View File
@@ -17,6 +17,7 @@ import {usePostQuotesQuery} from '#/state/queries/post-quotes'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {Post} from '#/view/com/post/Post'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import * as bsky from '#/types/bsky'
import {List} from '../util/List'
function renderItem({
@@ -70,7 +71,13 @@ export function PostQuotes({uri}: {uri: string}) {
data?.pages
.flatMap(page =>
page.posts.map(post => {
if (!AppBskyFeedPost.isRecord(post.record) || !moderationOpts) {
if (
!bsky.dangerousIsType<AppBskyFeedPost.Record>(
post.record,
AppBskyFeedPost.isRecord,
) ||
!moderationOpts
) {
return null
}
const moderation = moderatePost(post, moderationOpts)
+1 -1
View File
@@ -28,7 +28,7 @@ function renderItem({
)
}
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
function keyExtractor(item: ActorDefs.ProfileView) {
return item.did
}
+1 -1
View File
@@ -43,7 +43,7 @@ function renderItem({
)
}
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
function keyExtractor(item: ActorDefs.ProfileView) {
return item.did
}
+1 -1
View File
@@ -39,7 +39,7 @@ function renderItem({
)
}
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
function keyExtractor(item: ActorDefs.ProfileView) {
return item.did
}
+14 -5
View File
@@ -28,8 +28,10 @@ import * as Layout from '#/components/Layout'
export type ListMethods = {
scrollToTop: () => void
scrollToOffset: (options: {animated: boolean; offset: number}) => void
scrollToEnd: (options?: {animated?: boolean}) => void
// Signature kept compatible with FlatList's scrollToOffset (the native
// ListMethods type) so callers stay platform-agnostic.
scrollToOffset: (options: {animated?: boolean | null; offset: number}) => void
scrollToEnd: (options?: {animated?: boolean | null}) => void
// Signature kept compatible with FlatList's scrollToIndex (the native
// ListMethods type) so callers stay platform-agnostic. viewOffset is
// accepted for parity but not currently used by the web implementation.
@@ -40,7 +42,8 @@ export type ListMethods = {
viewPosition?: number
}) => void
}
export type ListProps<ItemT> = Omit<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ListProps<ItemT = any> = Omit<
FlatListProps<ItemT>,
| 'onScroll' // Use ScrollContext instead.
| 'refreshControl' // Pass refreshing and/or onRefresh instead.
@@ -59,7 +62,7 @@ export type ListProps<ItemT> = Omit<
*/
sideBorders?: boolean
}
export type ListRef = React.RefObject<View>
export type ListRef = React.RefObject<ListMethods | null>
const ON_ITEM_SEEN_WAIT_DURATION = 0.5e3 // when we consider post to be "seen"
const ON_ITEM_SEEN_INTERSECTION_OPTS = {
@@ -248,7 +251,13 @@ function ListImpl<ItemT>(
getScrollableNode()?.scrollTo({top: 0})
},
scrollToOffset({animated, offset}: {animated: boolean; offset: number}) {
scrollToOffset({
animated,
offset,
}: {
animated?: boolean | null
offset: number
}) {
getScrollableNode()?.scrollTo({
left: 0,
top: offset,
+12
View File
@@ -0,0 +1,12 @@
import {findNodeHandle} from 'react-native'
import {type ListMethods} from './List'
/**
* Returns the native view tag backing a List, for handing to native code
* (e.g. the pager's scrollViewTag). Always null on web.
*/
export function findListNativeTag(list: ListMethods | null): number | null {
if (!list) return null
return findNodeHandle(list)
}
+9
View File
@@ -0,0 +1,9 @@
import {type ListMethods} from './List'
/**
* Returns the native view tag backing a List, for handing to native code
* (e.g. the pager's scrollViewTag). Always null on web.
*/
export function findListNativeTag(_list: ListMethods | null): number | null {
return null
}