Commit 5f82322e authored by Robert Knight's avatar Robert Knight

Add missing types to various callbacks / props in components

parent 6986607d
......@@ -116,6 +116,7 @@ function AnnotationEditor({
};
// Allow saving of annotation by pressing CMD/CTRL-Enter
/** @param {KeyboardEvent} event */
const onKeyDown = event => {
const key = normalizeKeyName(event.key);
if (isEmpty) {
......
......@@ -10,7 +10,7 @@ import { applyTheme } from '../helpers/theme';
* @typedef InlineControlsProps
* @prop {boolean} isCollapsed
* @prop {(collapsed: boolean) => any} setCollapsed
* @prop {object} [linkStyle]
* @prop {Record<string, string>} [linkStyle]
*/
/**
......@@ -120,12 +120,13 @@ function Excerpt({
const isCollapsed = inlineControls ? collapsedByInlineControls : collapse;
const isExpandable = isOverflowing && isCollapsed;
/** @type {object} */
/** @type {Record<string, number>} */
const contentStyle = {};
if (contentHeight !== 0) {
contentStyle['max-height'] = isExpandable ? collapsedHeight : contentHeight;
}
/** @param {boolean} collapsed */
const setCollapsed = collapsed =>
inlineControls
? setCollapsedByInlineControls(collapsed)
......
......@@ -74,8 +74,15 @@ function HelpPanel({ auth, session }) {
const hasAutoDisplayPreference =
!!store.profile().preferences.show_sidebar_tutorial;
const subPanelTitles = {
tutorial: 'Getting started',
versionInfo: 'About this version',
};
// The "Tutorial" (getting started) subpanel is the default panel shown
const [activeSubPanel, setActiveSubPanel] = useState('tutorial');
const [activeSubPanel, setActiveSubPanel] = useState(
/** @type {keyof subPanelTitles} */ ('tutorial')
);
// Build version details about this session/app
const versionData = useMemo(() => {
......@@ -100,17 +107,17 @@ function HelpPanel({ auth, session }) {
// create-new-ticket form
const supportTicketURL = `https://web.hypothes.is/get-help/?sys_info=${versionData.asEncodedURLString()}`;
const subPanelTitles = {
tutorial: 'Getting started',
versionInfo: 'About this version',
};
/**
* @param {Event} e
* @param {keyof subPanelTitles} panelName
*/
const openSubPanel = (e, panelName) => {
e.preventDefault();
setActiveSubPanel(panelName);
};
const onActiveChanged = useCallback(
/** @param {boolean} active */
active => {
if (!active && hasAutoDisplayPreference) {
// If the tutorial is currently being auto-displayed, update the user
......
......@@ -213,6 +213,8 @@ function Toolbar({ isPreviewing, onCommand, onTogglePreview }) {
/**
* Handles left and right arrow navigation as well as home and end
* keys so the user may navigate the toolbar without multiple tab stops.
*
* @param {KeyboardEvent} e
*/
const handleKeyDown = e => {
let lowerLimit = 0;
......
......@@ -116,6 +116,7 @@ export default function Menu({
// Toggle menu when user presses toggle button. The menu is shown on mouse
// press for a more responsive/native feel but also handles a click event for
// activation via other input methods.
/** @param {Event} event */
const toggleMenu = event => {
// If the menu was opened on press, don't close it again on the subsequent
// mouse up ("click") event.
......@@ -144,9 +145,11 @@ export default function Menu({
// are user interactions outside of it (e.g. clicks) in the document
useElementShouldClose(menuRef, isOpen, closeMenu);
/** @param {Event} e */
const stopPropagation = e => e.stopPropagation();
// It should also close if the user presses a key which activates menu items.
/** @param {KeyboardEvent} event */
const handleMenuKeyDown = event => {
const key = normalizeKeyName(event.key);
if (key === 'Enter' || key === ' ') {
......
......@@ -110,6 +110,7 @@ export default function MenuItem({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
/** @param {Event} event */
const onCloseSubmenu = event => {
if (onToggleSubmenu) {
onToggleSubmenu(event);
......@@ -120,6 +121,7 @@ export default function MenuItem({
});
};
/** @param {KeyboardEvent} event */
const onKeyDown = event => {
switch (normalizeKeyName(event.key)) {
case 'ArrowRight':
......
import { normalizeKeyName } from '@hypothesis/frontend-shared';
import { useEffect, useRef } from 'preact/hooks';
/** @param {HTMLElement} element */
function isElementVisible(element) {
return element.offsetParent !== null;
}
......@@ -10,7 +11,7 @@ function isElementVisible(element) {
* @prop {string} [className]
* @prop {(e: KeyboardEvent) => any} [closeMenu] - Callback when the menu is closed via keyboard input
* @prop {boolean} [visible] - When true`, sets focus on the first item in the list
* @prop {object} children - Array of nodes which may contain <MenuItems> or any nodes
* @prop {import('preact').ComponentChildren} children - Array of nodes which may contain <MenuItems> or any nodes
*/
/**
......@@ -47,6 +48,7 @@ export default function MenuKeyboardNavigation({
};
}, [visible]);
/** @param {KeyboardEvent} event */
const onKeyDown = event => {
const menuItems = Array.from(
/** @type {NodeListOf<HTMLElement>} */
......
......@@ -56,6 +56,7 @@ function NotebookView({ loadAnnotationsService, streamer }) {
// of them: this is a performance safety valve.
const maxResults = 5000;
/** @param {Error} error */
const onLoadError = error => {
if (error instanceof ResultSizeError) {
setHasTooManyAnnotationsError(true);
......@@ -101,7 +102,7 @@ function NotebookView({ loadAnnotationsService, streamer }) {
}
}, [loadAnnotationsService, groupId, store]);
// Pagination-page-changing callback
/** @param {number} newPage */
const onChangePage = newPage => {
setPaginationPage(newPage);
};
......
......@@ -22,13 +22,17 @@ function PaginationNavigation({ currentPage, onChangePage, totalPages }) {
const hasPreviousPage = currentPage > 1;
const pageNumbers = pageNumberOptions(currentPage, totalPages);
const changePageTo = (pageNumber, eventTarget) => {
/**
* @param {number} pageNumber
* @param {HTMLElement} element
*/
const changePageTo = (pageNumber, element) => {
onChangePage(pageNumber);
// Because changing pagination page doesn't reload the page (as it would
// in a "traditional" HTML context), the clicked-upon navigation button
// will awkwardly retain focus unless it is actively removed.
// TODO: Evaluate this for a11y issues
/** @type HTMLElement */ (eventTarget)?.blur();
element.blur();
};
return (
......@@ -39,7 +43,12 @@ function PaginationNavigation({ currentPage, onChangePage, totalPages }) {
classes="PaginationPageButton"
icon="arrow-left"
title="Go to previous page"
onClick={e => changePageTo(currentPage - 1, e.target)}
onClick={e =>
changePageTo(
currentPage - 1,
/** @type {HTMLElement} */ (e.target)
)
}
variant="dark"
>
prev
......@@ -57,7 +66,9 @@ function PaginationNavigation({ currentPage, onChangePage, totalPages }) {
key={`page-${idx}`}
title={`Go to page ${page}`}
pressed={page === currentPage}
onClick={e => changePageTo(page, e.target)}
onClick={e =>
changePageTo(page, /** @type {HTMLElement} */ (e.target))
}
variant="dark"
>
{page.toString()}
......@@ -73,7 +84,12 @@ function PaginationNavigation({ currentPage, onChangePage, totalPages }) {
icon="arrow-right"
iconPosition="right"
title="Go to next page"
onClick={e => changePageTo(currentPage + 1, e.target)}
onClick={e =>
changePageTo(
currentPage + 1,
/** @type {HTMLElement} */ (e.target)
)
}
variant="dark"
>
next
......
......@@ -36,6 +36,7 @@ export default function SearchInput({ alwaysExpanded, query, onSearch }) {
// The query that the user is currently typing, but may not yet have applied.
const [pendingQuery, setPendingQuery] = useState(query);
/** @param {Event} e */
const onSubmit = e => {
e.preventDefault();
if (input.current.value || prevQuery) {
......
......@@ -35,8 +35,10 @@ function ShareLink({ label, iconName, uri }) {
/**
* A list of share links to social-media platforms.
*
* @param {ShareLinksProps} props
*/
function ShareLinks({ shareURI }) {
export default function ShareLinks({ shareURI }) {
// This is the double-encoded format needed for other services (the entire
// URI needs to be encoded because it's used as the value of querystring params)
const encodedURI = encodeURIComponent(shareURI);
......@@ -65,5 +67,3 @@ function ShareLinks({ shareURI }) {
</ul>
);
}
export default ShareLinks;
......@@ -18,6 +18,7 @@ import SearchInput from './SearchInput';
function StreamSearchInput({ router }) {
const store = useStoreProxy();
const query = store.routeParams().q;
/** @param {string} query */
const setQuery = query => {
// Re-route the user to `/stream` if they are on `/a/:id` and then set
// the search query.
......
......@@ -24,10 +24,9 @@ function StreamView({ api, toastMessenger }) {
/**
* Fetch annotations from the API and display them in the stream.
*
* @param {string} query - The user-supplied search query
*/
const loadAnnotations = useCallback(
/** @param {string} query */
async query => {
const queryParams = {
_separate_replies: true,
......
......@@ -76,7 +76,7 @@ function ToastMessage({ message, onDismiss }) {
/**
* @typedef ToastMessagesProps
* @prop {object} toastMessenger - Injected service
* @prop {import('../services/toast-messenger').ToastMessengerService} toastMessenger
*/
/**
......
......@@ -30,6 +30,9 @@ function TutorialInstruction({ commandName, iconName }) {
/**
* Tutorial for using the sidebar app
*
* @param {object} props
* @param {import('../../types/config').SidebarSettings} props.settings
*/
function Tutorial({ settings }) {
const canCreatePrivateGroups = !isThirdPartyService(settings);
......
......@@ -48,6 +48,7 @@ function UserMenu({ auth, frameSync, onLogout, settings }) {
const isNotebookEnabled = store.isFeatureEnabled('notebook_launch');
const [isOpen, setOpen] = useState(false);
/** @param {keyof import('../../types/config').Service} feature */
const serviceSupports = feature => service && !!service[feature];
const isSelectableProfile =
......@@ -61,6 +62,8 @@ function UserMenu({ auth, frameSync, onLogout, settings }) {
// Temporary access to the Notebook without feature flag:
// type the key 'n' when user menu is focused/open
/** @param {KeyboardEvent} event */
const onKeyDown = event => {
if (event.key === 'n') {
onSelectNotebook();
......
......@@ -22,6 +22,7 @@ export function useUserFilterOptions() {
return useMemo(() => {
// Determine unique users (authors) in annotation collection
/** @type {Record<string, string>} */
const users = {};
annotations.forEach(annotation => {
const username_ = username(annotation.user);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment