Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions frontend/biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,9 @@
},
"rules": {
"recommended": true,
// TODO: all disabled rules below should be enabled again and fixed
"complexity": {
"noUselessFragments": "warn"
},
"correctness": {
"useExhaustiveDependencies": "off"
},
"style": {
"useComponentExportOnlyModules": {
"level": "on",
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/api/ApiProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function ApiProvider({ children, fetchInitialUser = true }: ApiProviderPr
};

return client.subscribeToApiErrors(callback);
}, [user, setUser, setAirGapError]);
}, [user, setUser]);

// Store the next session expiration time in the user state
useEffect(() => {
Expand All @@ -43,7 +43,7 @@ export function ApiProvider({ children, fetchInitialUser = true }: ApiProviderPr
};

return client.subscribeToSessionExpiration(callback);
}, [user, setUser, expiration, setExpiration]);
}, [expiration, setExpiration]);

const apiState: ApiState = {
client,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/useInitialApiGet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function useInitialApiGetInner<T>(
return () => {
controller.abort(DEFAULT_CANCEL_REASON);
};
}, [client, path, throwFatalErrors]);
}, [client, path]);

return {
requestState,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/messages/Messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function Messages() {
startTransition(() => {
setMessages(popMessages());
});
}, [messages, popMessages]);
}, [popMessages]);

function closeHandler(index: number) {
return () => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/page_title/PageTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useLocation } from "react-router";
export const PageTitle = ({ title }: { title: string }) => {
const location = useLocation();

// biome-ignore lint/correctness/useExhaustiveDependencies(location): page title needs to update when the location changes
useEffect(() => {
document.title = title;
}, [location, title]);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ui/Feedback/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function Feedback({ id, type, data, userRole, shouldFocus = true }: Feedb
if (shouldFocus) {
feedbackHeader.current?.focus();
}
}, [data, shouldFocus]);
}, [shouldFocus]);

return (
<article id={id} className={cn(cls.feedback, cls[type])}>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/ui/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function Modal({ title, noFlex = false, autoWidth = false, onClose, child
dialogRef.current.showModal();
document.getElementById("modal-title")?.focus();
}
}, [dialogRef]);
}, []);

// handle cancel / close actions for the modal
useEffect(() => {
Expand All @@ -59,7 +59,7 @@ export function Modal({ title, noFlex = false, autoWidth = false, onClose, child
dialog.removeEventListener("cancel", cancel);
};
}
}, [dialogRef, onClose]);
}, [onClose]);

