From c03144226f23a2af432018bcaecbf6c50044b744 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 18:54:30 +0000 Subject: [PATCH 01/15] Initial plan From 3fbfd6b63544bdc0b571f7cd676740f7a89faed6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 18:59:42 +0000 Subject: [PATCH 02/15] Replace image rendering with markdown placeholders to fix flickering - Remove opacity-based virtualization from CellOutputs - Replace tags with text placeholders: ![Image: image/png] - Add CSS styling for image-placeholder class - Remove unused isVisible prop from CellOutputs component Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- src/web/client/CellContent.tsx | 19 ++++++++++--------- src/web/client/styles.ts | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/web/client/CellContent.tsx b/src/web/client/CellContent.tsx index 2205552..06b81d0 100644 --- a/src/web/client/CellContent.tsx +++ b/src/web/client/CellContent.tsx @@ -95,7 +95,7 @@ export function CellContent({ )} {showOutputs && cellType === 'code' && cell.outputs && cell.outputs.length > 0 && ( - + )} ); @@ -186,14 +186,13 @@ function getInlineChangeClass(type: 'unchanged' | 'added' | 'removed', side: 'ba interface CellOutputsProps { outputs: CellOutput[]; - isVisible?: boolean; } -function CellOutputs({ outputs, isVisible = true }: CellOutputsProps): React.ReactElement { - // Always render the actual outputs to prevent size changes that cause flickering. - // Use CSS visibility/opacity for performance optimization instead of conditional rendering. +function CellOutputs({ outputs }: CellOutputsProps): React.ReactElement { + // Always render outputs consistently without CSS changes that trigger ResizeObserver. + // Using text placeholders for images prevents browser decoding and size oscillation. return ( -
+
{outputs.map((output, i) => ( ))} @@ -210,12 +209,14 @@ function OutputItem({ output }: { output: CellOutput }): React.ReactElement | nu if ((output.output_type === 'display_data' || output.output_type === 'execute_result') && output.data) { const data = output.data; - // Try image first + // Use text placeholders for images instead of rendering actual tags. + // This prevents browser decoding overhead, ResizeObserver feedback loops, + // and flickering from invalid/broken image data. if (data['image/png']) { - return output; + return
![Image: image/png]
; } if (data['image/jpeg']) { - return output; + return
![Image: image/jpeg]
; } // HTML diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index ce43f5e..8725694 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -449,6 +449,20 @@ body { height: auto; } +.image-placeholder { + color: var(--text-secondary); + font-style: italic; + padding: 8px; + background: var(--bg-secondary); + border: 1px dashed var(--border-color); + border-radius: 4px; + font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; +} + +.error-output { + color: #f48771; +} + /* Auto-resolve banner */ .auto-resolve-banner { background: rgba(78, 201, 176, 0.1); From d6a4a9c14c3bfd435886d32d94899b720d552b00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:02:14 +0000 Subject: [PATCH 03/15] Refine image-placeholder styles per code review feedback - Add 'Courier New' as fallback font for better cross-platform support - Remove unrelated .error-output style to keep changes focused Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- src/web/client/styles.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index 8725694..bd1fd35 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -456,11 +456,7 @@ body { background: var(--bg-secondary); border: 1px dashed var(--border-color); border-radius: 4px; - font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; -} - -.error-output { - color: #f48771; + font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Courier New', monospace; } /* Auto-resolve banner */ From e43775c5e8b170f36772a563e45ce4982109db77 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:03:01 +0000 Subject: [PATCH 04/15] Improve image placeholder accessibility and maintainability - Extract ImagePlaceholder component with aria-label for screen readers - Add role="img" attribute for semantic HTML - Use helper function for consistent placeholder formatting Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- src/web/client/CellContent.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/web/client/CellContent.tsx b/src/web/client/CellContent.tsx index 06b81d0..bff8759 100644 --- a/src/web/client/CellContent.tsx +++ b/src/web/client/CellContent.tsx @@ -213,10 +213,10 @@ function OutputItem({ output }: { output: CellOutput }): React.ReactElement | nu // This prevents browser decoding overhead, ResizeObserver feedback loops, // and flickering from invalid/broken image data. if (data['image/png']) { - return
![Image: image/png]
; + return ; } if (data['image/jpeg']) { - return
![Image: image/jpeg]
; + return ; } // HTML @@ -240,3 +240,20 @@ function OutputItem({ output }: { output: CellOutput }): React.ReactElement | nu return null; } + +/** + * Renders a text placeholder for an image output. + * Uses markdown-style format to provide a consistent, stable representation. + */ +function ImagePlaceholder({ mimeType }: { mimeType: string }): React.ReactElement { + const placeholderText = `![Image: ${mimeType}]`; + return ( +
+            {placeholderText}
+        
+ ); +} From 0439e772652c4639f5ba22195dbe46d50c9888df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:04:00 +0000 Subject: [PATCH 05/15] Fix accessibility and CSS syntax issues per code review - Use
instead of
 for image placeholder to avoid semantic conflict with role="img"
