mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-25 17:42:51 +08:00
fix(tables): re-broadcast selection when a peer's row change shifts it
End-to-end lifecycle audit (Low-Med): the selection emit resolved the stable (rowId, columnId) only on selection/editing change, not when a live edit/schema refetch inserted/deleted/reordered rows. The index-based local selection then sat on a different logical row than the rowId peers held, so your outline showed on the old row until you moved. Re-run the emit on rows/displayColumns change and dedup an unchanged result (also drops the redundant null-on-open emit) so the broadcast stays consistent with the local highlight.
This commit is contained in:
+7
-8
@@ -836,16 +836,15 @@ export function TableGrid({
|
|||||||
: null
|
: null
|
||||||
|
|
||||||
// Broadcast the local viewer's cell selection to the presence room. Resolves the
|
// Broadcast the local viewer's cell selection to the presence room. Resolves the
|
||||||
// index-based selection to stable (rowId, columnId) through refs so it re-emits only
|
// index-based selection to stable (rowId, columnId), re-running on `rows`/`displayColumns`
|
||||||
// on a real selection/editing change, not on every data update. `editing` marks the
|
// too so a peer's row insert/delete/reorder re-broadcasts the shifted id under the same
|
||||||
// active cell so peers darken it (the "someone is typing here" signal).
|
// index (the emitter dedups an unchanged result). `editing` marks the active cell so
|
||||||
|
// peers darken it (the "someone is typing here" signal).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentRows = rowsRef.current
|
|
||||||
const currentCols = columnsRef.current
|
|
||||||
const resolve = (coord: CellCoord | null) => {
|
const resolve = (coord: CellCoord | null) => {
|
||||||
if (!coord) return null
|
if (!coord) return null
|
||||||
const rowId = currentRows[coord.rowIndex]?.id
|
const rowId = rows[coord.rowIndex]?.id
|
||||||
const columnId = currentCols[coord.colIndex]?.key
|
const columnId = displayColumns[coord.colIndex]?.key
|
||||||
return rowId && columnId ? { rowId, columnId } : null
|
return rowId && columnId ? { rowId, columnId } : null
|
||||||
}
|
}
|
||||||
const anchor = resolve(selectionAnchor)
|
const anchor = resolve(selectionAnchor)
|
||||||
@@ -858,7 +857,7 @@ export function TableGrid({
|
|||||||
// most common selection would never broadcast and would clear the prior outline.
|
// most common selection would never broadcast and would clear the prior outline.
|
||||||
const focus = resolve(selectionFocus) ?? anchor
|
const focus = resolve(selectionFocus) ?? anchor
|
||||||
emitCellSelection({ anchor, focus, editing: editingCell !== null })
|
emitCellSelection({ anchor, focus, editing: editingCell !== null })
|
||||||
}, [selectionAnchor, selectionFocus, editingCell, emitCellSelection])
|
}, [selectionAnchor, selectionFocus, editingCell, rows, displayColumns, emitCellSelection])
|
||||||
|
|
||||||
const { data: findData, isFetching: isFindFetching } = useFindTableRows({
|
const { data: findData, isFetching: isFindFetching } = useFindTableRows({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ export function useTableRoom(tableId: string): UseTableRoomResult {
|
|||||||
const lastEmitRef = useRef(0)
|
const lastEmitRef = useRef(0)
|
||||||
const trailingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
const trailingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
const pendingCellRef = useRef<TableCellSelection>(null)
|
const pendingCellRef = useRef<TableCellSelection>(null)
|
||||||
|
/** Key of the last selection sent, to skip re-emitting an unchanged selection. */
|
||||||
|
const lastSentKeyRef = useRef<string | null>(null)
|
||||||
|
|
||||||
// Reset the throttle when the table changes (or on unmount): a pending selection for
|
// Reset the throttle when the table changes (or on unmount): a pending selection for
|
||||||
// the table we're leaving must not flush into the next table's room after a switch.
|
// the table we're leaving must not flush into the next table's room after a switch.
|
||||||
@@ -161,12 +163,19 @@ export function useTableRoom(tableId: string): UseTableRoomResult {
|
|||||||
}
|
}
|
||||||
pendingCellRef.current = null
|
pendingCellRef.current = null
|
||||||
currentCellRef.current = null
|
currentCellRef.current = null
|
||||||
|
lastSentKeyRef.current = null
|
||||||
lastEmitRef.current = 0
|
lastEmitRef.current = 0
|
||||||
},
|
},
|
||||||
[tableId]
|
[tableId]
|
||||||
)
|
)
|
||||||
|
|
||||||
const emitCellSelection = useCallback((cell: TableCellSelection) => {
|
const emitCellSelection = useCallback((cell: TableCellSelection) => {
|
||||||
|
// Skip re-emitting an unchanged selection: the caller re-resolves on every data
|
||||||
|
// refetch (so a peer's row insert re-broadcasts the shifted rowId), but most refetches
|
||||||
|
// don't move the selection — dedup those, and the no-selection state on table open.
|
||||||
|
const key = cell === null ? 'null' : JSON.stringify(cell)
|
||||||
|
if (key === lastSentKeyRef.current) return
|
||||||
|
lastSentKeyRef.current = key
|
||||||
currentCellRef.current = cell
|
currentCellRef.current = cell
|
||||||
pendingCellRef.current = cell
|
pendingCellRef.current = cell
|
||||||
const flush = () => {
|
const flush = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user