Skip to content
Merged
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
6 changes: 3 additions & 3 deletions examples/puppeteer/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1090,9 +1090,9 @@ create-jest@^29.7.0:
prompts "^2.0.1"

cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
Expand Down
6 changes: 3 additions & 3 deletions examples/typescript-legacy/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -988,9 +988,9 @@ create-jest@^29.7.0:
prompts "^2.0.1"

cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
Expand Down
6 changes: 3 additions & 3 deletions examples/typescript-nodenext/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -988,9 +988,9 @@ create-jest@^29.7.0:
prompts "^2.0.1"

cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
Expand Down
6 changes: 3 additions & 3 deletions examples/vue/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1522,9 +1522,9 @@ convert-source-map@^2.0.0:
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==

cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
Expand Down
8 changes: 3 additions & 5 deletions src/Virtual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
ERR_VIRTUAL_MISSING_CONTAINER,
ERR_VIRTUAL_NOT_STARTED,
} from "./errors";
import { getLiveSpokenPhrase, Live } from "./getLiveSpokenPhrase";
import { getLiveSpokenPhrase, LIVE } from "./getLiveSpokenPhrase";
import { flattenTree } from "./flattenTree";
import { getElementNode } from "./commands/getElementNode";
import { getItemText } from "./getItemText";
Expand Down Expand Up @@ -180,10 +180,8 @@ const defaultUserEventOptions = {
* "heading, Section Heading, level 1",
* "Section Text",
* "article",
* "banner",
* "heading, Article Header Heading, level 1",
* "Article Header Text",
* "end of banner",
* "Article Text",
* "end of article",
* "end of region",
Expand Down Expand Up @@ -360,8 +358,8 @@ export class Virtual {
#spokenPhraseLogWithoutLiveRegions() {
return this.#spokenPhraseLog.filter(
(spokenPhrase) =>
!spokenPhrase.startsWith(Live.ASSERTIVE) &&
!spokenPhrase.startsWith(Live.POLITE)
!spokenPhrase.startsWith(LIVE.ASSERTIVE) &&
!spokenPhrase.startsWith(LIVE.POLITE)
);
}

Expand Down
76 changes: 41 additions & 35 deletions src/getLiveSpokenPhrase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { getRole } from "./getNodeAccessibilityData/getRole";
import { isElement } from "./isElement";
import { sanitizeString } from "./sanitizeString";

type ValueOf<T> = T[keyof T];

/**
* Live region roles:
*
Expand Down Expand Up @@ -36,31 +38,30 @@ import { sanitizeString } from "./sanitizeString";
* - https://www.w3.org/TR/wai-aria-1.2/#aria-live
*/

export enum Live {
ASSERTIVE = "assertive",
OFF = "off",
POLITE = "polite",
}
export const LIVE = {
ASSERTIVE: "assertive",
OFF: "off",
POLITE: "polite",
};

enum Relevant {
ADDITIONS = "additions",
ALL = "all",
REMOVALS = "removals",
TEXT = "text",
}
const RELEVANT = {
ADDITIONS: "additions",
ALL: "all",
REMOVALS: "removals",
TEXT: "text",
};

const RELEVANT_VALUES = new Set(Object.values(Relevant));
const RELEVANT_VALUES = new Set(Object.values(RELEVANT));
const DEFAULT_ATOMIC = false;
const DEFAULT_LIVE = Live.OFF;
const DEFAULT_RELEVANT = [Relevant.ADDITIONS, Relevant.TEXT];
const DEFAULT_LIVE = LIVE.OFF;
const DEFAULT_RELEVANT = [RELEVANT.ADDITIONS, RELEVANT.TEXT];

