Prechádzať zdrojové kódy

Merge pull request #23940 from overleaf/td-react-18

Upgrade to React 18

GitOrigin-RevId: 9b81936e6eea2bccd97fe5c2c5841f0b946371b8
Tim Down 1 rok pred
rodič
commit
7abafb01ea
100 zmenil súbory, kde vykonal 632 pridanie a 499 odobranie
  1. 238 234
      package-lock.json
  2. 1 1
      services/web/cypress/support/ct/commands/index.ts
  3. 2 2
      services/web/frontend/js/features/chat/context/chat-context.tsx
  4. 1 1
      services/web/frontend/js/features/dictionary/components/dictionary-modal-content.tsx
  5. 5 3
      services/web/frontend/js/features/editor-left-menu/components/editor-left-menu-context.tsx
  6. 3 1
      services/web/frontend/js/features/editor-left-menu/context/project-settings-context.tsx
  7. 2 2
      services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.tsx
  8. 9 7
      services/web/frontend/js/features/file-tree/components/file-tree-context.tsx
  9. 1 1
      services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.tsx
  10. 1 1
      services/web/frontend/js/features/file-tree/components/file-tree-inner.tsx
  11. 9 7
      services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.tsx
  12. 3 1
      services/web/frontend/js/features/file-tree/contexts/file-tree-create-form.tsx
  13. 3 4
      services/web/frontend/js/features/file-tree/contexts/file-tree-create-name.tsx
  14. 5 3
      services/web/frontend/js/features/file-tree/contexts/file-tree-draggable.tsx
  15. 7 5
      services/web/frontend/js/features/file-tree/contexts/file-tree-main.tsx
  16. 3 1
      services/web/frontend/js/features/file-tree/contexts/file-tree-path.tsx
  17. 24 16
      services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.tsx
  18. 1 1
      services/web/frontend/js/features/file-tree/hooks/file-tree-socket-listener.ts
  19. 2 2
      services/web/frontend/js/features/group-management/components/group-members.tsx
  20. 4 4
      services/web/frontend/js/features/group-management/components/managers-table.tsx
  21. 3 3
      services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx
  22. 0 1
      services/web/frontend/js/features/group-management/components/members-table/offboard-managed-user-modal.tsx
  23. 1 1
      services/web/frontend/js/features/group-management/components/members-table/select-user-checkbox.tsx
  24. 1 1
      services/web/frontend/js/features/group-management/components/members-table/unlink-user-modal.tsx
  25. 1 1
      services/web/frontend/js/features/group-management/components/user-row.tsx
  26. 4 4
      services/web/frontend/js/features/group-management/context/group-members-context.tsx
  27. 6 5
      services/web/frontend/js/features/header-footer-react/index.tsx
  28. 1 1
      services/web/frontend/js/features/history/components/diff-view/document-diff-viewer.tsx
  29. 3 1
      services/web/frontend/js/features/ide-react/components/ide-root.tsx
  30. 3 1
      services/web/frontend/js/features/ide-react/components/resize/horizontal-resize-handle.tsx
  31. 1 1
      services/web/frontend/js/features/ide-react/components/unsaved-docs/unsaved-docs.tsx
  32. 3 1
      services/web/frontend/js/features/ide-react/context/command-registry-context.tsx
  33. 3 1
      services/web/frontend/js/features/ide-react/context/connection-context.tsx
  34. 3 1
      services/web/frontend/js/features/ide-react/context/editor-manager-context.tsx
  35. 3 1
      services/web/frontend/js/features/ide-react/context/file-tree-open-context.tsx
  36. 3 1
      services/web/frontend/js/features/ide-react/context/global-alerts-context.tsx
  37. 1 1
      services/web/frontend/js/features/ide-react/context/ide-react-context.tsx
  38. 3 1
      services/web/frontend/js/features/ide-react/context/ide-redesign-switcher-context.tsx
  39. 1 1
      services/web/frontend/js/features/ide-react/context/metadata-context.tsx
  40. 3 1
      services/web/frontend/js/features/ide-react/context/modals-context.tsx
  41. 3 1
      services/web/frontend/js/features/ide-react/context/online-users-context.tsx
  42. 5 5
      services/web/frontend/js/features/ide-react/context/outline-context.tsx
  43. 3 1
      services/web/frontend/js/features/ide-react/context/permissions-context.tsx
  44. 5 4
      services/web/frontend/js/features/ide-react/context/react-context-root.tsx
  45. 3 1
      services/web/frontend/js/features/ide-react/context/references-context.tsx
  46. 1 1
      services/web/frontend/js/features/ide-react/context/snapshot-context.tsx
  47. 1 1
      services/web/frontend/js/features/ide-redesign/components/toolbar/project-title.tsx
  48. 1 1
      services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx
  49. 1 1
      services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx
  50. 4 4
      services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.tsx
  51. 1 1
      services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.tsx
  52. 3 2
      services/web/frontend/js/features/pdf-preview/components/pdf-preview-detached-root.tsx
  53. 3 1
      services/web/frontend/js/features/pdf-preview/components/pdf-preview-messages.tsx
  54. 3 1
      services/web/frontend/js/features/pdf-preview/components/pdf-preview-provider.tsx
  55. 2 2
      services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx
  56. 3 3
      services/web/frontend/js/features/pdf-preview/hooks/use-presentation-mode.ts
  57. 7 4
      services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts
  58. 1 1
      services/web/frontend/js/features/project-list/components/modals/create-tag-modal.tsx
  59. 2 2
      services/web/frontend/js/features/project-list/components/modals/edit-tag-modal.tsx
  60. 2 2
      services/web/frontend/js/features/project-list/components/modals/manage-tag-modal.tsx
  61. 9 2
      services/web/frontend/js/features/project-list/components/modals/rename-project-modal.tsx
  62. 3 1
      services/web/frontend/js/features/project-list/components/project-list-root.tsx
  63. 2 2
      services/web/frontend/js/features/project-list/components/table/project-checkbox.tsx
  64. 5 4
      services/web/frontend/js/features/project-list/components/table/project-tools/buttons/tags-dropdown.tsx
  65. 5 3
      services/web/frontend/js/features/project-list/components/use-is-ds-nav.tsx
  66. 1 1
      services/web/frontend/js/features/project-list/context/project-list-context.tsx
  67. 4 4
      services/web/frontend/js/features/project-list/hooks/use-tag.tsx
  68. 5 4
      services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx
  69. 2 2
      services/web/frontend/js/features/review-panel-new/components/review-panel-comment-content.tsx
  70. 16 14
      services/web/frontend/js/features/review-panel-new/components/review-panel-entry.tsx
  71. 3 1
      services/web/frontend/js/features/review-panel-new/context/changes-users-context.tsx
  72. 1 1
      services/web/frontend/js/features/review-panel-new/context/ranges-context.tsx
  73. 3 1
      services/web/frontend/js/features/review-panel-new/context/review-panel-providers.tsx
  74. 3 1
      services/web/frontend/js/features/review-panel-new/context/review-panel-view-context.tsx
  75. 2 2
      services/web/frontend/js/features/review-panel-new/context/threads-context.tsx
  76. 3 1
      services/web/frontend/js/features/review-panel-new/context/track-changes-state-context.tsx
  77. 2 2
      services/web/frontend/js/features/share-project-modal/components/share-project-modal.tsx
  78. 28 13
      services/web/frontend/js/features/source-editor/components/codemirror-search-form.tsx
  79. 1 1
      services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx
  80. 1 1
      services/web/frontend/js/features/source-editor/components/codemirror-view.tsx
  81. 2 2
      services/web/frontend/js/features/source-editor/components/command-tooltip/href-tooltip.tsx
  82. 1 1
      services/web/frontend/js/features/source-editor/components/editor-switch.tsx
  83. 3 1
      services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-context.tsx
  84. 4 1
      services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-help.tsx
  85. 2 1
      services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal.tsx
  86. 2 2
      services/web/frontend/js/features/source-editor/components/table-generator/cell.tsx
  87. 4 2
      services/web/frontend/js/features/source-editor/components/table-generator/contexts/editing-context.tsx
  88. 3 1
      services/web/frontend/js/features/source-editor/components/table-generator/contexts/selection-context.tsx
  89. 9 7
      services/web/frontend/js/features/source-editor/components/table-generator/contexts/table-context.tsx
  90. 1 1
      services/web/frontend/js/features/source-editor/components/table-generator/contexts/tabular-context.tsx
  91. 1 1
      services/web/frontend/js/features/source-editor/components/table-generator/tabular.tsx
  92. 9 7
      services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar-button-menu.tsx
  93. 2 2
      services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar-button.tsx
  94. 20 16
      services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar-dropdown.tsx
  95. 8 6
      services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx
  96. 8 7
      services/web/frontend/js/features/source-editor/components/toolbar/overflow.tsx
  97. 2 2
      services/web/frontend/js/features/source-editor/components/toolbar/toolbar-button.tsx
  98. 5 5
      services/web/frontend/js/features/source-editor/extensions/spelling/context-menu.tsx
  99. 4 4
      services/web/frontend/js/features/source-editor/extensions/visual/pasted-content.tsx
  100. 18 4
      services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/tabular.tsx

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 238 - 234
package-lock.json


+ 1 - 1
services/web/cypress/support/ct/commands/index.ts

