[AI] fix: let Firefox place the caret when renaming a category (#8950)

* fix: let Firefox place the caret when renaming a category or account

react-dnd's HTML5 backend sets draggable="true" on the connected node and
only consults canDrag on dragstart, so the attribute stays on while a row
is being edited. Firefox refuses to place the caret in an input under a
draggable ancestor, which made click-to-position impossible in the
category and account rename fields. useDraggable now mirrors canDrag onto
the attribute, which covers every caller at once.

Closes #5620

* chore: name the release note after the PR

* chore: scope release note to category rename
This commit is contained in:
John Costa
2026-10-06 17:44:22 +00:00
committed by GitHub
parent 4cfa914675
commit 231e43bbe6
3 changed files with 82 additions and 1 deletions
@@ -0,0 +1,58 @@
import React from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { render, screen } from '@testing-library/react';
import { useDragRef } from '#hooks/useDragRef';
import { useDraggable } from './sort';
function Row({ canDrag }: { canDrag: boolean }) {
const { dragRef } = useDraggable({
type: 'row',
item: { id: 'row-1' },
canDrag,
onDragChange: vi.fn(),
});
const handleDragRef = useDragRef(dragRef);
return <div data-testid="row" ref={handleDragRef} />;
}
function renderRow(canDrag: boolean) {
return render(
<DndProvider backend={HTML5Backend}>
<Row canDrag={canDrag} />
</DndProvider>,
);
}
describe('useDraggable', () => {
// #5620: react-dnd only consults canDrag on dragstart and leaves
// draggable="true" on the node, which stops Firefox from placing the caret
// in an input rendered inside it (e.g. the category rename field).
it('mirrors canDrag onto the draggable attribute', () => {
const { rerender } = renderRow(true);
expect(screen.getByTestId('row')).toHaveAttribute('draggable', 'true');
rerender(
<DndProvider backend={HTML5Backend}>
<Row canDrag={false} />
</DndProvider>,
);
expect(screen.getByTestId('row')).toHaveAttribute('draggable', 'false');
rerender(
<DndProvider backend={HTML5Backend}>
<Row canDrag />
</DndProvider>,
);
expect(screen.getByTestId('row')).toHaveAttribute('draggable', 'true');
});
it('is not draggable when mounted while editing', () => {
renderRow(false);
expect(screen.getByTestId('row')).toHaveAttribute('draggable', 'false');
});
});
@@ -1,5 +1,6 @@
import React, {
createContext,
useCallback,
useContext,
useEffect,
useLayoutEffect,
@@ -42,8 +43,9 @@ export function useDraggable<T>({
onDragChange,
}: UseDraggableArgs<T>) {
const _onDragChange = useRef(onDragChange);
const node = useRef<HTMLElement | null>(null);
const [, dragRef] = useDrag({
const [, connectDragSource] = useDrag({
type,
item: () => {
void _onDragChange.current({ state: 'start-preview', type, item });
@@ -69,6 +71,21 @@ export function useDraggable<T>({
_onDragChange.current = onDragChange;
}, [onDragChange]);
const dragRef = useCallback(
(el: HTMLElement | null) => {
node.current = el;
connectDragSource(el);
},
[connectDragSource],
);
// react-dnd sets draggable="true" when it connects and only consults
// canDrag on dragstart. Firefox refuses to place the caret in an input under
// a draggable ancestor, so mirror canDrag onto the attribute (#5620).
useLayoutEffect(() => {
node.current?.setAttribute('draggable', String(canDrag));
}, [canDrag, dragRef]);
return { dragRef };
}
+6
View File
@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [costajohnt]
---
Allow clicking to place the cursor when renaming a category in Firefox