fix(web): focus the enlarged field before paint, and derive its section width
Copilot review round 3 on #2109. - The focus hand-off ran in a passive effect, so the browser presented at least one frame with focus on the document body. useLayoutEffect instead. - The right-section widths were pixel totals. They are now built from the measured button widths plus the gap and inset, and emitted through `rem` so they track a user's root font size rather than pinning to CSS pixels. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JTHVxSu8AUgHRLvo1ntZ8H Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
7db596b5e6
commit
121ce4f051
1 changed files with 20 additions and 5 deletions
|
|
@ -7,6 +7,7 @@ import {
|
|||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
rem,
|
||||
Textarea,
|
||||
type TextareaProps,
|
||||
TextInput,
|
||||
|
|
@ -14,6 +15,7 @@ import {
|
|||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type ChangeEvent,
|
||||
|
|
@ -75,7 +77,10 @@ const MultilineStringInput = Textarea.withProps({
|
|||
function EnlargedStringField(props: TextareaProps) {
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Layout, not passive: a passive effect runs after paint, so the browser would
|
||||
// present at least one frame with focus on the document body — long enough for
|
||||
// a fast Tab to land somewhere else entirely.
|
||||
useLayoutEffect(() => {
|
||||
const node = inputRef.current;
|
||||
/* v8 ignore next -- an effect runs after mount, so the ref is always set */
|
||||
if (!node) return;
|
||||
|
|
@ -90,10 +95,20 @@ function EnlargedStringField(props: TextareaProps) {
|
|||
// them on one line inside a section only wide enough for the two.
|
||||
const FieldActions = Group.withProps({ gap: 2, wrap: "nowrap" });
|
||||
|
||||
// Widths of the string field's right section, which has to be declared rather
|
||||
// than measured. One button's worth, and two.
|
||||
const ONE_ACTION_WIDTH = 34;
|
||||
const TWO_ACTION_WIDTH = 62;
|
||||
// Width of the string field's right section, which Mantine takes as a number it
|
||||
// cannot derive from its content. Built from the parts rather than written as a
|
||||
// pixel total, so it stays right if a button or the gap changes, and emitted as
|
||||
// `rem` so it tracks a user's root font size rather than pinning to CSS pixels.
|
||||
// The two button widths are what they measure at their current sizes (ActionIcon
|
||||
// `sm` and CloseButton's default); the inset keeps them off the field's edge.
|
||||
const ENLARGE_WIDTH = 22;
|
||||
const CLEAR_WIDTH = 28;
|
||||
const ACTION_GAP = 2;
|
||||
const SECTION_INSET = 6;
|
||||
const ONE_ACTION_WIDTH = rem(CLEAR_WIDTH + SECTION_INSET);
|
||||
const TWO_ACTION_WIDTH = rem(
|
||||
ENLARGE_WIDTH + ACTION_GAP + CLEAR_WIDTH + SECTION_INSET,
|
||||
);
|
||||
|
||||
function serializeJson(value: unknown): string {
|
||||
return JSON.stringify(value, null, 2);
|
||||
|
|
|
|||
Loading…
Reference in a new issue