mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-04 01:44:44 -06:00
keyboard improvements to question list (#292429)
* keyboard improvements to question list * fix test * address comments
This commit is contained in:
parent
b2f9a674cb
commit
fd9aeb781c
@ -23,6 +23,7 @@ import './media/chatQuestionCarousel.css';
|
||||
|
||||
export interface IChatQuestionCarouselOptions {
|
||||
onSubmit: (answers: Map<string, unknown> | undefined) => void;
|
||||
shouldAutoFocus?: boolean;
|
||||
}
|
||||
|
||||
export class ChatQuestionCarouselPart extends Disposable implements IChatContentPart {
|
||||
@ -397,9 +398,11 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
|
||||
this._questionContainer.appendChild(headerRow);
|
||||
|
||||
const isSingleQuestion = this.carousel.questions.length === 1;
|
||||
// Update step indicator in footer
|
||||
if (this._stepIndicator) {
|
||||
this._stepIndicator.textContent = `${this._currentIndex + 1}/${this.carousel.questions.length}`;
|
||||
this._stepIndicator.style.display = isSingleQuestion ? 'none' : '';
|
||||
}
|
||||
|
||||
// Render input based on question type
|
||||
@ -409,13 +412,14 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
|
||||
// Update navigation button states (prevButton and nextButton are guaranteed non-null from guard above)
|
||||
this._prevButton!.enabled = this._currentIndex > 0;
|
||||
this._prevButton!.element.style.display = isSingleQuestion ? 'none' : '';
|
||||
|
||||
// Update next button icon/label for last question
|
||||
const isLastQuestion = this._currentIndex === this.carousel.questions.length - 1;
|
||||
const submitLabel = localize('submit', 'Submit');
|
||||
const nextLabel = localize('next', 'Next');
|
||||
if (isLastQuestion) {
|
||||
this._nextButton!.label = `$(${Codicon.check.id})`;
|
||||
this._nextButton!.label = submitLabel;
|
||||
this._nextButton!.element.title = submitLabel;
|
||||
this._nextButton!.element.setAttribute('aria-label', submitLabel);
|
||||
// Switch to primary style for submit
|
||||
@ -476,7 +480,9 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
this._textInputBoxes.set(question.id, inputBox);
|
||||
|
||||
// Focus on input when rendered using proper DOM scheduling
|
||||
this._inputBoxes.add(dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(inputBox.element), () => inputBox.focus()));
|
||||
if (this._options.shouldAutoFocus !== false) {
|
||||
this._inputBoxes.add(dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(inputBox.element), () => inputBox.focus()));
|
||||
}
|
||||
}
|
||||
|
||||
private renderSingleSelect(container: HTMLElement, question: IChatQuestion): void {
|
||||
@ -540,12 +546,15 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
listItem.id = `option-${question.id}-${index}`;
|
||||
listItem.tabIndex = -1;
|
||||
|
||||
const number = dom.$('.chat-question-list-number');
|
||||
number.textContent = `${index + 1}`;
|
||||
listItem.appendChild(number);
|
||||
|
||||
// Selection indicator (checkmark when selected)
|
||||
const indicator = dom.$('.chat-question-list-indicator');
|
||||
if (isSelected) {
|
||||
indicator.classList.add('codicon', 'codicon-check');
|
||||
}
|
||||
listItem.appendChild(indicator);
|
||||
indicators.push(indicator);
|
||||
|
||||
// Label with optional description (format: "Title - Description")
|
||||
@ -563,6 +572,7 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
label.textContent = option.label;
|
||||
}
|
||||
listItem.appendChild(label);
|
||||
listItem.appendChild(indicator);
|
||||
|
||||
if (isSelected) {
|
||||
listItem.classList.add('selected');
|
||||
@ -586,35 +596,13 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
selectContainer.setAttribute('aria-activedescendant', listItems[selectedIndex].id);
|
||||
}
|
||||
|
||||
// Keyboard navigation for the list
|
||||
this._inputBoxes.add(dom.addDisposableListener(selectContainer, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
const data = this._singleSelectItems.get(question.id);
|
||||
if (!data || !listItems.length) {
|
||||
return;
|
||||
}
|
||||
let newIndex = data.selectedIndex;
|
||||
|
||||
if (event.keyCode === KeyCode.DownArrow) {
|
||||
e.preventDefault();
|
||||
newIndex = Math.min(data.selectedIndex + 1, listItems.length - 1);
|
||||
} else if (event.keyCode === KeyCode.UpArrow) {
|
||||
e.preventDefault();
|
||||
newIndex = Math.max(data.selectedIndex - 1, 0);
|
||||
} else if (event.keyCode === KeyCode.Space || event.keyCode === KeyCode.Enter) {
|
||||
// Space/Enter confirms current selection (already selected, nothing extra to do)
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (newIndex !== data.selectedIndex && newIndex >= 0) {
|
||||
updateSelection(newIndex);
|
||||
}
|
||||
}));
|
||||
|
||||
// Always show freeform input for single-select questions
|
||||
const freeformContainer = dom.$('.chat-question-freeform');
|
||||
|
||||
const freeformNumber = dom.$('.chat-question-freeform-number');
|
||||
freeformNumber.textContent = `${options.length + 1}`;
|
||||
freeformContainer.appendChild(freeformNumber);
|
||||
|
||||
const freeformTextarea = dom.$<HTMLTextAreaElement>('textarea.chat-question-freeform-textarea');
|
||||
freeformTextarea.placeholder = localize('chat.questionCarousel.enterCustomAnswer', 'Enter custom answer');
|
||||
freeformTextarea.rows = 1;
|
||||
@ -637,10 +625,62 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
container.appendChild(freeformContainer);
|
||||
this._freeformTextareas.set(question.id, freeformTextarea);
|
||||
|
||||
// Keyboard navigation for the list
|
||||
this._inputBoxes.add(dom.addDisposableListener(selectContainer, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
const data = this._singleSelectItems.get(question.id);
|
||||
if (!data || !listItems.length) {
|
||||
return;
|
||||
}
|
||||
let newIndex = data.selectedIndex;
|
||||
|
||||
if (event.keyCode === KeyCode.DownArrow) {
|
||||
e.preventDefault();
|
||||
newIndex = Math.min(data.selectedIndex + 1, listItems.length - 1);
|
||||
} else if (event.keyCode === KeyCode.UpArrow) {
|
||||
e.preventDefault();
|
||||
newIndex = Math.max(data.selectedIndex - 1, 0);
|
||||
} else if (event.keyCode === KeyCode.Enter || event.keyCode === KeyCode.Space) {
|
||||
// Enter confirms current selection and advances to next question
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.handleNext();
|
||||
return;
|
||||
} else if (event.keyCode >= KeyCode.Digit1 && event.keyCode <= KeyCode.Digit9) {
|
||||
// Number keys 1-9 select the corresponding option, or focus freeform for next number
|
||||
const numberIndex = event.keyCode - KeyCode.Digit1;
|
||||
if (numberIndex < listItems.length) {
|
||||
e.preventDefault();
|
||||
updateSelection(numberIndex);
|
||||
} else if (numberIndex === listItems.length) {
|
||||
e.preventDefault();
|
||||
updateSelection(-1);
|
||||
freeformTextarea.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (newIndex !== data.selectedIndex && newIndex >= 0) {
|
||||
updateSelection(newIndex);
|
||||
}
|
||||
}));
|
||||
|
||||
// Resize textarea if it has restored content
|
||||
if (previousFreeform !== undefined) {
|
||||
this._inputBoxes.add(dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(freeformTextarea), () => autoResize()));
|
||||
}
|
||||
|
||||
// focus on the row when first rendered
|
||||
if (this._options.shouldAutoFocus !== false && listItems.length > 0) {
|
||||
const focusIndex = selectedIndex >= 0 ? selectedIndex : 0;
|
||||
// if no default, select the first answer
|
||||
if (selectedIndex < 0) {
|
||||
updateSelection(0);
|
||||
}
|
||||
this._inputBoxes.add(dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(selectContainer), () => {
|
||||
listItems[focusIndex]?.focus();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private renderMultiSelect(container: HTMLElement, question: IChatQuestion): void {
|
||||
@ -669,6 +709,7 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
const checkboxes: Checkbox[] = [];
|
||||
const listItems: HTMLElement[] = [];
|
||||
let focusedIndex = 0;
|
||||
let firstCheckedIndex = -1;
|
||||
|
||||
options.forEach((option, index) => {
|
||||
// Determine initial checked state
|
||||
@ -685,6 +726,10 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
listItem.id = `option-${question.id}-${index}`;
|
||||
listItem.tabIndex = -1;
|
||||
|
||||
const number = dom.$('.chat-question-list-number');
|
||||
number.textContent = `${index + 1}`;
|
||||
listItem.appendChild(number);
|
||||
|
||||
// Create checkbox using the VS Code Checkbox component
|
||||
const checkbox = this._inputBoxes.add(new Checkbox(option.label, isChecked, defaultCheckboxStyles));
|
||||
checkbox.domNode.classList.add('chat-question-list-checkbox');
|
||||
@ -710,6 +755,9 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
|
||||
if (isChecked) {
|
||||
listItem.classList.add('checked');
|
||||
if (firstCheckedIndex === -1) {
|
||||
firstCheckedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
// Sync checkbox state with list item visual state
|
||||
@ -736,6 +784,29 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
|
||||
this._multiSelectCheckboxes.set(question.id, checkboxes);
|
||||
|
||||
// Always show freeform input for multi-select questions
|
||||
const freeformContainer = dom.$('.chat-question-freeform');
|
||||
|
||||
// Number indicator for freeform (comes after all options)
|
||||
const freeformNumber = dom.$('.chat-question-freeform-number');
|
||||
freeformNumber.textContent = `${options.length + 1}`;
|
||||
freeformContainer.appendChild(freeformNumber);
|
||||
|
||||
const freeformTextarea = dom.$<HTMLTextAreaElement>('textarea.chat-question-freeform-textarea');
|
||||
freeformTextarea.placeholder = localize('chat.questionCarousel.enterCustomAnswer', 'Enter custom answer');
|
||||
freeformTextarea.rows = 1;
|
||||
|
||||
if (previousFreeform !== undefined) {
|
||||
freeformTextarea.value = previousFreeform;
|
||||
}
|
||||
|
||||
// Setup auto-resize behavior
|
||||
const autoResize = this.setupTextareaAutoResize(freeformTextarea);
|
||||
|
||||
freeformContainer.appendChild(freeformTextarea);
|
||||
container.appendChild(freeformContainer);
|
||||
this._freeformTextareas.set(question.id, freeformTextarea);
|
||||
|
||||
// Keyboard navigation for the list
|
||||
this._inputBoxes.add(dom.addDisposableListener(selectContainer, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
@ -753,37 +824,42 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
e.preventDefault();
|
||||
focusedIndex = Math.max(focusedIndex - 1, 0);
|
||||
listItems[focusedIndex].focus();
|
||||
} else if (event.keyCode === KeyCode.Enter) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.handleNext();
|
||||
} else if (event.keyCode === KeyCode.Space) {
|
||||
e.preventDefault();
|
||||
// Toggle the currently focused checkbox using click() to trigger onChange
|
||||
if (focusedIndex >= 0 && focusedIndex < checkboxes.length) {
|
||||
checkboxes[focusedIndex].domNode.click();
|
||||
}
|
||||
} else if (event.keyCode >= KeyCode.Digit1 && event.keyCode <= KeyCode.Digit9) {
|
||||
// Number keys 1-9 toggle the corresponding checkbox, or focus freeform for next number
|
||||
const numberIndex = event.keyCode - KeyCode.Digit1;
|
||||
if (numberIndex < checkboxes.length) {
|
||||
e.preventDefault();
|
||||
checkboxes[numberIndex].domNode.click();
|
||||
} else if (numberIndex === checkboxes.length) {
|
||||
e.preventDefault();
|
||||
freeformTextarea.focus();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Always show freeform input for multi-select questions
|
||||
const freeformContainer = dom.$('.chat-question-freeform');
|
||||
|
||||
const freeformTextarea = dom.$<HTMLTextAreaElement>('textarea.chat-question-freeform-textarea');
|
||||
freeformTextarea.placeholder = localize('chat.questionCarousel.enterCustomAnswer', 'Enter custom answer');
|
||||
freeformTextarea.rows = 1;
|
||||
|
||||
if (previousFreeform !== undefined) {
|
||||
freeformTextarea.value = previousFreeform;
|
||||
}
|
||||
|
||||
// Setup auto-resize behavior
|
||||
const autoResize = this.setupTextareaAutoResize(freeformTextarea);
|
||||
|
||||
freeformContainer.appendChild(freeformTextarea);
|
||||
container.appendChild(freeformContainer);
|
||||
this._freeformTextareas.set(question.id, freeformTextarea);
|
||||
|
||||
// Resize textarea if it has restored content
|
||||
if (previousFreeform !== undefined) {
|
||||
this._inputBoxes.add(dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(freeformTextarea), () => autoResize()));
|
||||
}
|
||||
|
||||
// Focus on the appropriate row when rendered (first checked row, or first row if none)
|
||||
if (this._options.shouldAutoFocus !== false && listItems.length > 0) {
|
||||
const initialFocusIndex = firstCheckedIndex >= 0 ? firstCheckedIndex : 0;
|
||||
focusedIndex = initialFocusIndex;
|
||||
this._inputBoxes.add(dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(selectContainer), () => {
|
||||
listItems[initialFocusIndex]?.focus();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private getCurrentAnswer(): unknown {
|
||||
@ -836,9 +912,14 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
}
|
||||
});
|
||||
}
|
||||
// Include defaults if nothing selected (defaultValue is option id or array of ids)
|
||||
|
||||
// Always include freeform value for multi-select questions
|
||||
const freeformTextarea = this._freeformTextareas.get(question.id);
|
||||
const freeformValue = freeformTextarea?.value !== '' ? freeformTextarea?.value : undefined;
|
||||
|
||||
// Only include defaults if nothing selected AND no freeform input
|
||||
let finalSelectedValues = selectedValues;
|
||||
if (selectedValues.length === 0 && question.defaultValue !== undefined) {
|
||||
if (selectedValues.length === 0 && !freeformValue && question.defaultValue !== undefined) {
|
||||
const defaultIds = Array.isArray(question.defaultValue)
|
||||
? question.defaultValue
|
||||
: [question.defaultValue];
|
||||
@ -848,9 +929,6 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
|
||||
finalSelectedValues = defaultValues?.filter(v => v !== undefined) || [];
|
||||
}
|
||||
|
||||
// Always include freeform value for multi-select questions
|
||||
const freeformTextarea = this._freeformTextareas.get(question.id);
|
||||
const freeformValue = freeformTextarea?.value !== '' ? freeformTextarea?.value : undefined;
|
||||
if (freeformValue || finalSelectedValues.length > 0) {
|
||||
return { selectedValues: finalSelectedValues, freeformValue };
|
||||
}
|
||||
|
||||
@ -67,7 +67,6 @@
|
||||
min-width: 0;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--vscode-chat-requestBorder);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.chat-question-header-row .chat-question-title {
|
||||
@ -117,6 +116,7 @@
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.chat-question-nav-arrows {
|
||||
@ -149,6 +149,9 @@
|
||||
.chat-question-carousel-nav .monaco-button.chat-question-nav-arrow.chat-question-nav-submit {
|
||||
background: var(--vscode-button-background) !important;
|
||||
color: var(--vscode-button-foreground) !important;
|
||||
width: auto;
|
||||
min-width: auto;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.chat-question-carousel-nav .monaco-button.chat-question-nav-arrow.chat-question-nav-submit:hover:not(.disabled) {
|
||||
@ -168,7 +171,6 @@
|
||||
.chat-question-carousel-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
background: var(--vscode-chat-requestBackground);
|
||||
padding: 12px 16px;
|
||||
overflow: hidden;
|
||||
@ -204,7 +206,6 @@
|
||||
.chat-question-input-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
@ -214,8 +215,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
border: 1px solid var(--vscode-input-border, var(--vscode-chat-requestBorder));
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
padding: 4px 0;
|
||||
}
|
||||
@ -233,23 +232,21 @@
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
user-select: none;
|
||||
border-bottom: 1px solid var(--vscode-chat-requestBorder);
|
||||
}
|
||||
|
||||
.chat-question-list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.chat-question-list-item:hover {
|
||||
background-color: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
|
||||
.chat-question-list-item:focus {
|
||||
outline: none;
|
||||
background-color: var(--vscode-list-hoverBackground);
|
||||
.interactive-session .interactive-response .value {
|
||||
.chat-question-list-item:focus,
|
||||
.chat-question-list:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Single-select: highlight entire row when selected */
|
||||
/* Single-select: highlight entire row when selected and also outline */
|
||||
.chat-question-list-item.selected {
|
||||
background-color: var(--vscode-list-activeSelectionBackground);
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
@ -259,7 +256,41 @@
|
||||
background-color: var(--vscode-list-activeSelectionBackground);
|
||||
}
|
||||
|
||||
/* Selection indicator (checkmark) for single select */
|
||||
/* todo: change to use keybinding service so we don't have to recreate this */
|
||||
.chat-question-list-number,
|
||||
.chat-question-freeform-number {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 14px;
|
||||
padding: 2px 4px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
background-color: var(--vscode-keybindingLabel-background);
|
||||
color: var(--vscode-keybindingLabel-foreground);
|
||||
border-color: var(--vscode-keybindingLabel-border);
|
||||
border-bottom-color: var(--vscode-keybindingLabel-bottomBorder);
|
||||
box-shadow: inset 0 -1px 0 var(--vscode-widget-shadow);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-question-freeform-number {
|
||||
margin-top: 4px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.chat-question-list-item.selected .chat-question-list-number {
|
||||
background-color: transparent;
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
border-color: var(--vscode-list-activeSelectionForeground);
|
||||
border-bottom-color: var(--vscode-list-activeSelectionForeground);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Selection indicator (checkmark) for single select - positioned on right */
|
||||
.chat-question-list-indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@ -267,6 +298,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.chat-question-list-indicator.codicon-check {
|
||||
@ -290,6 +322,7 @@
|
||||
background-color: var(--vscode-button-background) !important;
|
||||
border-color: var(--vscode-button-background) !important;
|
||||
color: var(--vscode-button-foreground) !important;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.chat-question-list-checkbox.monaco-custom-toggle.checked .codicon {
|
||||
@ -414,10 +447,11 @@
|
||||
|
||||
|
||||
.chat-question-freeform {
|
||||
margin-top: 8px;
|
||||
margin-left: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.chat-question-freeform-label {
|
||||
@ -439,6 +473,7 @@
|
||||
font-size: var(--vscode-chat-font-size-body-s);
|
||||
box-sizing: border-box;
|
||||
overflow-y: hidden;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.chat-question-freeform-textarea:focus {
|
||||
|
||||
@ -1899,7 +1899,11 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||
private renderQuestionCarousel(context: IChatContentPartRenderContext, carousel: IChatQuestionCarousel, templateData: IChatListItemTemplate): IChatContentPart {
|
||||
this.finalizeCurrentThinkingPart(context, templateData);
|
||||
|
||||
const widget = isResponseVM(context.element) ? this.chatWidgetService.getWidgetBySessionResource(context.element.sessionResource) : undefined;
|
||||
const shouldAutoFocus = widget ? widget.getInput() === '' : true;
|
||||
|
||||
const part = this.instantiationService.createInstance(ChatQuestionCarouselPart, carousel, context, {
|
||||
shouldAutoFocus,
|
||||
onSubmit: async (answers) => {
|
||||
// Mark the carousel as used and store the answers
|
||||
const answersRecord = answers ? Object.fromEntries(answers) : undefined;
|
||||
|
||||
@ -383,17 +383,26 @@ suite('ChatQuestionCarouselPart', () => {
|
||||
type: 'singleSelect',
|
||||
title: 'Choose one',
|
||||
options: [
|
||||
{ id: 'a', label: 'Option A', value: 'a' }
|
||||
{ id: 'a', label: 'Option A', value: 'a' },
|
||||
{ id: 'b', label: 'Option B', value: 'b' }
|
||||
]
|
||||
}
|
||||
]);
|
||||
createWidget(carousel);
|
||||
|
||||
const listItem = widget.domNode.querySelector('.chat-question-list-item') as HTMLElement;
|
||||
assert.ok(listItem, 'List item should exist');
|
||||
assert.strictEqual(listItem.getAttribute('role'), 'option');
|
||||
assert.ok(listItem.id, 'List item should have an id');
|
||||
assert.strictEqual(listItem.getAttribute('aria-selected'), 'false', 'Unselected item should have aria-selected=false');
|
||||
const listItems = widget.domNode.querySelectorAll('.chat-question-list-item');
|
||||
assert.strictEqual(listItems.length, 2, 'Should have 2 list items');
|
||||
|
||||
// First item should be auto-selected (no default value, so first is selected)
|
||||
const firstItem = listItems[0] as HTMLElement;
|
||||
assert.strictEqual(firstItem.getAttribute('role'), 'option');
|
||||
assert.ok(firstItem.id, 'List item should have an id');
|
||||
assert.strictEqual(firstItem.getAttribute('aria-selected'), 'true', 'First item should be auto-selected');
|
||||
|
||||
// Second item should not be selected
|
||||
const secondItem = listItems[1] as HTMLElement;
|
||||
assert.strictEqual(secondItem.getAttribute('role'), 'option');
|
||||
assert.strictEqual(secondItem.getAttribute('aria-selected'), 'false', 'Unselected item should have aria-selected=false');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user