diff --git a/.eslintrc.json b/.eslintrc.json
index db4da79..c368dda 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -8,7 +8,7 @@
},
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"parserOptions": {
- "ecmaVersion": 8
+ "ecmaVersion": "latest"
},
"rules": {
"no-unused-vars": "off",
diff --git a/script/aria.js b/script/aria.js
index 67d6a62..68ad486 100644
--- a/script/aria.js
+++ b/script/aria.js
@@ -1,3 +1,16 @@
+/**
+ * Clones a node but strips IDs
+ * @param {HTMLElement} node - an element node
+ * @returns {HTMLElement} - cloned node without IDs
+ */
+function cloneWithoutIds(node) {
+ const clone = node.cloneNode(true);
+ for (const elementWithId of clone.querySelectorAll("[id]")) {
+ elementWithId.removeAttribute("id");
+ }
+ return clone;
+}
+
/**
* roleInfo is structured like this:
*
@@ -7,746 +20,395 @@
* localprops: local properties and states
*/
-var roleInfo = {};
+const roleInfo = {};
-function ariaAttributeReferences() {
- var propList = {};
- var globalSP = [];
+/**
+ * Populates propList for given sdef/pdef
+ * @param {Object} propList -
+ * @param {HTMLElement} item - from nodeList.forEach
+ */
+const populatePropList = function (propList, item) {
+ const type = item.localName === "pdef" ? "property" : "state";
+ const content = item.innerHTML;
+ const title = item.getAttribute("title") || content;
+ const dRef = item.nextElementSibling;
+ const desc = cloneWithoutIds(dRef.firstElementChild).innerHTML;
+ propList[title] = {
+ is: type,
+ title: title,
+ name: content,
+ desc: desc,
+ roles: [],
+ };
+};
- var skipIndex = 0;
- var myURL = document.URL;
- if (myURL.match(/\?fast/)) {
- skipIndex = 1;
+/**
+ * Populates globalSP for given sdef/pdef
+ * @param {Object} propList -
+ * @param {Object} globalSP -
+ * @param {HTMLElement} item - from nodeList.forEach
+ */
+const populateGlobalSP = function (propList, globalSP, item) {
+ const title = item.getAttribute("title") || item.innerHTML;
+ const container = item.parentElement;
+ const itemEntry = propList[title];
+
+ const applicabilityText = container.querySelector(
+ "." + itemEntry.is + "-applicability"
+ ).innerText;
+ const isDefault = applicabilityText === "All elements of the base markup";
+ const isProhibited =
+ applicabilityText ===
+ "All elements of the base markup except for some roles or elements that prohibit its use";
+ const isDeprecated =
+ applicabilityText === "Use as a global deprecated in ARIA 1.2";
+ // NOTE: the only other value for applicabilityText appears to be "Placeholder"
+ if (isDefault || isProhibited || isDeprecated) {
+ globalSP.push(
+ Object.assign(itemEntry, {
+ prohibited: isProhibited,
+ deprecated: isDeprecated,
+ })
+ );
}
+};
- // process the document before anything else is done
- // first get the properties
- Array.prototype.slice
- .call(document.querySelectorAll("pdef, sdef"))
- .forEach(function (item) {
- var type = item.localName === "pdef" ? "property" : "state";
- var container = item.parentNode;
- var content = item.innerHTML;
- var sp = document.createElement("span");
- var title = item.getAttribute("title");
- if (!title) {
- title = content;
- }
- sp.className = type + "-name";
- sp.title = title;
- sp.innerHTML =
- "" +
- content +
- '
' +
- type +
- "";
- sp.setAttribute("aria-describedby", "desc-" + title);
- var dRef = item.nextElementSibling;
- var desc = cloneWithoutIds(dRef.firstElementChild).innerHTML;
- dRef.id = "desc-" + title;
- dRef.setAttribute("role", "definition");
- var heading = document.createElement("h4");
- heading.appendChild(sp);
- container.replaceChild(heading, item);
- // add this item to the index
- propList[title] = {
- is: type,
- title: title,
- name: content,
- desc: desc,
- roles: [],
- };
- var abstract = container.querySelector(
- "." + type + "-applicability"
- );
- if (
- (abstract.textContent || abstract.innerText) ===
- "All elements of the base markup"
- ) {
- globalSP.push({
- is: type,
- title: title,
- name: content,
- desc: desc,
- prohibited: false,
- deprecated: false,
- });
- } else if (
- (abstract.textContent || abstract.innerText) ===
- "All elements of the base markup except for some roles or elements that prohibit its use"
- ) {
- globalSP.push({
- is: type,
- title: title,
- name: content,
- desc: desc,
- prohibited: true,
- deprecated: false,
- });
- } else if (
- (abstract.textContent || abstract.innerText) ===
- "Use as a global deprecated in ARIA 1.2"
- ) {
- globalSP.push({
- is: type,
- title: title,
- name: content,
- desc: desc,
- prohibited: false,
- deprecated: true,
- });
- }
- // the rdef is gone. if we are in a div, convert that div to a section
-
- if (container.nodeName.toLowerCase() == "div") {
- // change the enclosing DIV to a section with notoc
- var sec = document.createElement("section");
- Array.prototype.slice
- .call(container.attributes)
- .forEach(function (attr) {
- sec.setAttribute(attr.name, attr.value);
- });
- sec.classList.add("notoc");
- var theContents = container.innerHTML;
- sec.innerHTML = theContents;
- container.parentNode.replaceChild(sec, container);
- }
+/**
+ *
+ * @param {HTMLElement} container - parent of sdef or pdef or rdef
+ */
+const rewriteDefContainer = (container) => {
+ // if we are in a div, convert that div to a section
+ // TODO:
+ // a) seems to be always the case.
+ // b) Why don't we author the spec this way?
+ if (container.nodeName.toLowerCase() == "div") {
+ // change the enclosing DIV to a section with notoc
+ const sec = document.createElement("section");
+ [...container.attributes].forEach(function (attr) {
+ sec.setAttribute(attr.name, attr.value);
});
+ sec.classList.add("notoc");
+ const theContents = container.innerHTML;
+ sec.innerHTML = theContents;
+ container.parentNode.replaceChild(sec, container);
+ }
+};
- if (!skipIndex) {
- // we have all the properties and states - spit out the
- // index
- var propIndex = "";
- var sortedList = [];
-
- Object.keys(propList).forEach(function (key) {
- sortedList.push(key);
- });
- sortedList = sortedList.sort();
-
- for (var i = 0; i < sortedList.length; i++) {
- var item = propList[sortedList[i]];
- propIndex +=
- '
' +
- item.name +
- "\n";
- propIndex += "" + item.desc + "\n";
- }
- var node = document.getElementById("index_state_prop");
- var parentNode = node.parentNode;
- var l = document.createElement("dl");
- l.id = "index_state_prop";
- l.className = "compact";
- l.innerHTML = propIndex;
- parentNode.replaceChild(l, node);
-
- var globalSPIndex = "";
- sortedList = globalSP.sort(function (a, b) {
- return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
- });
- for (i = 0; i < sortedList.length; i++) {
- var lItem = sortedList[i];
- globalSPIndex += "";
- if (lItem.is === "state") {
- globalSPIndex +=
- "' +
- lItem.name +
- " (state)";
- } else {
- globalSPIndex +=
- "" +
- lItem.name +
- "";
- }
- if (lItem.prohibited) {
- globalSPIndex += " (Except where prohibited)";
- }
- if (lItem.deprecated) {
- globalSPIndex += " (Global use deprecated in ARIA 1.2)";
- }
- globalSPIndex += "\n";
- }
- parentNode = document.querySelector("#global_states");
- if (parentNode) {
- node = parentNode.querySelector(".placeholder");
- if (node) {
- l = document.createElement("ul");
- l.innerHTML = globalSPIndex;
- parentNode.replaceChild(l, node);
- }
- }
- // there is only one role that uses the global properties
- parentNode = document.querySelector(
- "#roletype td.role-properties span.placeholder"
- );
- if (parentNode) {
- node = parentNode.parentNode;
- if (
- (parentNode.textContent || parentNode.innerText) ===
- "Placeholder for global states and properties"
- ) {
- l = document.createElement("ul");
- l.innerHTML = globalSPIndex;
- node.replaceChild(l, parentNode);
- }
- }
+/**
+ *
+ * @param {HTMLElement} item - rdef element
+ */
+const rewriteRdef = function (item) {
+ // TODO: merge with generateHTMLStatesAndProperties() but that creates different HTML
+ const content = item.innerHTML;
+ let title = item.getAttribute("title") || content;
+ let type = "role";
+ const abstract = item.parentNode.querySelectorAll(".role-abstract"); //TODO: maybe #105
+ if (abstract.innerText === "True") {
+ type = "abstract role";
}
+ const dRef = item.nextElementSibling;
+ dRef.id = "desc-" + title;
+ dRef.setAttribute("role", "definition");
+ item.outerHTML = `${content}
${type}`;
+};
- // what about roles?
- //
- // we need to do a few things here:
- // 1. expand the rdef elements.
- // 2. accumulate the roles into a table for the indices
- // 3. grab the parent role reference so we can build up the tree
- // 4. grab any local states and properties so we can hand those down to the children
- //
+/**
+ * Replaces sdef/pdef with desired HTML
+ * @param {Object} propList -
+ * @param {HTMLElement} item - sdef or pdef, from nodeList.forEach
+ */
+const generateHTMLStatesAndProperties = function (propList, item) {
+ const title = item.getAttribute("title") || item.innerHTML;
+ const itemEntry = propList[title];
+ const dRef = item.nextElementSibling;
+ dRef.id = "desc-" + title; // TODO: too much of a side-effect?
+ dRef.setAttribute("role", "definition"); // TODO: ditto?
+ // Replace pdef/sdef with HTML
+ item.outerHTML = `${itemEntry.name}
${itemEntry.is}
`;
+};
- var subRoles = [];
- var roleIndex = "";
- var fromAuthor = "";
- var fromHeading = "";
- var fromContent = "";
- var fromProhibited = "";
+/**
+ * Generate index of states and properties
+ * @param {Object} propList
+ */
+const generateIndexStatesAndProperties = (propList) => {
+ const indexStatePropPlaceholder =
+ document.getElementById("index_state_prop");
+ const indexStatePropContent = Object.values(propList)
+ .map(
+ (item) =>
+ `${item.name}\n${item.desc}\n`
+ )
+ .join("");
+ indexStatePropPlaceholder.outerHTML = `${indexStatePropContent}
`;
+};
- Array.prototype.slice
- .call(document.querySelectorAll("rdef"))
- .forEach(function (item) {
- var container = item.parentNode;
- var content = item.innerHTML;
- var sp = document.createElement("h4");
- var title = item.getAttribute("title");
- if (!title) {
- title = content;
- }
+/**
+ * Generate index of global states and properties
+ * @param {Object} globalSP
+ */
+const generateIndexGlobalStatesAndProperties = (globalSP) => {
+ const globalStatesPropertiesContent = globalSP
+ .map((item) => {
+ // TODO: This is the only use of globalSP - why does it not just consist of the markup we create here in this loop?
+ const isState = item.is === "state";
+ const tagName = isState ? "sref" : "pref";
+ return `
<${tagName} ${
+ item.prohibited ? "data-prohibited " : ""
+ }${item.deprecated ? "data-deprecated " : ""}${
+ isState ? `title="${item.name}"` : ""
+ }>${item.name}${isState ? " (state)" : ""}${tagName}>${
+ // TODO: consider moving "(state)" out of sref/pref tag; then maybe remove title attr for sref (after checking resolveReferences interference)
+ // TODO: cf. extractStatesProperties() and populateRoleInfoPropList() which have extra logic for title set here)
+
+ item.prohibited ? " (Except where prohibited)" : ""
+ }${
+ item.deprecated ? " (Global use deprecated in ARIA 1.2)" : ""
+ }\n`;
+ })
+ .join("");
+ const globalStatesPropertiesPlaceholder = document.querySelector(
+ "#global_states .placeholder"
+ );
+ globalStatesPropertiesPlaceholder.outerHTML = `${globalStatesPropertiesContent}
`;
- var pnID = title;
- container.id = pnID;
- sp.className = "role-name";
- sp.title = title;
- // is this a role or an abstract role
- var type = "role";
- var isAbstract = false;
- var abstract = container.querySelectorAll(".role-abstract");
- if (abstract.innerText === "True") {
- type = "abstract role";
- isAbstract = true;
- }
- sp.innerHTML =
- "" +
- content +
- '
' +
- type +
- "";
- // sp.id = title;
- sp.setAttribute("aria-describedby", "desc-" + title);
- var dRef = item.nextElementSibling;
- var desc = cloneWithoutIds(dRef.firstElementChild).innerHTML;
- dRef.id = "desc-" + title;
- dRef.setAttribute("role", "definition");
- container.replaceChild(sp, item);
- roleIndex +=
- '' +
- content +
- "
" +
- (isAbstract ? " (abstract role) " : "") +
- "\n";
- roleIndex += "" + desc + "\n";
- // grab info about this role
- // do we have a parent class? if so, put us in that parents list
- var node = Array.prototype.slice.call(
- container.querySelectorAll(".role-parent rref")
- );
- // s will hold the name of the parent role if any
- var s = null;
- var parentRoles = [];
- if (node.length) {
- node.forEach(function (roleref) {
- s = roleref.textContent || roleref.innerText;
-
- if (!subRoles[s]) {
- subRoles.push(s);
- subRoles[s] = [];
- }
- subRoles[s].push(title);
- parentRoles.push(s);
- });
- }
- // are there supported states / properties in this role?
- var attrs = [];
- Array.prototype.slice
- .call(
- container.querySelectorAll(
- ".role-properties, .role-required-properties, .role-disallowed"
- )
- )
- .forEach(function (node) {
- if (
- node &&
- ((node.textContent && node.textContent.length !== 1) ||
- (node.innerText && node.innerText.length !== 1))
- ) {
- // looks like we do
- Array.prototype.slice
- .call(node.querySelectorAll("pref,sref"))
- .forEach(function (item) {
- var name = item.getAttribute("title");
- if (!name) {
- name = item.textContent || item.innerText;
- }
- var type =
- item.localName === "pref"
- ? "property"
- : "state";
- var req = node.classList.contains(
- "role-required-properties"
- );
- var dis =
- node.classList.contains("role-disallowed");
- var dep = item.hasAttribute("data-deprecated");
- attrs.push({
- is: type,
- name: name,
- required: req,
- disallowed: dis,
- deprecated: dep,
- });
-
- // remember that the state or property is
- // referenced by this role
- propList[name].roles.push(title);
- });
- }
- });
- roleInfo[title] = {
- name: title,
- fragID: pnID,
- parentRoles: parentRoles,
- localprops: attrs,
- };
-
- // is there a namefrom indication? If so, add this one to
- // the list
- if (!isAbstract) {
- Array.prototype.slice
- .call(container.querySelectorAll(".role-namefrom"))
- .forEach(function (node) {
- var reqRef =
- container.querySelector(".role-namerequired");
- var req = "";
- if (reqRef && reqRef.innerText === "True") {
- req = " (name required)";
- }
-
- if (node.textContent.indexOf("author") !== -1) {
- fromAuthor +=
- '' +
- content +
- "
" +
- req +
- "";
- }
- if (node.textContent.indexOf("heading") !== -1) {
- fromHeading +=
- '' +
- content +
- "
" +
- req +
- "";
- }
- if (
- !isAbstract &&
- node.textContent.indexOf("content") !== -1
- ) {
- fromContent +=
- '' +
- content +
- "
" +
- req +
- "";
- }
- if (node.textContent.indexOf("prohibited") !== -1) {
- fromProhibited +=
- '' +
- content +
- "
" +
- req +
- "";
- }
- });
- }
- if (container.nodeName.toLowerCase() == "div") {
- // change the enclosing DIV to a section with notoc
- var sec = document.createElement("section");
- Array.prototype.slice
- .call(container.attributes)
- .forEach(function (attr) {
- sec.setAttribute(attr.name, attr.value);
- });
-
- sec.classList.add("notoc");
- var theContents = container.innerHTML;
- sec.innerHTML = theContents;
- container.parentNode.replaceChild(sec, container);
- }
- });
+ // Populate role=roletype properties with global properties
+ const roletypePropsPlaceholder = document.querySelector(
+ "#roletype td.role-properties span.placeholder"
+ );
+ roletypePropsPlaceholder.outerHTML = `${globalStatesPropertiesContent}
`;
+};
- var getStates = function (role) {
- var ref = roleInfo[role];
- if (!ref) {
- msg.pub("error", "No role definition for " + role);
- } else if (ref.allprops) {
- return ref.allprops;
- } else {
- var myList = ref.localprops;
- Array.prototype.slice
- .call(ref.parentRoles)
- .forEach(function (item) {
- var pList = getStates(item);
- myList = myList.concat(pList);
- });
- ref.allprops = myList;
- return myList;
- }
+/**
+ * For an rdef element, generates DT+DD content to be added to the Index of Roles
+ * @param {HTMLElement} item - rdef element
+ */
+const generateHTMLRoleIndexEntry = function (item) {
+ const container = item.parentNode;
+ const content = item.innerText;
+ container.id = content;
+ // is this a role or an abstract role
+ let type = "role";
+ let isAbstract = false;
+ const abstract = container.querySelectorAll(".role-abstract"); //TODO: maybe #105
+ if (abstract.innerText === "True") {
+ type = "abstract role";
+ isAbstract = true;
+ }
+ const dRef = item.nextElementSibling;
+ const desc = cloneWithoutIds(dRef.firstElementChild).innerHTML; // TODO: should the spec markup provide something more robust than "next sibling first child"? [same for sdef/pdef "desc"]
+ return `${content}
${
+ isAbstract ? " (abstract role) " : ""
+ }\n${desc}\n`;
+};
+
+/**
+ * Generates subrole information
+ * @param {NodeList} rdefs - rdefs
+ */
+const generateSubRoles = (rdefs) => {
+ const subRoles = {};
+ rdefs.forEach((rdef) => {
+ const title = rdef.innerHTML;
+ rdef.parentNode
+ .querySelectorAll(".role-parent rref")
+ .forEach(function (roleref) {
+ const parentRole = roleref.innerText;
+ const parentChildrenRoles = (subRoles[parentRole] ??=
+ new Set());
+ parentChildrenRoles.add(title);
+ });
+ });
+ return subRoles;
+};
+
+/**
+ *
+ * @param {HTMLElement} item - sdef or pdef inside rdef Characteristics table
+ * @returns
+ */
+const extractStatesProperties = function (item) {
+ const name = item.getAttribute("title") || item.innerText; // TODO: raw HTML doesn't have sref/pref with title attributes but generateIndexGlobalStatesAndProperties() creates them
+ const type = item.localName === "pref" ? "property" : "state";
+ const req = !!item.closest(".role-required-properties");
+ const dis = !!item.closest(".role-disallowed");
+ const dep = item.hasAttribute("data-deprecated");
+ return {
+ is: type,
+ name: name,
+ required: req,
+ disallowed: dis,
+ deprecated: dep,
};
+};
- // TODO: test this on a page where `skipIndex` is truthy
- if (!skipIndex) {
- // build up the complete inherited SP lists for each role
- // however, if the role already specifies an item, do not include it
- Object.entries(roleInfo).forEach(function (index) {
- var item = index[1];
- var output = "";
- var placeholder = document.querySelector(
- "#" + item.fragID + " .role-inherited"
- );
-
- if (placeholder) {
- var myList = [];
- item.parentRoles.forEach(function (role) {
- myList = myList.concat(getStates(role));
- });
- // strip out any items that we have locally
- if (item.localprops.length && myList.length) {
- for (var j = myList.length - 1; j >= 0; j--) {
- item.localprops.forEach(function (x) {
- if (x.name == myList[j].name) {
- myList.splice(j, 1);
- }
- });
- }
- }
+/**
+ *
+ * @param {String} indexTest - string to decide if this index needs it
+ * @param {HTMLElement} rdef - rdef node
+ */
+const generateHTMLNameFromIndices = (indexTest, rdef) => {
+ const container = rdef.parentNode;
+ // is there a namefrom indication? If so, add this one to
+ // the list
+ const roleFromNode = container.querySelector(".role-namefrom");
+ // is this a role or an abstract role
+ let isAbstract = false;
+ const abstract = container.querySelectorAll(".role-abstract"); //TODO: maybe #105
+ if (abstract.innerText === "True") {
+ isAbstract = true;
+ }
+ if (!isAbstract && roleFromNode) {
+ const content = rdef.innerText;
+ const isRequired =
+ roleFromNode.closest("table").querySelector(".role-namerequired")
+ ?.innerText === "True";
+ if (roleFromNode.textContent.indexOf(indexTest) !== -1)
+ return `${content}
${
+ isRequired ? " (name required)" : ""
+ }`; // TODO: `textContent.indexOf` feels brittle; right now it's either the exact string or proper list markup with LI with exact string
+ }
+};
- var reducedList = myList.reduce((uniqueList, item) => {
- return uniqueList.includes(item)
- ? uniqueList
- : [...uniqueList, item];
- }, []);
-
- var sortedList = reducedList.sort((a, b) => {
- if (a.name == b.name) {
- // Ensure deprecated false properties occur first
- if (a.deprecated !== b.deprecated) {
- return a.deprecated ? 1 : b.deprecated ? -1 : 0;
- }
- }
- return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
- }, []);
-
- var prev;
- for (var k = 0; k < sortedList.length; k++) {
- var property = sortedList[k];
- var req = "";
- var dep = "";
- if (property.required) {
- req = " (required)";
- }
- if (property.deprecated) {
- dep =
- " (deprecated on this role in ARIA 1.2)";
- }
- if (prev != property.name) {
- output += "";
- if (property.is === "state") {
- output +=
- "" +
- property.name +
- " (state)" +
- req +
- dep;
- } else {
- output +=
- "" +
- property.name +
- "" +
- req +
- dep;
- }
- output += "\n";
- prev = property.name;
- }
- }
- if (output !== "") {
- output = "\n";
- placeholder.innerHTML = output;
- }
- }
- });
+/**
+ * Populates roleInfo and updates proplist alongside it
+ * TODO: separate out propList updates
+ * @param {Object} roleInfo - the roleInfo object
+ * @param {Object} propList - the "list" of properties
+ * @param {HTMLElement} item - an rdef node
+ */
+const populateRoleInfoPropList = function (roleInfo, propList, item) {
+ const container = item.parentNode;
+ const content = item.innerText;
+ container.id = content;
+
+ // grab info about this role
+ // do we have a parent class? if so, put us in that parents list
+ const rrefs = container.querySelectorAll(".role-parent rref");
+ const parentRoles = [...rrefs].map((rref) => rref.innerText);
+ // are there supported states / properties in this role?
+ const PSDefs = container.querySelectorAll(
+ `:is(.role-properties, .role-required-properties, .role-disallowed) :is(pref, sref)`
+ );
+ const attrs = [...PSDefs].map(extractStatesProperties);
+ // remember that the state or property is
+ // referenced by this role
+ PSDefs.forEach((node) =>
+ propList[node.getAttribute("title") || node.innerText].roles.push(
+ // TODO: cf. generateIndexGlobalStatesAndProperties() TODO for simplifying title || node.innerText
+ content
+ )
+ );
- // Update state and property role references
- var getAllSubRoles = function (role) {
- var ref = subRoles[role];
- if (ref && ref.length) {
- var myList = [];
- ref.forEach(function (item) {
- if (!myList.item) {
- myList[item] = 1;
- myList.push(item);
- var childList = getAllSubRoles(item);
- myList = myList.concat(childList);
- }
- });
- return myList;
- } else {
- return [];
- }
- };
-
- Object.entries(propList).forEach(function (index) {
- var output = "";
- var item = index[1];
- var section = document.querySelector("#" + item.name);
- var placeholder = section.querySelector(
- ".state-applicability, .property-applicability"
- );
- if (
- placeholder &&
- (placeholder.textContent || placeholder.innerText) ===
- "Placeholder" &&
- item.roles.length
- ) {
- // update the used in roles list
- var sortedList = [];
- sortedList = item.roles.sort();
- for (var j = 0; j < sortedList.length; j++) {
- output += "" + sortedList[j] + "\n";
- }
- if (output !== "") {
- output = "\n";
- }
- placeholder.innerHTML = output;
- // also update any inherited roles
- var myList = [];
- item.roles.forEach(function (role) {
- var children = getAllSubRoles(role);
- // Some subroles have required properties which are also required by the superclass.
- // Example: The checked state of radio, which is also required by superclass checkbox.
- // We only want to include these one time, so filter out the subroles.
- children = children.filter(function (subrole) {
- return (
- subrole.indexOf(propList[item.name].roles) === -1
- );
- });
- myList = myList.concat(children);
- });
- placeholder = section.querySelector(
- ".state-descendants, .property-descendants"
- );
- if (placeholder && myList.length) {
- sortedList = myList.sort();
- output = "";
- var last = "";
- for (j = 0; j < sortedList.length; j++) {
- var sItem = sortedList[j];
- if (last != sItem) {
- output += "" + sItem + "\n";
- last = sItem;
- }
- }
- if (output !== "") {
- output = "\n";
- }
- placeholder.innerHTML = output;
- }
- } else if (
- placeholder &&
- (placeholder.textContent || placeholder.innerText) ===
- "Use as a global deprecated in ARIA 1.2" &&
- item.roles.length
- ) {
- // update the used in roles list
- var sortedList = [];
- sortedList = item.roles.sort();
- //remove roletype from the sorted list
- const index = sortedList.indexOf("roletype");
- if (index > -1) {
- sortedList.splice(index, 1);
- }
+ roleInfo[content] = {
+ name: content,
+ fragID: content,
+ parentRoles: parentRoles,
+ localprops: attrs,
+ };
+};
- for (var j = 0; j < sortedList.length; j++) {
- output += "" + sortedList[j] + "\n";
- }
- if (output !== "") {
- output = "\n";
- }
- placeholder.innerHTML = output;
- // also update any inherited roles
- var myList = [];
- item.roles.forEach(function (role) {
- var children = getAllSubRoles(role);
- // Some subroles have required properties which are also required by the superclass.
- // Example: The checked state of radio, which is also required by superclass checkbox.
- // We only want to include these one time, so filter out the subroles.
- children = children.filter(function (subrole) {
- return (
- subrole.indexOf(propList[item.name].roles) === -1
- );
- });
- myList = myList.concat(children);
- });
- placeholder = section.querySelector(
- ".state-descendants, .property-descendants"
- );
- if (placeholder && myList.length) {
- sortedList = myList.sort();
- output = "";
- var last = "";
- for (j = 0; j < sortedList.length; j++) {
- var sItem = sortedList[j];
- if (last != sItem) {
- output += "" + sItem + "\n";
- last = sItem;
- }
- }
- if (output !== "") {
- output = "\n";
- }
- placeholder.innerHTML = output;
- }
- } else if (
- placeholder &&
- (placeholder.textContent || placeholder.innerText) ===
- "All elements of the base markup except for some roles or elements that prohibit its use" &&
- item.roles.length
- ) {
- // for prohibited roles the roles list just includes those roles which are prohibited... weird I know but it is what it is
- var sortedList = [];
- sortedList = item.roles.sort();
- //remove roletype from the sorted list
- const index = sortedList.indexOf("roletype");
- if (index > -1) {
- sortedList.splice(index, 1);
- }
- output +=
- "All elements of the base markup except for the following roles: ";
- for (var j = 0; j < sortedList.length - 1; j++) {
- output += "" + sortedList[j] + ", ";
- }
- output +=
- "" + sortedList[sortedList.length - 1] + "";
- placeholder.innerHTML = output;
- }
+/**
+ * TODO: depends on global roleInfo object
+ * Generats `allprops` array for a role entry in roleInfo
+ * @param {string} role - name of a role
+ * @returns
+ */
+const getStates = function (role) {
+ // TODO: pkra would like to use sets here but allprops part of roleInfo serializaton
+ const ref = roleInfo[role];
+ if (!ref) {
+ msg.pub("error", "No role definition for " + role);
+ } else if (ref.allprops) {
+ return ref.allprops;
+ } else {
+ let myList = ref.localprops;
+ ref.parentRoles.forEach(function (item) {
+ const pList = getStates(item);
+ myList = myList.concat(pList);
});
+ ref.allprops = myList;
+ return myList;
+ }
+};
- // spit out the index
- var node = document.getElementById("index_role");
- var parentNode = node.parentNode;
- var list = document.createElement("dl");
- list.id = "index_role";
- list.className = "compact";
- list.innerHTML = roleIndex;
- parentNode.replaceChild(list, node);
-
- // and the namefrom lists
- node = document.getElementById("index_fromauthor");
- if (node) {
- parentNode = node.parentNode;
- list = document.createElement("ul");
- list.id = "index_fromauthor";
- list.className = "compact";
- list.innerHTML = fromAuthor;
- parentNode.replaceChild(list, node);
- }
-
- node = document.getElementById("index_fromheading");
- if (node) {
- parentNode = node.parentNode;
- list = document.createElement("ul");
- list.id = "index_fromheading";
- list.className = "compact";
- list.innerHTML = fromHeading;
- parentNode.replaceChild(list, node);
+/**
+ * Builds up the complete inherited SP lists for each role
+ * However, if the role already specifies an item, do not include it
+ * @param {Object} item - value from Object.values(roleInfo)
+ */
+const buildInheritedStatesProperties = function (item) {
+ // BEGIN TODO: why can't we do, e.g.,
+ // 1. in the main function: Object.keys(roleInfo).forEach(role=> getStates(role)); (see also TODO: near where buildInheritedStatesProperties() is called)
+ // - Then: let myList = item.allprops; (instead of myList = myList.concat(getStates(role)))
+ // - NOTE: the HTML stays the same but the exported roleInfo isn't.
+ // - TODO: BUG? in the existing roleInfo allprops only occurs 30 times
+ let myList = [];
+ item.parentRoles.forEach(function (role) {
+ myList = myList.concat(getStates(role));
+ });
+ // END TODO
+ // strip out any items that we have locally
+ // BEGIN TODO: why can't we do myList.filter( inherited => item.localprops.includes(local => local.name === inherited.name))?
+ // or do something else to simplify this
+ if (item.localprops.length && myList.length) {
+ for (let j = myList.length - 1; j >= 0; j--) {
+ item.localprops.forEach(function (x) {
+ if (x.name == myList[j].name) {
+ myList.splice(j, 1);
+ }
+ });
}
+ }
- node = document.getElementById("index_fromcontent");
- if (node) {
- parentNode = node.parentNode;
- list = document.createElement("ul");
- list.id = "index_fromcontent";
- list.className = "compact";
- list.innerHTML = fromContent;
- parentNode.replaceChild(list, node);
- }
+ const reducedList = [...new Set(myList)];
- node = document.getElementById("index_fromprohibited");
- if (node) {
- parentNode = node.parentNode;
- list = document.createElement("ul");
- list.id = "index_fromprohibited";
- list.className = "compact";
- list.innerHTML = fromProhibited;
- parentNode.replaceChild(list, node);
- }
- // assuming we found some parent roles, update those parents with their children
- for (var i = 0; i < subRoles.length; i++) {
- var item = subRoles[subRoles[i]];
- var sortedList = item.sort(function (a, b) {
- return a < b ? -1 : a > b ? 1 : 0;
- });
- var output = "\n";
- for (var j = 0; j < sortedList.length; j++) {
- output += "- " + sortedList[j] + "
\n";
- }
- output += "
\n";
- // put it somewhere
- var subRolesContainer = document.querySelector("#" + subRoles[i]);
- if (subRolesContainer) {
- var subRolesListContainer =
- subRolesContainer.querySelector(".role-children");
- if (subRolesListContainer) {
- subRolesListContainer.innerHTML = output;
- }
+ const sortedList = reducedList.sort((a, b) => {
+ if (a.name == b.name) {
+ //TODO: BUG: deprecated states&props do not actually appear at end
+ // NOTE: removing if (a.deprecated !== b.deprecated) seems to fix this
+ // Ensure deprecated false properties occur first
+ if (a.deprecated !== b.deprecated) {
+ return a.deprecated ? 1 : b.deprecated ? -1 : 0;
}
}
+ return a.name.localeCompare(b.name);
+ }, []);
+
+ const uniquePropNames = new Set(sortedList.map((prop) => prop.name));
+ // NOTE: uniquePropNames is needed because sortedList can have duplicates, in particular with different deprecation states. E.g., treeitem inherits aria-disabled from option but also as deprecated-in-1.2 from listitem.
+ // TODO: is it just luck that the not-deprecated state is listed first? (see same comment below)
+ const output = [...uniquePropNames]
+ .map((propName) => {
+ const property = sortedList.find((p) => p.name === propName); // TODO: is it just luck that the not-deprecated state is listed first?
+ const isState = property.is === "state";
+ const suffix = isState ? " (state)" : "";
+ const tag = isState ? "sref" : "pref";
+ const req = property.required ? " (required)" : "";
+ const dep = property.deprecated
+ ? " (deprecated on this role in ARIA 1.2)"
+ : "";
+
+ return `<${tag}>${property.name}${tag}>${suffix}${req}${dep}\n`;
+ })
+ .join("");
+ if (output !== "") {
+ document.querySelector(
+ "#" + item.fragID + " .role-inherited"
+ ).innerHTML = `\n`;
}
+};
- // prune out unused rows throughout the document
- Array.prototype.slice
- .call(
- document.querySelectorAll(
- ".role-abstract, .role-parent, .role-base, .role-related, .role-scope, .role-mustcontain, .role-required-properties, .role-properties, .role-namefrom, .role-namerequired, .role-namerequired-inherited, .role-childpresentational, .role-presentational-inherited, .state-related, .property-related,.role-inherited, .role-children, .property-descendants, .state-descendants, .implicit-values"
- )
+/**
+ * prune out unused rows throughout the document
+ *
+ */
+const pruneUnusedRows = () => {
+ document
+ .querySelectorAll(
+ ".role-abstract, .role-parent, .role-base, .role-related, .role-scope, .role-mustcontain, .role-required-properties, .role-properties, .role-namefrom, .role-namerequired, .role-namerequired-inherited, .role-childpresentational, .role-presentational-inherited, .state-related, .property-related,.role-inherited, .role-children, .property-descendants, .state-descendants, .implicit-values"
)
.forEach(function (item) {
var content = item.innerText;
@@ -755,7 +417,6 @@ function ariaAttributeReferences() {
item.parentNode.parentNode.removeChild(item.parentNode);
} else if (
content === "Placeholder" &&
- !skipIndex &&
(item.className === "role-inherited" ||
item.className === "role-children" ||
item.className === "property-descendants" ||
@@ -764,16 +425,229 @@ function ariaAttributeReferences() {
item.parentNode.remove();
}
});
+};
- updateReferences(document);
+/**
+ * Generates the HTML for various indices in the spec
+ * @param {NodeList} rdefs - all the rdefs
+ */
+const generateHTMLIndices = (rdefs) => {
+ let fromAuthor = [...rdefs]
+ .map(generateHTMLNameFromIndices.bind(null, "author"))
+ .join("");
+ let fromHeading = [...rdefs]
+ .map(generateHTMLNameFromIndices.bind(null, "heading"))
+ .join("");
+ let fromContent = [...rdefs]
+ .map(generateHTMLNameFromIndices.bind(null, "content"))
+ .join("");
+ let fromProhibited = [...rdefs]
+ .map(generateHTMLNameFromIndices.bind(null, "prohibited"))
+ .join("");
+
+ const roleIndex = [...rdefs].map(generateHTMLRoleIndexEntry).join("");
+
+ // spit out the indices
+ document.getElementById(
+ "index_role"
+ ).outerHTML = `${roleIndex}
`;
+ document.getElementById(
+ "index_fromauthor"
+ ).outerHTML = ``;
+ document.getElementById(
+ "index_fromcontent"
+ ).outerHTML = ``;
+ document.getElementById(
+ "index_fromprohibited"
+ ).outerHTML = ``;
+ // TODO: remove if-check after w3c/aria#1860
+ if (document.getElementById("index_fromheading"))
+ document.getElementById(
+ "index_fromheading"
+ ).outerHTML = ``;
+};
- function cloneWithoutIds(node) {
- const clone = node.cloneNode(true);
- for (const elementWithId of clone.querySelectorAll("[id]")) {
- elementWithId.removeAttribute("id");
- }
- return clone;
+/**
+ * Creates dictionary of "descendant" roles
+ * @param {Object} subRoles - the subroles collection
+ * @returns
+ */
+const createDescendantRoles = (subRoles) => {
+ const descendantRoles = {};
+ const getAllSubRoles = function (key) {
+ const subroleSet = new Set();
+ if (!subRoles[key]) return subroleSet; // NOTE: recursion end
+ subRoles[key].forEach(function (childRole) {
+ subroleSet.add(childRole);
+ const descendantRolesSet = getAllSubRoles(childRole);
+ descendantRolesSet.forEach((role) => subroleSet.add(role));
+ });
+ return subroleSet;
+ };
+ Object.keys(subRoles).forEach(
+ (item) => (descendantRoles[item] = getAllSubRoles(item))
+ );
+ return descendantRoles;
+};
+
+/**
+ * The propList loop.
+ * @param {Object} propList - the propList
+ * @param {Object} descendantRoles - the list of "descendant" roles
+ * @param {Object} item - value from object.values(propList)
+ * @returns
+ */
+const propListLoop = function (propList, descendantRoles, item) {
+ const section = document.querySelector("#" + item.name);
+ let placeholder = section.querySelector(
+ ".state-applicability, .property-applicability"
+ );
+ const placeholderText = placeholder.innerText;
+ // Current values for placeholderText:
+ // * "All elements of the base markup"
+ // * "Placeholder"
+ // * "Use as a global deprecated in ARIA 1.2"
+ // * "All elements of the base markup except for some roles or elements that prohibit its use"
+ // TODO: Maybe use a data attribute instead?
+
+ // Case: nothing to do
+ if (placeholderText === "All elements of the base markup") return;
+
+ // update roles list: sort & maybe remove roletype
+ item.roles.sort();
+ if (placeholderText !== "Placeholder")
+ item.roles.splice(item.roles.indexOf("roletype"), 1);
+
+ // Case: partially prohibited
+ if (
+ placeholderText ===
+ "All elements of the base markup except for some roles or elements that prohibit its use"
+ ) {
+ // for prohibited roles the roles list just includes those roles which are prohibited... weird I know but it is what it is
+
+ placeholder.innerHTML = `All elements of the base markup except for the following roles: ${item.roles
+ .map((role) => `${role}`)
+ .join(", ")}`;
+ return;
}
+
+ // Otherwise, i.e.,
+ // Cases: placeholderText "Placeholder" or "Use as a global deprecated in ARIA 1.2"
+
+ // populate placeholder
+ placeholder.innerHTML = `\n${item.roles
+ .map((role) => `- ${role}
\n`)
+ .join("")}
\n`;
+
+ // also update any inherited roles
+ const placeholderInheritedRoles = section.querySelector(
+ ".state-descendants, .property-descendants"
+ );
+ let inheritedRoles = new Set();
+ item.roles.forEach(function (role) {
+ // Some subroles have required properties which are also required by the superclass.
+ // Example: The checked state of radio, which is also required by superclass checkbox.
+ // We only want to include these one time, so filter out the subroles.
+ if (!descendantRoles[role]) return;
+ descendantRoles[role].forEach((subrole) => {
+ if (subrole.indexOf(propList[item.name].roles) === -1)
+ inheritedRoles.add(subrole);
+ // TODO: the if-check doesn't make sense
+ // Should it be the other way around? I.e.
+ // if (propList[item.name].roles.indexOf(subrole) === -1)
+ // inheritedRoles.add(subrole);
+ // But this changes the spec, adding some, removing other entries
+ });
+ });
+
+ placeholderInheritedRoles.innerHTML = `\n${[...inheritedRoles]
+ .sort()
+ .map((role) => `- ${role}
\n`)
+ .join("")}
\n`;
+};
+
+/**
+ * In Object.entries loop, generates HTML for child role entries
+ * @param {String} role - subRoles key
+ * @param {Object} subRolesSet - subRoles value
+ */
+const generateHTMLRoleChildren = ([role, subroleSet]) => {
+ const item = [...subroleSet];
+ document.querySelector(`#${role} .role-children`).innerHTML = `\n${item
+ .map((subrole) => `- ${subrole}
\n`)
+ .join("")}
\n`;
+};
+
+function ariaAttributeReferences() {
+ const propList = {};
+ const globalSP = [];
+
+ let skipIndex = 0;
+ const myURL = document.URL;
+ if (myURL.match(/\?fast/)) {
+ skipIndex = 1;
+ }
+
+ // process the document before anything else is done
+ // first get the properties
+ const pdefsAndsdefs = document.querySelectorAll("pdef, sdef");
+ const pdefsAndsdefsContainer = [...pdefsAndsdefs].map(
+ (node) => node.parentNode
+ );
+
+ pdefsAndsdefs.forEach(populatePropList.bind(null, propList));
+ pdefsAndsdefs.forEach(populateGlobalSP.bind(null, propList, globalSP));
+ pdefsAndsdefs.forEach(generateHTMLStatesAndProperties.bind(null, propList));
+ pdefsAndsdefsContainer.forEach(rewriteDefContainer);
+
+ if (!skipIndex) {
+ // Generate index of states and properties
+ generateIndexStatesAndProperties(propList);
+
+ // Generate index of global states and properties
+ generateIndexGlobalStatesAndProperties(globalSP);
+ }
+
+ // what about roles?
+ //
+ // we need to do a few things here:
+ // 1. expand the rdef elements.
+ // 2. accumulate the roles into a table for the indices
+ // 3. grab the parent role reference so we can build up the tree
+ // 4. grab any local states and properties so we can hand those down to the children
+ //
+
+ const rdefs = document.querySelectorAll("rdef");
+ const rdefsContainer = [...rdefs].map((node) => node.parentNode);
+
+ const subRoles = generateSubRoles(rdefs);
+
+ generateHTMLIndices(rdefs);
+
+ rdefs.forEach(populateRoleInfoPropList.bind(null, roleInfo, propList));
+
+ rdefs.forEach(rewriteRdef);
+
+ rdefsContainer.forEach(rewriteDefContainer);
+
+ // TODO: test this on a page where `skipIndex` is truthy
+ if (!skipIndex) {
+ // TODO: why not run `Object.keys(roleInfo).forEach(role=> getStates(role))` here? (cf. TODO: in buildInheritedStatesProperties )
+ Object.values(roleInfo).forEach(buildInheritedStatesProperties);
+
+ const descendantRoles = createDescendantRoles(subRoles);
+
+ Object.values(propList).forEach(
+ propListLoop.bind(null, propList, descendantRoles)
+ );
+
+ // assuming we found some parent roles, update those parents with their children
+ Object.entries(subRoles).forEach(generateHTMLRoleChildren);
+ }
+
+ pruneUnusedRows();
+
+ updateReferences(document);
}
require(["core/pubsubhub"], function (respecEvents) {
diff --git a/test.sh b/test.sh
new file mode 100644
index 0000000..3b2fda1
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# NOTE: Assumes there's a copy of w3c/aria in ../aria/
+
+rm before.html after.html
+git -C ../aria/ checkout ./common/script/aria.js
+echo "Run respec on ../aria/index.html to generate 'before.html'"
+npx respec --src ../aria/index.html --out before.html
+echo "Copy ./script/aria.js to ../aria/common/script/"
+cp ./script/aria.js ../aria/common/script/.
+echo "Run respec on ../aria/index.html to generate 'after.html'"
+npx respec --src ../aria/index.html --out after.html
+echo "Run diff on 'before.html' and 'after.html'"
+diff before.html after.html
+echo "Clean up aria spec"
+git -C ../aria/ checkout ./common/script/aria.js