return (
<dialog id="modal-dialog" className={cls.modal} ref={dialogRef} aria-labelledby="modal-title">
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/ui/NumberInput/NumberInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(functi
setTooltipInvalidValue(null);
};

// biome-ignore lint/correctness/useExhaustiveDependencies(setTooltipInvalidValue): needed to preserve existing memoization
const onPaste: ClipboardEventHandler<HTMLInputElement> = useCallback(
(event) => {
const pastedInput = event.clipboardData.getData("text/plain");
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/features/data_entry/hooks/useDataEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function useDataEntry(
const deleteRequestPath: POLLING_STATION_DATA_ENTRY_DELETE_REQUEST_PATH = `/api/polling_stations/${pollingStationId}/data_entries/${entryNumber}`;
const finaliseRequestPath: POLLING_STATION_DATA_ENTRY_FINALISE_REQUEST_PATH = `/api/polling_stations/${pollingStationId}/data_entries/${entryNumber}/finalise`;
const claimRequestPath: POLLING_STATION_DATA_ENTRY_CLAIM_REQUEST_PATH = `${saveRequestPath}/claim`;
useInitialDataEntryState(client, dispatch, election, saveRequestPath, claimRequestPath);
useInitialDataEntryState(client, dispatch, claimRequestPath);

// navigate to the correct section
useDataEntryNavigation(state, dispatch, election, pollingStationId, entryNumber, sectionId);
Expand Down
113 changes: 55 additions & 58 deletions frontend/src/features/data_entry/hooks/useFormKeyboardNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,64 +7,61 @@ type Dir = "up" | "down" | "first" | "last";
export function useFormKeyboardNavigation(): RefObject<HTMLFormElement | null> {
const innerRef = useRef<HTMLFormElement>(null);

const moveFocus = useCallback(
(dir: Dir) => {
if (!innerRef.current) {
return;
const moveFocus = useCallback((dir: Dir) => {
if (!innerRef.current) {
return;
}

const activeElement = document.activeElement;

// check whether the activeElement is inside the form
if (!activeElement || !innerRef.current.contains(activeElement)) {
return;
}

const inputs: NodeListOf<HTMLInputElement> = innerRef.current.querySelectorAll("input, select, textarea");
const submitButton: HTMLButtonElement | null = innerRef.current.querySelector("button[type=submit]");

const elements: HTMLElement[] = [...inputs];
if (submitButton) {
elements.push(submitButton);
}

// Note that targetIndex might be -1 if the active element is not in the list
// (e.g. if the user is focused on a button or link outside the input elements, but within the form)
// In this case, the down button will focus to the first input element
// biome-ignore lint/complexity/useIndexOf: can't use useIndexOf because types in predicate are different
let targetIndex = elements.findIndex((element) => document.activeElement === element);

switch (dir) {
case "up":
targetIndex -= 1;
break;
case "down":
targetIndex += 1;
break;
case "first":
targetIndex = 0;
break;
case "last":
targetIndex = inputs.length - 1;
break;
}

if (targetIndex < 0 || targetIndex >= elements.length) {
return;
}

const element = elements[targetIndex];
if (element) {
element.focus();
if (element instanceof HTMLInputElement) {
setTimeout(() => {
element.select();
}, 1);
}

const activeElement = document.activeElement;

// check whether the activeElement is inside the form
if (!activeElement || !innerRef.current.contains(activeElement)) {
return;
}

const inputs: NodeListOf<HTMLInputElement> = innerRef.current.querySelectorAll("input, select, textarea");
const submitButton: HTMLButtonElement | null = innerRef.current.querySelector("button[type=submit]");

const elements: HTMLElement[] = [...inputs];
if (submitButton) {
elements.push(submitButton);
}

// Note that targetIndex might be -1 if the active element is not in the list
// (e.g. if the user is focused on a button or link outside the input elements, but within the form)
// In this case, the down button will focus to the first input element
// biome-ignore lint/complexity/useIndexOf: can't use useIndexOf because types in predicate are different
let targetIndex = elements.findIndex((element) => document.activeElement === element);

switch (dir) {
case "up":
targetIndex -= 1;
break;
case "down":
targetIndex += 1;
break;
case "first":
targetIndex = 0;
break;
case "last":
targetIndex = inputs.length - 1;
break;
}

if (targetIndex < 0 || targetIndex >= elements.length) {
return;
}

const element = elements[targetIndex];
if (element) {
element.focus();
if (element instanceof HTMLInputElement) {
setTimeout(() => {
element.select();
}, 1);
}
}
},
[innerRef],
);
}
}, []);

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
Expand Down Expand Up @@ -112,7 +109,7 @@ export function useFormKeyboardNavigation(): RefObject<HTMLFormElement | null> {
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [innerRef, moveFocus]);
}, [moveFocus]);

return innerRef;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@ import { useEffect } from "react";

import { type ApiClient, DEFAULT_CANCEL_REASON } from "@/api/ApiClient";
import { isSuccess } from "@/api/ApiResult";
import type { ClaimDataEntryResponse, Election } from "@/types/generated/openapi";
import type { ClaimDataEntryResponse } from "@/types/generated/openapi";

import type { DataEntryDispatch } from "../types/types";

export function useInitialDataEntryState(
client: ApiClient,
dispatch: DataEntryDispatch,
election: Required<Election>,
saveRequestPath: string,
claimRequestPath: string,
) {
export function useInitialDataEntryState(client: ApiClient, dispatch: DataEntryDispatch, claimRequestPath: string) {
useEffect(() => {
const abortController = new AbortController();

Expand All @@ -39,5 +33,5 @@ export function useInitialDataEntryState(
return () => {
abortController.abort(DEFAULT_CANCEL_REASON);
};
}, [client, dispatch, election, claimRequestPath, saveRequestPath]);
}, [client, dispatch, claimRequestPath]);
}
Loading