function getSpokenPhraseForNode(node: Node) {
return (
getAccessibleName(node) ||
getAccessibleValue(node) ||
// `node.textContent` is only `null` if the `node` is a `document` or a
// `doctype`. We don't consider either.

sanitizeString(node.textContent!)
);
}
Expand Down Expand Up @@ -141,36 +142,36 @@ function getTextSpokenPhrase({
}

const relevantToSpokenPhraseMap = {
[Relevant.ADDITIONS]: getAdditionsSpokenPhrase,
[Relevant.ALL]: getAllSpokenPhrase,
[Relevant.REMOVALS]: getRemovalsSpokenPhrase,
[Relevant.TEXT]: getTextSpokenPhrase,
[RELEVANT.ADDITIONS]: getAdditionsSpokenPhrase,
[RELEVANT.ALL]: getAllSpokenPhrase,
[RELEVANT.REMOVALS]: getRemovalsSpokenPhrase,
[RELEVANT.TEXT]: getTextSpokenPhrase,
};

const roleToImplicitLiveRegionStatesAndPropertiesMap: Record<
string,
{ atomic?: boolean; live: Live }
{ atomic?: boolean; live: ValueOf<typeof LIVE> }
> = {
alert: {
atomic: true,
live: Live.ASSERTIVE,
live: LIVE.ASSERTIVE,
},
log: {
live: Live.POLITE,
live: LIVE.POLITE,
},
marquee: {
live: Live.OFF,
live: LIVE.OFF,
},
status: {
atomic: true,
live: Live.POLITE,
live: LIVE.POLITE,
},
timer: {
live: Live.OFF,
live: LIVE.OFF,
},
alertdialog: {
atomic: true,
live: Live.ASSERTIVE,
live: LIVE.ASSERTIVE,
},
};

Expand All @@ -189,11 +190,16 @@ function getLiveRegionAttributes(
relevant,
}: {
atomic?: boolean;
live?: Live;
live?: ValueOf<typeof LIVE>;
liveTarget?: Element;
relevant?: Relevant[];
relevant?: ValueOf<typeof RELEVANT>[];
} = {}
): { atomic: boolean; live: Live; liveTarget?: Element; relevant: Relevant[] } {
): {
atomic: boolean;
live: ValueOf<typeof LIVE>;
liveTarget?: Element;
relevant: ValueOf<typeof RELEVANT>[];
} {
// TODO: it would be far better if worked with the accessibility tree rather
// than reconstructing here and making assumptions (though probable) about
// the allowed roles or inherited presentational roles.
Expand All @@ -213,7 +219,7 @@ function getLiveRegionAttributes(
}

if (typeof live === "undefined" && target.hasAttribute("aria-live")) {
live = target.getAttribute("aria-live") as Live;
live = target.getAttribute("aria-live") as ValueOf<typeof LIVE>;
liveTarget = target;
}

Expand All @@ -234,11 +240,11 @@ function getLiveRegionAttributes(
.getAttribute("aria-relevant")!
.split(" ")
.filter(
(token) => !!RELEVANT_VALUES.has(token as Relevant)
) as Relevant[];
(token) => !!RELEVANT_VALUES.has(token as ValueOf<typeof RELEVANT>)
) as ValueOf<typeof RELEVANT>[];

if (relevant.includes(Relevant.ALL)) {
relevant = [Relevant.ALL];
if (relevant.includes(RELEVANT.ALL)) {
relevant = [RELEVANT.ALL];
}
}

Expand Down Expand Up @@ -289,7 +295,7 @@ export function getLiveSpokenPhrase({
target: getElementFromNode(target),
});

if (live === Live.OFF || !liveTarget) {
if (live === LIVE.OFF || !liveTarget) {
return "";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@ import { getAccessibleValue } from "../getAccessibleValue";
import { getItemText } from "../../getItemText";
import { getNodeByIdRef } from "../../getNodeByIdRef";

enum State {
BUSY = "busy",
CHECKED = "checked",
CURRENT = "current item",
DISABLED = "disabled",
EXPANDED = "expanded",
INVALID = "invalid",
MODAL = "modal",
MULTI_SELECTABLE = "multi-selectable",
PARTIALLY_CHECKED = "partially checked",
PARTIALLY_PRESSED = "partially pressed",
PRESSED = "pressed",
READ_ONLY = "read only",
REQUIRED = "required",
SELECTED = "selected",
}
type ValueOf<T> = T[keyof T];

const STATE = {
BUSY: "busy",
CHECKED: "checked",
CURRENT: "current item",
DISABLED: "disabled",
EXPANDED: "expanded",
INVALID: "invalid",
MODAL: "modal",
MULTI_SELECTABLE: "multi-selectable",
PARTIALLY_CHECKED: "partially checked",
PARTIALLY_PRESSED: "partially pressed",
PRESSED: "pressed",
READ_ONLY: "read only",
REQUIRED: "required",
SELECTED: "selected",
};

// https://www.w3.org/TR/wai-aria-1.2/#state_prop_def
const ariaPropertyToVirtualLabelMap: Record<
Expand All @@ -35,8 +37,8 @@ const ariaPropertyToVirtualLabelMap: Record<
}),
"aria-braillelabel": null, // Currently won't do - not implementing a braille screen reader
"aria-brailleroledescription": null, // Currently won't do - not implementing a braille screen reader
"aria-busy": state(State.BUSY),
"aria-checked": tristate(State.CHECKED, State.PARTIALLY_CHECKED),
"aria-busy": state(STATE.BUSY),
"aria-checked": tristate(STATE.CHECKED, STATE.PARTIALLY_CHECKED),
"aria-colcount": integer("column count"),
"aria-colindex": integer("column index"),
"aria-colindextext": string("column index"),
Expand All @@ -48,16 +50,16 @@ const ariaPropertyToVirtualLabelMap: Record<
location: "current location",
date: "current date",
time: "current time",
true: State.CURRENT,
false: `not ${State.CURRENT}`,
true: STATE.CURRENT,
false: `not ${STATE.CURRENT}`,
}),
"aria-describedby": null, // Handled by accessible description
"aria-description": null, // Handled by accessible description
"aria-details": idRefs("linked details", "linked details", false),
"aria-disabled": state(State.DISABLED),
"aria-disabled": state(STATE.DISABLED),
"aria-dropeffect": null, // Deprecated in WAI-ARIA 1.1
"aria-errormessage": errorMessageIdRefs("error message", "error messages"),
"aria-expanded": state(State.EXPANDED),
"aria-expanded": state(STATE.EXPANDED),
"aria-flowto": idRefs("alternate reading order", "alternate reading orders"), // Handled by virtual.perform()
"aria-grabbed": null, // Deprecated in WAI-ARIA 1.1
"aria-haspopup": token({
Expand All @@ -78,34 +80,34 @@ const ariaPropertyToVirtualLabelMap: Record<
"aria-hidden": null, // Excluded from accessibility tree
"aria-invalid": token({
grammar: "grammatical error detected",
false: `not ${State.INVALID}`,
false: `not ${STATE.INVALID}`,
spelling: "spelling error detected",
true: State.INVALID,
true: STATE.INVALID,
}),
"aria-keyshortcuts": string("key shortcuts"),
"aria-label": null, // Handled by accessible name
"aria-labelledby": null, // Handled by accessible name
"aria-level": integer("level"),
"aria-live": null, // Handled by live region logic
"aria-modal": state(State.MODAL),
"aria-multiselectable": state(State.MULTI_SELECTABLE),
"aria-modal": state(STATE.MODAL),
"aria-multiselectable": state(STATE.MULTI_SELECTABLE),
"aria-orientation": token({
horizontal: "orientated horizontally",
vertical: "orientated vertically",
}),
"aria-owns": null, // Handled by accessibility tree construction
"aria-placeholder": string("placeholder"),
"aria-posinset": integer("position"),
"aria-pressed": tristate(State.PRESSED, State.PARTIALLY_PRESSED),
"aria-readonly": state(State.READ_ONLY),
"aria-pressed": tristate(STATE.PRESSED, STATE.PARTIALLY_PRESSED),
"aria-readonly": state(STATE.READ_ONLY),
"aria-relevant": null, // Handled by live region logic
"aria-required": state(State.REQUIRED),
"aria-required": state(STATE.REQUIRED),
"aria-roledescription": null, // Handled by accessible description
"aria-rowcount": integer("row count"),
"aria-rowindex": integer("row index"),
"aria-rowindextext": string("row index"),
"aria-rowspan": integer("row span"),
"aria-selected": state(State.SELECTED),
"aria-selected": state(STATE.SELECTED),
"aria-setsize": integer("set size"),
"aria-sort": token({
ascending: "sorted in ascending order",
Expand All @@ -126,7 +128,7 @@ interface MapperArgs {
node?: HTMLElement;
}

function state(stateValue: State) {
function state(stateValue: ValueOf<typeof STATE>) {
return function stateMapper({ attributeValue, negative }: MapperArgs) {
if (negative) {
return attributeValue !== "false" ? `not ${stateValue}` : stateValue;
Expand Down Expand Up @@ -198,7 +200,10 @@ function idRef(propertyName: string) {
};
}

function tristate(stateValue: State, mixedValue: State) {
function tristate(
stateValue: ValueOf<typeof STATE>,
mixedValue: ValueOf<typeof STATE>
) {
return function stateMapper({ attributeValue }: MapperArgs) {
if (attributeValue === "mixed") {
return mixedValue;
Expand Down
2 changes: 0 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,8 @@ import { StartOptions, Virtual } from "./Virtual";
* "heading, Section Heading, level 1",
* "Section Text",
* "article",
* "banner",
* "heading, Article Header Heading, level 1",
* "Article Header Text",
* "end of banner",
* "Article Text",
* "end of article",
* "end of region",
Expand Down
Loading