diff --git a/nextjs/src/components/admin/InlineEditable.tsx b/nextjs/src/components/admin/InlineEditable.tsx index 9099c2d..5494497 100644 --- a/nextjs/src/components/admin/InlineEditable.tsx +++ b/nextjs/src/components/admin/InlineEditable.tsx @@ -1,7 +1,7 @@ 'use client' import { useField } from '@payloadcms/ui' -import { useEffect, useRef } from 'react' +import { useLayoutEffect, useRef } from 'react' type Props = { path: string @@ -15,15 +15,22 @@ export function InlineEditable({ path, as: Tag = 'span', className, multiline, p const { value, setValue } = useField({ path }) const ref = useRef(null) - useEffect(() => { - if (ref.current && ref.current.textContent !== (value ?? '')) { - ref.current.textContent = value ?? '' - } + // Sync textContent from value. React doesn't reconcile children of + // contentEditable elements, so we drive the DOM imperatively — and + // never clobber the DOM while the user is actively typing in it. + useLayoutEffect(() => { + const el = ref.current + if (!el) return + if (typeof document !== 'undefined' && document.activeElement === el) return + const next = value ?? '' + if (el.textContent !== next) el.textContent = next + el.classList.toggle('rebours-editable--empty', !value) }, [value]) - const commit = (e: React.FocusEvent | React.KeyboardEvent) => { + const commit = (e: React.FocusEvent) => { const next = (e.currentTarget.textContent ?? '').replace(/\u00A0/g, ' ') if (next !== (value ?? '')) setValue(next) + e.currentTarget.classList.toggle('rebours-editable--empty', !next) } const onKeyDown = (e: React.KeyboardEvent) => { @@ -42,7 +49,7 @@ export function InlineEditable({ path, as: Tag = 'span', className, multiline, p return ( - {value ?? ''} - + /> ) } diff --git a/nextjs/src/components/admin/PriceEditable.tsx b/nextjs/src/components/admin/PriceEditable.tsx index b060319..5b58f6d 100644 --- a/nextjs/src/components/admin/PriceEditable.tsx +++ b/nextjs/src/components/admin/PriceEditable.tsx @@ -1,7 +1,7 @@ 'use client' import { useField } from '@payloadcms/ui' -import { useEffect, useRef } from 'react' +import { useLayoutEffect, useRef } from 'react' function formatEuros(cents: number | null | undefined): string { if (cents == null) return '' @@ -22,10 +22,13 @@ export function PriceEditable() { const { value: currency } = useField({ path: 'currency' }) const ref = useRef(null) - useEffect(() => { - if (!ref.current) return + useLayoutEffect(() => { + const el = ref.current + if (!el) return + if (typeof document !== 'undefined' && document.activeElement === el) return const rendered = formatEuros(value) - if (ref.current.textContent !== rendered) ref.current.textContent = rendered + if (el.textContent !== rendered) el.textContent = rendered + el.classList.toggle('rebours-editable--empty', value == null) }, [value]) const commit = (e: React.FocusEvent) => { @@ -33,6 +36,7 @@ export function PriceEditable() { const cents = parseEuros(txt) if (cents !== value) setValue(cents) e.currentTarget.textContent = formatEuros(cents) + e.currentTarget.classList.toggle('rebours-editable--empty', cents == null) } const onKeyDown = (e: React.KeyboardEvent) => { @@ -48,16 +52,14 @@ export function PriceEditable() { - {formatEuros(value)} - {' '} + />{' '} {symbol} )