@@ -1,4 +1,4 @@
-import { mount } from 'cypress/react'
+import { mount } from 'cypress/react18'
 
 // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-namespace
 declare global {

+ 2 - 2
services/web/frontend/js/features/chat/context/chat-context.tsx

@@ -190,7 +190,7 @@ export const ChatContext = createContext<
   | undefined
 >(undefined)
 
-export const ChatProvider: FC = ({ children }) => {
+export const ChatProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const chatEnabled = getMeta('ol-chatEnabled')
 
   const clientId = useRef<string>()
@@ -283,7 +283,7 @@ export const ChatProvider: FC = ({ children }) => {
   ])
 
   const sendMessage = useCallback(
-    content => {
+    (content: string) => {
       if (!chatEnabled) {
         debugConsole.warn(`chat is disabled, won't send message`)
         return

+ 1 - 1
services/web/frontend/js/features/dictionary/components/dictionary-modal-content.tsx

@@ -33,7 +33,7 @@ export default function DictionaryModalContent({
   const { isError, runAsync } = useAsync()
 
   const handleRemove = useCallback(
-    word => {
+    (word: string) => {
       runAsync(postJSON('/spelling/unlearn', { body: { word } }))
         .then(() => {
           setLearnedWords(prevLearnedWords => {

+ 5 - 3
services/web/frontend/js/features/editor-left-menu/components/editor-left-menu-context.tsx

@@ -9,17 +9,19 @@ export const EditorLeftMenuContext = createContext<
   EditorLeftMenuState | undefined
 >(undefined)
 
-export const EditorLeftMenuProvider: FC = ({ children }) => {
+export const EditorLeftMenuProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [value, setValue] = useState<EditorLeftMenuState>(() => ({
     settingToFocus: undefined,
   }))
 
   useEventListener(
     'ui.focus-setting',
-    useCallback(event => {
+    useCallback((event: CustomEvent<string>) => {
       setValue(value => ({
         ...value,
-        settingToFocus: (event as CustomEvent<string>).detail,
+        settingToFocus: event.detail,
       }))
     }, [])
   )

+ 3 - 1
services/web/frontend/js/features/editor-left-menu/context/project-settings-context.tsx

@@ -37,7 +37,9 @@ export const ProjectSettingsContext = createContext<
   ProjectSettingsContextValue | undefined
 >(undefined)
 
-export const ProjectSettingsProvider: FC = ({ children }) => {
+export const ProjectSettingsProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const {
     compiler,
     setCompiler,

+ 2 - 2
services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.tsx

@@ -56,7 +56,7 @@ const EditorNavigationToolbarRoot = React.memo(
     }, [chatIsOpen, setChatIsOpen, markMessagesAsRead])
 
     const toggleReviewPanelOpen = useCallback(
-      event => {
+      (event: any) => {
         event.preventDefault()
         eventTracking.sendMB('navigation-clicked-review', {
           action: isOpentoString(!reviewPanelOpen),
@@ -93,7 +93,7 @@ const EditorNavigationToolbarRoot = React.memo(
     }, [setLeftMenuShown])
 
     const goToUser = useCallback(
-      user => {
+      (user: any) => {
         if (user.doc && typeof user.row === 'number') {
           openDoc(user.doc, { gotoLine: user.row + 1 })
         }

+ 9 - 7
services/web/frontend/js/features/file-tree/components/file-tree-context.tsx

@@ -9,13 +9,15 @@ import { FC } from 'react'
 // FileTreeActionable: global UI state for actions (rename, delete, etc.)
 // FileTreeMutable: provides entities mutation operations
 // FileTreeSelectable: handles selection and multi-selection
-const FileTreeContext: FC<{
-  refProviders: Record<string, boolean>
-  setRefProviderEnabled: (provider: string, value: boolean) => void
-  setStartedFreeTrial: (value: boolean) => void
-  onSelect: () => void
-  fileTreeContainer?: HTMLDivElement
-}> = ({
+const FileTreeContext: FC<
+  React.PropsWithChildren<{
+    refProviders: Record<string, boolean>
+    setRefProviderEnabled: (provider: string, value: boolean) => void
+    setStartedFreeTrial: (value: boolean) => void
+    onSelect: () => void
+    fileTreeContainer?: HTMLDivElement
+  }>
+> = ({
   refProviders,
   setRefProviderEnabled,
   setStartedFreeTrial,

+ 1 - 1
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.tsx

@@ -50,7 +50,7 @@ export default function FileTreeImportFromProject() {
 
   // use the basename of a path as the file name
   const setNameFromPath = useCallback(
-    path => {
+    (path: string) => {
       const filename = path.split('/').pop()
 
       if (filename) {

+ 1 - 1
services/web/frontend/js/features/file-tree/components/file-tree-inner.tsx

@@ -1,7 +1,7 @@
 import { useFileTreeSelectable } from '../contexts/file-tree-selectable'
 import { FC, useCallback } from 'react'
 
-const FileTreeInner: FC = ({ children }) => {
+const FileTreeInner: FC<React.PropsWithChildren> = ({ children }) => {
   const { setIsRootFolderSelected, selectedEntityIds, select } =
     useFileTreeSelectable()
 

+ 9 - 7
services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.tsx

@@ -218,7 +218,9 @@ function fileTreeActionableReducer(state: State, action: Action) {
   }
 }
 
-export const FileTreeActionableProvider: FC = ({ children }) => {
+export const FileTreeActionableProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const { _id: projectId } = useProjectContext()
   const { fileTreeReadOnly } = useFileTreeData()
   const { indexAllReferences } = useReferencesContext()
@@ -400,7 +402,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
   }, [fileTreeData, selectedEntityIds])
 
   const finishCreatingEntity = useCallback(
-    entity => {
+    (entity: any) => {
       const error = validateCreate(fileTreeData, parentFolderId, entity)
       if (error) {
         return Promise.reject(error)
@@ -412,7 +414,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
   )
 
   const finishCreatingFolder = useCallback(
-    name => {
+    (name: any) => {
       dispatch({ type: ACTION_TYPES.CREATING_FOLDER })
       return finishCreatingEntity({ endpoint: 'folder', name })
         .then(() => {
@@ -425,7 +427,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
     [finishCreatingEntity]
   )
 
-  const startCreatingFile = useCallback(newFileCreateMode => {
+  const startCreatingFile = useCallback((newFileCreateMode: any) => {
     dispatch({ type: ACTION_TYPES.START_CREATE_FILE, newFileCreateMode })
   }, [])
 
@@ -438,7 +440,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
   }, [startCreatingFile])
 
   const finishCreatingDocOrFile = useCallback(
-    entity => {
+    (entity: any) => {
       dispatch({ type: ACTION_TYPES.CREATING_FILE })
 
       return finishCreatingEntity(entity)
@@ -453,7 +455,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
   )
 
   const finishCreatingDoc = useCallback(
-    entity => {
+    (entity: any) => {
       entity.endpoint = 'doc'
       return finishCreatingDocOrFile(entity)
     },
@@ -461,7 +463,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
   )
 
   const finishCreatingLinkedFile = useCallback(
-    entity => {
+    (entity: any) => {
       entity.endpoint = 'linked_file'
       return finishCreatingDocOrFile(entity)
     },

+ 3 - 1
services/web/frontend/js/features/file-tree/contexts/file-tree-create-form.tsx

@@ -16,7 +16,9 @@ export const useFileTreeCreateForm = () => {
   return context
 }
 
-const FileTreeCreateFormProvider: FC = ({ children }) => {
+const FileTreeCreateFormProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   // is the form valid
   const [valid, setValid] = useState(false)
 

+ 3 - 4
services/web/frontend/js/features/file-tree/contexts/file-tree-create-name.tsx

@@ -28,10 +28,9 @@ type State = {
   touchedName: boolean
 }
 
-const FileTreeCreateNameProvider: FC<{ initialName?: string }> = ({
-  children,
-  initialName = '',
-}) => {
+const FileTreeCreateNameProvider: FC<
+  React.PropsWithChildren<{ initialName?: string }>
+> = ({ children, initialName = '' }) => {
   const [state, setName] = useReducer(
     (state: State, name: string) => ({
       name, // the file name

+ 5 - 3
services/web/frontend/js/features/file-tree/contexts/file-tree-draggable.tsx

@@ -18,9 +18,11 @@ import { isAcceptableFile } from '@/features/file-tree/util/is-acceptable-file'
 import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
 
 const DRAGGABLE_TYPE = 'ENTITY'
-export const FileTreeDraggableProvider: FC<{
-  fileTreeContainer?: HTMLDivElement
-}> = ({ fileTreeContainer, children }) => {
+export const FileTreeDraggableProvider: FC<
+  React.PropsWithChildren<{
+    fileTreeContainer?: HTMLDivElement
+  }>
+> = ({ fileTreeContainer, children }) => {
   const options = useMemo(
     () => ({ rootElement: fileTreeContainer }),
     [fileTreeContainer]

+ 7 - 5
services/web/frontend/js/features/file-tree/contexts/file-tree-main.tsx

@@ -25,11 +25,13 @@ export function useFileTreeMainContext() {
   return context
 }
 
-export const FileTreeMainProvider: FC<{
-  refProviders: object
-  setRefProviderEnabled: (provider: string, value: boolean) => void
-  setStartedFreeTrial: (value: boolean) => void
-}> = ({
+export const FileTreeMainProvider: FC<
+  React.PropsWithChildren<{
+    refProviders: object
+    setRefProviderEnabled: (provider: string, value: boolean) => void
+    setStartedFreeTrial: (value: boolean) => void
+  }>
+> = ({
   refProviders,
   setRefProviderEnabled,
   setStartedFreeTrial,

+ 3 - 1
services/web/frontend/js/features/file-tree/contexts/file-tree-path.tsx

@@ -22,7 +22,9 @@ export const FileTreePathContext = createContext<
   FileTreePathContextValue | undefined
 >(undefined)
 
-export const FileTreePathProvider: FC = ({ children }) => {
+export const FileTreePathProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const { fileTreeData }: { fileTreeData: Folder } = useFileTreeData()
   const projectId = getMeta('ol-project_id')
 

+ 24 - 16
services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.tsx

@@ -1,3 +1,6 @@
+// TODO: The types in this file have mismatches between string and string[] and
+// it's not immediately clear how to resolve it. I've therefore left in a bunch
+// of `any` types. We should fix this.
 import {
   createContext,
   useCallback,
@@ -120,9 +123,11 @@ function fileTreeSelectableReadOnlyReducer(
   }
 }
 
-export const FileTreeSelectableProvider: FC<{
-  onSelect: (value: FindResult[]) => void
-}> = ({ onSelect, children }) => {
+export const FileTreeSelectableProvider: FC<
+  React.PropsWithChildren<{
+    onSelect: (value: FindResult[]) => void
+  }>
+> = ({ onSelect, children }) => {
   const { _id: projectId, rootDocId } = useProjectContext()
 
   const [initialSelectedEntityId] = usePersistedState(
@@ -206,21 +211,24 @@ export const FileTreeSelectableProvider: FC<{
     )
   )
 
-  const select = useCallback(id => {
+  const select = useCallback((id: any) => {
     dispatch({ type: ACTION_TYPES.SELECT, id })
   }, [])
 
-  const unselect = useCallback(id => {
+  const unselect = useCallback((id: any) => {
     dispatch({ type: ACTION_TYPES.UNSELECT, id })
   }, [])
 
-  const selectOrMultiSelectEntity = useCallback((id, isMultiSelect) => {
-    const actionType = isMultiSelect
-      ? ACTION_TYPES.MULTI_SELECT
-      : ACTION_TYPES.SELECT
+  const selectOrMultiSelectEntity = useCallback(
+    (id: any, isMultiSelect: any) => {
+      const actionType = isMultiSelect
+        ? ACTION_TYPES.MULTI_SELECT
+        : ACTION_TYPES.SELECT
 
-    dispatch({ type: actionType, id })
-  }, [])
+      dispatch({ type: actionType, id })
+    },
+    []
+  )
 
   // TODO: wrap in useMemo
   const value = {
@@ -254,7 +262,7 @@ export function useSelectableEntity(id: string, type: string) {
   const isSelected = selectedEntityIds.has(id)
 
   const buildSelectedRange = useCallback(
-    id => {
+    (id: string) => {
       const selected = []
 
       let started = false
@@ -306,7 +314,7 @@ export function useSelectableEntity(id: string, type: string) {
   }, [fileTreeData, selectedEntityIds, view])
 
   const handleEvent = useCallback(
-    ev => {
+    (ev: any) => {
       ev.stopPropagation()
       // use Command (macOS) or Ctrl (other OS) to select multiple items,
       // as long as the root folder wasn't selected
@@ -342,7 +350,7 @@ export function useSelectableEntity(id: string, type: string) {
   )
 
   const handleClick = useCallback(
-    ev => {
+    (ev: any) => {
       handleEvent(ev)
       if (!ev.ctrlKey && !ev.metaKey) {
         setContextMenuCoords(null)
@@ -352,7 +360,7 @@ export function useSelectableEntity(id: string, type: string) {
   )
 
   const handleKeyPress = useCallback(
-    ev => {
+    (ev: any) => {
       if (ev.key === 'Enter' || ev.key === ' ') {
         handleEvent(ev)
       }
@@ -361,7 +369,7 @@ export function useSelectableEntity(id: string, type: string) {
   )
 
   const handleContextMenu = useCallback(
-    ev => {
+    (ev: any) => {
       // make sure the right-clicked entity gets selected
       if (!selectedEntityIds.has(id)) {
         handleEvent(ev)

+ 1 - 1
services/web/frontend/js/features/file-tree/hooks/file-tree-socket-listener.ts

@@ -25,7 +25,7 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
 
   const selectEntityIfCreatedByUser = useCallback(
     // hack to automatically re-open refreshed linked files
-    (entityId, entityName, userId) => {
+    (entityId: any, entityName: any, userId: string) => {
       // If the created entity's user exists and is the current user
       if (userId && user?.id === userId) {
         // And we're expecting a refreshed socket for this entity

+ 2 - 2
services/web/frontend/js/features/group-management/components/group-members.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from 'react'
+import React, { ChangeEvent, useCallback, useState } from 'react'
 import { Trans, useTranslation } from 'react-i18next'
 import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
 import getMeta from '../../../utils/meta'
@@ -37,7 +37,7 @@ export default function GroupMembers() {
   const canUseAddSeatsFeature = getMeta('ol-canUseAddSeatsFeature')
 
   const handleEmailsChange = useCallback(
-    e => {
+    (e: ChangeEvent<HTMLInputElement>) => {
       setEmailString(e.target.value)
     },
     [setEmailString]

+ 4 - 4
services/web/frontend/js/features/group-management/components/managers-table.tsx

@@ -1,4 +1,4 @@
-import { useCallback, useState } from 'react'
+import { ChangeEvent, FormEvent, useCallback, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import { deleteJSON, FetchError, postJSON } from '@/infrastructure/fetch-json'
 import getMeta from '../../../utils/meta'
@@ -58,7 +58,7 @@ export function ManagersTable({
   const [removeMemberError, setRemoveMemberError] = useState<APIError>()
 
   const addManagers = useCallback(
-    e => {
+    (e: FormEvent | React.MouseEvent) => {
       e.preventDefault()
       setInviteError(undefined)
       const emails = parseEmails(emailString)
@@ -92,7 +92,7 @@ export function ManagersTable({
   )
 
   const removeManagers = useCallback(
-    e => {
+    (e: React.MouseEvent) => {
       e.preventDefault()
       setRemoveMemberError(undefined)
       ;(async () => {
@@ -131,7 +131,7 @@ export function ManagersTable({
   )
 
   const handleEmailsChange = useCallback(
-    e => {
+    (e: ChangeEvent<HTMLInputElement>) => {
       setEmailString(e.target.value)
     },
     [setEmailString]

+ 3 - 3
services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx

@@ -64,7 +64,7 @@ export default function DropdownButton({
   const isUserManaged = !userPending && user.enrollment?.managedBy === groupId
 
   const handleResendManagedUserInvite = useCallback(
-    async user => {
+    async (user: User) => {
       try {
         const result = await runResendManagedUserInviteAsync(
           postJSON(
@@ -96,7 +96,7 @@ export default function DropdownButton({
   )
 
   const handleResendLinkSSOInviteAsync = useCallback(
-    async user => {
+    async (user: User) => {
       try {
         const result = await runResendLinkSSOInviteAsync(
           postJSON(`/manage/groups/${groupId}/resendSSOLinkInvite/${user._id}`)
@@ -126,7 +126,7 @@ export default function DropdownButton({
   )
 
   const handleResendGroupInvite = useCallback(
-    async user => {
+    async (user: User) => {
       try {
         await runResendGroupInviteAsync(
           postJSON(`/manage/groups/${groupId}/resendInvite/`, {

+ 0 - 1
services/web/frontend/js/features/group-management/components/members-table/offboard-managed-user-modal.tsx

@@ -101,7 +101,6 @@ export default function OffboardManagedUserModal({
             <OLFormSelect
               aria-label={t('select_user')}
               required
-              placeholder={t('choose_from_group_members')}
               value={selectedRecipientId || ''}
               onChange={e => setSelectedRecipientId(e.target.value)}
             >

+ 1 - 1
services/web/frontend/js/features/group-management/components/members-table/select-user-checkbox.tsx

@@ -16,7 +16,7 @@ export default function SelectUserCheckbox({
     useGroupMembersContext()
 
   const handleSelectUser = useCallback(
-    (event, user) => {
+    (event: React.ChangeEvent<HTMLInputElement>, user: User) => {
       if (event.target.checked) {
         selectUser(user)
       } else {

+ 1 - 1
services/web/frontend/js/features/group-management/components/members-table/unlink-user-modal.tsx

@@ -47,7 +47,7 @@ export default function UnlinkUserModal({
   }, [groupId, updateMemberView, user])
 
   const handleUnlink = useCallback(
-    event => {
+    (event: React.MouseEvent) => {
       event.preventDefault()
       setHasError(undefined)
       if (!user) {

+ 1 - 1
services/web/frontend/js/features/group-management/components/user-row.tsx

@@ -21,7 +21,7 @@ export default function UserRow({
   const { t } = useTranslation()
 
   const handleSelectUser = useCallback(
-    (event, user) => {
+    (event: React.ChangeEvent<HTMLInputElement>, user: User) => {
       if (event.target.checked) {
         selectUser(user)
       } else {

+ 4 - 4
services/web/frontend/js/features/group-management/context/group-members-context.tsx

@@ -72,7 +72,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
   )
 
   const addMembers = useCallback(
-    emailString => {
+    (emailString: string) => {
       setInviteError(undefined)
       const emails = parseEmails(emailString)
       mapSeries(emails, async email => {
@@ -102,7 +102,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
   )
 
   const removeMember = useCallback(
-    async user => {
+    async (user: User) => {
       let url
       if (paths.removeInvite && user.invite && user._id == null) {
         url = `${paths.removeInvite}/${encodeURIComponent(user.email)}`
@@ -126,7 +126,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
   )
 
   const removeMembers = useCallback(
-    e => {
+    (e: any) => {
       e.preventDefault()
       setRemoveMemberError(undefined)
       ;(async () => {
@@ -142,7 +142,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
   )
 
   const updateMemberView = useCallback(
-    (userId, updatedUser) => {
+    (userId: string, updatedUser: User) => {
       setUsers(
         users.map(u => {
           if (u._id === userId) {

+ 6 - 5
services/web/frontend/js/features/header-footer-react/index.tsx

@@ -1,4 +1,4 @@
-import ReactDOM from 'react-dom'
+import { createRoot } from 'react-dom/client'
 import getMeta from '@/utils/meta'
 import DefaultNavbar from '@/features/ui/components/bootstrap-5/navbar/default-navbar'
 import Footer from '@/features/ui/components/bootstrap-5/footer/footer'
@@ -7,16 +7,17 @@ import { SplitTestProvider } from '@/shared/context/split-test-context'
 const navbarElement = document.getElementById('navbar-container')
 if (navbarElement) {
   const navbarProps = getMeta('ol-navbar')
-  ReactDOM.render(
+  const root = createRoot(navbarElement)
+  root.render(
     <SplitTestProvider>
       <DefaultNavbar {...navbarProps} />
-    </SplitTestProvider>,
-    navbarElement
+    </SplitTestProvider>
   )
 }
 
 const footerElement = document.getElementById('footer-container')
 if (footerElement) {
   const footerProps = getMeta('ol-footer')
-  ReactDOM.render(<Footer {...footerProps} />, footerElement)
+  const root = createRoot(footerElement)
+  root.render(<Footer {...footerProps} />)
 }

+ 1 - 1
services/web/frontend/js/features/history/components/diff-view/document-diff-viewer.tsx

@@ -77,7 +77,7 @@ function DocumentDiffViewer({
 
   // Append the editor view DOM to the container node when mounted
   const containerRef = useCallback(
-    node => {
+    (node: HTMLDivElement) => {
       if (node) {
         node.appendChild(view.dom)
       }

+ 3 - 1
services/web/frontend/js/features/ide-react/components/ide-root.tsx

@@ -15,4 +15,6 @@ const IdeRoot: FC = () => {
   )
 }
 
-export default withErrorBoundary(memo(IdeRoot), GenericErrorBoundaryFallback)
+export default withErrorBoundary(memo(IdeRoot), () => (
+  <GenericErrorBoundaryFallback />
+))

+ 3 - 1
services/web/frontend/js/features/ide-react/components/resize/horizontal-resize-handle.tsx

@@ -10,7 +10,9 @@ type HorizontalResizeHandleOwnProps = {
 }
 
 export const HorizontalResizeHandle: FC<
-  HorizontalResizeHandleOwnProps & PanelResizeHandleProps
+  React.PropsWithChildren<
+    HorizontalResizeHandleOwnProps & PanelResizeHandleProps
+  >
 > = ({ children, resizable = true, onDoubleClick, ...props }) => {
   const { t } = useTranslation()
   const [isDragging, setIsDragging] = useState(false)

+ 1 - 1
services/web/frontend/js/features/ide-react/components/unsaved-docs/unsaved-docs.tsx

@@ -30,7 +30,7 @@ export const UnsavedDocs: FC = () => {
   useEventListener(
     'beforeunload',
     useCallback(
-      event => {
+      (event: BeforeUnloadEvent) => {
         if (openDocs.hasUnsavedChanges()) {
           // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
           event.preventDefault()

+ 3 - 1
services/web/frontend/js/features/ide-react/context/command-registry-context.tsx

@@ -23,7 +23,9 @@ type CommandRegistry = {
   unregister: (...id: string[]) => void
 }
 
-export const CommandRegistryProvider: React.FC = ({ children }) => {
+export const CommandRegistryProvider: React.FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [registry, setRegistry] = useState(new Map<string, Command>())
   const register = useCallback((...elements: Command[]) => {
     setRegistry(

+ 3 - 1
services/web/frontend/js/features/ide-react/context/connection-context.tsx

@@ -36,7 +36,9 @@ export const ConnectionContext = createContext<
   ConnectionContextValue | undefined
 >(undefined)
 
-export const ConnectionProvider: FC = ({ children }) => {
+export const ConnectionProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const location = useLocation()
 
   const [connectionManager] = useState(() => new ConnectionManager())

+ 3 - 1
services/web/frontend/js/features/ide-react/context/editor-manager-context.tsx

@@ -88,7 +88,9 @@ export const EditorManagerContext = createContext<EditorManager | undefined>(
   undefined
 )
 
-export const EditorManagerProvider: FC = ({ children }) => {
+export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const { t } = useTranslation()
   const { scopeStore } = useIdeContext()
   const { reportError, eventEmitter, projectId } = useIdeReactContext()

+ 3 - 1
services/web/frontend/js/features/ide-react/context/file-tree-open-context.tsx

@@ -36,7 +36,9 @@ const FileTreeOpenContext = createContext<
   | undefined
 >(undefined)
 
-export const FileTreeOpenProvider: FC = ({ children }) => {
+export const FileTreeOpenProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const { rootDocId, owner } = useProjectContext()
   const { eventEmitter, projectJoined } = useIdeReactContext()
   const { openDocWithId, currentDocumentId, openInitialDoc } =

+ 3 - 1
services/web/frontend/js/features/ide-react/context/global-alerts-context.tsx

@@ -4,7 +4,9 @@ const GlobalAlertsContext = createContext<HTMLDivElement | null | undefined>(
   undefined
 )
 
-export const GlobalAlertsProvider: FC = ({ children }) => {
+export const GlobalAlertsProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [globalAlertsContainer, setGlobalAlertsContainer] =
     useState<HTMLDivElement | null>(null)
 

+ 1 - 1
services/web/frontend/js/features/ide-react/context/ide-react-context.tsx

@@ -77,7 +77,7 @@ export function createReactScopeValueStore(projectId: string) {
   return scopeStore
 }
 
-export const IdeReactProvider: FC = ({ children }) => {
+export const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const projectId = getMeta('ol-project_id')
   const [scopeStore] = useState(() => createReactScopeValueStore(projectId))
   const [eventEmitter] = useState(createIdeEventEmitter)

+ 3 - 1
services/web/frontend/js/features/ide-react/context/ide-redesign-switcher-context.tsx

@@ -16,7 +16,9 @@ export const IdeRedesignSwitcherContext = createContext<
   IdeRedesignSwitcherContextValue | undefined
 >(undefined)
 
-export const IdeRedesignSwitcherProvider: FC = ({ children }) => {
+export const IdeRedesignSwitcherProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [showSwitcherModal, setShowSwitcherModal] = useState(false)
 
   return (

+ 1 - 1
services/web/frontend/js/features/ide-react/context/metadata-context.tsx

@@ -47,7 +47,7 @@ export const MetadataContext = createContext<
   | undefined
 >(undefined)
 
-export const MetadataProvider: FC = ({ children }) => {
+export const MetadataProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const { t } = useTranslation()
   const { eventEmitter, projectId } = useIdeReactContext()
   const { socket } = useConnectionContext()

+ 3 - 1
services/web/frontend/js/features/ide-react/context/modals-context.tsx

@@ -30,7 +30,9 @@ type ModalsContextValue = {
 
 const ModalsContext = createContext<ModalsContextValue | undefined>(undefined)
 
-export const ModalsContextProvider: FC = ({ children }) => {
+export const ModalsContextProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [showGenericModal, setShowGenericModal] = useState(false)
   const [showConfirmModal, setShowConfirmModal] = useState(false)
   const [genericMessageModalData, setGenericMessageModalData] =

+ 3 - 1
services/web/frontend/js/features/ide-react/context/online-users-context.tsx

@@ -65,7 +65,9 @@ export const OnlineUsersContext = createContext<
   OnlineUsersContextValue | undefined
 >(undefined)
 
-export const OnlineUsersProvider: FC = ({ children }) => {
+export const OnlineUsersProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const { eventEmitter } = useIdeReactContext()
   const { socket } = useConnectionContext()
   const { currentDocumentId } = useEditorManagerContext()

+ 5 - 5
services/web/frontend/js/features/ide-react/context/outline-context.tsx

@@ -42,7 +42,7 @@ const OutlineContext = createContext<
   | undefined
 >(undefined)
 
-export const OutlineProvider: FC = ({ children }) => {
+export const OutlineProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const [flatOutline, setFlatOutline] = useState<FlatOutlineState>(undefined)
   const [currentlyHighlightedLine, setCurrentlyHighlightedLine] =
     useState<number>(-1)
@@ -55,7 +55,7 @@ export const OutlineProvider: FC = ({ children }) => {
 
   useEventListener(
     'file-view:file-opened',
-    useCallback(_ => {
+    useCallback(() => {
       setBinaryFileOpened(true)
     }, [])
   )
@@ -63,7 +63,7 @@ export const OutlineProvider: FC = ({ children }) => {
   useEventListener(
     'scroll:editor:update',
     useCallback(
-      evt => {
+      (evt: CustomEvent) => {
         if (ignoreNextScroll) {
           setIgnoreNextScroll(false)
           return
@@ -77,7 +77,7 @@ export const OutlineProvider: FC = ({ children }) => {
   useEventListener(
     'cursor:editor:update',
     useCallback(
-      evt => {
+      (evt: CustomEvent) => {
         if (ignoreNextCursorUpdate) {
           setIgnoreNextCursorUpdate(false)
           return
@@ -90,7 +90,7 @@ export const OutlineProvider: FC = ({ children }) => {
 
   useEventListener(
     'doc:after-opened',
-    useCallback(evt => {
+    useCallback((evt: CustomEvent) => {
       if (evt.detail.isNewDoc) {
         setIgnoreNextCursorUpdate(true)
       }

+ 3 - 1
services/web/frontend/js/features/ide-react/context/permissions-context.tsx

@@ -79,7 +79,9 @@ const noTrackChangesPermissionsMap: typeof permissionsMap = {
   owner: permissionsMap.owner,
 }
 
-export const PermissionsProvider: React.FC = ({ children }) => {
+export const PermissionsProvider: React.FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [permissions, setPermissions] =
     useScopeValue<Readonly<Permissions>>('permissions')
   const { connectionState } = useConnectionContext()

+ 5 - 4
services/web/frontend/js/features/ide-react/context/react-context-root.tsx

@@ -27,10 +27,11 @@ import { UserSettingsProvider } from '@/shared/context/user-settings-context'
 import { IdeRedesignSwitcherProvider } from './ide-redesign-switcher-context'
 import { CommandRegistryProvider } from './command-registry-context'
 
-export const ReactContextRoot: FC<{ providers?: Record<string, FC> }> = ({
-  children,
-  providers = {},
-}) => {
+export const ReactContextRoot: FC<
+  React.PropsWithChildren<{
+    providers?: Record<string, FC>
+  }>
+> = ({ children, providers = {} }) => {
   const Providers = {
     ChatProvider,
     ConnectionProvider,

+ 3 - 1
services/web/frontend/js/features/ide-react/context/references-context.tsx

@@ -26,7 +26,9 @@ export const ReferencesContext = createContext<
   | undefined
 >(undefined)
 
-export const ReferencesProvider: FC = ({ children }) => {
+export const ReferencesProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const { fileTreeData } = useFileTreeData()
   const { eventEmitter, projectId } = useIdeReactContext()
   const { socket } = useConnectionContext()

+ 1 - 1
services/web/frontend/js/features/ide-react/context/snapshot-context.tsx

@@ -50,7 +50,7 @@ export const SnapshotContext = createContext<
   | undefined
 >(undefined)
 
-export const SnapshotProvider: FC = ({ children }) => {
+export const SnapshotProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const { _id: projectId } = useProjectContext()
   const [snapshotLoadingState, setSnapshotLoadingState] =
     useState<SnapshotLoadingState>('')

+ 1 - 1
services/web/frontend/js/features/ide-redesign/components/toolbar/project-title.tsx

@@ -27,7 +27,7 @@ export const ToolbarProjectTitle = () => {
   const hasRenamePermissions = permissionsLevel === 'owner'
   const [isRenaming, setIsRenaming] = useState(false)
   const onRename = useCallback(
-    name => {
+    (name: string) => {
       if (name) {
         renameProject(name)
       }

+ 1 - 1
services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx

@@ -39,7 +39,7 @@ const RailContext = createContext<
   | undefined
 >(undefined)
 
-export const RailProvider: FC = ({ children }) => {
+export const RailProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const [isOpen, setIsOpen] = useState(true)
   const [resizing, setResizing] = useState(false)
   const [activeModal, setActiveModalInternal] = useState<RailModalKey | null>(

+ 1 - 1
services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx

@@ -15,7 +15,7 @@ function CompileTimeWarningUpgradePrompt() {
   >(`has-dismissed-10s-compile-time-warning-until`)
 
   const handleNewCompile = useCallback(
-    compileTime => {
+    (compileTime: number) => {
       setShowWarning(false)
       if (compileTime > 10000) {
         if (isProjectOwner) {

+ 4 - 4
services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.tsx

@@ -62,10 +62,10 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
 
   // create the viewer when the container is mounted
   const handleContainer = useCallback(
-    parent => {
+    (parent: HTMLDivElement | null) => {
       if (parent) {
         try {
-          setPdfJsWrapper(new PDFJSWrapper(parent.firstChild))
+          setPdfJsWrapper(new PDFJSWrapper(parent.firstChild as HTMLDivElement))
         } catch (error: any) {
           setLoadingError(true)
           captureException(error)
@@ -385,7 +385,7 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
 
   // set the scale in response to zoom option changes
   const setZoom = useCallback(
-    zoom => {
+    (zoom: any) => {
       switch (zoom) {
         case 'zoom-in':
           if (pdfJsWrapper) {
@@ -430,7 +430,7 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
   }, [pdfJsWrapper])
 
   const handleKeyDown = useCallback(
-    event => {
+    (event: React.KeyboardEvent) => {
       if (!initialised || !pdfJsWrapper) {
         return
       }

+ 1 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.tsx

@@ -17,7 +17,7 @@ export default function PdfLogEntryRawContent({
 
   const { elementRef } = useResizeObserver(
     useCallback(
-      element => {
+      (element: Element) => {
         if (element.scrollHeight === 0) return // skip update when logs-pane is closed
         setNeedsExpander(element.scrollHeight > collapsedSize)
       },

+ 3 - 2
services/web/frontend/js/features/pdf-preview/components/pdf-preview-detached-root.tsx

@@ -1,4 +1,4 @@
-import ReactDOM from 'react-dom'
+import { createRoot } from 'react-dom/client'
 import PdfPreview from './pdf-preview'
 import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
 import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
@@ -21,5 +21,6 @@ export default PdfPreviewDetachedRoot // for testing
 
 const element = document.getElementById('pdf-preview-detached-root')
 if (element) {
-  ReactDOM.render(<PdfPreviewDetachedRoot />, element)
+  const root = createRoot(element)
+  root.render(<PdfPreviewDetachedRoot />)
 }

+ 3 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-preview-messages.tsx

@@ -1,5 +1,7 @@
 import { FC } from 'react'
 
-export const PdfPreviewMessages: FC = ({ children }) => {
+export const PdfPreviewMessages: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   return <div className="pdf-preview-messages">{children}</div>
 }

+ 3 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-preview-provider.tsx

@@ -18,7 +18,9 @@ export const usePdfPreviewContext = () => {
   return context
 }
 
-export const PdfPreviewProvider: FC = ({ children }) => {
+export const PdfPreviewProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [loadingError, setLoadingError] = useState(false)
 
   const value = useMemo(

+ 2 - 2
services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx

@@ -36,8 +36,8 @@ function PdfViewerControlsToolbar({
   const [availableWidth, setAvailableWidth] = useState<number>(1000)
 
   const handleResize = useCallback(
-    element => {
-      setAvailableWidth(element.offsetWidth)
+    (element: Element) => {
+      setAvailableWidth((element as HTMLElement).offsetWidth)
     },
     [setAvailableWidth]
   )

+ 3 - 3
services/web/frontend/js/features/pdf-preview/hooks/use-presentation-mode.ts

@@ -33,8 +33,8 @@ export default function usePresentationMode(
   }, [handlePageChange, page])
 
   const clickListener = useCallback(
-    event => {
-      if (event.target.tagName === 'A') {
+    (event: MouseEvent) => {
+      if ((event.target as HTMLElement).tagName === 'A') {
         return
       }
 
@@ -48,7 +48,7 @@ export default function usePresentationMode(
   )
 
   const arrowKeyListener = useCallback(
-    event => {
+    (event: KeyboardEvent) => {
       switch (event.key) {
         case 'ArrowLeft':
         case 'ArrowUp':

+ 7 - 4
services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts

@@ -51,7 +51,7 @@ export default function useSynctex(): {
 
   useEventListener(
     'cursor:editor:update',
-    useCallback(event => setCursorPosition(event.detail), [])
+    useCallback((event: CustomEvent) => setCursorPosition(event.detail), [])
   )
 
   const [syncToPdfInFlight, setSyncToPdfInFlight] = useState(false)
@@ -86,7 +86,7 @@ export default function useSynctex(): {
   }, [dirname, getCurrentDocumentId, pathInFolder, rootDocId])
 
   const goToCodeLine = useCallback(
-    (file, line) => {
+    (file?: string, line?: number) => {
       if (file) {
         const doc = findEntityByPath(file)?.entity
         if (doc) {
@@ -102,7 +102,7 @@ export default function useSynctex(): {
   )
 
   const goToPdfLocation = useCallback(
-    params => {
+    (params: string) => {
       setSyncToPdfInFlight(true)
 
       if (clsiServerId) {
@@ -251,7 +251,10 @@ export default function useSynctex(): {
 
   useEventListener(
     'synctex:sync-to-position',
-    useCallback(event => syncToCode({ position: event.detail }), [syncToCode])
+    useCallback(
+      (event: CustomEvent) => syncToCode({ position: event.detail }),
+      [syncToCode]
+    )
   )
 
   const [hasSingleSelectedDoc, setHasSingleSelectedDoc] = useDetachState(

+ 1 - 1
services/web/frontend/js/features/project-list/components/modals/create-tag-modal.tsx

@@ -55,7 +55,7 @@ export default function CreateTagModal({
   }, [runAsync, tagName, selectedColor, onCreate])
 
   const handleSubmit = useCallback(
-    e => {
+    (e: React.FormEvent) => {
       e.preventDefault()
       runCreateTag()
     },

+ 2 - 2
services/web/frontend/js/features/project-list/components/modals/edit-tag-modal.tsx

@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useState } from 'react'
+import { FormEvent, useCallback, useEffect, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import { Tag } from '../../../../../../app/src/Features/Tags/types'
 import useAsync from '../../../../shared/hooks/use-async'
@@ -56,7 +56,7 @@ export function EditTagModal({ id, tag, onEdit, onClose }: EditTagModalProps) {
   )
 
   const handleSubmit = useCallback(
-    e => {
+    (e: FormEvent) => {
       e.preventDefault()
       if (tag) {
         runEditTag(tag._id)

+ 2 - 2
services/web/frontend/js/features/project-list/components/modals/manage-tag-modal.tsx

@@ -1,4 +1,4 @@
-import { useCallback, useState } from 'react'
+import { FormEvent, useCallback, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import useAsync from '../../../../shared/hooks/use-async'
 import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
@@ -73,7 +73,7 @@ export function ManageTagModal({
   )
 
   const handleSubmit = useCallback(
-    e => {
+    (e: FormEvent) => {
       e.preventDefault()
       if (tag) {
         runUpdateTag(tag._id)

+ 9 - 2
services/web/frontend/js/features/project-list/components/modals/rename-project-modal.tsx

@@ -1,4 +1,11 @@
-import { memo, useCallback, useEffect, useMemo, useState } from 'react'
+import {
+  FormEvent,
+  memo,
+  useCallback,
+  useEffect,
+  useMemo,
+  useState,
+} from 'react'
 import { useTranslation } from 'react-i18next'
 import * as eventTracking from '../../../../infrastructure/event-tracking'
 import { Project } from '../../../../../../types/project/dashboard/api'
@@ -58,7 +65,7 @@ function RenameProjectModal({
   }, [project.name])
 
   const handleSubmit = useCallback(
-    event => {
+    (event: FormEvent) => {
       event.preventDefault()
 
       if (!isValid) return

+ 3 - 1
services/web/frontend/js/features/project-list/components/project-list-root.tsx

@@ -100,4 +100,6 @@ function ProjectListPageContent() {
   )
 }
 
-export default withErrorBoundary(ProjectListRoot, GenericErrorBoundaryFallback)
+export default withErrorBoundary(ProjectListRoot, () => (
+  <GenericErrorBoundaryFallback />
+))

+ 2 - 2
services/web/frontend/js/features/project-list/components/table/project-checkbox.tsx

@@ -1,4 +1,4 @@
-import { memo, useCallback } from 'react'
+import { ChangeEvent, memo, useCallback } from 'react'
 import { useTranslation } from 'react-i18next'
 import { useProjectListContext } from '@/features/project-list/context/project-list-context'
 import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
@@ -10,7 +10,7 @@ export const ProjectCheckbox = memo<{ projectId: string; projectName: string }>(
       useProjectListContext()
 
     const handleCheckboxChange = useCallback(
-      event => {
+      (event: ChangeEvent<HTMLInputElement>) => {
         toggleSelectedProject(projectId, event.target.checked)
       },
       [projectId, toggleSelectedProject]

+ 5 - 4
services/web/frontend/js/features/project-list/components/table/project-tools/buttons/tags-dropdown.tsx

@@ -14,6 +14,7 @@ import {
   DropdownMenu,
   DropdownToggle,
 } from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import { Tag } from '../../../../../../../../app/src/Features/Tags/types'
 
 function TagsDropdown() {
   const {
@@ -26,7 +27,7 @@ function TagsDropdown() {
   const { openCreateTagModal, CreateTagModal } = useTag()
 
   const handleOpenCreateTagModal = useCallback(
-    e => {
+    (e: React.MouseEvent) => {
       e.preventDefault()
       openCreateTagModal()
     },
@@ -34,7 +35,7 @@ function TagsDropdown() {
   )
 
   const handleAddTagToSelectedProjects = useCallback(
-    (e, tagId) => {
+    (e: React.MouseEvent, tagId: string) => {
       e.preventDefault()
       const tag = tags.find(tag => tag._id === tagId)
       const projectIds = []
@@ -50,7 +51,7 @@ function TagsDropdown() {
   )
 
   const handleRemoveTagFromSelectedProjects = useCallback(
-    (e, tagId) => {
+    (e: React.MouseEvent, tagId: string) => {
       e.preventDefault()
       for (const selectedProject of selectedProjects) {
         removeProjectFromTagInView(tagId, selectedProject.id)
@@ -64,7 +65,7 @@ function TagsDropdown() {
   )
 
   const containsAllSelectedProjects = useCallback(
-    tag => {
+    (tag: Tag) => {
       for (const project of selectedProjects) {
         if (!(tag.project_ids || []).includes(project.id)) {
           return false

+ 5 - 3
services/web/frontend/js/features/project-list/components/use-is-ds-nav.tsx

@@ -6,9 +6,11 @@ import { createContext, type FC, type ReactNode, useContext } from 'react'
  */
 const DsNavStyleContext = createContext<boolean | undefined>(undefined)
 
-export const DsNavStyleProvider: FC<{
-  children: ReactNode
-}> = ({ children }) => (
+export const DsNavStyleProvider: FC<
+  React.PropsWithChildren<{
+    children: ReactNode
+  }>
+> = ({ children }) => (
   <DsNavStyleContext.Provider value>{children}</DsNavStyleContext.Provider>
 )
 

+ 1 - 1
services/web/frontend/js/features/project-list/context/project-list-context.tsx

@@ -293,7 +293,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
   }, [selectedProjectIds, visibleProjects])
 
   const selectOrUnselectAllProjects = useCallback(
-    checked => {
+    (checked: any) => {
       setSelectedProjectIds(prevSelectedProjectIds => {
         const selectedProjectIds = new Set(prevSelectedProjectIds)
         for (const project of visibleProjects) {

+ 4 - 4
services/web/frontend/js/features/project-list/hooks/use-tag.tsx

@@ -50,7 +50,7 @@ function useTag() {
   )
 
   const handleEditTag = useCallback(
-    (e, tagId) => {
+    (e: React.MouseEvent, tagId: string) => {
       e.preventDefault()
       const tag = find(tags, ['_id', tagId])
       if (tag) {
@@ -69,7 +69,7 @@ function useTag() {
   )
 
   const handleDeleteTag = useCallback(
-    (e, tagId) => {
+    (e: React.MouseEvent, tagId: string) => {
       e.preventDefault()
       const tag = find(tags, ['_id', tagId])
       if (tag) {
@@ -80,7 +80,7 @@ function useTag() {
   )
 
   const onDelete = useCallback(
-    tagId => {
+    (tagId: string) => {
       deleteTag(tagId)
       setDeletingTag(undefined)
     },
@@ -88,7 +88,7 @@ function useTag() {
   )
 
   const handleManageTag = useCallback(
-    (e, tagId) => {
+    (e: React.MouseEvent, tagId: string) => {
       e.preventDefault()
       const tag = find(tags, ['_id', tagId])
       if (tag) {

+ 5 - 4
services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx

@@ -36,7 +36,7 @@ export const ReviewPanelAddComment = memo<{
   }, [view, threadId])
 
   const submitForm = useCallback(
-    async message => {
+    async (message: string) => {
       setSubmitting(true)
 
       const content = view.state.sliceDoc(from, to)
@@ -85,14 +85,15 @@ export const ReviewPanelAddComment = memo<{
   // We cannot use the autofocus attribute as we need to wait until the parent element
   // has been positioned (with the "top" attribute) to avoid scrolling to the initial
   // position of the element
-  const observerCallback = useCallback(mutationList => {
+  const observerCallback = useCallback((mutationList: MutationRecord[]) => {
     if (hasBeenFocused.current) {
       return
     }
 
     for (const mutation of mutationList) {
-      if (mutation.target.style.top) {
-        const textArea = mutation.target.getElementsByTagName('textarea')[0]
+      const target = mutation.target as HTMLElement
+      if (target.style.top) {
+        const textArea = target.getElementsByTagName('textarea')[0]
         if (textArea) {
           textArea.focus()
           hasBeenFocused.current = true

+ 2 - 2
services/web/frontend/js/features/review-panel-new/components/review-panel-comment-content.tsx

@@ -1,4 +1,4 @@
-import { memo, useCallback, useState } from 'react'
+import { Dispatch, memo, SetStateAction, useCallback, useState } from 'react'
 import { Change, CommentOperation } from '../../../../../types/change'
 import { ReviewPanelMessage } from './review-panel-message'
 import { useTranslation } from 'react-i18next'
@@ -41,7 +41,7 @@ export const ReviewPanelCommentContent = memo<{
     const [submitting, setSubmitting] = useState(false)
 
     const handleSubmit = useCallback(
-      (content, setContent) => {
+      (content: string, setContent: Dispatch<SetStateAction<string>>) => {
         if (!onReply || submitting) {
           return
         }

+ 16 - 14
services/web/frontend/js/features/review-panel-new/components/review-panel-entry.tsx

@@ -16,19 +16,21 @@ import { EditorSelection } from '@codemirror/state'
 import MaterialIcon from '@/shared/components/material-icon'
 import { OFFSET_FOR_ENTRIES_ABOVE } from '../utils/position-items'
 
-export const ReviewPanelEntry: FC<{
-  position: number
-  op: AnyOperation
-  docId: string
-  top?: number
-  className?: string
-  selectLineOnFocus?: boolean
-  hoverRanges?: boolean
-  disabled?: boolean
-  onEnterEntryIndicator?: () => void
-  onLeaveEntryIndicator?: () => void
-  entryIndicator?: 'comment' | 'edit'
-}> = ({
+export const ReviewPanelEntry: FC<
+  React.PropsWithChildren<{
+    position: number
+    op: AnyOperation
+    docId: string
+    top?: number
+    className?: string
+    selectLineOnFocus?: boolean
+    hoverRanges?: boolean
+    disabled?: boolean
+    onEnterEntryIndicator?: () => void
+    onLeaveEntryIndicator?: () => void
+    entryIndicator?: 'comment' | 'edit'
+  }>
+> = ({
   children,
   position,
   top,
@@ -58,7 +60,7 @@ export const ReviewPanelEntry: FC<{
   }, [setReviewPanelOpen])
 
   const selectEntry = useCallback(
-    event => {
+    (event: React.FocusEvent | React.MouseEvent) => {
       setFocused(true)
 
       if (event.target instanceof HTMLTextAreaElement) {

+ 3 - 1
services/web/frontend/js/features/review-panel-new/context/changes-users-context.tsx

@@ -26,7 +26,9 @@ export const ChangesUsersContext = createContext<ChangesUsers | undefined>(
   undefined
 )
 
-export const ChangesUsersProvider: FC = ({ children }) => {
+export const ChangesUsersProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const { _id: projectId, members, owner } = useProjectContext()
   const { isRestrictedTokenMember } = useEditorContext()
 

+ 1 - 1
services/web/frontend/js/features/review-panel-new/context/ranges-context.tsx

@@ -78,7 +78,7 @@ const buildRanges = (currentDocument: DocumentContainer | null) => {
 
 const RangesActionsContext = createContext<RangesActions | undefined>(undefined)
 
-export const RangesProvider: FC = ({ children }) => {
+export const RangesProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const view = useCodeMirrorViewContext()
   const { projectId } = useIdeReactContext()
   const { currentDocument } = useEditorManagerContext()

+ 3 - 1
services/web/frontend/js/features/review-panel-new/context/review-panel-providers.tsx

@@ -5,7 +5,9 @@ import { TrackChangesStateProvider } from './track-changes-state-context'
 import { ThreadsProvider } from './threads-context'
 import { ReviewPanelViewProvider } from './review-panel-view-context'
 
-export const ReviewPanelProviders: FC = ({ children }) => {
+export const ReviewPanelProviders: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   return (
     <ReviewPanelViewProvider>
       <ChangesUsersProvider>

+ 3 - 1
services/web/frontend/js/features/review-panel-new/context/review-panel-view-context.tsx

@@ -20,7 +20,9 @@ const ReviewPanelViewActionsContext = createContext<ViewActions | undefined>(
   undefined
 )
 
-export const ReviewPanelViewProvider: FC = ({ children }) => {
+export const ReviewPanelViewProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [view, setView] = useState<View>('cur_file')
 
   const actions = useMemo(

+ 2 - 2
services/web/frontend/js/features/review-panel-new/context/threads-context.tsx

@@ -48,7 +48,7 @@ const ThreadsActionsContext = createContext<ThreadsActions | undefined>(
   undefined
 )
 
-export const ThreadsProvider: FC = ({ children }) => {
+export const ThreadsProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const { _id: projectId } = useProjectContext()
   const { currentDocument } = useEditorManagerContext()
   const { isRestrictedTokenMember } = useEditorContext()
@@ -225,7 +225,7 @@ export const ThreadsProvider: FC = ({ children }) => {
   useSocketListener(
     socket,
     'new-comment-threads',
-    useCallback(threads => {
+    useCallback((threads: any) => {
       setData(prevState => {
         const newThreads = { ...prevState }
         for (const threadId of Object.keys(threads)) {

+ 3 - 1
services/web/frontend/js/features/review-panel-new/context/track-changes-state-context.tsx

@@ -44,7 +44,9 @@ const TrackChangesStateActionsContext = createContext<
   TrackChangesStateActions | undefined
 >(undefined)
 
-export const TrackChangesStateProvider: FC = ({ children }) => {
+export const TrackChangesStateProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const permissions = usePermissionsContext()
   const { socket } = useConnectionContext()
   const project = useProjectContext()

+ 2 - 2
services/web/frontend/js/features/share-project-modal/components/share-project-modal.tsx

@@ -126,7 +126,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
   }, [handleHide, inFlight])
 
   // update `error` and `inFlight` while sending a request
-  const monitorRequest = useCallback(request => {
+  const monitorRequest = useCallback((request: () => any) => {
     setError(undefined)
     setInFlight(true)
 
@@ -149,7 +149,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
 
   // merge the new data with the old project data
   const updateProject = useCallback(
-    data => Object.assign(project, data),
+    (data: ProjectContextUpdateValue) => Object.assign(project, data),
     [project]
   )
 

+ 28 - 13
services/web/frontend/js/features/source-editor/components/codemirror-search-form.tsx

@@ -2,7 +2,15 @@ import {
   useCodeMirrorStateContext,
   useCodeMirrorViewContext,
 } from './codemirror-context'
-import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import {
+  FC,
+  FormEvent,
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react'
 import { runScopeHandlers } from '@codemirror/view'
 import {
   closeSearchPanel,
@@ -49,7 +57,7 @@ type MatchPositions = {
   interrupted: boolean
 }
 
-const CodeMirrorSearchForm: FC = () => {
+const CodeMirrorSearchForm: FC<React.PropsWithChildren> = () => {
   const view = useCodeMirrorViewContext()
   const state = useCodeMirrorStateContext()
   const { setProjectSearchIsOpen } = useLayoutContext()
@@ -78,7 +86,7 @@ const CodeMirrorSearchForm: FC = () => {
 
   const newEditor = useIsNewEditorEnabled()
 
-  const handleInputRef = useCallback(node => {
+  const handleInputRef = useCallback((node: HTMLInputElement) => {
     inputRef.current = node
 
     // focus the search input when the panel opens
@@ -88,11 +96,11 @@ const CodeMirrorSearchForm: FC = () => {
     }
   }, [])
 
-  const handleReplaceRef = useCallback(node => {
+  const handleReplaceRef = useCallback((node: HTMLInputElement) => {
     replaceRef.current = node
   }, [])
 
-  const handleSubmit = useCallback(event => {
+  const handleSubmit = useCallback((event: FormEvent) => {
     event.preventDefault()
   }, [])
 
@@ -125,8 +133,8 @@ const CodeMirrorSearchForm: FC = () => {
   }, [handleChange, state, view])
 
   const handleFormKeyDown = useCallback(
-    event => {
-      if (runScopeHandlers(view, event, 'search-panel')) {
+    (event: React.KeyboardEvent<HTMLFormElement>) => {
+      if (runScopeHandlers(view, event.nativeEvent, 'search-panel')) {
         event.preventDefault()
       }
     },
@@ -135,7 +143,7 @@ const CodeMirrorSearchForm: FC = () => {
 
   // Returns true if the event was handled, false otherwise
   const handleEmacsNavigation = useCallback(
-    event => {
+    (event: KeyboardEvent) => {
       const emacsCtrlSeq =
         emacsKeybindingsActive &&
         event.ctrlKey &&
@@ -163,7 +171,14 @@ const CodeMirrorSearchForm: FC = () => {
           event.stopPropagation()
           event.preventDefault()
           closeSearchPanel(view)
-          document.dispatchEvent(new CustomEvent('cm:emacs-close-search-panel'))
+          // Wait for the search panel to close before moving the cursor
+          window.setTimeout(
+            () =>
+              document.dispatchEvent(
+                new CustomEvent('cm:emacs-close-search-panel')
+              ),
+            0
+          )
           return true
         }
         default: {
@@ -175,7 +190,7 @@ const CodeMirrorSearchForm: FC = () => {
   )
 
   const handleSearchKeyDown = useCallback(
-    event => {
+    (event: React.KeyboardEvent<HTMLInputElement>) => {
       switch (event.key) {
         case 'Enter':
           event.preventDefault()
@@ -191,13 +206,13 @@ const CodeMirrorSearchForm: FC = () => {
           }
           break
       }
-      handleEmacsNavigation(event)
+      handleEmacsNavigation(event.nativeEvent)
     },
     [view, handleEmacsNavigation, emacsKeybindingsActive]
   )
 
   const handleReplaceKeyDown = useCallback(
-    event => {
+    (event: React.KeyboardEvent<HTMLInputElement>) => {
       switch (event.key) {
         case 'Enter':
           event.preventDefault()
@@ -216,7 +231,7 @@ const CodeMirrorSearchForm: FC = () => {
           }
         }
       }
-      handleEmacsNavigation(event)
+      handleEmacsNavigation(event.nativeEvent)
     },
     [view, handleEmacsNavigation]
   )

+ 1 - 1
services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx

@@ -106,7 +106,7 @@ const Toolbar = memo(function Toolbar() {
   // calculate overflow when buttons change
   const observerRef = useRef<MutationObserver | null>(null)
   const handleButtons = useCallback(
-    node => {
+    (node: HTMLDivElement) => {
       if (!('MutationObserver' in window)) {
         return
       }

+ 1 - 1
services/web/frontend/js/features/source-editor/components/codemirror-view.tsx

@@ -10,7 +10,7 @@ function CodeMirrorView() {
 
   // append the editor view dom to the container node when mounted
   const containerRef = useCallback(
-    node => {
+    (node: HTMLDivElement) => {
       if (node) {
         node.appendChild(view.dom)
       }

+ 2 - 2
services/web/frontend/js/features/source-editor/components/command-tooltip/href-tooltip.tsx

@@ -1,4 +1,4 @@
-import { FC, useCallback, useEffect, useRef, useState } from 'react'
+import { FC, FormEvent, useCallback, useEffect, useRef, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import {
   useCodeMirrorStateContext,
@@ -71,7 +71,7 @@ export const HrefTooltipContent: FC = () => {
   }, [view])
 
   const handleSubmit = useCallback(
-    event => {
+    (event: FormEvent) => {
       event.preventDefault()
       view.dispatch(closeCommandTooltip())
       view.focus()

+ 1 - 1
services/web/frontend/js/features/source-editor/components/editor-switch.tsx

@@ -14,7 +14,7 @@ function EditorSwitch() {
   const richTextAvailable = openDocName ? isValidTeXFile(openDocName) : false
 
   const handleChange = useCallback(
-    event => {
+    (event: ChangeEvent<HTMLInputElement>) => {
       const editorType = event.target.value
 
       switch (editorType) {

+ 3 - 1
services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-context.tsx

@@ -80,7 +80,9 @@ const FigureModalExistingFigureContext = createContext<
   | undefined
 >(undefined)
 
-export const FigureModalProvider: FC = ({ children }) => {
+export const FigureModalProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [state, dispatch] = useReducer(reducer, {
     source: FigureModalSource.NONE,
     helpShown: false,

+ 4 - 1
services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-help.tsx

@@ -1,7 +1,10 @@
 import { FC } from 'react'
 import { Trans, useTranslation } from 'react-i18next'
 
-const LearnWikiLink: FC<{ article: string }> = ({ article, children }) => {
+const LearnWikiLink: FC<React.PropsWithChildren<{ article: string }>> = ({
+  article,
+  children,
+}) => {
   return <a href={`/learn/latex/${article}`}>{children}</a>
 }
 

+ 2 - 1
services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal.tsx

@@ -104,7 +104,8 @@ const FigureModalContent = () => {
   const hide = useCallback(() => {
     dispatch({ source: FigureModalSource.NONE })
     view.requestMeasure()
-    view.focus()
+    // Wait for the modal to close before focusing the editor
+    window.setTimeout(() => view.focus(), 0)
   }, [dispatch, view])
 
   useEventListener(

+ 2 - 2
services/web/frontend/js/features/source-editor/components/table-generator/cell.tsx

@@ -176,8 +176,8 @@ export const Cell: FC<{
   }, [cellData.content, editing, view])
 
   const onInput = useCallback(
-    e => {
-      update(filterInput(e.target.value))
+    (e: React.FormEvent<HTMLTextAreaElement>) => {
+      update(filterInput((e.target as HTMLTextAreaElement).value))
     },
     [update, filterInput]
   )

+ 4 - 2
services/web/frontend/js/features/source-editor/components/table-generator/contexts/editing-context.tsx

@@ -38,7 +38,9 @@ export const useEditingContext = () => {
   return context
 }
 
-export const EditingContextProvider: FC = ({ children }) => {
+export const EditingContextProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const { table } = useTableContext()
   const [cellData, setCellData] = useState<EditingContextData | null>(null)
   const [initialContent, setInitialContent] = useState<string | undefined>(
@@ -85,7 +87,7 @@ export const EditingContextProvider: FC = ({ children }) => {
   }, [setCellData])
 
   const startEditing = useCallback(
-    (rowIndex: number, cellIndex: number, initialContent = undefined) => {
+    (rowIndex: number, cellIndex: number, initialContent?: string) => {
       if (cellData?.dirty) {
         // We're already editing something else
         commitCellData()

+ 3 - 1
services/web/frontend/js/features/source-editor/components/table-generator/contexts/selection-context.tsx

@@ -411,7 +411,9 @@ export const useSelectionContext = () => {
   return context
 }
 
-export const SelectionContextProvider: FC = ({ children }) => {
+export const SelectionContextProvider: FC<React.PropsWithChildren> = ({
+  children,
+}) => {
   const [selection, setSelection] = useState<TableSelection | null>(null)
   const [dragging, setDragging] = useState(false)
   return (

+ 9 - 7
services/web/frontend/js/features/source-editor/components/table-generator/contexts/table-context.tsx

@@ -34,13 +34,15 @@ const TableContext = createContext<
   | undefined
 >(undefined)
 
-export const TableProvider: FC<{
-  tableData: ParsedTableData
-  tableNode: SyntaxNode | null
-  tabularNode: SyntaxNode
-  view: EditorView
-  directTableChild?: boolean
-}> = ({
+export const TableProvider: FC<
+  React.PropsWithChildren<{
+    tableData: ParsedTableData
+    tableNode: SyntaxNode | null
+    tabularNode: SyntaxNode
+    view: EditorView
+    directTableChild?: boolean
+  }>
+> = ({
   tableData,
   children,
   tableNode,

+ 1 - 1
services/web/frontend/js/features/source-editor/components/table-generator/contexts/tabular-context.tsx

@@ -21,7 +21,7 @@ const TabularContext = createContext<
   | undefined
 >(undefined)
 
-export const TabularProvider: FC = ({ children }) => {
+export const TabularProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const ref = useRef<HTMLDivElement>(null)
   const [helpShown, setHelpShown] = useState(false)
   const [columnWidthModalShown, setColumnWidthModalShown] = useState(false)

+ 1 - 1
services/web/frontend/js/features/source-editor/components/table-generator/tabular.tsx

@@ -273,7 +273,7 @@ export const Tabular: FC<{
   )
 }
 
-const TabularWrapper: FC = () => {
+const TabularWrapper: FC<React.PropsWithChildren> = () => {
   const { setSelection, selection } = useSelectionContext()
   const { commitCellData, cellData } = useEditingContext()
   const { ref } = useTabularContext()

+ 9 - 7
services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar-button-menu.tsx

@@ -7,13 +7,15 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
 import MaterialIcon from '../../../../../shared/components/material-icon'
 import { useTabularContext } from '../contexts/tabular-context'
 
-export const ToolbarButtonMenu: FC<{
-  id: string
-  label: string
-  icon: string
-  disabled?: boolean
-  disabledLabel?: string
-}> = memo(function ButtonMenu({
+export const ToolbarButtonMenu: FC<
+  React.PropsWithChildren<{
+    id: string
+    label: string
+    icon: string
+    disabled?: boolean
+    disabledLabel?: string
+  }>
+> = memo(function ButtonMenu({
   icon,
   id,
   label,

+ 2 - 2
services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar-button.tsx

@@ -28,12 +28,12 @@ export const ToolbarButton = memo<{
   disabledLabel,
 }) {
   const view = useCodeMirrorViewContext()
-  const handleMouseDown = useCallback(event => {
+  const handleMouseDown = useCallback((event: React.MouseEvent) => {
     event.preventDefault()
   }, [])
 
   const handleClick = useCallback(
-    event => {
+    (event: React.MouseEvent) => {
       if (command) {
         emitTableGeneratorEvent(view, id)
         event.preventDefault()

+ 20 - 16
services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar-dropdown.tsx

@@ -9,16 +9,18 @@ import { emitTableGeneratorEvent } from '../analytics'
 import { useCodeMirrorViewContext } from '../../codemirror-context'
 import classNames from 'classnames'
 
-export const ToolbarDropdown: FC<{
-  id: string
-  label?: string
-  btnClassName?: string
-  icon?: string
-  tooltip?: string
-  disabled?: boolean
-  disabledTooltip?: string
-  showCaret?: boolean
-}> = ({
+export const ToolbarDropdown: FC<
+  React.PropsWithChildren<{
+    id: string
+    label?: string
+    btnClassName?: string
+    icon?: string
+    tooltip?: string
+    disabled?: boolean
+    disabledTooltip?: string
+    showCaret?: boolean
+  }>
+> = ({
   id,
   label,
   children,
@@ -112,12 +114,14 @@ export const ToolbarDropdown: FC<{
 }
 
 export const ToolbarDropdownItem: FC<
-  Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> & {
-    command: () => void
-    id: string
-    icon?: string
-    active?: boolean
-  }
+  React.PropsWithChildren<
+    Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> & {
+      command: () => void
+      id: string
+      icon?: string
+      active?: boolean
+    }
+  >
 > = ({ children, command, id, icon, active, ...props }) => {
   const view = useCodeMirrorViewContext()
   const onClick = useCallback(() => {

+ 8 - 6
services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx

@@ -8,12 +8,14 @@ import { EditorView } from '@codemirror/view'
 import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
 import { useCodeMirrorViewContext } from '../codemirror-context'
 
-export const ToolbarButtonMenu: FC<{
-  id: string
-  label: string
-  icon: React.ReactNode
-  altCommand?: (view: EditorView) => void
-}> = memo(function ButtonMenu({ icon, id, label, altCommand, children }) {
+export const ToolbarButtonMenu: FC<
+  React.PropsWithChildren<{
+    id: string
+    label: string
+    icon: React.ReactNode
+    altCommand?: (view: EditorView) => void
+  }>
+> = memo(function ButtonMenu({ icon, id, label, altCommand, children }) {
   const target = useRef<any>(null)
   const { open, onToggle, ref } = useDropdown()
   const view = useCodeMirrorViewContext()

+ 8 - 7
services/web/frontend/js/features/source-editor/components/toolbar/overflow.tsx

@@ -6,14 +6,15 @@ import { useCodeMirrorViewContext } from '../codemirror-context'
 import OLOverlay from '@/features/ui/components/ol/ol-overlay'
 import OLPopover from '@/features/ui/components/ol/ol-popover'
 
-export const ToolbarOverflow: FC<{
-  overflowed: boolean
-  overflowOpen: boolean
-  setOverflowOpen: (open: boolean) => void
-  overflowRef?: React.Ref<HTMLDivElement>
-}> = ({ overflowed, overflowOpen, setOverflowOpen, overflowRef, children }) => {
+export const ToolbarOverflow: FC<
+  React.PropsWithChildren<{
+    overflowed: boolean
+    overflowOpen: boolean
+    setOverflowOpen: (open: boolean) => void
+    overflowRef?: React.Ref<HTMLDivElement>
+  }>
+> = ({ overflowed, overflowOpen, setOverflowOpen, overflowRef, children }) => {
   const { t } = useTranslation()
-
   const buttonRef = useRef<HTMLButtonElement>(null)
   const keyboardInputRef = useRef(false)
   const view = useCodeMirrorViewContext()

+ 2 - 2
services/web/frontend/js/features/source-editor/components/toolbar/toolbar-button.tsx

@@ -31,12 +31,12 @@ export const ToolbarButton = memo<{
 }) {
   const view = useCodeMirrorViewContext()
 
-  const handleMouseDown = useCallback(event => {
+  const handleMouseDown = useCallback((event: React.MouseEvent) => {
     event.preventDefault()
   }, [])
 
   const handleClick = useCallback(
-    event => {
+    (event: React.MouseEvent) => {
       emitToolbarEvent(view, id)
       if (command) {
         event.preventDefault()

+ 5 - 5
services/web/frontend/js/features/source-editor/extensions/spelling/context-menu.tsx

@@ -1,3 +1,4 @@
+import { createRoot } from 'react-dom/client'
 import {
   StateField,
   StateEffect,
@@ -12,7 +13,6 @@ import {
   getSpellCheckLanguage,
 } from '@/features/source-editor/extensions/spelling/index'
 import { sendMB } from '@/infrastructure/event-tracking'
-import ReactDOM from 'react-dom'
 import { SpellingSuggestions } from '@/features/source-editor/extensions/spelling/spelling-suggestions'
 import { SplitTestProvider } from '@/shared/context/split-test-context'
 import { addLearnedWord } from '@/features/source-editor/extensions/spelling/learned-words'
@@ -159,7 +159,8 @@ const createSpellingSuggestionList = (word: Word) => (view: EditorView) => {
   const dom = document.createElement('div')
   dom.classList.add('ol-cm-spelling-context-menu-tooltip')
 
-  ReactDOM.render(
+  const root = createRoot(dom)
+  root.render(
     <SplitTestProvider>
       <SpellingSuggestions
         word={word}
@@ -235,12 +236,11 @@ const createSpellingSuggestionList = (word: Word) => (view: EditorView) => {
           })
         }}
       />
-    </SplitTestProvider>,
-    dom
+    </SplitTestProvider>
   )
 
   const destroy = () => {
-    ReactDOM.unmountComponentAtNode(dom)
+    root.unmount()
   }
 
   return { dom, destroy }

+ 4 - 4
services/web/frontend/js/features/source-editor/extensions/visual/pasted-content.tsx

@@ -7,7 +7,7 @@ import {
 import { Decoration, EditorView, WidgetType } from '@codemirror/view'
 import { undo } from '@codemirror/commands'
 import { ancestorNodeOfType } from '../../utils/tree-operations/ancestors'
-import ReactDOM from 'react-dom'
+import { createRoot } from 'react-dom/client'
 import { PastedContentMenu } from '../../components/paste-html/pasted-content-menu'
 import { SplitTestProvider } from '../../../../shared/context/split-test-context'
 
@@ -171,7 +171,8 @@ class PastedContentMenuWidget extends WidgetType {
 
   toDOM(view: EditorView) {
     const element = document.createElement('span')
-    ReactDOM.render(
+    const root = createRoot(element)
+    root.render(
       <SplitTestProvider>
         <PastedContentMenu
           insertPastedContent={this.insertPastedContent}
@@ -179,8 +180,7 @@ class PastedContentMenuWidget extends WidgetType {
           formatted={this.formatted}
           pastedContent={this.pastedContent}
         />
-      </SplitTestProvider>,
-      element
+      </SplitTestProvider>
     )
     return element
   }

+ 18 - 4
services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/tabular.tsx

@@ -1,10 +1,12 @@
+import { createRoot, Root } from 'react-dom/client'
 import { EditorView, WidgetType } from '@codemirror/view'
 import { SyntaxNode } from '@lezer/common'
-import * as ReactDOM from 'react-dom'
 import { Tabular } from '../../../components/table-generator/tabular'
 import { ParsedTableData } from '../../../components/table-generator/utils'
 
 export class TabularWidget extends WidgetType {
+  static roots: WeakMap<HTMLElement, Root> = new WeakMap()
+
   constructor(
     private parsedTableData: ParsedTableData,
     private tabularNode: SyntaxNode,
@@ -15,13 +17,21 @@ export class TabularWidget extends WidgetType {
     super()
   }
 
+  renderInDOMContainer(children: React.ReactNode, element: HTMLElement) {
+    const root = TabularWidget.roots.get(element) || createRoot(element)
+    if (!TabularWidget.roots.has(element)) {
+      TabularWidget.roots.set(element, root)
+    }
+    root.render(children)
+  }
+
   toDOM(view: EditorView) {
     const element = document.createElement('div')
     element.classList.add('ol-cm-tabular')
     if (this.tableNode) {
       element.classList.add('ol-cm-environment-table')
     }
-    ReactDOM.render(
+    this.renderInDOMContainer(
       <Tabular
         view={view}
         tabularNode={this.tabularNode}
@@ -46,7 +56,7 @@ export class TabularWidget extends WidgetType {
   }
 
   updateDOM(element: HTMLElement, view: EditorView): boolean {
-    ReactDOM.render(
+    this.renderInDOMContainer(
       <Tabular
         view={view}
         tabularNode={this.tabularNode}
@@ -68,6 +78,10 @@ export class TabularWidget extends WidgetType {
   }
 
   destroy(element: HTMLElement) {
-    ReactDOM.unmountComponentAtNode(element)
+    const root = TabularWidget.roots.get(element)
+    if (root) {
+      TabularWidget.roots.delete(element)
+      root.unmount()
+    }
   }
 }

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov