From 648c8c634470b34ea3e66498b9c46c6474145d54 Mon Sep 17 00:00:00 2001 From: ExFlo Date: Tue, 29 Oct 2024 14:34:15 +0100 Subject: [PATCH] feat(collapse): add accessibility elements of the collapse (#982) * feat(collapse): add accessibility elements of the collapse * fix: small fixes --------- Co-authored-by: Quentin Deroubaix --- .../components/collapse/collapse.component.ts | 9 +++++- .../src/app/samples/collapse/default.route.ts | 26 ++++++++++------- .../src/components/collapse/collapse.ts | 25 ++++++++++++---- .../components/collapse/examples/+page.svelte | 12 ++++++++ .../bootstrap-collapse-default.html | 29 +++++-------------- .../src/components/collapse/collapse.tsx | 4 +-- .../samples/collapse/Default.route.tsx | 23 +++++---------- .../src/components/collapse/Collapse.svelte | 15 +++++++--- .../samples/collapse/Default.route.svelte | 12 ++++---- 9 files changed, 89 insertions(+), 66 deletions(-) diff --git a/angular/bootstrap/src/components/collapse/collapse.component.ts b/angular/bootstrap/src/components/collapse/collapse.component.ts index 4b671328dc..bd0e3b2ec3 100644 --- a/angular/bootstrap/src/components/collapse/collapse.component.ts +++ b/angular/bootstrap/src/components/collapse/collapse.component.ts @@ -45,6 +45,13 @@ export class CollapseDirective extends BaseWidgetDirective { */ @Input({alias: 'auVisible', transform: auBooleanAttribute}) visible: boolean | undefined; + /** + * id of the collapse + * + * @defaultValue `''` + */ + @Input('auId') id: string | undefined; + /** * Callback called when the collapse visibility changed. * @@ -86,7 +93,7 @@ export class CollapseDirective extends BaseWidgetDirective { onHidden: () => this.hidden.emit(), }, afterInit: (widget) => { - useDirectiveForHost(widget.directives.transitionDirective); + useDirectiveForHost(widget.directives.collapseDirective); }, }), ); diff --git a/angular/demo/bootstrap/src/app/samples/collapse/default.route.ts b/angular/demo/bootstrap/src/app/samples/collapse/default.route.ts index ccc0d1a3d7..b200c5d159 100644 --- a/angular/demo/bootstrap/src/app/samples/collapse/default.route.ts +++ b/angular/demo/bootstrap/src/app/samples/collapse/default.route.ts @@ -1,18 +1,24 @@ -import {AgnosUIAngularModule} from '@agnos-ui/angular-bootstrap'; -import {Component} from '@angular/core'; +import {CollapseDirective} from '@agnos-ui/angular-bootstrap'; +import {Component, signal} from '@angular/core'; @Component({ standalone: true, - imports: [AgnosUIAngularModule], + imports: [CollapseDirective], template: ` -

- - - -

-
+ +
Visible content
`, }) -export default class DefaultCollapseComponent {} +export default class DefaultCollapseComponent { + readonly expanded = signal(true); + toggle() { + this.expanded.update((expanded) => !expanded); + } + onHidden() { + console.log('Hidden'); + } +} diff --git a/core-bootstrap/src/components/collapse/collapse.ts b/core-bootstrap/src/components/collapse/collapse.ts index 5837010a54..73c778a71f 100644 --- a/core-bootstrap/src/components/collapse/collapse.ts +++ b/core-bootstrap/src/components/collapse/collapse.ts @@ -1,7 +1,7 @@ import {createTransition} from '@agnos-ui/core/services/transitions/baseTransitions'; import type {ConfigValidator, Directive, PropsConfig, Widget} from '@agnos-ui/core/types'; import {stateStores, writablesForProps} from '@agnos-ui/core/utils/stores'; -import {bindDirectiveNoArg} from '@agnos-ui/core/utils/directive'; +import {bindDirectiveNoArg, createAttributesDirective, mergeDirectives} from '@agnos-ui/core/utils/directive'; import {typeBoolean, typeFunction, typeString} from '@agnos-ui/core/utils/writables'; import {collapseHorizontalTransition, collapseVerticalTransition} from '../../services/transitions/collapse'; import {asWritable, computed} from '@amadeus-it-group/tansu'; @@ -77,6 +77,12 @@ export interface CollapseProps extends CollapseCommonPropsAndState { * @defaultValue `true` */ animated: boolean; + /** + * id of the collapse + * + * @defaultValue `''` + */ + id: string; } export interface CollapseApi { @@ -98,9 +104,9 @@ export interface CollapseApi { export interface CollapseDirectives { /** - * the transition directive, piloting what is the visual effect of going from hidden to visible + * Directive to apply the collapse. */ - transitionDirective: Directive; + collapseDirective: Directive; } export type CollapseWidget = Widget; @@ -114,6 +120,7 @@ const defaultCollapseConfig: CollapseProps = { animated: true, animatedOnInit: false, className: '', + id: '', }; /** @@ -133,6 +140,7 @@ const commonCollapseConfigValidator: ConfigValidator = { animated: typeBoolean, className: typeString, visible: typeBoolean, + id: typeString, }; /** @@ -141,7 +149,7 @@ const commonCollapseConfigValidator: ConfigValidator = { * @returns an CollapseWidget */ export function createCollapse(config?: PropsConfig): CollapseWidget { - const [{animatedOnInit$, animated$, visible$: requestedVisible$, onVisibleChange$, onHidden$, onShown$, horizontal$, ...stateProps}, patch] = + const [{animatedOnInit$, animated$, visible$: requestedVisible$, onVisibleChange$, onHidden$, onShown$, horizontal$, id$, ...stateProps}, patch] = writablesForProps(defaultCollapseConfig, config, commonCollapseConfigValidator); const currentTransitionFn$ = asWritable(computed(() => (horizontal$() ? collapseHorizontalTransition : collapseVerticalTransition))); @@ -169,7 +177,14 @@ export function createCollapse(config?: PropsConfig): CollapseWid toggle: transition.api.toggle, }, directives: { - transitionDirective: bindDirectiveNoArg(transition.directives.directive), + collapseDirective: mergeDirectives( + bindDirectiveNoArg(transition.directives.directive), + createAttributesDirective(() => ({ + attributes: { + id: computed(() => id$() || undefined), + }, + })), + ), }, }; } diff --git a/demo/src/routes/docs/[framework]/components/collapse/examples/+page.svelte b/demo/src/routes/docs/[framework]/components/collapse/examples/+page.svelte index 4c967855c8..7491e42c27 100644 --- a/demo/src/routes/docs/[framework]/components/collapse/examples/+page.svelte +++ b/demo/src/routes/docs/[framework]/components/collapse/examples/+page.svelte @@ -7,3 +7,15 @@
+ +
+

+ The collapse component for being accessible oblige the usage of ARIA attribute aria-controls + as well as the + ARIA attribute aria-expanded as shown + in the examples. +

+
diff --git a/e2e/samplesMarkup.singlebrowser-e2e-spec.ts-snapshots/bootstrap-collapse-default.html b/e2e/samplesMarkup.singlebrowser-e2e-spec.ts-snapshots/bootstrap-collapse-default.html index 3e6ddfe07e..8199058ca1 100644 --- a/e2e/samplesMarkup.singlebrowser-e2e-spec.ts-snapshots/bootstrap-collapse-default.html +++ b/e2e/samplesMarkup.singlebrowser-e2e-spec.ts-snapshots/bootstrap-collapse-default.html @@ -3,30 +3,17 @@ class="container p-3" id="root" > -

- - - -

+ "Toggle collapse" +
> & RefAttributes> = forwardRef( function Collapse(props: PropsWithChildren>, ref: ForwardedRef) { - const {api, directives} = useWidgetWithConfig(createCollapse, props, 'collapse', {}); + const {api, directives} = useWidgetWithConfig(createCollapse, props, 'collapse'); useImperativeHandle(ref, () => api, []); - return
{props.children}
; + return
{props.children}
; }, ); diff --git a/react/demo/src/bootstrap/samples/collapse/Default.route.tsx b/react/demo/src/bootstrap/samples/collapse/Default.route.tsx index bbe2b8b69a..e4e0cd7995 100644 --- a/react/demo/src/bootstrap/samples/collapse/Default.route.tsx +++ b/react/demo/src/bootstrap/samples/collapse/Default.route.tsx @@ -1,24 +1,15 @@ -import type {CollapseApi} from '@agnos-ui/react-bootstrap/components/collapse'; import {Collapse} from '@agnos-ui/react-bootstrap/components/collapse'; -import {useRef} from 'react'; +import {useState} from 'react'; const CollapseDemo = () => { - const refCollapse = useRef(null); - + const [visible, setVisible] = useState(true); + const id = 'auId-0'; return ( <> -

- - - -

- + + console.log('Hidden')}>
Visible content
diff --git a/svelte/bootstrap/src/components/collapse/Collapse.svelte b/svelte/bootstrap/src/components/collapse/Collapse.svelte index 845af4620d..b4936b0544 100644 --- a/svelte/bootstrap/src/components/collapse/Collapse.svelte +++ b/svelte/bootstrap/src/components/collapse/Collapse.svelte @@ -4,20 +4,27 @@ import {callWidgetFactory} from '../../config'; import type {Snippet} from 'svelte'; - let {children, ...props}: Partial & {children: Snippet} = $props(); + let {children, visible = $bindable(), ...props}: Partial & {children: Snippet} = $props(); const { - directives: {transitionDirective}, + directives: {collapseDirective}, api: collapseApi, } = callWidgetFactory({ factory: createCollapse, widgetName: 'collapse', - props, + get props() { + return {...props, visible}; + }, + events: { + onVisibleChange: (event) => { + visible = event; + }, + }, enablePatchChanged: true, }); export const api: CollapseApi = collapseApi; -
+
{@render children()}
diff --git a/svelte/demo/src/bootstrap/samples/collapse/Default.route.svelte b/svelte/demo/src/bootstrap/samples/collapse/Default.route.svelte index ed9129d645..c04e8009ce 100644 --- a/svelte/demo/src/bootstrap/samples/collapse/Default.route.svelte +++ b/svelte/demo/src/bootstrap/samples/collapse/Default.route.svelte @@ -1,12 +1,10 @@ -

- - - -

- console.log('Hidden')}>
Visible content
+ + console.log('Hidden')} id="auId-0">
Visible content