Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Account for presentational role conflict resolution in ARIA feature mappings #264

Merged
merged 16 commits into from
Jun 17, 2020
Merged
27 changes: 19 additions & 8 deletions packages/alfa-aria/src/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,26 @@ import { Scope, Table } from "@siteimprove/alfa-table";
import { Role } from "./role";

const { hasName, isElement } = Element;
const { and, equals, test } = Predicate;
const { and } = Predicate;

export class Feature<N extends string = string> {
public static of<N extends string>(
name: N,
role: Feature.Aspect<Option<string>> = () => None,
role: Feature.Aspect<Option<string>, [Feature.RoleOptions]> = () => None,
attributes: Feature.Aspect<Map<string, string>> = () => Map.empty(),
status: Feature.Status = { obsolete: false }
): Feature<N> {
return new Feature(name, role, attributes, status);
}

private readonly _name: N;
private readonly _role: Feature.Aspect<Option<string>>;
private readonly _role: Feature.Aspect<Option<string>, [Feature.RoleOptions]>;
private readonly _attributes: Feature.Aspect<Map<string, string>>;
private readonly _status: Feature.Status;

private constructor(
name: N,
role: Feature.Aspect<Option<string>>,
role: Feature.Aspect<Option<string>, [Feature.RoleOptions]>,
attributes: Feature.Aspect<Map<string, string>>,
status: Feature.Status
) {
Expand All @@ -43,7 +43,7 @@ export class Feature<N extends string = string> {
return this._name;
}

public get role(): Feature.Aspect<Option<string>> {
public get role(): Feature.Aspect<Option<string>, [Feature.RoleOptions]> {
return this._role;
}

Expand All @@ -57,12 +57,23 @@ export class Feature<N extends string = string> {
}

export namespace Feature {
export type Aspect<T> = Mapper<Element, T>;
export type Aspect<T, A extends Array<unknown> = []> = Mapper<
Element,
T,
A
>;

export interface Status {
readonly obsolete: boolean;
}

export interface RoleOptions {
/**
* @internal
*/
readonly allowPresentational?: boolean;
}

const features = Cache.empty<Namespace, Cache<string, Feature>>();

export function register<N extends string>(
Expand Down Expand Up @@ -298,9 +309,9 @@ Feature.register(

Feature.register(
Namespace.HTML,
Feature.of("img", (element) =>
Feature.of("img", (element, { allowPresentational = true }) =>
Option.of(
element.attribute("alt").some((alt) => alt.value === "")
allowPresentational && element.attribute("alt").some((alt) => alt.value === "")
? "presentation"
: "img"
)
Expand Down
5 changes: 4 additions & 1 deletion packages/alfa-aria/src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,10 @@ export namespace Node {
role.some(isPresentational) &&
!isAllowedPresentational(node)
) {
return Role.from(node, { explicit: false });
return Role.from(node, {
explicit: false,
allowPresentational: false,
});
}

return Branched.of(role);
Expand Down
8 changes: 6 additions & 2 deletions packages/alfa-aria/src/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,11 @@ export namespace Role {
const feature = Feature.lookup(namespace, element.name);

return feature.flatMap((feature) =>
feature.role(element).flatMap(Role.lookup)
feature
.role(element, {
allowPresentational: options.allowPresentational,
})
.flatMap(Role.lookup)
);
});
}
Expand All @@ -280,7 +284,7 @@ export namespace Role {
}

export namespace from {
export interface Options {
export interface Options extends Feature.RoleOptions {
readonly explicit?: boolean;
readonly implicit?: boolean;
}
Expand Down