Skip to content

Commit bd5c7c6

Browse files
committed
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
1 parent 6c465ef commit bd5c7c6

2 files changed

Lines changed: 101 additions & 36 deletions

File tree

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

Lines changed: 64 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,66 @@ 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.
115+
useEffect(() => {
116+
if (complianceRef.current && complianceLevel) {
117+
(complianceRef.current as any).value = complianceLevel;
118+
}
119+
}, [complianceLevel, availableComplianceLevels]);
120+
useEffect(() => {
121+
if (sourceRef.current && sourceLevel) {
122+
(sourceRef.current as any).value = sourceLevel;
123+
}
124+
}, [sourceLevel, availableComplianceLevels]);
125+
useEffect(() => {
126+
if (targetRef.current && targetLevel) {
127+
(targetRef.current as any).value = targetLevel;
128+
}
129+
}, [targetLevel, availableComplianceLevels]);
130+
131+
const jdkLevels = (selectedLevel: string, label: string) => {
77132
return availableComplianceLevels.map((level) => {
78133

79134
return (
80135
<vscode-option
81136
className="setting-section-option"
82137
key={`${label}-${level}`}
83138
value={level}
84-
selected={level === selectedLevel}
85-
onClick={() => onClick(level)}
139+
selected={level === selectedLevel ? true : undefined}
86140
>
87141
<span>{level}</span>
88142
</vscode-option>
@@ -104,27 +158,6 @@ const CompilerConfigurationView = (): JSX.Element | null => {
104158
}));
105159
};
106160

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-
128161
const onClickGenerateDebugInfo = (e: any) => {
129162
dispatch(updateCompilerSettings({
130163
activeProjectIndex,
@@ -158,8 +191,8 @@ const CompilerConfigurationView = (): JSX.Element | null => {
158191
<span>Bytecode version:</span>
159192
</vscode-table-cell>
160193
<vscode-table-cell className="flex-center pl-0 pr-0" >
161-
<vscode-single-select value={complianceLevel}>
162-
{jdkLevels(complianceLevel, "compliance", onClickComplianceLevel)}
194+
<vscode-single-select ref={complianceRef}>
195+
{jdkLevels(complianceLevel, "compliance")}
163196
</vscode-single-select>
164197
</vscode-table-cell>
165198
</vscode-table-row>
@@ -168,8 +201,8 @@ const CompilerConfigurationView = (): JSX.Element | null => {
168201
<span>Source compatibility:</span>
169202
</vscode-table-cell>
170203
<vscode-table-cell className="flex-center pl-0 pr-0" >
171-
<vscode-single-select value={sourceLevel}>
172-
{jdkLevels(sourceLevel, "source", onClickSourceLevel)}
204+
<vscode-single-select ref={sourceRef}>
205+
{jdkLevels(sourceLevel, "source")}
173206
</vscode-single-select>
174207
</vscode-table-cell>
175208
</vscode-table-row>
@@ -178,8 +211,8 @@ const CompilerConfigurationView = (): JSX.Element | null => {
178211
<span>Target compatibility:</span>
179212
</vscode-table-cell>
180213
<vscode-table-cell className="flex-center pl-0 pr-0" >
181-
<vscode-single-select value={targetLevel}>
182-
{jdkLevels(targetLevel, "target", onClickTargetLevel)}
214+
<vscode-single-select ref={targetRef}>
215+
{jdkLevels(targetLevel, "target")}
183216
</vscode-single-select>
184217
</vscode-table-cell>
185218
</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)