feat: migrate to React 19
Upgrade React 18.3.1 → 19.2.4 with full type compatibility. Changes: - react, react-dom → ^19.0.0 - @types/react, @types/react-dom → ^19.0.0 - lucide-react → ^0.577.0 (React 19 type fixes) - @tiptap/* → ^3.20.4 (React 19 support) - useRef calls now require explicit initial value (undefined) - RefObject types updated for React 19 (includes null) - MutableRefObject → RefObject (deprecated in 19) - act() import moved from react-dom/test-utils to react - Scoped JSX namespace imports added where needed
This commit is contained in:
parent
e0d4782c80
commit
47dac2e8b5
28 changed files with 747 additions and 748 deletions
20
package.json
20
package.json
|
|
@ -112,11 +112,11 @@
|
||||||
"@sentry/electron": "^7.10.0",
|
"@sentry/electron": "^7.10.0",
|
||||||
"@sentry/react": "^10.45.0",
|
"@sentry/react": "^10.45.0",
|
||||||
"@tanstack/react-virtual": "^3.10.8",
|
"@tanstack/react-virtual": "^3.10.8",
|
||||||
"@tiptap/extension-placeholder": "^3.20.1",
|
"@tiptap/extension-placeholder": "^3.20.4",
|
||||||
"@tiptap/markdown": "^3.20.1",
|
"@tiptap/markdown": "^3.20.4",
|
||||||
"@tiptap/pm": "^3.20.1",
|
"@tiptap/pm": "^3.20.4",
|
||||||
"@tiptap/react": "^3.20.1",
|
"@tiptap/react": "^3.20.4",
|
||||||
"@tiptap/starter-kit": "^3.20.1",
|
"@tiptap/starter-kit": "^3.20.4",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
"@xterm/addon-web-links": "^0.12.0",
|
"@xterm/addon-web-links": "^0.12.0",
|
||||||
"@xterm/xterm": "^6.0.0",
|
"@xterm/xterm": "^6.0.0",
|
||||||
|
|
@ -135,13 +135,13 @@
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"idb-keyval": "^6.2.2",
|
"idb-keyval": "^6.2.2",
|
||||||
"isbinaryfile": "^6.0.0",
|
"isbinaryfile": "^6.0.0",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.577.0",
|
||||||
"mdast-util-to-hast": "^13.2.1",
|
"mdast-util-to-hast": "^13.2.1",
|
||||||
"mermaid": "^11.12.3",
|
"mermaid": "^11.12.3",
|
||||||
"node-diff3": "^3.2.0",
|
"node-diff3": "^3.2.0",
|
||||||
"node-pty": "^1.1.0",
|
"node-pty": "^1.1.0",
|
||||||
"react": "^18.3.1",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^19.0.0",
|
||||||
"react-grid-layout": "^2.2.2",
|
"react-grid-layout": "^2.2.2",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-resizable": "^3.1.3",
|
"react-resizable": "^3.1.3",
|
||||||
|
|
@ -170,8 +170,8 @@
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/mdast": "^4.0.4",
|
"@types/mdast": "^4.0.4",
|
||||||
"@types/node": "^25.0.7",
|
"@types/node": "^25.0.7",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
"@types/ssh2": "^1.15.5",
|
"@types/ssh2": "^1.15.5",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"@vitest/coverage-v8": "^3.1.4",
|
"@vitest/coverage-v8": "^3.1.4",
|
||||||
|
|
|
||||||
1393
pnpm-lock.yaml
1393
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { type JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { isNearBottom, useAutoScrollBottom } from '@renderer/hooks/useAutoScrollBottom';
|
import { isNearBottom, useAutoScrollBottom } from '@renderer/hooks/useAutoScrollBottom';
|
||||||
import { useTabNavigationController } from '@renderer/hooks/useTabNavigationController';
|
import { useTabNavigationController } from '@renderer/hooks/useTabNavigationController';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { JSX } from 'react';
|
||||||
/**
|
/**
|
||||||
* Empty state for ChatHistory when no conversation exists.
|
* Empty state for ChatHistory when no conversation exists.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { type JSX } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getHighlightProps,
|
getHighlightProps,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { JSX } from 'react';
|
||||||
/**
|
/**
|
||||||
* Loading skeleton for ChatHistory while conversation is loading.
|
* Loading skeleton for ChatHistory while conversation is loading.
|
||||||
* Industrial shimmer with organic line widths — no generic pulse.
|
* Industrial shimmer with organic line widths — no generic pulse.
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ export const AssessmentBadge = ({ assessment, metricKey }: AssessmentBadgeProps)
|
||||||
const [showTooltip, setShowTooltip] = useState(false);
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
const [tooltipPos, setTooltipPos] = useState({ top: 0, left: 0 });
|
const [tooltipPos, setTooltipPos] = useState({ top: 0, left: 0 });
|
||||||
const badgeRef = useRef<HTMLSpanElement>(null);
|
const badgeRef = useRef<HTMLSpanElement>(null);
|
||||||
const enterTimer = useRef<ReturnType<typeof setTimeout>>();
|
const enterTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
const leaveTimer = useRef<ReturnType<typeof setTimeout>>();
|
const leaveTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
|
|
||||||
const handleMouseEnter = useCallback(() => {
|
const handleMouseEnter = useCallback(() => {
|
||||||
if (!explanation) return;
|
if (!explanation) return;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export const AdvancedSection = ({
|
||||||
const checkForUpdates = useStore((s) => s.checkForUpdates);
|
const checkForUpdates = useStore((s) => s.checkForUpdates);
|
||||||
|
|
||||||
// Auto-revert "not-available" / "error" status back to idle after a brief display
|
// Auto-revert "not-available" / "error" status back to idle after a brief display
|
||||||
const revertTimerRef = useRef<ReturnType<typeof setTimeout>>();
|
const revertTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (updateStatus === 'not-available' || updateStatus === 'error') {
|
if (updateStatus === 'not-available' || updateStatus === 'error') {
|
||||||
revertTimerRef.current = setTimeout(() => {
|
revertTimerRef.current = setTimeout(() => {
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ export const ConfigEditorDialog = ({
|
||||||
}: ConfigEditorDialogProps): React.JSX.Element | null => {
|
}: ConfigEditorDialogProps): React.JSX.Element | null => {
|
||||||
const editorRef = useRef<HTMLDivElement>(null);
|
const editorRef = useRef<HTMLDivElement>(null);
|
||||||
const viewRef = useRef<EditorView | null>(null);
|
const viewRef = useRef<EditorView | null>(null);
|
||||||
const saveTimerRef = useRef<ReturnType<typeof setTimeout>>();
|
const saveTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
const savedRevertTimerRef = useRef<ReturnType<typeof setTimeout>>();
|
const savedRevertTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle');
|
const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle');
|
||||||
const [jsonError, setJsonError] = useState<string | null>(null);
|
const [jsonError, setJsonError] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { type JSX, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import type { CSSProperties, MutableRefObject, PropsWithChildren, Ref } from 'react';
|
import type { CSSProperties, MutableRefObject, PropsWithChildren, Ref } from 'react';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import {
|
||||||
|
type JSX,
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { MemberBadge } from '@renderer/components/team/MemberBadge';
|
import { MemberBadge } from '@renderer/components/team/MemberBadge';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { type JSX, memo, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
|
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
|
||||||
import { CopyButton } from '@renderer/components/common/CopyButton';
|
import { CopyButton } from '@renderer/components/common/CopyButton';
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,6 @@ export const MarkdownSplitView = React.memo(function MarkdownSplitView({
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Preview pane */}
|
{/* Preview pane */}
|
||||||
<div className="flex-1 overflow-hidden bg-surface">
|
<div className="flex-1 overflow-hidden bg-surface">
|
||||||
<MarkdownPreviewPane
|
<MarkdownPreviewPane
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export const SearchInFilesPanel = ({
|
||||||
const [expandedFiles, setExpandedFiles] = useState<Set<string>>(new Set());
|
const [expandedFiles, setExpandedFiles] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const debounceRef = useRef<ReturnType<typeof setTimeout>>();
|
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
// Monotonic request ID — prevents stale results from overwriting fresh ones
|
// Monotonic request ID — prevents stale results from overwriting fresh ones
|
||||||
const requestIdRef = useRef(0);
|
const requestIdRef = useRef(0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ export const ChangeReviewDialog = ({
|
||||||
const [selectionInfo, setSelectionInfo] = useState<EditorSelectionInfo | null>(null);
|
const [selectionInfo, setSelectionInfo] = useState<EditorSelectionInfo | null>(null);
|
||||||
const [containerRect, setContainerRect] = useState<DOMRect>(new DOMRect());
|
const [containerRect, setContainerRect] = useState<DOMRect>(new DOMRect());
|
||||||
const diffContentRef = useRef<HTMLDivElement>(null);
|
const diffContentRef = useRef<HTMLDivElement>(null);
|
||||||
const selectionTimerRef = useRef<ReturnType<typeof setTimeout>>();
|
const selectionTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
const activeSelectionFileRef = useRef<string | null>(null);
|
const activeSelectionFileRef = useRef<string | null>(null);
|
||||||
|
|
||||||
// EditorView map for all visible file editors
|
// EditorView map for all visible file editors
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ export const CodeMirrorDiffView = ({
|
||||||
const onContentChangedRef = useRef(onContentChanged);
|
const onContentChangedRef = useRef(onContentChanged);
|
||||||
const onViewChangeRef = useRef(onViewChange);
|
const onViewChangeRef = useRef(onViewChange);
|
||||||
const onSelectionChangeRef = useRef(onSelectionChange);
|
const onSelectionChangeRef = useRef(onSelectionChange);
|
||||||
const debounceTimer = useRef<ReturnType<typeof setTimeout>>();
|
const debounceTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onAcceptRef.current = onHunkAccepted;
|
onAcceptRef.current = onHunkAccepted;
|
||||||
onRejectRef.current = onHunkRejected;
|
onRejectRef.current = onHunkRejected;
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,9 @@ interface ContinuousScrollViewProps {
|
||||||
collapsedFiles?: Set<string>;
|
collapsedFiles?: Set<string>;
|
||||||
onToggleCollapse?: (filePath: string) => void;
|
onToggleCollapse?: (filePath: string) => void;
|
||||||
onVisibleFileChange: (filePath: string) => void;
|
onVisibleFileChange: (filePath: string) => void;
|
||||||
scrollContainerRef: React.RefObject<HTMLDivElement>;
|
scrollContainerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
editorViewMapRef: React.MutableRefObject<Map<string, EditorView>>;
|
editorViewMapRef: React.MutableRefObject<Map<string, EditorView>>;
|
||||||
isProgrammaticScroll: React.RefObject<boolean>;
|
isProgrammaticScroll: React.RefObject<boolean | null>;
|
||||||
teamName: string;
|
teamName: string;
|
||||||
memberName: string | undefined;
|
memberName: string | undefined;
|
||||||
fetchFileContent: (
|
fetchFileContent: (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, type ReactNode } from 'react';
|
import { Component, type JSX, type ReactNode } from 'react';
|
||||||
|
|
||||||
import { AlertTriangle } from 'lucide-react';
|
import { AlertTriangle } from 'lucide-react';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { type JSX, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { FileIcon } from '@renderer/components/team/editor/FileIcon';
|
import { FileIcon } from '@renderer/components/team/editor/FileIcon';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from 'react';
|
import { type JSX, useState } from 'react';
|
||||||
|
|
||||||
import { cn } from '@renderer/lib/utils';
|
import { cn } from '@renderer/lib/utils';
|
||||||
import { AlertTriangle, ChevronRight, Info, ShieldCheck, X } from 'lucide-react';
|
import { AlertTriangle, ChevronRight, Info, ShieldCheck, X } from 'lucide-react';
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createContext, type ReactNode } from 'react';
|
import { createContext, type JSX, type ReactNode } from 'react';
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Context Definition
|
// Context Definition
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ interface UseAutoScrollBottomOptions {
|
||||||
* ref instead of creating its own. Useful when the ref needs to be shared
|
* ref instead of creating its own. Useful when the ref needs to be shared
|
||||||
* with other hooks (e.g., navigation coordinator).
|
* with other hooks (e.g., navigation coordinator).
|
||||||
*/
|
*/
|
||||||
externalRef?: React.RefObject<HTMLDivElement>;
|
externalRef?: React.RefObject<HTMLDivElement | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When this value changes, reset isAtBottom state to true.
|
* When this value changes, reset isAtBottom state to true.
|
||||||
|
|
@ -56,7 +56,7 @@ interface UseAutoScrollBottomReturn {
|
||||||
/**
|
/**
|
||||||
* Ref to attach to the scroll container element.
|
* Ref to attach to the scroll container element.
|
||||||
*/
|
*/
|
||||||
scrollContainerRef: React.RefObject<HTMLDivElement>;
|
scrollContainerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get whether the user is currently at the bottom of the scroll container.
|
* Get whether the user is currently at the bottom of the scroll container.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ interface UseContinuousScrollNavOptions {
|
||||||
|
|
||||||
interface UseContinuousScrollNavReturn {
|
interface UseContinuousScrollNavReturn {
|
||||||
scrollToFile: (filePath: string) => void;
|
scrollToFile: (filePath: string) => void;
|
||||||
isProgrammaticScroll: RefObject<boolean>;
|
isProgrammaticScroll: RefObject<boolean | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useContinuousScrollNav(
|
export function useContinuousScrollNav(
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ export function useLazyFileContent(options: UseLazyFileContentOptions): UseLazyF
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Refs for loadFile/processQueue to avoid circular useCallback deps
|
// Refs for loadFile/processQueue to avoid circular useCallback deps
|
||||||
const loadFileRef = useRef<(fp: string) => Promise<void>>();
|
const loadFileRef = useRef<(fp: string) => Promise<void>>(undefined);
|
||||||
const processQueueRef = useRef<() => void>();
|
const processQueueRef = useRef<() => void>(undefined);
|
||||||
|
|
||||||
loadFileRef.current = async (filePath: string) => {
|
loadFileRef.current = async (filePath: string) => {
|
||||||
if (!shouldLoad(filePath)) return;
|
if (!shouldLoad(filePath)) return;
|
||||||
|
|
|
||||||
|
|
@ -64,15 +64,15 @@ interface UseTabNavigationControllerOptions {
|
||||||
/** Tab ID for consuming navigation */
|
/** Tab ID for consuming navigation */
|
||||||
tabId: string;
|
tabId: string;
|
||||||
/** Refs to AI group DOM elements */
|
/** Refs to AI group DOM elements */
|
||||||
aiGroupRefs: React.MutableRefObject<Map<string, HTMLElement>>;
|
aiGroupRefs: React.RefObject<Map<string, HTMLElement>>;
|
||||||
/** Refs to individual chat item DOM elements */
|
/** Refs to individual chat item DOM elements */
|
||||||
chatItemRefs: React.MutableRefObject<Map<string, HTMLElement>>;
|
chatItemRefs: React.RefObject<Map<string, HTMLElement>>;
|
||||||
/** Refs to individual tool item DOM elements */
|
/** Refs to individual tool item DOM elements */
|
||||||
toolItemRefs: React.MutableRefObject<Map<string, HTMLElement>>;
|
toolItemRefs: React.RefObject<Map<string, HTMLElement>>;
|
||||||
/** Function to expand an AI group (per-tab state) */
|
/** Function to expand an AI group (per-tab state) */
|
||||||
expandAIGroup: (groupId: string) => void;
|
expandAIGroup: (groupId: string) => void;
|
||||||
/** Ref to scroll container */
|
/** Ref to scroll container */
|
||||||
scrollContainerRef: React.RefObject<HTMLDivElement>;
|
scrollContainerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
/** Height of sticky elements at top of scroll container */
|
/** Height of sticky elements at top of scroll container */
|
||||||
stickyOffset?: number;
|
stickyOffset?: number;
|
||||||
/** Optional helper to ensure a target group is mounted (e.g., virtualized lists) */
|
/** Optional helper to ensure a target group is mounted (e.g., virtualized lists) */
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ interface UseVisibleAIGroupOptions {
|
||||||
onVisibleChange: (aiGroupId: string) => void;
|
onVisibleChange: (aiGroupId: string) => void;
|
||||||
threshold?: number; // Default 0.5
|
threshold?: number; // Default 0.5
|
||||||
/** Optional scroll container to observe against (important for nested scroll areas). */
|
/** Optional scroll container to observe against (important for nested scroll areas). */
|
||||||
rootRef?: RefObject<HTMLElement>;
|
rootRef?: RefObject<HTMLElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseVisibleAIGroupReturn {
|
interface UseVisibleAIGroupReturn {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { type RefObject, useCallback, useEffect, useRef } from 'react';
|
||||||
interface UseVisibleFileSectionOptions {
|
interface UseVisibleFileSectionOptions {
|
||||||
onVisibleFileChange: (filePath: string) => void;
|
onVisibleFileChange: (filePath: string) => void;
|
||||||
scrollContainerRef: RefObject<HTMLElement | null>;
|
scrollContainerRef: RefObject<HTMLElement | null>;
|
||||||
isProgrammaticScroll: RefObject<boolean>;
|
isProgrammaticScroll: RefObject<boolean | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseVisibleFileSectionReturn {
|
interface UseVisibleFileSectionReturn {
|
||||||
|
|
@ -18,7 +18,7 @@ export function useVisibleFileSection(
|
||||||
const visibleFilePaths = useRef<Set<string>>(new Set());
|
const visibleFilePaths = useRef<Set<string>>(new Set());
|
||||||
const elementRefs = useRef<Map<string, HTMLElement>>(new Map());
|
const elementRefs = useRef<Map<string, HTMLElement>>(new Map());
|
||||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
const observerRef = useRef<IntersectionObserver | null>(null);
|
||||||
const debounceRef = useRef<ReturnType<typeof setTimeout>>();
|
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
|
|
||||||
const updateTopmostVisible = useCallback(() => {
|
const updateTopmostVisible = useCallback(() => {
|
||||||
if (isProgrammaticScroll.current) return;
|
if (isProgrammaticScroll.current) return;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React, { act } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { useVisibleAIGroup } from '../../../src/renderer/hooks/useVisibleAIGroup';
|
import { useVisibleAIGroup } from '../../../src/renderer/hooks/useVisibleAIGroup';
|
||||||
|
|
@ -23,8 +22,9 @@ describe('useVisibleAIGroup', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses provided rootRef as IntersectionObserver root', async () => {
|
it('uses provided rootRef as IntersectionObserver root', async () => {
|
||||||
const observerSpy = vi.fn((cb: IntersectionObserverCallback, opts?: IntersectionObserverInit) =>
|
const observerSpy = vi.fn(
|
||||||
new FakeIntersectionObserver(cb, opts)
|
(cb: IntersectionObserverCallback, opts?: IntersectionObserverInit) =>
|
||||||
|
new FakeIntersectionObserver(cb, opts)
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.stubGlobal('IntersectionObserver', observerSpy as unknown as typeof IntersectionObserver);
|
vi.stubGlobal('IntersectionObserver', observerSpy as unknown as typeof IntersectionObserver);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue