Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,47 @@ describe('ModelDropdownComponent', () => {
expect(setEffort).toHaveBeenCalledWith('high');
});

it('rotates the trigger chevron while the menu is open', () => {
// The open state comes from the trigger's cdkMenuOpened/cdkMenuClosed
// outputs. CdkMenu has no `opened` output, so an `(opened)` binding on the
// panel compiles to a DOM listener that never fires and the chevron sticks.
const { fixture } = setup();
const trigger: HTMLButtonElement = fixture.nativeElement.querySelector(
'button[aria-label="Select model"]',
);
const chevron = () => trigger.querySelector('svg')!;
expect(chevron().classList).not.toContain('rotate-180');

trigger.click();
fixture.detectChanges();
expect(menuItems().length).toBeGreaterThan(0);
expect(chevron().classList).toContain('rotate-180');

// A second click on the trigger toggles the menu closed.
trigger.click();
fixture.detectChanges();
expect(menuItems().length).toBe(0);
expect(chevron().classList).not.toContain('rotate-180');
});

it('un-rotates the chevron when choosing a model closes the menu', () => {
const { fixture, setSelectedModel } = setup({
featured: [
makeModel({ id: 'a', modelId: 'a', modelName: 'Alpha' }),
makeModel({ id: 'b', modelId: 'b', modelName: 'Beta' }),
],
});
const chevron = () =>
fixture.nativeElement.querySelector('button[aria-label="Select model"] svg') as SVGElement;
openMenu(fixture);
expect(chevron().classList).toContain('rotate-180');

itemLabelled('Beta')!.click();
fixture.detectChanges();
expect(setSelectedModel).toHaveBeenCalled();
expect(chevron().classList).not.toContain('rotate-180');
});

it('locks the picker to a plain label when an agent pins the model', () => {
const { fixture, modelService } = setup();
modelService.agentModelLocked.set(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';
import { Component, ChangeDetectionStrategy, computed, inject, input, signal } from '@angular/core';
import { Router } from '@angular/router';
import { CdkMenuTrigger, CdkMenu, CdkMenuItem } from '@angular/cdk/menu';
import { ConnectedPosition } from '@angular/cdk/overlay';
Expand Down Expand Up @@ -35,6 +35,8 @@ import { ModelOptionComponent } from './components/model-option.component';
type="button"
[cdkMenuTriggerFor]="modelMenu"
[cdkMenuPosition]="menuPositions()"
(cdkMenuOpened)="onMenuOpened()"
(cdkMenuClosed)="onMenuClosed()"
[class]="triggerSizeClass()"
class="flex items-center rounded-lg text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary)] dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
aria-label="Select model"
Expand Down Expand Up @@ -65,8 +67,6 @@ import { ModelOptionComponent } from './components/model-option.component';
<ng-template #modelMenu>
<div
cdkMenu
(closed)="onMenuClosed()"
(opened)="onMenuOpened()"
class="w-72 rounded-md bg-white p-1.5 shadow-lg ring-1 ring-black/5 focus:outline-hidden dark:bg-gray-800 dark:ring-white/10 animate-in fade-in slide-in-from-top-1 duration-200"
role="menu"
aria-orientation="vertical"
Expand Down Expand Up @@ -258,8 +258,12 @@ export class ModelDropdownComponent {
protected sessionService = inject(SessionService);
private router = inject(Router);

// Internal state
protected menuOpen = false;
/**
* Whether the model menu is open, driving the trigger's chevron. Fed by the
* trigger's `cdkMenuOpened` / `cdkMenuClosed` outputs — `CdkMenu` itself has
* no `opened` output, so an `(opened)` binding on the panel never fires.
*/
private readonly menuOpen = signal(false);

/** The effort control for the selected model, or null when it offers none. */
protected readonly effortControl = this.modelService.effortControl;
Expand Down Expand Up @@ -335,15 +339,15 @@ export class ModelDropdownComponent {
];

protected isMenuOpen(): boolean {
return this.menuOpen;
return this.menuOpen();
}

protected onMenuOpened(): void {
this.menuOpen = true;
this.menuOpen.set(true);
}

protected onMenuClosed(): void {
this.menuOpen = false;
this.menuOpen.set(false);
}

protected levelLabel(level: string): string {
Expand Down