- Add white-space: pre-wrap and font-size to CSS for proper formatting
- Use double quotes for font-family names containing spaces (CSS best practice)

Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com>
---
 src/web/client/CellContent.tsx | 4 ++--
 src/web/client/styles.ts       | 4 +++-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/web/client/CellContent.tsx b/src/web/client/CellContent.tsx
index bff8759..4e40ddb 100644
--- a/src/web/client/CellContent.tsx
+++ b/src/web/client/CellContent.tsx
@@ -248,12 +248,12 @@ function OutputItem({ output }: { output: CellOutput }): React.ReactElement | nu
 function ImagePlaceholder({ mimeType }: { mimeType: string }): React.ReactElement {
     const placeholderText = `![Image: ${mimeType}]`;
     return (
-        
             {placeholderText}
-        
+
); } diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index bd1fd35..bfa025a 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -456,7 +456,9 @@ body { background: var(--bg-secondary); border: 1px dashed var(--border-color); border-radius: 4px; - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Courier New', monospace; + font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace; + white-space: pre-wrap; + font-size: 12px; } /* Auto-resolve banner */ From 8115f6eae98cecaf8efaaf7dc21eba4bc5d7c747 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:04:56 +0000 Subject: [PATCH 06/15] Improve aria-label readability for screen readers - Convert MIME types to user-friendly format (e.g., 'PNG image output' instead of 'Image output of type image/png') - Add fallback for unknown image types Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- src/web/client/CellContent.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/web/client/CellContent.tsx b/src/web/client/CellContent.tsx index 4e40ddb..5287cc8 100644 --- a/src/web/client/CellContent.tsx +++ b/src/web/client/CellContent.tsx @@ -247,11 +247,15 @@ function OutputItem({ output }: { output: CellOutput }): React.ReactElement | nu */ function ImagePlaceholder({ mimeType }: { mimeType: string }): React.ReactElement { const placeholderText = `![Image: ${mimeType}]`; + // Convert MIME type to user-friendly label for screen readers + const imageType = mimeType === 'image/png' ? 'PNG' : + mimeType === 'image/jpeg' ? 'JPEG' : + mimeType.replace('image/', '').toUpperCase(); return (
{placeholderText}
From abc1e7785666f843a018a98b95b4ce28d50e75b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:07:15 +0000 Subject: [PATCH 07/15] Simplify ImagePlaceholder logic to remove unreachable code - Remove unreachable fallback case since only PNG and JPEG are supported - Simplify conditional to binary choice for clarity Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- src/web/client/CellContent.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/web/client/CellContent.tsx b/src/web/client/CellContent.tsx index 5287cc8..0983b6f 100644 --- a/src/web/client/CellContent.tsx +++ b/src/web/client/CellContent.tsx @@ -248,9 +248,7 @@ function OutputItem({ output }: { output: CellOutput }): React.ReactElement | nu function ImagePlaceholder({ mimeType }: { mimeType: string }): React.ReactElement { const placeholderText = `![Image: ${mimeType}]`; // Convert MIME type to user-friendly label for screen readers - const imageType = mimeType === 'image/png' ? 'PNG' : - mimeType === 'image/jpeg' ? 'JPEG' : - mimeType.replace('image/', '').toUpperCase(); + const imageType = mimeType === 'image/png' ? 'PNG' : 'JPEG'; return (
Date: Tue, 10 Feb 2026 19:08:03 +0000 Subject: [PATCH 08/15] Add JSDoc comment to clarify supported MIME types - Document that only image/png and image/jpeg are currently supported - Makes the intentional constraint explicit for future maintainers Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- src/web/client/CellContent.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/web/client/CellContent.tsx b/src/web/client/CellContent.tsx index 0983b6f..c31cb1a 100644 --- a/src/web/client/CellContent.tsx +++ b/src/web/client/CellContent.tsx @@ -244,6 +244,8 @@ function OutputItem({ output }: { output: CellOutput }): React.ReactElement | nu /** * Renders a text placeholder for an image output. * Uses markdown-style format to provide a consistent, stable representation. + * + * @param mimeType - Currently only 'image/png' or 'image/jpeg' are supported and passed by OutputItem */ function ImagePlaceholder({ mimeType }: { mimeType: string }): React.ReactElement { const placeholderText = `![Image: ${mimeType}]`; From ff0baf719fb699e0faf5443f1f0796143da4cad1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:50:04 +0000 Subject: [PATCH 09/15] Restore opacity-based virtualization optimization The previous fix removed the isVisible/opacity optimization entirely, causing all cells to render at full opacity. This made everything slow. Now: text placeholders (fixes flickering) + opacity optimization (fixes performance). Text placeholders have consistent dimensions, so opacity changes don't trigger ResizeObserver. Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- src/web/client/CellContent.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/web/client/CellContent.tsx b/src/web/client/CellContent.tsx index c31cb1a..ab853b5 100644 --- a/src/web/client/CellContent.tsx +++ b/src/web/client/CellContent.tsx @@ -95,7 +95,7 @@ export function CellContent({ )}
{showOutputs && cellType === 'code' && cell.outputs && cell.outputs.length > 0 && ( - + )}
); @@ -186,13 +186,14 @@ function getInlineChangeClass(type: 'unchanged' | 'added' | 'removed', side: 'ba interface CellOutputsProps { outputs: CellOutput[]; + isVisible?: boolean; } -function CellOutputs({ outputs }: CellOutputsProps): React.ReactElement { - // Always render outputs consistently without CSS changes that trigger ResizeObserver. - // Using text placeholders for images prevents browser decoding and size oscillation. +function CellOutputs({ outputs, isVisible = true }: CellOutputsProps): React.ReactElement { + // Text placeholders for images prevent ResizeObserver feedback loops. + // Opacity optimization improves performance for off-screen cells without triggering dimension changes. return ( -
+
{outputs.map((output, i) => ( ))} From 86bea3aa677a526cb97b255a6a95543555302ec7 Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Tue, 10 Feb 2026 14:18:11 -0600 Subject: [PATCH 10/15] =?UTF-8?q?[FIX]=20Perf=20improvements=20**1.=20`scr?= =?UTF-8?q?ollTop`=20state=20is=20tracked=20but=20never=20used=20in=20JSX*?= =?UTF-8?q?*=20=E2=80=94=20every=20scroll=20event=20calls=20`setScrollTop(?= =?UTF-8?q?)`,=20which=20forces=20a=20full=20`ConflictResolver`=20re-rende?= =?UTF-8?q?r.=20This=20is=20pure=20waste.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **2. No `React.memo` on `MergeRow`** — every state change in the parent (scroll, typing, ResizeObserver, drag) re-renders ALL `MergeRow` components. With 100+ rows, each keystroke triggers 100+ component renders. **3. No `React.memo` on `CellContent`** — even within a single MergeRow re-render, all 3 `CellContent` children fully re-render, including expensive diff computations and markdown rendering. **4. `getRefCallback(i)` creates a new closure on every render** — React sees a different `ref` function, triggers ResizeObserver unobserve/observe on every render for every row. **5. `DiffContent` runs `computeLineDiff` without `useMemo`** — expensive LCS computation runs on every render even when inputs haven't changed. **6. Inline drag handlers in MergeRow** — `onDragStart` closures are recreated each render, defeating any memoization on `CellContent`. The cascade on each keystroke: type → `setChoices` → ConflictResolver renders → ALL MergeRows render → ALL CellContent children render → ALL diffs recompute → ResizeObserver fires → `setRowHeights` → ANOTHER full render cycle. --- src/web/client/CellContent.tsx | 30 ++++++- src/web/client/ConflictResolver.tsx | 14 +++- src/web/client/MergeRow.tsx | 125 +++++++++++++++++++++------- 3 files changed, 133 insertions(+), 36 deletions(-) diff --git a/src/web/client/CellContent.tsx b/src/web/client/CellContent.tsx index ab853b5..f99065d 100644 --- a/src/web/client/CellContent.tsx +++ b/src/web/client/CellContent.tsx @@ -25,7 +25,7 @@ interface CellContentProps { isVisible?: boolean; // For lazy rendering optimization } -export function CellContent({ +export function CellContentInner({ cell, cellIndex, side, @@ -46,7 +46,8 @@ export function CellContent({ const source = normalizeCellSource(cell.source); const cellType = cell.cell_type; - const encodedCell = encodeURIComponent(JSON.stringify(cell)); + // Memoize expensive JSON serialization + const encodedCell = useMemo(() => encodeURIComponent(JSON.stringify(cell)), [cell]); const cellClasses = [ 'notebook-cell', @@ -124,7 +125,8 @@ interface DiffContentProps { } function DiffContent({ source, compareSource, side }: DiffContentProps): React.ReactElement { - const diff = computeLineDiff(compareSource, source); + // Memoize expensive LCS-based diff computation + const diff = useMemo(() => computeLineDiff(compareSource, source), [compareSource, source]); // Use the right side for display (shows the "new" content with change markers) const diffLines = diff.right; // Filter out empty alignment lines to avoid unnecessary whitespace @@ -262,3 +264,25 @@ function ImagePlaceholder({ mimeType }: { mimeType: string }): React.ReactElemen
); } + +/** + * Custom comparator for React.memo. + * Compares props that affect rendered output, treating drag handler reference + * changes as equal if their defined/undefined status hasn't changed. + */ +function areCellContentPropsEqual(prev: CellContentProps, next: CellContentProps): boolean { + if (prev.cell !== next.cell) return false; + if (prev.cellIndex !== next.cellIndex) return false; + if (prev.side !== next.side) return false; + if (prev.isConflict !== next.isConflict) return false; + if (prev.compareCell !== next.compareCell) return false; + if (prev.showOutputs !== next.showOutputs) return false; + if (prev.isVisible !== next.isVisible) return false; + // Drag handlers: only care if defined/undefined changed (affects draggable attribute), + // not about reference identity (the actual handler logic is stable) + if (Boolean(prev.onDragStart) !== Boolean(next.onDragStart)) return false; + if (Boolean(prev.onDragEnd) !== Boolean(next.onDragEnd)) return false; + return true; +} + +export const CellContent = React.memo(CellContentInner, areCellContentPropsEqual); diff --git a/src/web/client/ConflictResolver.tsx b/src/web/client/ConflictResolver.tsx index 9ce6c64..f43501b 100644 --- a/src/web/client/ConflictResolver.tsx +++ b/src/web/client/ConflictResolver.tsx @@ -115,7 +115,6 @@ export function ConflictResolver({ // Virtualization state const mainContentRef = useRef(null); const [visibleRange, setVisibleRange] = useState({ start: 0, end: INITIAL_VISIBLE_ROWS }); - const [scrollTop, setScrollTop] = useState(0); // Track actual row heights using ResizeObserver const [rowHeights, setRowHeights] = useState>(new Map()); @@ -326,8 +325,16 @@ export function ConflictResolver({ } }, []); - const getRefCallback = useCallback((index: number) => (element: HTMLDivElement | null) => { - registerRowRef(index, element); + const refCallbackCache = useRef(new Map void>()); + const getRefCallback = useCallback((index: number): (el: HTMLDivElement | null) => void => { + let cb = refCallbackCache.current.get(index); + if (!cb) { + cb = (element: HTMLDivElement | null) => { + registerRowRef(index, element); + }; + refCallbackCache.current.set(index, cb); + } + return cb; }, [registerRowRef]); // Adjust scroll position when rows are deleted to prevent "black spots" @@ -371,7 +378,6 @@ export function ConflictResolver({ const endIndex = Math.min(rows.length, rawEndIndex + VIRTUALIZATION_OVERSCAN_ROWS + 1); setVisibleRange({ start: startIndex, end: endIndex }); - setScrollTop(currentScrollTop); }; const element = mainContentRef.current; diff --git a/src/web/client/MergeRow.tsx b/src/web/client/MergeRow.tsx index 567b5ee..c97d43a 100644 --- a/src/web/client/MergeRow.tsx +++ b/src/web/client/MergeRow.tsx @@ -9,7 +9,7 @@ * 4. If user changes the selected branch after editing, show a warning */ -import React, { useState, useCallback, useEffect, useMemo } from 'react'; +import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'; import type { MergeRow as MergeRowType, NotebookCell, ResolutionChoice } from './types'; import { CellContent } from './CellContent'; import { normalizeCellSource } from '../../notebookUtils'; @@ -61,7 +61,7 @@ interface MergeRowProps { 'data-testid'?: string; } -export function MergeRow({ +export function MergeRowInner({ row, rowIndex, conflictIndex, @@ -90,6 +90,20 @@ export function MergeRow({ const [pendingChoice, setPendingChoice] = useState(null); const [showWarning, setShowWarning] = useState(false); + // Local textarea state: keystrokes stay here, only pushed to parent on blur. + // This prevents the parent from re-rendering all rows on every keystroke. + const [localContent, setLocalContent] = useState(resolutionState?.resolvedContent ?? ''); + const lastPushedContent = useRef(resolutionState?.resolvedContent ?? ''); + + // Sync from parent when content changes externally (branch switch, undo/redo) + useEffect(() => { + const parentContent = resolutionState?.resolvedContent ?? ''; + if (parentContent !== lastPushedContent.current) { + setLocalContent(parentContent); + lastPushedContent.current = parentContent; + } + }, [resolutionState?.resolvedContent]); + // Get content for a given choice const getContentForChoice = useCallback((choice: ResolutionChoice): string => { if (choice === 'delete') return ''; @@ -99,9 +113,9 @@ export function MergeRow({ return cell ? normalizeCellSource(cell.source) : ''; }, [row]); - // Check if content has been modified from the original + // Check if content has been modified from the original (use local content for immediate feedback) const isContentModified = resolutionState - ? resolutionState.resolvedContent !== resolutionState.originalContent + ? localContent !== resolutionState.originalContent : false; // Handle branch selection @@ -133,10 +147,41 @@ export function MergeRow({ setPendingChoice(null); }; - // Handle content editing in the resolved text area - const handleContentChange = (e: React.ChangeEvent) => { - onUpdateContent(conflictIndex, e.target.value); - }; + // Handle content editing in the resolved text area (local state only, no parent re-render) + const handleContentChange = useCallback((e: React.ChangeEvent) => { + setLocalContent(e.target.value); + }, []); + + // Push content to parent on blur + const handleBlur = useCallback(() => { + if (resolutionState && localContent !== resolutionState.resolvedContent) { + lastPushedContent.current = localContent; + onUpdateContent(conflictIndex, localContent); + } + onCommitContent(conflictIndex); + }, [resolutionState, localContent, onUpdateContent, onCommitContent, conflictIndex]); + + // Memoized cell drag handlers to prevent CellContent re-renders + const handleBaseCellDragStart = useCallback((e: React.DragEvent) => { + e.dataTransfer.effectAllowed = 'move'; + const src = row.baseCell?.source; + e.dataTransfer.setData('text/plain', src ? (Array.isArray(src) ? src.join('') : src) : ''); + if (row.baseCell) onCellDragStart(rowIndex, 'base', row.baseCell); + }, [onCellDragStart, rowIndex, row.baseCell]); + + const handleCurrentCellDragStart = useCallback((e: React.DragEvent) => { + e.dataTransfer.effectAllowed = 'move'; + const src = row.currentCell?.source; + e.dataTransfer.setData('text/plain', src ? (Array.isArray(src) ? src.join('') : src) : ''); + if (row.currentCell) onCellDragStart(rowIndex, 'current', row.currentCell); + }, [onCellDragStart, rowIndex, row.currentCell]); + + const handleIncomingCellDragStart = useCallback((e: React.DragEvent) => { + e.dataTransfer.effectAllowed = 'move'; + const src = row.incomingCell?.source; + e.dataTransfer.setData('text/plain', src ? (Array.isArray(src) ? src.join('') : src) : ''); + if (row.incomingCell) onCellDragStart(rowIndex, 'incoming', row.incomingCell); + }, [onCellDragStart, rowIndex, row.incomingCell]); const canDragRow = rowDragEnabled && Boolean(onRowDragStart); const rowDragHandle = canDragRow ? ( @@ -251,12 +296,8 @@ export function MergeRow({ compareCell={row.currentCell || row.incomingCell} showOutputs={showOutputs} isVisible={isVisible} - onDragStart={canDragCell ? (e) => { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/plain', Array.isArray(row.baseCell!.source) ? row.baseCell!.source.join('') : row.baseCell!.source); - onCellDragStart(rowIndex, 'base', row.baseCell!); - } : undefined} - onDragEnd={canDragCell ? () => onCellDragEnd() : undefined} + onDragStart={canDragCell ? handleBaseCellDragStart : undefined} + onDragEnd={canDragCell ? onCellDragEnd : undefined} /> ) : (
{ - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/plain', Array.isArray(row.currentCell!.source) ? row.currentCell!.source.join('') : row.currentCell!.source); - onCellDragStart(rowIndex, 'current', row.currentCell!); - } : undefined} - onDragEnd={canDragCell ? () => onCellDragEnd() : undefined} + onDragStart={canDragCell ? handleCurrentCellDragStart : undefined} + onDragEnd={canDragCell ? onCellDragEnd : undefined} /> ) : (
{ - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/plain', Array.isArray(row.incomingCell!.source) ? row.incomingCell!.source.join('') : row.incomingCell!.source); - onCellDragStart(rowIndex, 'incoming', row.incomingCell!); - } : undefined} - onDragEnd={canDragCell ? () => onCellDragEnd() : undefined} + onDragStart={canDragCell ? handleIncomingCellDragStart : undefined} + onDragEnd={canDragCell ? onCellDragEnd : undefined} /> ) : (