mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-18 13:44:12 +00:00
c960a983a0
Render a lightweight, focusable TextLikeInput for cue/title/note/custom cells and only mount the real input/textarea editor (with reactive-input hooks and autosize) on click/focus, mirroring the existing time/duration cells. Measured baseline vs Exp A at CPU 4x (median of 3, ~1140 row mounts): - longFrames 201 -> 127 (-37%), maxFrameMs 517 -> 417 (-19%) - longtask total 58.9s -> 30.7s (-48%), EventRow.mount avg 179 -> 146ms (-18%) - cell.autosize and cell.reactiveInputInit eliminated from the scroll path - per-row re-renders 4648 -> 3048 (-34%): stable display height, no virtuoso churn (the opposite of the reverted lazy-autosize Exp B, which doubled re-renders) Editing verified end-to-end: click mounts+focuses the editor, edits persist, editor unmounts back to the display on commit. TextLikeInput now fills its cell so the whole cell is clickable to edit.
87 lines
2.3 KiB
TypeScript
87 lines
2.3 KiB
TypeScript
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
|
|
|
import MultiLineCell from './MultiLineCell';
|
|
import SingleLineCell from './SingleLineCell';
|
|
import TextLikeInput from './TextLikeInput';
|
|
|
|
interface EditableCellProps {
|
|
initialValue: string;
|
|
multiline?: boolean;
|
|
fieldId?: string;
|
|
fieldLabel?: string;
|
|
handleUpdate: (newValue: string) => void;
|
|
}
|
|
|
|
interface FocusableEditor {
|
|
focus: () => void;
|
|
select?: () => void;
|
|
}
|
|
|
|
/**
|
|
* Lazily mounts the text editor for a cell.
|
|
*
|
|
* Mounting an `<input>`/`<textarea>` editor (with its reactive-input hooks and autosize) for every
|
|
* cell is expensive when many rows mount at once during virtualised scroll. While the cell is not
|
|
* being edited we render a lightweight, focusable display element and only mount the real editor
|
|
* when the user clicks/focuses the cell — mirroring how the time/duration cells already behave.
|
|
*/
|
|
function EditableCell({ initialValue, multiline, fieldId, fieldLabel, handleUpdate }: EditableCellProps) {
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const editorRef = useRef<FocusableEditor | null>(null);
|
|
|
|
// focus the editor once it mounts on entering edit mode
|
|
useEffect(() => {
|
|
if (isEditing) {
|
|
editorRef.current?.focus();
|
|
editorRef.current?.select?.();
|
|
}
|
|
}, [isEditing]);
|
|
|
|
const enterEdit = useCallback(() => setIsEditing(true), []);
|
|
|
|
const onSubmit = useCallback(
|
|
(newValue: string) => {
|
|
setIsEditing(false);
|
|
handleUpdate(newValue);
|
|
},
|
|
[handleUpdate],
|
|
);
|
|
|
|
const onCancel = useCallback(() => setIsEditing(false), []);
|
|
|
|
if (!isEditing) {
|
|
return (
|
|
<TextLikeInput
|
|
onClick={enterEdit}
|
|
onFocus={enterEdit}
|
|
multiline={multiline}
|
|
aria-label={fieldLabel ? `${fieldLabel} cell` : undefined}
|
|
>
|
|
{initialValue}
|
|
</TextLikeInput>
|
|
);
|
|
}
|
|
|
|
return multiline ? (
|
|
<MultiLineCell
|
|
ref={editorRef}
|
|
initialValue={initialValue}
|
|
fieldId={fieldId}
|
|
fieldLabel={fieldLabel}
|
|
handleUpdate={onSubmit}
|
|
handleCancelUpdate={onCancel}
|
|
/>
|
|
) : (
|
|
<SingleLineCell
|
|
ref={editorRef}
|
|
initialValue={initialValue}
|
|
fieldId={fieldId}
|
|
fieldLabel={fieldLabel}
|
|
handleUpdate={onSubmit}
|
|
handleCancelUpdate={onCancel}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default memo(EditableCell);
|