import { Element } from "@siteimprove/alfa-dom" ; import { Equatable } from "@siteimprove/alfa-equatable" ; import { Hashable , Hash } from "@siteimprove/alfa-hash" ; import { Iterable } from "@siteimprove/alfa-iterable" ; import { Serializable } from "@siteimprove/alfa-json" ; import { Option , None } from "@siteimprove/alfa-option" ; import { Predicate } from "@siteimprove/alfa-predicate" ; import { Sequence } from "@siteimprove/alfa-sequence" ; import * as json from "@siteimprove/alfa-json" ; import { Attribute } from "./attribute" ; import { Feature } from "./feature" ; import { Roles } from "./role/data" ; import * as predicate from "./role/predicate" ; const { and , not , nor } = Predicate ; const roles = new Map < string , Role > ( ) ; public export class Role < N extends Role . Name = Role . Name > implements Equatable , Hashable , Serializable { public static of < N extends Role . Name > ( name ) { let role = roles . get ( name ) ; if ( role === undefined ) { const { inherited } = Roles [ name ] ; const attributes = new Set ( [ ... Roles [ name ] . attributes ] . map ( ( [ attribute ] ) => attribute ) ) ; for ( const parent of inherited ) { for ( const attribute of Role . of ( parent ) . attributes ) { attributes . add ( attribute ) ; } } role = new Role ( name , [ ... attributes ] ) ; roles . set ( name , role ) ; } return role as Role < N > ; } private readonly _name : N ; private readonly _attributes : Array < Attribute . Name > ; private constructor ( name , attributes ) { this . _name = name ; this . _attributes = attributes ; } public get name ( ) : N { return this . _name ; } /**
     * Get all attributes supported by this role and its inherited roles.
     */ public get attributes ( ) : ReadonlyArray < Attribute . Name > { return this . _attributes ; } /**
     * Get the required parent of this role.
     */ public get requiredParent ( ) : ReadonlyArray < ReadonlyArray < Role . Name > > { return Roles [ this . _name ] . parent . required ; } /**
     * Get the required children of this role.
     */ public get requiredChildren ( ) : ReadonlyArray < ReadonlyArray < Role . Name > > { return Roles [ this . _name ] . children . required ; } /**
     * Check if this role has the specified name.
     */ public hasName < N extends Role . Name > ( name ) { return this . _name === ( name as Role . Name ) ; } /**
     * Check if this role is a superclass of the role with the specified name.
     */ public isSuperclassOf < N extends Role . Name > ( name ) { const { inherited } = Roles [ name ] ; for ( const parent of inherited ) { if ( parent === this . _name || this . isSuperclassOf ( parent ) ) { return true ; } } return false ; } /**
     * Check if this role is a subclass of the role with the specified name.
     */ public isSubclassOf < N extends Role . Name > ( name ) { return Role . of ( name ) . isSuperclassOf ( this . _name ) ; } /**
     * Check if this role either is, or is a subclass of, the role with the
     * specified name.
     */ public is < N extends Role . Name > ( name ) { return this . hasName ( name ) || this . isSubclassOf ( name ) ; } /**
     * Check if this role is abstract.
     */ public isAbstract ( ) { return Roles [ this . _name ] . abstract ; } /**
     * Check if this role is non-abstract.
     */ public isConcrete ( ) { return ! this . isAbstract ( ) ; } /**
     * Check if this role is presentational.
     */ public isPresentational ( ) { return this . hasName ( "presentation" ) || this . hasName ( "none" ) ; } /**
     * Check if this role is a widget.
     */ public isWidget ( ) { return this . is ( "widget" ) ; } /**
     * Check if this role is a landmark.
     */ public isLandmark ( ) { return this . is ( "landmark" ) ; } /**
     * Check if this role supports naming by the specified method.
     */ public isNamedBy ( method ) { for ( const found of Roles [ this . _name ] . name . from ) { if ( found === method ) { return true ; } } return false ; } /**
     * Check if this role prohibits naming.
     */ public isNameProhibited ( ) { return Roles [ this . _name ] . name . prohibited ; } /**
     * Check if this role has a required parent.
     */ public hasRequiredParent ( ) { return Roles [ this . _name ] . parent . required . length > 0 ; } /**
     * Check if this role has presentational children.
     */ public hasPresentationalChildren ( ) { return Roles [ this . _name ] . children . presentational ; } /**
     * Check if this role has required children.
     */ public hasRequiredChildren ( ) { return Roles [ this . _name ] . children . required . length > 0 ; } /**
     * Check if this role supports the specified attribute.
     */ public isAttributeSupported ( name ) { const { inherited , attributes } = Roles [ this . _name ] ; for ( const [ found ] of attributes ) { if ( name === found ) { return true ; } } for ( const parent of inherited ) { if ( Role . of ( parent ) . isAttributeSupported ( name ) ) { return true ; } } return false ; } /**
     * Check if this role requires the specified attribute.
     */ public isAttributeRequired ( name ) { const { inherited , attributes } = Roles [ this . _name ] ; for ( const [ found , { required } ] of attributes ) { if ( name === found && required ) { return true ; } } for ( const parent of inherited ) { if ( Role . of ( parent ) . isAttributeRequired ( name ) ) { return true ; } } return false ; } /**
     * Get the implicit value of the specified attribute, if any.
     */ public implicitAttributeValue ( name ) { const { inherited , attributes } = Roles [ this . _name ] ; for ( const [ found , { value } ] of attributes ) { if ( name === found && value !== null ) { return Option . from ( value ) ; } } for ( const parent of inherited ) { for ( const value of Role . of ( parent ) . implicitAttributeValue ( name ) ) { return Option . of ( value ) ; } } return None ; } public equals ( value ) { return value instanceof Role && value . _name === this . _name ; } public hash ( hash ) { hash . writeString ( this . _name ) ; } public toJSON ( ) { return { name : this . _name , } ; } } public export namespace Role { export interface JSON { [ key ] : json . JSON ; name : Name ; } export type Name = keyof Roles ; export function isName ( value ) { return value in Roles ; } /**
     * The names of all abstract roles.
     */ export type Abstract = { [ N in Name ] : Roles [ N ] [ "abstract" ] extends true ? N : never ; } [ Name ] ; /**
     * The names of all non-abstract roles.
     */ export type Concrete = Exclude < Name , Abstract > ; remarks export type Presentational = "presentation" | "none" ; /**
     * The names of all widget roles.
     */ export type Widget = SubclassOf < "widget" > ; /**
     * The names of all landmark roles.
     */ export type Landmark = SubclassOf < "landmark" > ; /**
     * The inherited roles for the specified role.
     */ export type Inherited < N extends Name > = N extends "roletype" | "none" ? never : Members < Roles [ N ] [ "inherited" ] > ; /**
     * All roles that are subclasses of the specified role.
     */ export type SubclassOf < N extends Name > = { [ M in Name ] : N extends SuperclassOf < M > ? M : never ; } [ Name ] ; remarks export type SuperclassOf < N extends Name > = N extends "roletype" | "none" ? never : Inherited < N > | { [ M in Inherited < N > ] : SuperclassOf < M > ; } [ Inherited < N > ] ; /**
     * The methods by which the element assigned to the specified role may receive
     * its name.
     */ export type NamedBy < N extends Name = Name > = Members < Roles [ N ] [ "name" ] [ "from" ] > ; export function isRole < N extends Name > ( value , name ? ) { return value instanceof Role && ( name === undefined || value . name === name ) ; } /**
     * Get the role assigned either explicitly or implicitly to an element, if
     * any.
     */ export function from ( element ) { return fromExplicit ( element ) . orElse ( ( ) => fromImplicit ( element ) ) ; } /**
     * Get the role explicitly assigned to an element, if any.
     */ export function fromExplicit ( element ) { const roles = element . attribute ( "role" ) . map ( ( attribute ) => attribute . tokens ( ) ) . getOrElse ( ( ) => Sequence . empty ( ) ) ; return ( roles . map ( ( role ) => role . toLowerCase ( ) ) . filter ( isName ) . map ( Role . of ) . reject ( ( role ) => role . isAbstract ( ) ) . reject ( ( role ) => isAllowedPresentational ( element ) ? false : role . isPresentational ( ) ) . first ( ) ) ; } /**
     * Get the role implicitly assigned to an element, if any.
     */ export function fromImplicit ( element ) { return element . namespace . flatMap ( ( namespace ) => Feature . from ( namespace , element . name ) . flatMap ( ( feature ) => Sequence . from ( feature . role ( element ) ) . reject ( ( role ) => isAllowedPresentational ( element ) ? false : role . isPresentational ( ) ) . first ( ) ) ) ; } export const { hasName } = predicate ; } type Members < T > = T extends Iterable < infer T > ? T : never ; /**
 * Check if an element has one or more global `aria-*` attributes.
 */ const hasGlobalAttributes = ( element ) => Iterable . some ( Role . of ( "roletype" ) . attributes , ( attribute ) => element . attribute ( attribute ) . isSome ( ) ) ; /**
 * Check if an element is potentially focusable, not accounting for whether or
 * not the element is rendered.
 */ const isPotentiallyFocusable = and ( Element . hasTabIndex ( ) , not ( Element . isDisabled ) ) ; /**
 * Check if an element is allowed to be assigned a presentational role.
 */ const isAllowedPresentational = nor ( hasGlobalAttributes , isPotentiallyFocusable ) ;