@@ -525,111 +525,116 @@ describe('Combobox', () => {
525525 } ) ;
526526 } ) ;
527527
528- describe ( 'Readonly' , ( ) => {
529- beforeEach ( async ( ) => await setupCombobox ( ComboboxListboxExample , { readonly : true } ) ) ;
528+ describe ( 'Interactivity and Disablement (disabled, softDisabled, readonly)' , ( ) => {
529+ describe ( 'Readonly' , ( ) => {
530+ beforeEach ( async ( ) => await setupCombobox ( ComboboxListboxExample , { readonly : true } ) ) ;
530531
531- it ( 'should close on selection' , async ( ) => {
532- await focus ( ) ;
533- await down ( ) ;
534- await click ( getOption ( 'Alabama' ) ! ) ;
535- expect ( inputElement . value ) . toBe ( 'Alabama' ) ;
536- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
537- } ) ;
532+ it ( 'should set native readonly attribute on editable inputs without assigning disabled' , ( ) => {
533+ expect ( inputElement . hasAttribute ( 'readonly' ) ) . toBe ( true ) ;
534+ expect ( inputElement . hasAttribute ( 'disabled' ) ) . toBe ( false ) ;
535+ } ) ;
538536
539- it ( 'should close on escape' , async ( ) => {
540- await focus ( ) ;
541- await down ( ) ;
542- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
543- await escape ( ) ;
544- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
545- } ) ;
546- } ) ;
537+ it ( 'should suppress typing when readonly' , async ( ) => {
538+ await focus ( ) ;
539+ inputElement . value = 'New' ;
540+ inputElement . dispatchEvent ( new Event ( 'input' ) ) ;
541+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
542+ } ) ;
547543
548- describe ( 'Always Expanded' , ( ) => {
549- beforeEach ( async ( ) => await setupCombobox ( ) ) ;
544+ it ( 'should block expansion on arrow down when readonly' , async ( ) => {
545+ await focus ( ) ;
546+ await down ( ) ;
547+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
548+ } ) ;
549+ } ) ;
550550
551- it ( 'should not close on escape when alwaysExpanded is true' , async ( ) => {
552- fixture . componentInstance . alwaysExpanded . set ( true ) ;
553- await fixture . whenStable ( ) ;
554- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
551+ describe ( 'Disabled (Soft and Hard)' , ( ) => {
552+ beforeEach ( async ( ) => await setupCombobox ( ) ) ;
555553
556- await escape ( ) ;
557- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( ' true' ) ;
558- } ) ;
554+ it ( 'should keep the input focusable by default when disabled' , async ( ) => {
555+ fixture . componentInstance . disabled . set ( true ) ;
556+ await fixture . whenStable ( ) ;
559557
560- it ( 'should automatically report as expanded when alwaysExpanded is true' , async ( ) => {
561- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
562- fixture . componentInstance . alwaysExpanded . set ( true ) ;
563- await fixture . whenStable ( ) ;
564- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
565- } ) ;
566- } ) ;
558+ expect ( inputElement . disabled ) . toBe ( false ) ;
559+ expect ( inputElement . getAttribute ( 'disabled' ) ) . toBeNull ( ) ;
560+ expect ( inputElement . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
561+ } ) ;
567562
568- describe ( 'Disabled' , ( ) => {
569- beforeEach ( async ( ) => await setupCombobox ( ) ) ;
563+ it ( 'should assign readonly when disabled and softDisabled is true on editable inputs' , async ( ) => {
564+ fixture . componentInstance . disabled . set ( true ) ;
565+ await fixture . whenStable ( ) ;
570566
571- it ( 'should keep the input focusable by default when disabled' , async ( ) => {
572- fixture . componentInstance . disabled . set ( true ) ;
573- await fixture . whenStable ( ) ;
567+ expect ( inputElement . hasAttribute ( 'readonly' ) ) . toBe ( true ) ;
568+ } ) ;
574569
575- expect ( inputElement . disabled ) . toBe ( false ) ;
576- expect ( inputElement . getAttribute ( 'disabled' ) ) . toBeNull ( ) ;
577- expect ( inputElement . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
578- } ) ;
570+ it ( 'should block interactions when disabled' , async ( ) => {
571+ fixture . componentInstance . disabled . set ( true ) ;
572+ await fixture . whenStable ( ) ;
579573
580- it ( 'should make the input read-only when disabled and softDisabled is true' , async ( ) => {
581- fixture . componentInstance . disabled . set ( true ) ;
582- await fixture . whenStable ( ) ;
574+ await focus ( ) ;
575+ await keydown ( 'ArrowDown' ) ;
576+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
577+ } ) ;
583578
584- expect ( inputElement . getAttribute ( 'readonly' ) ) . toBe ( '' ) ;
585- } ) ;
579+ it ( 'should assign disabled, readonly, and aria-disabled when hard-disabled' , async ( ) => {
580+ fixture . componentInstance . disabled . set ( true ) ;
581+ fixture . componentInstance . softDisabled . set ( false ) ;
582+ await fixture . whenStable ( ) ;
586583
587- it ( 'should block interactions when disabled' , async ( ) => {
588- fixture . componentInstance . disabled . set ( true ) ;
589- await fixture . whenStable ( ) ;
584+ expect ( inputElement . disabled ) . toBe ( true ) ;
585+ expect ( inputElement . getAttribute ( 'disabled' ) ) . toBe ( '' ) ;
586+ expect ( inputElement . hasAttribute ( 'readonly' ) ) . toBe ( true ) ;
587+ expect ( inputElement . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
588+ } ) ;
590589
591- await focus ( ) ;
592- await keydown ( 'ArrowDown' ) ;
593- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
594- } ) ;
590+ it ( 'should respect user-defined tabindex when softDisabled is true' , async ( ) => {
591+ fixture . componentInstance . disabled . set ( true ) ;
592+ fixture . componentInstance . tabIndex . set ( 0 ) ;
593+ await fixture . whenStable ( ) ;
595594
596- it ( 'should make the input unfocusable when softDisabled is false' , async ( ) => {
597- fixture . componentInstance . disabled . set ( true ) ;
598- fixture . componentInstance . softDisabled . set ( false ) ;
599- await fixture . whenStable ( ) ;
595+ expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
596+ } ) ;
600597
601- expect ( inputElement . disabled ) . toBe ( true ) ;
602- expect ( inputElement . getAttribute ( 'disabled' ) ) . toBe ( '' ) ;
603- expect ( inputElement . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
604- } ) ;
598+ it ( 'should respect user-defined tabindex when not disabled' , async ( ) => {
599+ fixture . componentInstance . tabIndex . set ( 0 ) ;
600+ await fixture . whenStable ( ) ;
605601
606- it ( 'should respect user-defined tabindex when softDisabled is true' , async ( ) => {
607- fixture . componentInstance . disabled . set ( true ) ;
608- fixture . componentInstance . tabIndex . set ( 0 ) ;
609- await fixture . whenStable ( ) ;
602+ expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
603+ } ) ;
610604
611- expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
612- } ) ;
605+ it ( 'should default to tabindex 0 when not disabled' , async ( ) => {
606+ await fixture . whenStable ( ) ;
607+ expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
608+ } ) ;
613609
614- it ( 'should respect user-defined tabindex when not disabled' , async ( ) => {
615- fixture . componentInstance . tabIndex . set ( 0 ) ;
616- await fixture . whenStable ( ) ;
610+ it ( 'should force tabindex to -1 when hard-disabled, ignoring user-defined tabindex' , async ( ) => {
611+ fixture . componentInstance . disabled . set ( true ) ;
612+ fixture . componentInstance . softDisabled . set ( false ) ;
613+ fixture . componentInstance . tabIndex . set ( 0 ) ;
614+ await fixture . whenStable ( ) ;
617615
618- expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
616+ expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '-1' ) ;
617+ } ) ;
619618 } ) ;
619+ } ) ;
620620
621- it ( 'should default to tabindex 0 when not disabled' , async ( ) => {
621+ describe ( 'Always Expanded' , ( ) => {
622+ beforeEach ( async ( ) => await setupCombobox ( ) ) ;
623+
624+ it ( 'should not close on escape when alwaysExpanded is true' , async ( ) => {
625+ fixture . componentInstance . alwaysExpanded . set ( true ) ;
622626 await fixture . whenStable ( ) ;
623- expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
627+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
628+
629+ await escape ( ) ;
630+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
624631 } ) ;
625632
626- it ( 'should force tabindex to -1 when hard-disabled, ignoring user-defined tabindex' , async ( ) => {
627- fixture . componentInstance . disabled . set ( true ) ;
628- fixture . componentInstance . softDisabled . set ( false ) ;
629- fixture . componentInstance . tabIndex . set ( 0 ) ;
633+ it ( 'should automatically report as expanded when alwaysExpanded is true' , async ( ) => {
634+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
635+ fixture . componentInstance . alwaysExpanded . set ( true ) ;
630636 await fixture . whenStable ( ) ;
631-
632- expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '-1' ) ;
637+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
633638 } ) ;
634639 } ) ;
635640 } ) ;
@@ -1341,7 +1346,8 @@ function getTreeNodes(): TreeNode[] {
13411346 placeholder="Search..."
13421347 [(value)]="searchString"
13431348 [(expanded)]="popupExpanded"
1344- [disabled]="readonly()"
1349+ [readonly]="readonly()"
1350+ [disabled]="disabled()"
13451351 (focusout)="onBlur()"
13461352 />
13471353
@@ -1394,6 +1400,7 @@ class ComboboxTreeExample {
13941400 readonly tree = viewChild ( Tree ) ;
13951401
13961402 readonly = signal ( false ) ;
1403+ disabled = signal ( false ) ;
13971404 popupExpanded = signal ( false ) ;
13981405 searchString = signal ( '' ) ;
13991406 value = signal < string [ ] > ( [ ] ) ;
@@ -1591,7 +1598,8 @@ class ComboboxGridExample {
15911598 placeholder="Search..."
15921599 [(value)]="searchString"
15931600 (input)="onInput()"
1594- [disabled]="readonly()"
1601+ [readonly]="readonly()"
1602+ [disabled]="disabled()"
15951603 (focusout)="onBlur()"
15961604 (click)="combobox.expanded.set(true)"
15971605 />
@@ -1611,6 +1619,7 @@ class ComboboxGridExample {
16111619} )
16121620class ComboboxListboxAutoSelectExample {
16131621 readonly = signal ( false ) ;
1622+ disabled = signal ( false ) ;
16141623 popupExpanded = signal ( false ) ;
16151624 searchString = signal ( '' ) ;
16161625 value = signal < string [ ] > ( [ ] ) ;
@@ -1656,7 +1665,8 @@ class ComboboxListboxAutoSelectExample {
16561665 [(value)]="searchString"
16571666 [(expanded)]="popupExpanded"
16581667 [inlineSuggestion]="value()[0] || options()[0]"
1659- [disabled]="readonly()"
1668+ [readonly]="readonly()"
1669+ [disabled]="disabled()"
16601670 (click)="popupExpanded.set(true)"
16611671 />
16621672
@@ -1676,6 +1686,7 @@ class ComboboxListboxAutoSelectExample {
16761686class ComboboxListboxHighlightExample {
16771687 readonly combobox = viewChild ( Combobox ) ;
16781688 readonly = signal ( false ) ;
1689+ disabled = signal ( false ) ;
16791690 popupExpanded = signal ( false ) ;
16801691 searchString = signal ( '' ) ;
16811692 value = signal < string [ ] > ( [ ] ) ;
0 commit comments