Repository navigation
feat: UI tweaks: inside-label dropdowns, menu arrows, Fit-mode gallery tiles, queue and project polish - #10136
joshistoast wants to merge 15 commits into
Conversation
Field gains labelPlacement="inside"; Select, Combobox and ModelSelect render the label at the start of their box. Generate and canvas panels use inside labels for their dropdowns. Quick settings show selects as a labelled row with a value-width select.
Queue offers its job scope, Workflow its snap, minimap and connection style, and Image Map its cluster options.
The board list marks the destination board with the Saving to icon; the docs describe it. The boards toggle icon follows the gallery body's measured layout instead of its region. Picker board labels keep the list's text size, and the search field gets space below it.
SegmentTabs gains isScrollable: wheel scrolling, edge fades and the selected tab kept in view. Scroll-edge measurement moves from the shell's overflow edges to a shared platform module. The Queue widget's filter strip and lists share the gallery rail's inset.
Menus, popovers and hover cards draw an arrow kept clear of the content's rounded corners. Context menus anchored to the pointer and submenus show none; the layers keyboard menu anchors to a point.
The menu renders outside the item branches, so its leftover target no longer reopens it on the next item. Focus returns to the opener when it survives the deletion, otherwise to Preview, via a new returnFocus prop.
The dialog lists projects with the virtualized List, dims incompatible ones and recovers from load errors. List gains defaultFocusKey and ListItem isDisabled; the project switcher lazy-loads the dialog.
The chip resolves one literal translation key per state instead of a missing interpolated key. Adds the loadingModel and loadingModelUnnamed strings, the latter for loads without a model name.
In Fit mode the tile border, selection and focus ring wrap the image's own rect inside the square cell; hover tints the cell and badges stay on its corners. Generate, canvas, upscale and video progress tiles use the job's target ratio; ratios clamp to 1:3–3:1. Clicks beside the image select and focus the tile, and mouse drags still start from anywhere in the cell.
lstein
left a comment
There was a problem hiding this comment.
Reviewed at 7a09ad9. The gallery, performance baselines, Open project dialog, quick-settings cogs, queue-status strings and the converted fields all held up. I found four low-severity issues, three of them confirmed in the browser, plus one focus issue I could not reproduce.
Confirmed in the browser
1. Hint cards on inside-label dropdowns open the dropdown.
FieldInsideLabel (src/platform/ui/Field.tsx:105) renders the FeatureHint inside the Select/ModelSelect trigger <button>. The hint card is portalled, but React synthetic events still bubble through the portal to the trigger. Clicking "Don't show me these" or "Learn more" in the card also opens the dropdown. Pressing Enter on them is caught by the trigger's keydown handler, so the dropdown opens and the link or button does nothing. This affects VAE, VAE precision, PID mode, encoder quantization, DyPE preset and Scheduler. With the outside label the bubble stopped at a plain <label>, so this is new.
Suggested fix: stop click/keydown/pointerdown from the hint card's content reaching the trigger, and add a test that interacts with the card.
2. Clicking a popover or menu arrow tip dismisses it.
arrowClearOfCorners (src/platform/ui/theme/recipes.ts:51) sets pointerEvents: 'none' on the arrow box. The hover-card arrowTip restores auto (:133); the popover (:159) and menu (:648) arrowTip recipes don't, so the tip inherits none. A click on the visible tip falls through, Zag treats it as an outside interaction, and the popover closes (or toggles, if it lands on the trigger). Before this PR the tip was part of the content's hit area.
Suggested fix: pointerEvents: 'auto' on the popover and menu arrowTip; the clip-path already limits the hit area to the visible half.
Separately, the tip is very hard to see in the dark theme: its fill matches the popover background and only a 1px outline shows.
3. Clicking the Scheduler's inside label while its list is open closes and reopens it, and the typed filter is lost.
The inside <label> (src/platform/ui/Combobox.tsx:231) sits inside Combobox.Control, but Zag's dismiss layer only excludes the input, trigger and clear trigger. Pointerdown on the label counts as outside: handleOpenChange(false) clears the query, then the label activates the input and openOnClick reopens an unfiltered list. To reproduce, open Scheduler, type dpm, click the word "Scheduler". The existing test only clicks the label while the list is closed.
Suggested fix: preventDefault the label's pointerdown while open, or add the label to the dismiss layer's exclusions.
From code reading, not verified
4. Hovering an invalid Combobox with an inside label tints the input part twice.
INSIDE_INPUT_CSS sets bg: 'transparent' on the input, but that doesn't cancel the recipe's nested _hover._invalid background from formControlInteraction (combobox.input in recipes.ts). The Control also gets bg.error/60 on hover, so the input region shows two stacked tints and the label region one, with a seam between them. I couldn't produce an error state on Scheduler to check it on screen.
Could not reproduce, may depend on environment
Preview: deleting without confirmation from a filmstrip thumbnail or the header ⋮ may drop focus to <body>.
closeContextMenu (src/workbench/widgets/preview/PreviewWidgetView.tsx:500) resolves the return target through getContextMenuReturnFocus (:485) synchronously when the menu closes. With confirmation off, deleteItems applies the optimistic removal in the same click, but React hasn't committed yet, so opener.isConnected is still true and focus goes to the thumb or ⋮. The commit then removes that element. I tested both paths on Linux (Cinnamon, click-to-focus) and focus stayed in Preview, so something in the real ordering saves it there. Because the code relies on timing rather than resolving the target after the commit, it may still fail in other browsers or under different load. The tests only cover the thumb and ⋮ paths with confirmation on; a {asks: false} variant of those would settle it.
…card Hint cards stop pressed, clicked and keyed events from reaching the field that holds their label. An open Combobox no longer dismisses, and loses its filter, when its inside label is pressed.
The visible tip takes the pointer; a menu ignores outside presses on its own arrow and keeps focus.
…tion The menu's return target is resolved after the removal commits, and a removed filmstrip thumb hands focus to Preview.
Pfannkuchensack
left a comment
There was a problem hiding this comment.
Reviewed at 31bafcd, with lstein's review at 7a09ad9 as the starting point. No blockers. Findings come from reading the code and tests; nothing was run, so "fixed" below rests on the code and the new tests as written.
Status of lstein's points
- Hint card reaching the trigger: fixed.
CARD_EVENT_BOUNDARYinFeatureHint.tsx, with pointer and Enter tests for both dropdown types. - Arrow tip: the click is fixed (
pointerEvents: 'auto'inrecipes.ts:73, plus theMenu.tsxguard). The dark-theme visibility is unchanged:recipes.tshas no colour or contrast change since 7a09ad9. - Combobox inside label while open: fixed in
Combobox.tsxhandlePointerDownOutside. The test asserts the list, filter,aria-expandedand focus survive. - Double tint on invalid hover: fixed through
INSIDE_INPUT_CSS, with a computed-style test. - Preview delete without confirmation: fixed for the tested paths, with
{asks: true/false}variants for stage, thumb and header ⋮. Those tests delete synchronously. Removal is optimistic inuseImageActions, so the real path should match, but that is not confirmed.
Low
PreviewWidgetView.tsx:486-534: a later removal of the header ⋮ has no focus handoff of its own, while thumbs havereleaseFocusOnRemoval. Only a removal landing in the same click is covered.PreviewWidgetView.tsx,closeContextMenu(about:521): ifonCloseis ever reached from an effect,flushSynclogs and does not flush. The possible route is theuseRegisterWidgetOverlaymount effect callingsetOpen(false). Not verified; checking whethercloseWidgetOverlayscan fire while this menu is open would settle it.CARD_EVENT_BOUNDARYstopsonKeyDown,onFocusandonBlurfor all React ancestors of every hint card. It is documented and Zag's capture listeners are unaffected, so this is acceptable. It is worth keeping in mind for any Escape or focus handler above a hint label.
Sound: the lazy Open project dialog, the baseline edits (they match the lazy split), the Select and ModelSelect inside-label wiring, the new en.json keys, and the accessibility script locator. Gallery tiles, SegmentTabs and the queue changes are unchanged since lstein's review, and I did not review them in depth.
Thumbs, the header ⋮ and the video return focus to Preview when removed while focused, so closing the context menu no longer flushes React.
…us regions Moves releaseFocusOnRemoval into the eagerly loaded focusRegions module instead of a new startup source.
Summary
A batch of UI refinements across forms, menus, gallery, queue and projects:
FieldgainslabelPlacement="inside". Select, Combobox and ModelSelect then show the label at the start of the box, the value right-aligned, and help text below. Converted:SegmentTabsgains an opt-inisScrollable.Listwith dividers. Incompatible projects show dimmed with their reason. The dialog loads lazily.widgets.queueStatus.loadingModelkey; it now shows English text, with or without a known model name.Related Issues / Discussions
None.
QA Instructions
Commands run from
invokeai/frontend/webv2with Node 22.14 and pnpm 10:pnpm check:release:InvokeButton"leaves focus where it is when a card it never held closes" andModelSelect"leads a required picker … to the Model Manager". Both pass when run alone (repeated twice). The ModelSelect one has flaked in batch runs before.pnpm lint:tscpasses on every commit.Manual checks against the mock backend (
pnpm dev:mock):Screenshots aren't attached yet; I'll add them in a follow-up edit.
Review
Material findings resolved:
<body>when opened from a filmstrip thumb or the header ⋮. Fixed throughreturnFocus.<body>after a retry, the focus ring on disabled rows was dimmed, andisDisabledrows weren't fully inert. All fixed.Remaining limits:
Checklist
What's Newcopy (if doing a release after this PR)Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.