From 3a202e1bf199895fbb243d20ad6d96a1455efa43 Mon Sep 17 00:00:00 2001 From: Pascal Corpet Date: Tue, 22 Mar 2022 10:50:55 +0100 Subject: [PATCH] Add the description for buttons This is for #25 and can be expanded to other renderers. --- src/AOM/types.ts | 21 +++++++++++++++++++++ src/store/index.ts | 9 +++++++++ src/view/renderers/Button.tsx | 14 ++++++++++++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/AOM/types.ts b/src/AOM/types.ts index 62c0c5a..5ab41d1 100644 --- a/src/AOM/types.ts +++ b/src/AOM/types.ts @@ -491,6 +491,7 @@ export class RawNodeAttributes { @observable "aria-controls"?: HtmlID = undefined; @observable "aria-disabled"?: string = undefined; @observable "aria-modal"?: string = undefined; + @observable "aria-description"?: string = undefined; @observable "aria-describedby"?: HtmlID = undefined; @observable "aria-haspopup"?: string = undefined; @observable "aria-expanded"?: string = undefined; @@ -838,6 +839,14 @@ export class Aria { return this.rawAttributes["aria-controls"]?.trim(); } + @computed get ariaDescription() { + return this.rawAttributes["aria-description"]?.trim(); + } + + @computed get ariaDescribedBy() { + return this.rawAttributes["aria-describedby"]?.trim(); + } + @computed get ariaLabel() { return this.rawAttributes["aria-label"]?.trim(); } @@ -1154,6 +1163,18 @@ export class NodeElement { } } + get description(): string { + if (this.relations.ariaDescribedBy?.length) { + return getAccessibleNameOf(this.relations.ariaDescribedBy).trim(); + } + + if (this.attributes.ariaDescription?.trim()) { + return this.attributes.ariaDescription.trim(); + } + + return ""; + } + constructor(node: HTMLElement) { this.domNode = node; this.htmlTag = node.tagName.toLowerCase(); diff --git a/src/store/index.ts b/src/store/index.ts index 0fd9cd7..ea37e83 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -15,6 +15,7 @@ import {getMap, reconcileFields} from "../AOM/reconcile"; class RelationsForId { @observable elementsWithId: NonNullable[] = []; + @observable ariaDescriptions: NonNullable[] = []; @observable ariaLabelOf: NonNullable[] = []; @observable ariaControlledBy: NonNullable[] = []; @observable ariaOwnedBy: NonNullable[] = []; @@ -298,6 +299,14 @@ export default class Store { } } + this.updateSingleReferenceRelation( + node, + oldAttributes?.ariaDescribedBy, + newAttributes?.ariaDescribedBy, + "ariaDescribedBy", + "ariaDescriptions" + ); + this.updateSingleReferenceRelation( node, oldAttributes?.ariaLabelledBy, diff --git a/src/view/renderers/Button.tsx b/src/view/renderers/Button.tsx index beb943c..cb91645 100644 --- a/src/view/renderers/Button.tsx +++ b/src/view/renderers/Button.tsx @@ -35,12 +35,13 @@ const SimpleButtonRole = styled.span<{isSelected:boolean}>` ${props => props.isSelected && selectedBoxShadow}; `; -const SimpleButtonContent = styled.span<{ isHovered: boolean }>` +const SimpleButtonContent = styled.span<{ hasDescription: boolean, isHovered: boolean }>` display: inline-block; padding: 0 5px; background: transparent; min-width: 100px; cursor: pointer; + line-height: 1.33; border-radius: 0 ${borderRadius} ${borderRadius} 0; border: ${props => (props.isHovered ? `1px solid ${color}` : ` 1px dashed #555`)}; @@ -48,9 +49,15 @@ const SimpleButtonContent = styled.span<{ isHovered: boolean }>` :hover { background: #555; } + + ${props => props.hasDescription && `::after { content: "🛈" }`}; +`; + +const SimpleButtonDescription = styled.span<{}>` `; const SimpleButton = observer(function SimpleButton({ node }: ComponentProps) { + const description = node.description const [isHovered, setHovered] = React.useState(false); const [ref, style] = useFocusable(node); const roleRef = useRef(null); @@ -76,7 +83,10 @@ const SimpleButton = observer(function SimpleButton({ node }: ComponentProps) { isSelected={node?.isOpenInSidePanel}> 🖱️ - {node.accessibleName}  + + {node.accessibleName} {node.isFocused && description && `- ${description}`} + ); });