Skip to content

Commit 3407fd6

Browse files
authored
fix: drive project settings dropdowns via native change events (#1655)
* fix: drive project settings dropdowns via native change events After the React 19 / @vscode-elements/elements migration, the vscode-single-select element renders its options in the shadow DOM and only emits a native 'change' event on the host. The onClick handlers on the slotted vscode-option children never fired, so selecting a project never updated activeProjectIndex and the compliance/source/target dropdowns never updated. As a result, project settings (e.g. 'Store information about method parameters') always read from and wrote to the first project in a multi-root/multi-project workspace. Switch both ProjectSelector and CompilerConfigurationView to listen for the native 'change' event (reading selectedIndex/value) and sync the rendered selection back from redux state, matching the working JdkRuntime pattern. Fixes #1650 * fix: always sync compiler dropdowns to redux state Set the vscode-single-select value unconditionally in the sync effects so the dropdown reflects redux state even when the level is empty (e.g. transiently while switching projects), instead of keeping the previous project's selection. Addresses PR review feedback on #1655.
1 parent 6c465ef commit 3407fd6

2 files changed

Lines changed: 103 additions & 36 deletions

File tree

src/project-settings/assets/compiler/features/CompilerConfigurationView.tsx

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import "@vscode-elements/elements/dist/vscode-table-row/index.js";
1111
import "@vscode-elements/elements/dist/vscode-table-cell/index.js";
1212

1313

14-
import { Dispatch, useEffect } from "react";
14+
import { Dispatch, useCallback, useEffect, useRef } from "react";
1515
import { useDispatch, useSelector } from "react-redux";
1616
import { updateCompilerSettings, updateAvailableComplianceLevels } from "./compilerConfigurationViewSlice";
1717
import { CompilerRequest } from "../../vscode/utils";
@@ -54,6 +54,10 @@ const CompilerConfigurationView = (): JSX.Element | null => {
5454
Number(sourceLevel) > currentJdkComplianceLevel ||
5555
Number(targetLevel) > currentJdkComplianceLevel;
5656

57+
const complianceRef = useRef<HTMLElement>(null);
58+
const sourceRef = useRef<HTMLElement>(null);
59+
const targetRef = useRef<HTMLElement>(null);
60+
5761
const onMessage = (event: any) => {
5862
const message = event.data;
5963
if (message.command === "compiler.onDidGetAvailableComplianceLevels") {
@@ -73,16 +77,68 @@ const CompilerConfigurationView = (): JSX.Element | null => {
7377
}
7478
}, []);
7579

76-
const jdkLevels = (selectedLevel: string, label: string, onClick: (value: string) => void) => {
80+
// The vscode-single-select element renders its options in the shadow DOM and
81+
// only emits a native `change` event on the host element, so the click handlers
82+
// on the slotted vscode-option children never fire. Listen to `change` instead.
83+
const useSelectChange = (ref: React.RefObject<HTMLElement | null>, onChange: (value: string) => void) => {
84+
useEffect(() => {
85+
const el = ref.current;
86+
if (!el) {
87+
return;
88+
}
89+
const handler = (e: Event) => {
90+
const value = (e.target as any).value;
91+
if (value) {
92+
onChange(value);
93+
}
94+
};
95+
el.addEventListener("change", handler);
96+
return () => el.removeEventListener("change", handler);
97+
}, [ref, onChange]);
98+
};
99+
100+
const onChangeComplianceLevel = useCallback((value: string) => {
101+
dispatch(updateCompilerSettings({ activeProjectIndex, complianceLevel: value }));
102+
}, [dispatch, activeProjectIndex]);
103+
const onChangeSourceLevel = useCallback((value: string) => {
104+
dispatch(updateCompilerSettings({ activeProjectIndex, sourceLevel: value }));
105+
}, [dispatch, activeProjectIndex]);
106+
const onChangeTargetLevel = useCallback((value: string) => {
107+
dispatch(updateCompilerSettings({ activeProjectIndex, targetLevel: value }));
108+
}, [dispatch, activeProjectIndex]);
109+
110+
useSelectChange(complianceRef, onChangeComplianceLevel);
111+
useSelectChange(sourceRef, onChangeSourceLevel);
112+
useSelectChange(targetRef, onChangeTargetLevel);
113+
114+
// Keep the rendered selection in sync with the redux state. Set the value
115+
// unconditionally (even when the level is "") so the dropdown always reflects
116+
// state instead of keeping the previous project's selection.
117+
useEffect(() => {
118+
if (complianceRef.current) {
119+
(complianceRef.current as any).value = complianceLevel;
120+
}
121+
}, [complianceLevel, availableComplianceLevels]);
122+
useEffect(() => {
123+
if (sourceRef.current) {
124+
(sourceRef.current as any).value = sourceLevel;
125+
}
126+
}, [sourceLevel, availableComplianceLevels]);
127+
useEffect(() => {
128+
if (targetRef.current) {
129+
(targetRef.current as any).value = targetLevel;
130+
}
131+
}, [targetLevel, availableComplianceLevels]);
132+
133+
const jdkLevels = (selectedLevel: string, label: string) => {
77134
return availableComplianceLevels.map((level) => {
78135

79136
return (
80137
<vscode-option
81138
className="setting-section-option"
82139
key={`${label}-${level}`}
83140
value={level}
84-
selected={level === selectedLevel}
85-
onClick={() => onClick(level)}
141+
selected={level === selectedLevel ? true : undefined}
86142
>
87143
<span>{level}</span>
88144
</vscode-option>
@@ -104,27 +160,6 @@ const CompilerConfigurationView = (): JSX.Element | null => {
104160
}));
105161
};
106162

107-
const onClickComplianceLevel = (value: string) => {
108-
dispatch(updateCompilerSettings({
109-
activeProjectIndex,
110-
complianceLevel: value
111-
}));
112-
};
113-
114-
const onClickSourceLevel = (value: string) => {
115-
dispatch(updateCompilerSettings({
116-
activeProjectIndex,
117-
sourceLevel: value
118-
}));
119-
};
120-
121-
const onClickTargetLevel = (value: string) => {
122-
dispatch(updateCompilerSettings({
123-
activeProjectIndex,
124-
targetLevel: value
125-
}));
126-
};
127-
128163
const onClickGenerateDebugInfo = (e: any) => {
129164
dispatch(updateCompilerSettings({
130165
activeProjectIndex,
@@ -158,8 +193,8 @@ const CompilerConfigurationView = (): JSX.Element | null => {
158193
<span>Bytecode version:</span>
159194
</vscode-table-cell>
160195
<vscode-table-cell className="flex-center pl-0 pr-0" >
161-
<vscode-single-select value={complianceLevel}>
162-
{jdkLevels(complianceLevel, "compliance", onClickComplianceLevel)}
196+
<vscode-single-select ref={complianceRef}>
197+
{jdkLevels(complianceLevel, "compliance")}
163198
</vscode-single-select>
164199
</vscode-table-cell>
165200
</vscode-table-row>
@@ -168,8 +203,8 @@ const CompilerConfigurationView = (): JSX.Element | null => {
168203
<span>Source compatibility:</span>
169204
</vscode-table-cell>
170205
<vscode-table-cell className="flex-center pl-0 pr-0" >
171-
<vscode-single-select value={sourceLevel}>
172-
{jdkLevels(sourceLevel, "source", onClickSourceLevel)}
206+
<vscode-single-select ref={sourceRef}>
207+
{jdkLevels(sourceLevel, "source")}
173208
</vscode-single-select>
174209
</vscode-table-cell>
175210
</vscode-table-row>
@@ -178,8 +213,8 @@ const CompilerConfigurationView = (): JSX.Element | null => {
178213
<span>Target compatibility:</span>
179214
</vscode-table-cell>
180215
<vscode-table-cell className="flex-center pl-0 pr-0" >
181-
<vscode-single-select value={targetLevel}>
182-
{jdkLevels(targetLevel, "target", onClickTargetLevel)}
216+
<vscode-single-select ref={targetRef}>
217+
{jdkLevels(targetLevel, "target")}
183218
</vscode-single-select>
184219
</vscode-table-cell>
185220
</vscode-table-row>

src/project-settings/assets/mainpage/features/component/ProjectSelector.tsx

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import "@vscode-elements/elements/dist/vscode-single-select/index.js";
55
import "@vscode-elements/elements/dist/vscode-option/index.js";
66

7-
import { useEffect } from "react";
7+
import { useCallback, useEffect, useRef } from "react";
88
import { useSelector, useDispatch } from "react-redux";
99
import { ProjectInfo } from "../../../../types";
1010
import { Dispatch } from "@reduxjs/toolkit";
@@ -17,10 +17,37 @@ const ProjectSelector = (): JSX.Element | null => {
1717
const projects: ProjectInfo[] = useSelector((state: any) => state.commonConfig.data.projects);
1818

1919
const dispatch: Dispatch<any> = useDispatch();
20+
const selectRef = useRef<HTMLElement>(null);
2021

21-
const handleActiveProjectChange = (index: number) => {
22+
const handleActiveProjectChange = useCallback((index: number) => {
2223
dispatch(activeProjectChange(index));
23-
};
24+
}, [dispatch]);
25+
26+
// The vscode-single-select element renders its options in the shadow DOM and
27+
// only emits a native `change` event on the host element, so the click handlers
28+
// on the slotted vscode-option children never fire. Listen to `change` instead.
29+
useEffect(() => {
30+
const el = selectRef.current;
31+
if (!el) {
32+
return;
33+
}
34+
const onChange = (e: Event) => {
35+
const index = (e.target as any).selectedIndex;
36+
if (typeof index === "number" && index >= 0) {
37+
handleActiveProjectChange(index);
38+
}
39+
};
40+
el.addEventListener("change", onChange);
41+
return () => el.removeEventListener("change", onChange);
42+
}, [handleActiveProjectChange]);
43+
44+
// Keep the rendered selection in sync with the active project index.
45+
useEffect(() => {
46+
const el = selectRef.current;
47+
if (el && projects.length > 0) {
48+
(el as any).selectedIndex = activeProjectIndex;
49+
}
50+
}, [activeProjectIndex, projects]);
2451

2552
useEffect(() => {
2653
if (projects.length === 0) {
@@ -38,7 +65,12 @@ const ProjectSelector = (): JSX.Element | null => {
3865
}
3966

4067
return (
41-
<vscode-option className="setting-section-option" key={project.rootPath} onClick={() => handleActiveProjectChange(index)}>
68+
<vscode-option
69+
className="setting-section-option"
70+
key={project.rootPath}
71+
value={project.rootPath}
72+
selected={index === activeProjectIndex ? true : undefined}
73+
>
4274
{project.name}
4375
</vscode-option>
4476
);
@@ -48,7 +80,7 @@ const ProjectSelector = (): JSX.Element | null => {
4880
<div id="project-selector" className="setting-section">
4981
<div className="flex-center mt-2 mb-2">
5082
<span className="setting-section-description ml-1 mr-1">Project:</span>
51-
<vscode-single-select className="setting-section-dropdown">
83+
<vscode-single-select ref={selectRef} className="setting-section-dropdown">
5284
{projectSelections}
5385
</vscode-single-select>
5486
</div>

0 commit comments

Comments
 (0)