Skip to content

Commit a6ea91e

Browse files
fix: UI glitch: config is not visible
Co-authored-by: sunlit-deng <253064511+sunlit-deng@users.noreply.github.com>
1 parent 9ff7abc commit a6ea91e

2 files changed

Lines changed: 57 additions & 18 deletions

File tree

ui/src/ui/views/config.browser.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,36 @@ describe("config view", () => {
473473
expect(content.scrollLeft).toBe(0);
474474
});
475475

476+
it("resets config content scroll when switching from form to raw mode", async () => {
477+
const container = document.createElement("div");
478+
document.body.append(container);
479+
480+
try {
481+
const renderCase = (overrides: Partial<ConfigProps>) =>
482+
render(renderConfig({ ...baseProps(), ...overrides }), container);
483+
484+
renderCase({ formMode: "form" });
485+
486+
const content = queryRequired(container, ".config-content", HTMLElement);
487+
content.scrollTop = 320;
488+
content.scrollLeft = 18;
489+
content.scrollTo = vi.fn(({ top, left }: { top?: number; left?: number }) => {
490+
content.scrollTop = top ?? content.scrollTop;
491+
content.scrollLeft = left ?? content.scrollLeft;
492+
}) as typeof content.scrollTo;
493+
494+
renderCase({ formMode: "raw" });
495+
await Promise.resolve();
496+
497+
expect(content["scrollTo"]).toHaveBeenCalledOnce();
498+
expect(content["scrollTo"]).toHaveBeenCalledWith({ top: 0, left: 0, behavior: "auto" });
499+
expect(content.scrollTop).toBe(0);
500+
expect(content.scrollLeft).toBe(0);
501+
} finally {
502+
container.remove();
503+
}
504+
});
505+
476506
it("can hide the root tab for scoped settings surfaces", () => {
477507
const { container } = renderConfigView({
478508
activeSection: "channels",

ui/src/ui/views/config.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,7 @@ function createConfigEphemeralState(): ConfigEphemeralState {
11841184

11851185
const cvs = createConfigEphemeralState();
11861186
let lastConfigContextKey: string | null = null;
1187+
let lastFormModeForScroll: ConfigProps["formMode"] | null = null;
11871188

11881189
function resetConfigEphemeralState() {
11891190
Object.assign(cvs, createConfigEphemeralState());
@@ -1222,6 +1223,7 @@ function toggleSensitivePathReveal(path: Array<string | number>) {
12221223
export function resetConfigViewStateForTests() {
12231224
resetConfigEphemeralState();
12241225
lastConfigContextKey = null;
1226+
lastFormModeForScroll = null;
12251227
}
12261228

12271229
export function renderConfig(props: ConfigProps) {
@@ -1237,6 +1239,31 @@ export function renderConfig(props: ConfigProps) {
12371239
const rawAvailable = props.rawAvailable ?? true;
12381240
const formMode = showModeToggle && rawAvailable ? props.formMode : "form";
12391241
const requestUpdate = props.onRequestUpdate ?? (() => {});
1242+
// Scroll helper: target-based (nav clicks) with global fallback (form/raw toggle)
1243+
const resetContentScroll = (target: EventTarget | null) => {
1244+
queueMicrotask(() => {
1245+
const origin = target instanceof Element ? target : null;
1246+
const content =
1247+
origin?.closest(".config-main")?.querySelector<HTMLElement>(".config-content") ??
1248+
globalThis.document?.querySelector<HTMLElement>(".config-content");
1249+
if (!content) {
1250+
return;
1251+
}
1252+
if (typeof content.scrollTo === "function") {
1253+
content.scrollTo({ top: 0, left: 0, behavior: "auto" });
1254+
return;
1255+
}
1256+
content.scrollTop = 0;
1257+
content.scrollLeft = 0;
1258+
});
1259+
};
1260+
1261+
// Reset scroll position when switching between form and raw mode
1262+
if (lastFormModeForScroll !== null && lastFormModeForScroll !== formMode) {
1263+
resetContentScroll(null);
1264+
}
1265+
lastFormModeForScroll = formMode;
1266+
12401267
const currentContextKey = configContextKey(props);
12411268
if (lastConfigContextKey !== currentContextKey) {
12421269
resetConfigEphemeralState();
@@ -1301,24 +1328,6 @@ export function renderConfig(props: ConfigProps) {
13011328
const settingsLayout = props.settingsLayout ?? "tabs";
13021329
const allCategories = [...visibleCategories, ...(otherCategory ? [otherCategory] : [])];
13031330

1304-
const resetContentScroll = (target: EventTarget | null) => {
1305-
queueMicrotask(() => {
1306-
const origin = target instanceof Element ? target : null;
1307-
const content = origin
1308-
?.closest(".config-main")
1309-
?.querySelector<HTMLElement>(".config-content");
1310-
if (!content) {
1311-
return;
1312-
}
1313-
if (typeof content.scrollTo === "function") {
1314-
content.scrollTo({ top: 0, left: 0, behavior: "auto" });
1315-
return;
1316-
}
1317-
content.scrollTop = 0;
1318-
content.scrollLeft = 0;
1319-
});
1320-
};
1321-
13221331
function renderAccordionNav() {
13231332
return html`
13241333
<div class="config-accordion-nav">

0 commit comments

Comments
 (0)