From a996655c4d801de05589048f0a3dfb6c973b6d0a Mon Sep 17 00:00:00 2001 From: Richard Goforth Date: Wed, 30 Sep 2026 11:20:08 -0500 Subject: [PATCH 1/2] fix: associate selector label with input Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 336317db-0945-4820-ae6a-7c2a231f1fc0 --- src/RestSelectorControl.tsx | 74 +++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/src/RestSelectorControl.tsx b/src/RestSelectorControl.tsx index 4feee9b..fb32238 100644 --- a/src/RestSelectorControl.tsx +++ b/src/RestSelectorControl.tsx @@ -5,9 +5,8 @@ import { Spinner, SpinnerSize } from "azure-devops-ui/Spinner"; import { ConditionalChildren } from "azure-devops-ui/ConditionalChildren"; import { TextField, TextFieldWidth } from "azure-devops-ui/TextField"; import { FormItem } from "azure-devops-ui/FormItem"; -import { getConfiguration } from 'azure-devops-extension-sdk'; +import { getConfiguration } from "azure-devops-extension-sdk"; -const inputField = React.createRef(); const optionsObservable = new ObservableValue>([]); const errorObservable = new ObservableValue(false); @@ -15,15 +14,33 @@ export interface ISelectorProps { selected: ObservableValue; options: Promise; fieldName: string; - placeholder:string; + placeholder: string; message: ObservableValue; } +function getFieldElementId(prefix: string, fieldName: string): string { + const safeFieldName = fieldName + .trim() + .toLowerCase() + .replace(/[^a-z0-9_]+/g, "-") + .replace(/^-+|-+$/g, "") || "field"; + let fieldNameHash = 0; + + for (let index = 0; index < fieldName.length; index++) { + fieldNameHash = (Math.imul(31, fieldNameHash) + fieldName.charCodeAt(index)) | 0; + } + + return `${prefix}-${safeFieldName}-${(fieldNameHash >>> 0).toString(36)}`; +} + export class RestSelectorControl extends React.Component { - constructor(props : ISelectorProps) { + private inputField = React.createRef(); + private dataLoaded = new ObservableValue(false); + + constructor(props: ISelectorProps) { super(props); this.state = { focused: false, value: "" }; - + this.props.options.then(o => optionsObservable.value = o); optionsObservable.subscribe(() => { @@ -31,45 +48,62 @@ export class RestSelectorControl extends React.Component { }); this.props.message.subscribe(m => { - errorObservable.value = !!m + errorObservable.value = !!m; }); } - private dataLoaded = new ObservableValue(false); - componentDidMount() { - inputField.current.setAttribute("list", "datalist-"+this.props.fieldName); + this.updateInputList(); + } + + componentDidUpdate(previousProps: ISelectorProps) { + if (previousProps.fieldName !== this.props.fieldName) { + this.updateInputList(); + } + } + + private getInputId(): string { + return getFieldElementId("rest-selector-input", this.props.fieldName); + } + + private getDatalistId(): string { + return getFieldElementId("rest-selector-options", this.props.fieldName); + } + + private updateInputList(): void { + this.inputField.current?.setAttribute("list", this.getDatalistId()); } public render() { - let labelVar = ''; - if (!getConfiguration().witInputs.HideFieldLabel) { - labelVar = this.props.fieldName; - } - return -
+ const label = getConfiguration().witInputs.HideFieldLabel ? undefined : this.props.fieldName; + const datalistId = this.getDatalistId(); + + return +
(this.props.selected.value = newValue)} placeholder={this.props.placeholder} width={TextFieldWidth.standard} autoComplete={true} - inputElement={inputField} + inputElement={this.inputField} + inputId={this.getInputId()} + label={label} />
- + {(props: { options: string[] }) => { return props.options.map(function (item) { - return - }) + return ; + }); }}
- + ; } } \ No newline at end of file From ffe78102ec49b41d3217b924b1eee03d498e4540 Mon Sep 17 00:00:00 2001 From: Richard Goforth Date: Wed, 30 Sep 2026 11:32:06 -0500 Subject: [PATCH 2/2] fix: name selector input when label hidden Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 336317db-0945-4820-ae6a-7c2a231f1fc0 --- src/RestSelectorControl.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/RestSelectorControl.tsx b/src/RestSelectorControl.tsx index fb32238..ec4de74 100644 --- a/src/RestSelectorControl.tsx +++ b/src/RestSelectorControl.tsx @@ -76,6 +76,7 @@ export class RestSelectorControl extends React.Component { public render() { const label = getConfiguration().witInputs.HideFieldLabel ? undefined : this.props.fieldName; + const ariaLabel = label ? undefined : this.props.fieldName; const datalistId = this.getDatalistId(); return @@ -89,6 +90,7 @@ export class RestSelectorControl extends React.Component { inputElement={this.inputField} inputId={this.getInputId()} label={label} + ariaLabel={ariaLabel} />