feat(base): seed editor value on type-to-edit for free-text cells

This commit is contained in:
Philipinho
2026-06-15 02:39:04 +01:00
parent 514c68e049
commit 16708cd7cc
5 changed files with 67 additions and 12 deletions
@@ -15,9 +15,18 @@ type CellEmailProps = {
const toDraft = (value: unknown) => (typeof value === "string" ? value : "");
const parse = (draft: string) => draft || null;
export function CellEmail({ value, isEditing, onCommit, onCancel }: CellEmailProps) {
export function CellEmail({ value, property, rowId, isEditing, onCommit, onCancel }: CellEmailProps) {
const { draft, setDraft, inputRef, handleKeyDown, handleBlur } =
useEditableTextCell({ value, isEditing, onCommit, onCancel, toDraft, parse });
useEditableTextCell({
value,
isEditing,
onCommit,
onCancel,
toDraft,
parse,
rowId,
propertyId: property.id,
});
if (isEditing) {
return (
@@ -92,6 +92,7 @@ export function parseNumberDraft(draft: string): number | null {
export function CellNumber({
value,
property,
rowId,
isEditing,
onCommit,
onCancel,
@@ -105,6 +106,8 @@ export function CellNumber({
onCancel,
toDraft,
parse: parseNumberDraft,
rowId,
propertyId: property.id,
});
if (isEditing) {
@@ -16,9 +16,18 @@ type CellTextProps = {
const toDraft = (value: unknown) => (typeof value === "string" ? value : "");
const parse = (draft: string) => draft;
export function CellText({ value, isEditing, onCommit, onCancel }: CellTextProps) {
export function CellText({ value, property, rowId, isEditing, onCommit, onCancel }: CellTextProps) {
const { draft, setDraft, inputRef, handleKeyDown, handleBlur } =
useEditableTextCell({ value, isEditing, onCommit, onCancel, toDraft, parse });
useEditableTextCell({
value,
isEditing,
onCommit,
onCancel,
toDraft,
parse,
rowId,
propertyId: property.id,
});
if (isEditing) {
return (
@@ -16,9 +16,18 @@ type CellUrlProps = {
const toDraft = (value: unknown) => (typeof value === "string" ? value : "");
const parse = (draft: string) => draft || null;
export function CellUrl({ value, isEditing, onCommit, onCancel }: CellUrlProps) {
export function CellUrl({ value, property, rowId, isEditing, onCommit, onCancel }: CellUrlProps) {
const { draft, setDraft, inputRef, handleKeyDown, handleBlur } =
useEditableTextCell({ value, isEditing, onCommit, onCancel, toDraft, parse });
useEditableTextCell({
value,
isEditing,
onCommit,
onCancel,
toDraft,
parse,
rowId,
propertyId: property.id,
});
if (isEditing) {
return (
@@ -1,4 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useStore, type PrimitiveAtom } from "jotai";
import { pendingTypeInsertAtom, type PendingTypeInsert } from "@/ee/base/atoms/base-atoms";
export type UseEditableTextCellParams = {
value: unknown;
@@ -9,6 +11,8 @@ export type UseEditableTextCellParams = {
toDraft: (value: unknown) => string;
/** draft string -> the value passed to onCommit */
parse: (draft: string) => unknown;
rowId?: string;
propertyId?: string;
};
export type EditableTextCell = {
@@ -26,6 +30,8 @@ export function useEditableTextCell({
onCancel,
toDraft,
parse,
rowId,
propertyId,
}: UseEditableTextCellParams): EditableTextCell {
const [draft, setDraft] = useState(() => toDraft(value));
const inputRef = useRef<HTMLInputElement>(null);
@@ -33,18 +39,37 @@ export function useEditableTextCell({
const wasEditingRef = useRef(false);
const toDraftRef = useRef(toDraft);
toDraftRef.current = toDraft;
const store = useStore();
useEffect(() => {
if (isEditing && !wasEditingRef.current) {
committedRef.current = false;
setDraft(toDraftRef.current(value));
requestAnimationFrame(() => {
inputRef.current?.focus();
inputRef.current?.select();
});
const pending = store.get(pendingTypeInsertAtom);
const seeded =
pending != null &&
pending.rowId === rowId &&
pending.propertyId === propertyId;
if (seeded) {
setDraft(pending.char);
store.set(pendingTypeInsertAtom as PrimitiveAtom<PendingTypeInsert>, null);
requestAnimationFrame(() => {
const el = inputRef.current;
if (el) {
el.focus();
const len = el.value.length;
el.setSelectionRange(len, len);
}
});
} else {
setDraft(toDraftRef.current(value));
requestAnimationFrame(() => {
inputRef.current?.focus();
inputRef.current?.select();
});
}
}
wasEditingRef.current = isEditing;
}, [isEditing, value]);
}, [isEditing, value, rowId, propertyId, store]);
const commitOnce = useCallback(
(val: unknown) => {