Skip to content
Draft
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
18 changes: 16 additions & 2 deletions src/aria/accordion/accordion-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {Directive, ElementRef, afterRenderEffect, computed, inject, input} from '@angular/core';
import {_IdGenerator} from '@angular/cdk/a11y';
import {DeferredContentAware, AccordionTriggerPattern} from '../private';
import {DeferredContentAware, AccordionTriggerPattern, LabelControl} from '../private';

/**
* The content panel of an accordion item that is conditionally visible.
Expand Down Expand Up @@ -43,7 +43,8 @@ import {DeferredContentAware, AccordionTriggerPattern} from '../private';
host: {
'role': 'region',
'[attr.id]': 'id()',
'[attr.aria-labelledby]': '_pattern?.id()',
'[attr.aria-label]': '_labelControl.label()',
'[attr.aria-labelledby]': '_labelControl.labelledBy()',
'[attr.inert]': '!visible() ? true : null',
},
})
Expand All @@ -57,9 +58,16 @@ export class AccordionPanel {
/** The DeferredContentAware host directive. */
private readonly _deferredContentAware = inject(DeferredContentAware);

/** Controls label for this tabpanel. */
readonly _labelControl: LabelControl;

/** A global unique identifier for the panel. */
readonly id = input(inject(_IdGenerator).getId('ng-accordion-panel-', true));

readonly label = input<string | undefined>(undefined);

readonly labelledBy = input<string[]>([]);

/** Whether the accordion panel is visible. True if the associated trigger is expanded. */
readonly visible = computed(() => this._pattern?.expanded() === true);

Expand All @@ -71,6 +79,12 @@ export class AccordionPanel {
_pattern?: AccordionTriggerPattern;

constructor() {
this._labelControl = new LabelControl({
defaultLabelledBy: computed(() => this._pattern!.id()),
label: this.label,
labelledBy: this.labelledBy,
});

// Connect the panel's hidden state to the DeferredContentAware's visibility.
afterRenderEffect({
write: () => {
Expand Down
10 changes: 5 additions & 5 deletions src/aria/private/behaviors/label/label.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {LabelControl, LabelControlInputs, LabelControlOptionalInputs} from './la
// This is a helper type for the initial values passed to the setup function.
type TestInputs = Partial<{
label: string | undefined;
defaultLabelledBy: string[];
defaultLabelledBy: string;
labelledBy: string[];
}>;

Expand All @@ -30,7 +30,7 @@ function getLabelControl(initialValues: TestInputs = {}): {
inputs: WritableLabelControlInputs;
} {
const inputs: WritableLabelControlInputs = {
defaultLabelledBy: signal(initialValues.defaultLabelledBy ?? []),
defaultLabelledBy: signal(initialValues.defaultLabelledBy),
label: signal(initialValues.label),
labelledBy: signal(initialValues.labelledBy ?? []),
};
Expand Down Expand Up @@ -65,20 +65,20 @@ describe('LabelControl', () => {
it('should return user-provided labelledBy even if a label is provided', () => {
const {control} = getLabelControl({
label: 'My Label',
defaultLabelledBy: ['default-id'],
defaultLabelledBy: 'default-id',
labelledBy: ['user-id'],
});
expect(control.labelledBy()).toEqual(['user-id']);
});

it('should return defaultLabelledBy if no user-provided labelledBy exists', () => {
const {control} = getLabelControl({defaultLabelledBy: ['default-id']});
const {control} = getLabelControl({defaultLabelledBy: 'default-id'});
expect(control.labelledBy()).toEqual(['default-id']);
});

it('should update when label changes from undefined to a string', () => {
const {control, inputs} = getLabelControl({
defaultLabelledBy: ['default-id'],
defaultLabelledBy: 'default-id',
});
expect(control.labelledBy()).toEqual(['default-id']);
inputs.label.set('A wild label appears');
Expand Down
6 changes: 3 additions & 3 deletions src/aria/private/behaviors/label/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {computed, SignalLike} from '../signal-like/signal-like';

/** Represents the required inputs for the label control. */
export interface LabelControlInputs {
/** The default `aria-labelledby` ids. */
defaultLabelledBy: SignalLike<string[]>;
/** The default `aria-labelledby` id. */
defaultLabelledBy: SignalLike<string | undefined>;
}

/** Represents the optional inputs for the label control. */
Expand Down Expand Up @@ -43,7 +43,7 @@ export class LabelControl {
return [];
}

return defaultLabelledBy;
return defaultLabelledBy ? [defaultLabelledBy] : [];
});

constructor(readonly inputs: LabelControlInputs & LabelControlOptionalInputs) {}
Expand Down
1 change: 1 addition & 0 deletions src/aria/private/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './listbox/listbox';
export * from './listbox/option';
export * from './listbox/combobox-listbox';
export * from './menu/menu';
export * from './behaviors/label/label';
export * from './behaviors/signal-like/signal-like';
export * from './tabs/tabs';
export * from './toolbar/toolbar';
Expand Down
Loading