diff --git a/app/javascript/mastodon/components/column.jsx b/app/javascript/mastodon/components/column.jsx deleted file mode 100644 index abc87a57e5..0000000000 --- a/app/javascript/mastodon/components/column.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { supportsPassiveEvents } from 'detect-passive-events'; - -import { scrollTop } from '../scroll'; - -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; - -export default class Column extends PureComponent { - - static propTypes = { - children: PropTypes.node, - label: PropTypes.string, - bindToDocument: PropTypes.bool, - }; - - scrollTop () { - let scrollable = null; - - if (this.props.bindToDocument) { - scrollable = document.scrollingElement; - } else { - scrollable = this.node.querySelector('.scrollable'); - } - - if (!scrollable) { - return; - } - - this._interruptScrollAnimation = scrollTop(scrollable); - } - - handleWheel = () => { - if (typeof this._interruptScrollAnimation !== 'function') { - return; - } - - this._interruptScrollAnimation(); - }; - - setRef = c => { - this.node = c; - }; - - componentDidMount () { - if (this.props.bindToDocument) { - document.addEventListener('wheel', this.handleWheel, listenerOptions); - } else { - this.node.addEventListener('wheel', this.handleWheel, listenerOptions); - } - } - - componentWillUnmount () { - if (this.props.bindToDocument) { - document.removeEventListener('wheel', this.handleWheel, listenerOptions); - } else { - this.node.removeEventListener('wheel', this.handleWheel, listenerOptions); - } - } - - render () { - const { label, children } = this.props; - - return ( -
- {children} -
- ); - } - -} diff --git a/app/javascript/mastodon/components/column.tsx b/app/javascript/mastodon/components/column.tsx new file mode 100644 index 0000000000..01c75d85c0 --- /dev/null +++ b/app/javascript/mastodon/components/column.tsx @@ -0,0 +1,52 @@ +import { forwardRef, useRef, useImperativeHandle } from 'react'; +import type { Ref } from 'react'; + +import { scrollTop } from 'mastodon/scroll'; + +export interface ColumnRef { + scrollTop: () => void; + node: HTMLDivElement | null; +} + +interface ColumnProps { + children?: React.ReactNode; + label?: string; + bindToDocument?: boolean; +} + +export const Column = forwardRef( + ({ children, label, bindToDocument }, ref: Ref) => { + const nodeRef = useRef(null); + + useImperativeHandle(ref, () => ({ + node: nodeRef.current, + + scrollTop() { + let scrollable = null; + + if (bindToDocument) { + scrollable = document.scrollingElement; + } else { + scrollable = nodeRef.current?.querySelector('.scrollable'); + } + + if (!scrollable) { + return; + } + + scrollTop(scrollable); + }, + })); + + return ( +
+ {children} +
+ ); + }, +); + +Column.displayName = 'Column'; + +// eslint-disable-next-line import/no-default-export +export default Column; diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx index d0e57600bb..ef2649a27f 100644 --- a/app/javascript/mastodon/features/directory/index.tsx +++ b/app/javascript/mastodon/features/directory/index.tsx @@ -15,7 +15,8 @@ import { changeColumnParams, } from 'mastodon/actions/columns'; import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { LoadMore } from 'mastodon/components/load_more'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; @@ -49,7 +50,7 @@ export const Directory: React.FC<{ const intl = useIntl(); const dispatch = useAppDispatch(); - const column = useRef(null); + const column = useRef(null); const [orderParam, setOrderParam] = useSearchParam('order'); const [localParam, setLocalParam] = useSearchParam('local'); diff --git a/app/javascript/mastodon/features/link_timeline/index.tsx b/app/javascript/mastodon/features/link_timeline/index.tsx index 262a21afcc..1b3f287177 100644 --- a/app/javascript/mastodon/features/link_timeline/index.tsx +++ b/app/javascript/mastodon/features/link_timeline/index.tsx @@ -5,7 +5,8 @@ import { useParams } from 'react-router-dom'; import ExploreIcon from '@/material-icons/400-24px/explore.svg?react'; import { expandLinkTimeline } from 'mastodon/actions/timelines'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import StatusListContainer from 'mastodon/features/ui/containers/status_list_container'; import type { Card } from 'mastodon/models/status'; @@ -17,7 +18,7 @@ export const LinkTimeline: React.FC<{ const { url } = useParams<{ url: string }>(); const decodedUrl = url ? decodeURIComponent(url) : undefined; const dispatch = useAppDispatch(); - const columnRef = useRef(null); + const columnRef = useRef(null); const firstStatusId = useAppSelector((state) => decodedUrl ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access diff --git a/app/javascript/mastodon/features/lists/index.tsx b/app/javascript/mastodon/features/lists/index.tsx index cf413a1fe0..25a537336e 100644 --- a/app/javascript/mastodon/features/lists/index.tsx +++ b/app/javascript/mastodon/features/lists/index.tsx @@ -11,7 +11,7 @@ import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react'; import { fetchLists } from 'mastodon/actions/lists'; import { openModal } from 'mastodon/actions/modal'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; diff --git a/app/javascript/mastodon/features/lists/members.tsx b/app/javascript/mastodon/features/lists/members.tsx index 184b54b92d..97b730f436 100644 --- a/app/javascript/mastodon/features/lists/members.tsx +++ b/app/javascript/mastodon/features/lists/members.tsx @@ -20,7 +20,7 @@ import { import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; import { Avatar } from 'mastodon/components/avatar'; import { Button } from 'mastodon/components/button'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { ColumnSearchHeader } from 'mastodon/components/column_search_header'; import { FollowersCounter } from 'mastodon/components/counters'; diff --git a/app/javascript/mastodon/features/lists/new.tsx b/app/javascript/mastodon/features/lists/new.tsx index cf39331d7c..100f126c37 100644 --- a/app/javascript/mastodon/features/lists/new.tsx +++ b/app/javascript/mastodon/features/lists/new.tsx @@ -14,7 +14,7 @@ import { fetchList } from 'mastodon/actions/lists'; import { createList, updateList } from 'mastodon/actions/lists_typed'; import { apiGetAccounts } from 'mastodon/api/lists'; import type { RepliesPolicyType } from 'mastodon/api_types/lists'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; diff --git a/app/javascript/mastodon/features/notifications_v2/index.tsx b/app/javascript/mastodon/features/notifications_v2/index.tsx index 730d95bcd5..bb476fe51f 100644 --- a/app/javascript/mastodon/features/notifications_v2/index.tsx +++ b/app/javascript/mastodon/features/notifications_v2/index.tsx @@ -36,7 +36,8 @@ import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { submitMarkers } from '../../actions/markers'; -import Column from '../../components/column'; +import { Column } from '../../components/column'; +import type { ColumnRef } from '../../components/column'; import { ColumnHeader } from '../../components/column_header'; import { LoadGap } from '../../components/load_gap'; import ScrollableList from '../../components/scrollable_list'; @@ -96,7 +97,7 @@ export const Notifications: React.FC<{ selectNeedsNotificationPermission, ); - const columnRef = useRef(null); + const columnRef = useRef(null); const selectChild = useCallback((index: number, alignTop: boolean) => { const container = columnRef.current?.node as HTMLElement | undefined; diff --git a/app/javascript/mastodon/features/onboarding/follows.tsx b/app/javascript/mastodon/features/onboarding/follows.tsx index 25ee48c8ac..a783bf774c 100644 --- a/app/javascript/mastodon/features/onboarding/follows.tsx +++ b/app/javascript/mastodon/features/onboarding/follows.tsx @@ -14,7 +14,7 @@ import { fetchSuggestions } from 'mastodon/actions/suggestions'; import { markAsPartial } from 'mastodon/actions/timelines'; import { apiRequest } from 'mastodon/api'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { ColumnSearchHeader } from 'mastodon/components/column_search_header'; import ScrollableList from 'mastodon/components/scrollable_list'; diff --git a/app/javascript/mastodon/features/onboarding/profile.tsx b/app/javascript/mastodon/features/onboarding/profile.tsx index e4d9137e9e..1e5e868f18 100644 --- a/app/javascript/mastodon/features/onboarding/profile.tsx +++ b/app/javascript/mastodon/features/onboarding/profile.tsx @@ -13,7 +13,7 @@ import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import PersonIcon from '@/material-icons/400-24px/person.svg?react'; import { updateAccount } from 'mastodon/actions/accounts'; import { Button } from 'mastodon/components/button'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; diff --git a/app/javascript/mastodon/features/ui/components/column_loading.tsx b/app/javascript/mastodon/features/ui/components/column_loading.tsx index d9563dda7a..8b20e76ffb 100644 --- a/app/javascript/mastodon/features/ui/components/column_loading.tsx +++ b/app/javascript/mastodon/features/ui/components/column_loading.tsx @@ -1,4 +1,4 @@ -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import type { Props as ColumnHeaderProps } from 'mastodon/components/column_header';