diff --git a/aria-practices.html b/aria-practices.html index 9d65b5e5ec..0956dad8e7 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -2743,18 +2743,23 @@

WAI-ARIA Roles, States, and Properties

Treegrid

+

+ NOTE: This section is not complete. + Development of content for this pattern is tracked by + issue 91. +

A treegrid widget presents a hierarchical data grid consisting of tabular information that is editable or interactive. Any row in the hierarchy may have child rows, and rows with children may be expanded or collapsed to show or hide the children. For example, a hierarchical e-mail discussion list uses a treegrid to display messages and responses to that message, a message row with responses can be expanded to reveal the response messages.

-

- In a treegrid both rows and cells are focusable. Every row and cell contains a focusable element or is itself focusable, regardless of whether individual cell content is editable or interactive. - There is one exception: if column cells do not provide functions, such as sort or filter, they do not need to be focusable. - One reason it is important for all cells to be able to receive or contain keyboard focus is that screen readers will typically be in their application reading mode, rather than their document reading mode, when users are interacting with the grid. - While in application mode, a screen reader user hears only focusable elements and content that labels focusable elements. - So, screen reader users may unknowingly overlook elements contained in a treegrid that are either not focusable or not used to label a column or row. -

+

+ In a treegrid both rows and cells are focusable. Every row and cell contains a focusable element or is itself focusable, regardless of whether individual cell content is editable or interactive. + There is one exception: if column cells do not provide functions, such as sort or filter, they do not need to be focusable. + One reason it is important for all cells to be able to receive or contain keyboard focus is that screen readers will typically be in their application reading mode, rather than their document reading mode, when users are interacting with the grid. + While in application mode, a screen reader user hears only focusable elements and content that labels focusable elements. + So, screen reader users may unknowingly overlook elements contained in a treegrid that are either not focusable or not used to label a column or row. +

When using a keyboard to navigate a treegrid, a visual keyboard indicator informs the user which row or cell is focused. If the treegrid allows the user to choose just one item for an action, then it is known as a single-select treegrid, and the item with focus also has a selected state. @@ -2771,152 +2776,152 @@

Examples

Hierarchical E-mail list treegrid Example with three types of focus navigation: Rows first, cells first and cells only interaction options. +
-
-

Keyboard Interaction For Data Grids

-

- The following keys provide treegrid navigation by moving focus among rows and cells of the grid. - Implementations of treegrid make these key commands available when an element in the grid has received focus, e.g., after a user has moved focus to the grid with Tab. Moving focus into the grid may result in the first cell or the first row being focused. Whether focus goes to a cell or the row depends on author preferences and whether row focus is supported, since some treegrids may not provide focus to rows. -

- - -

If a grid supports selection of cells, rows, or columns, the following keys are commonly used for these functions.

-
- +

WAI-ARIA Roles, States, and Properties

-
diff --git a/examples/treegrid/css/expand-icon-highlighted.svg b/examples/treegrid/css/expand-icon-highlighted.svg new file mode 100644 index 0000000000..4ee2b031f4 --- /dev/null +++ b/examples/treegrid/css/expand-icon-highlighted.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/treegrid/css/expand-icon.svg b/examples/treegrid/css/expand-icon.svg new file mode 100644 index 0000000000..f3ad2c5bec --- /dev/null +++ b/examples/treegrid/css/expand-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/treegrid/css/treegrid-1.css b/examples/treegrid/css/treegrid-1.css new file mode 100644 index 0000000000..7be628de02 --- /dev/null +++ b/examples/treegrid/css/treegrid-1.css @@ -0,0 +1,103 @@ +#treegrid { + width: 100%; + white-space: nowrap; + border-collapse: collapse; + table-layout: fixed; +} + +#treegrid tr { + display: table-row; + cursor: default; +} + +#treegrid tbody td { + cursor: default; +} + +#treegrid-col1, #treegrid-col3 { + width: 30%; +} + +#treegrid th { + text-align: left; + background-color: #eee; +} + +/* Extra space between columns for readability */ +#treegrid th, #treegrid td { + padding-bottom: 3px; + overflow-x: hidden; + text-overflow: ellipsis; +} + +#treegrid tr > td:not(:first-child), +#treegrid tr > th:not(:first-child) { + padding-left: 3ch; +} + +#treegrid a { + padding-left: .25ch; + padding-right: .25ch; +} + +#treegrid tr:focus, +#treegrid td:focus, +#treegrid a:focus { + outline: 2px solid hsl(216, 94%, 70%); + background-color: hsl(216, 80%, 97%); +} + +#treegrid a:focus { + border-bottom: none; +} + +/* Hide collapsed rows */ +#treegrid tr.hidden { + display: none; +} + +/* Indents */ +#treegrid tr[aria-level="2"] > td:first-child { + padding-left: 2.5ch; +} +#treegrid tr[aria-level="3"] > td:first-child { + padding-left: 5ch; +} +#treegrid tr[aria-level="4"] > td:first-child { + padding-left: 7.5ch; +} +#treegrid tr[aria-level="5"] > td:first-child { + padding-left: 10ch; +} + + +/* Collapse/expand icons */ +#treegrid tr > td:first-child::before { + font-family: monospace; + content: " "; + display: inline-block; + width: 2ch; + height: 11px; + transition: transform .3s; + transform-origin: 5px 5px; +} + +#treegrid tr[aria-expanded] > td:first-child::before, +#treegrid td[aria-expanded]:first-child::before { + cursor: pointer; + /* Load both right away so there is no lag when we need the other */ + background-image: url("expand-icon.svg"), url("expand-icon-highlighted.svg"); + background-repeat: no-repeat; +} + +#treegrid tr[aria-expanded]:focus > td:first-child::before, +#treegrid tr[aria-expanded] > td:focus:first-child::before, +#treegrid tr:focus > td[aria-expanded]:first-child::before, +#treegrid tr > td[aria-expanded]:focus:first-child::before { + background-image: url("expand-icon-highlighted.svg"); +} + +#treegrid tr[aria-expanded="true"] > td:first-child::before, +#treegrid td[aria-expanded="true"]:first-child::before { + transform: rotate(90deg); +} diff --git a/examples/treegrid/js/treegrid-1.js b/examples/treegrid/js/treegrid-1.js new file mode 100644 index 0000000000..6519a779ac --- /dev/null +++ b/examples/treegrid/js/treegrid-1.js @@ -0,0 +1,518 @@ +/* exported TreeGrid */ +function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { + function initAttributes () { + // Make sure focusable elements are not in the tab order + // They will be added back in for the active row + setTabIndexOfFocusableElems(treegridElem, -1); + + // Add tabindex="0" to first row, "-1" to other rows + // We will use the roving tabindex method since aria-activedescendant + // does not work in IE + var rows = getAllRows(); + var index = rows.length; + var startRowIndex = doStartRowFocus ? 0 : -1; + + while (index--) { + if (doAllowRowFocus) { + rows[index].tabIndex = index === startRowIndex ? 0 : -1; + } + else { + setTabIndexForCellsInRow(rows[index], -1); + moveAriaExpandedToFirstCell(rows[index]); + } + } + + if (doStartRowFocus) { + return; + } + + // Start with cell focus + var firstCell = getNavigableCols(rows[0])[0]; + setTabIndexForCell(firstCell); + } + + function setTabIndexForCell (cell, tabIndex) { + var focusable = getFocusableElems(cell)[0] || cell; + focusable.tabIndex = tabIndex; + } + + function setTabIndexForCellsInRow (row, tabIndex) { + var cells = getNavigableCols(row); + var cellIndex = cells.length; + while (cellIndex--) { + setTabIndexForCell(cells[cellIndex], tabIndex); + } + } + + function getAllRows () { + var nodeList = treegridElem.querySelectorAll('tbody > tr'); + return Array.prototype.slice.call(nodeList); + } + + function getFocusableElems (root) { + // textarea not supported as a cell widget as it's multiple lines + // and needs up/down keys + // These should all be descendants of a cell + var nodeList = root.querySelectorAll('a,button,input,td>[tabindex]'); + return Array.prototype.slice.call(nodeList); + } + + function setTabIndexOfFocusableElems (root, tabIndex) { + var focusableElems = getFocusableElems(root); + var index = focusableElems.length; + while (index--) { + focusableElems[index].tabIndex = tabIndex; + } + } + + function getAllNavigableRows () { + var nodeList = treegridElem.querySelectorAll('tbody > tr:not([class~="hidden"])'); + // Convert to array so that we can use array methods on it + return Array.prototype.slice.call(nodeList); + } + + function getNavigableCols (currentRow) { + var nodeList = currentRow.getElementsByTagName('td'); + return Array.prototype.slice.call(nodeList); + } + + function restrictIndex (index, numItems) { + if (index < 0) { + return 0; + } + return index >= numItems ? index - 1 : index; + } + + function focus (elem) { + elem.tabIndex = 0; // Ensure focusable + elem.focus(); + } + + function focusCell (cell) { + // Check for focusable child such as link or textbox + // and use that if available + var focusableChildren = getFocusableElems(cell); + focus(focusableChildren[0] || cell); + } + + // Restore tabIndex to what it should be when focus switches from + // one treegrid item to another + function onFocusIn (event) { + var newTreeGridFocus = + event.target !== window && treegridElem.contains(event.target) && + event.target; + + // The last row we considered focused + var oldCurrentRow = enableTabbingInActiveRowDescendants.tabbingRow; + if (oldCurrentRow) { + enableTabbingInActiveRowDescendants(false, oldCurrentRow); + } + if (doAllowRowFocus && onFocusIn.prevTreeGridFocus && + onFocusIn.prevTreeGridFocus.localName === 'td') { + // Was focused on td, remove tabIndex so that it's not focused on click + onFocusIn.prevTreeGridFocus.removeAttribute('tabindex'); + } + + if (newTreeGridFocus) { + // Stayed in treegrid + if (oldCurrentRow) { + // There will be a different current row that will be + // the tabbable one + oldCurrentRow.tabIndex = -1; + } + + // The new row + var currentRow = getRowWithFocus(); + if (currentRow) { + currentRow.tabIndex = 0; + // Items within current row are also tabbable + enableTabbingInActiveRowDescendants(true, currentRow); + } + } + + onFocusIn.prevTreeGridFocus = newTreeGridFocus; + } + + // Set whether interactive elements within a row are tabbable + function enableTabbingInActiveRowDescendants (isTabbingOn, row) { + if (row) { + setTabIndexOfFocusableElems(row, isTabbingOn ? 0 : -1); + if (isTabbingOn) { + enableTabbingInActiveRowDescendants.tabbingRow = row; + } + else { + if (enableTabbingInActiveRowDescendants.tabbingRow === row) { + enableTabbingInActiveRowDescendants.tabbingRow = null; + } + } + } + } + + // The row with focus is the row that either has focus or an element + // inside of it has focus + function getRowWithFocus () { + return getContainingRow(document.activeElement); + } + + function getContainingRow (start) { + var possibleRow = start; + if (treegridElem.contains(possibleRow)) { + while (possibleRow !== treegridElem) { + if (possibleRow.localName === 'tr') { + return possibleRow; + } + possibleRow = possibleRow.parentElement; + } + } + } + + function isRowFocused () { + return getRowWithFocus() === document.activeElement; + } + + // Note: contenteditable not currently supported + function isEditableFocused () { + var focusedElem = document.activeElement; + return focusedElem.localName === 'input'; + } + + function getColWithFocus (currentRow) { + if (currentRow) { + var possibleCol = document.activeElement; + if (currentRow.contains(possibleCol)) { + while (possibleCol !== currentRow) { + if (possibleCol.localName === 'td') { + return possibleCol; + } + possibleCol = possibleCol.parentElement; + } + } + } + } + + function getLevel (row) { + return row && parseInt(row.getAttribute('aria-level')); + } + + // Move backwards (direction = -1) or forwards (direction = 1) + // If we also need to move down/up a level, requireLevelChange = true + // When + function moveByRow (direction, requireLevelChange) { + var currentRow = getRowWithFocus(); + var requiredLevel = requireLevelChange && currentRow && + getLevel(currentRow) + direction; + var rows = getAllNavigableRows(); + var numRows = rows.length; + var rowIndex = currentRow ? rows.indexOf(currentRow) : -1; + // When moving down a level, only allow moving to next row as the + // first child will never be farther than that + var maxDistance = requireLevelChange && direction === 1 ? 1 : NaN; + + // Move in direction until required level is found + do { + if (maxDistance-- === 0) { + return; // Failed to find required level, return without focus change + } + rowIndex = restrictIndex(rowIndex + direction, numRows); + } + while (requiredLevel && requiredLevel !== getLevel(rows[rowIndex])); + + if (!focusSameColInDifferentRow(currentRow, rows[rowIndex])) { + focus(rows[rowIndex]); + } + } + + function focusSameColInDifferentRow (fromRow, toRow) { + var currentCol = getColWithFocus(fromRow); + if (!currentCol) { + return; + } + + var fromCols = getNavigableCols(fromRow); + var currentColIndex = fromCols.indexOf(currentCol); + + if (currentColIndex < 0) { + return; + } + + var toCols = getNavigableCols(toRow); + // Focus the first focusable element inside the + focusCell(toCols[currentColIndex]); + return true; + } + + function moveToExtreme (direction) { + var currentRow = getRowWithFocus(); + if (!currentRow) { + return; + } + var currentCol = getColWithFocus(currentRow); + if (currentCol) { + moveToExtremeCol(direction, currentRow); + } + else { + // Move to first/last row + moveToExtremeRow(direction); + } + } + + function moveByCol (direction) { + var currentRow = getRowWithFocus(); + if (!currentRow) { + return; + } + var cols = getNavigableCols(currentRow); + var numCols = cols.length; + var currentCol = getColWithFocus(currentRow); + var currentColIndex = cols.indexOf(currentCol); + // First right arrow moves to first column + var newColIndex = (currentCol || direction < 0) ? currentColIndex + + direction : 0; + // Moving past beginning focuses row + if (doAllowRowFocus && newColIndex < 0) { + focus(currentRow); + return; + } + newColIndex = restrictIndex(newColIndex, numCols); + focusCell(cols[newColIndex]); + } + + function moveToExtremeCol (direction, currentRow) { + // Move to first/last col + var cols = getNavigableCols(currentRow); + var desiredColIndex = direction < 0 ? 0 : cols.length - 1; + focusCell(cols[desiredColIndex]); + } + + function moveToExtremeRow (direction) { + var rows = getAllNavigableRows(); + var newRow = rows[direction > 0 ? rows.length - 1 : 0]; + if (!focusSameColInDifferentRow(getRowWithFocus(), newRow)) { + focus(newRow); + } + } + + function doPrimaryAction () { + var currentRow = getRowWithFocus(); + if (!currentRow) { + return; + } + + // If row has focus, open message + if (currentRow === document.activeElement) { + alert('Message from ' + currentRow.children[2].innerText + ':\n\n' + + currentRow.children[1].innerText); + return; + } + + // If first col has focused, toggle expand/collapse + toggleExpanded(currentRow); + } + + function toggleExpanded (row) { + var cols = getNavigableCols(row); + var currentCol = getColWithFocus(row); + if (currentCol === cols[0] && isExpandable(row)) { + changeExpanded(!isExpanded(row), row); + } + } + + function changeExpanded (doExpand, row) { + var currentRow = row || getRowWithFocus(); + if (!currentRow) { + return; + } + var currentLevel = getLevel(currentRow); + var rows = getAllRows(); + var currentRowIndex = rows.indexOf(currentRow); + var didChange; + var doExpandLevel = []; + doExpandLevel[currentLevel + 1] = doExpand; + + while (++currentRowIndex < rows.length) { + var nextRow = rows[currentRowIndex]; + var rowLevel = getLevel(nextRow); + if (rowLevel <= currentLevel) { + break; // Next row is not a level down from current row + } + // Only expand the next level if this level is expanded + // and previous level is expanded + doExpandLevel[rowLevel + 1] = + doExpandLevel[rowLevel] && + isExpanded(nextRow); + var willHideRow = !doExpandLevel[rowLevel]; + var isRowHidden = nextRow.classList.contains('hidden'); + + if (willHideRow !== isRowHidden) { + if (willHideRow) { + nextRow.classList.add('hidden'); + } + else { + nextRow.classList.remove('hidden'); + } + didChange = true; + } + } + if (didChange) { + setAriaExpanded(currentRow, doExpand); + return true; + } + } + + // Mirror aria-expanded from the row to the first cell in that row + // (TBD is this a good idea? How else will screen reader user hear + // that the cell represents the opportunity to collapse/expand rows?) + function moveAriaExpandedToFirstCell (row) { + var expandedValue = row.getAttribute('aria-expanded'); + var firstCell = getNavigableCols(row)[0]; + if (expandedValue) { + firstCell.setAttribute('aria-expanded', expandedValue); + row.removeAttribute('aria-expanded'); + } + } + + function getAriaExpandedElem (row) { + return doAllowRowFocus ? row : getNavigableCols(row)[0]; + } + + function setAriaExpanded (row, doExpand) { + var elem = getAriaExpandedElem(row); + elem.setAttribute('aria-expanded', doExpand); + } + + function isExpandable (row) { + var elem = getAriaExpandedElem(row); + return elem.hasAttribute('aria-expanded'); + } + + function isExpanded (row) { + var elem = getAriaExpandedElem(row); + return elem.getAttribute('aria-expanded') === 'true'; + } + + function onKeyDown (event) { + var ENTER = 13; + var UP = 38; + var DOWN = 40; + var LEFT = 37; + var RIGHT = 39; + var HOME = 36; + var END = 35; + var CTRL_HOME = -HOME; + var CTRL_END = -END; + + var numModifiersPressed = event.ctrlKey + event.altKey + event.shiftKey + + event.metaKey; + + var key = event.keyCode; + + if (numModifiersPressed === 1 && event.ctrlKey) { + key = -key; // Treat as negative key value when ctrl pressed + } + else if (numModifiersPressed) { + return; + } + + switch (key) { + case DOWN: + moveByRow(1); + break; + case UP: + moveByRow(-1); + break; + case LEFT: + if (isEditableFocused()) { + return; // Leave key for editable area + } + if (isRowFocused()) { + changeExpanded(false) || moveByRow(-1, true); + } + else { + moveByCol(-1); + } + break; + case RIGHT: + if (isEditableFocused()) { + return; // Leave key for editable area + } + + // If row: try to expand + // If col or can't expand, move column to right + if (!isRowFocused() || !changeExpanded(true)) { + moveByCol(1); + } + break; + case CTRL_HOME: + moveToExtremeRow(-1); + break; + case HOME: + if (isEditableFocused()) { + return; // Leave key for editable area + } + moveToExtreme(-1); + break; + case CTRL_END: + moveToExtremeRow(1); + break; + case END: + if (isEditableFocused()) { + return; // Leave key for editable area + } + moveToExtreme(1); + break; + case ENTER: + doPrimaryAction(); + break; + default: + return; + } + + // Important: don't use key for anything else, such as scrolling + event.preventDefault(); + } + + // Toggle row expansion if the click is over the expando triangle + // Since the triangle is a pseudo element we can't bind an event listener + // to it. Another option is to have an actual element with role="presentation" + function onClick (event) { + var target = event.target; + if (target.localName !== 'td') { + return; + } + + var row = getContainingRow(event.target); + if (!isExpandable(row)) { + return; + } + + // Determine if mouse coordinate is just to the left of the start of text + var range = document.createRange(); + range.selectNodeContents(target.firstChild); + var left = range.getBoundingClientRect().left; + var EXPANDO_WIDTH = 20; + + if (event.clientX < left && event.clientX > left - EXPANDO_WIDTH) { + changeExpanded(!isExpanded(row), row); + } + } + + // Double click on row toggles expansion + function onDoubleClick (event) { + var row = getContainingRow(event.target); + if (row) { + if (isExpandable(row)) { + changeExpanded(!isExpanded(row), row); + } + event.preventDefault(); + } + } + + initAttributes(); + treegridElem.addEventListener('keydown', onKeyDown); + treegridElem.addEventListener('click', onClick); + treegridElem.addEventListener('dblclick', onDoubleClick); + // Polyfill for focusin necessary for Firefox < 52 + window.addEventListener(window.onfocusin ? 'focusin' : 'focus', + onFocusIn, true); +} + diff --git a/examples/treegrid/treegrid-1.html b/examples/treegrid/treegrid-1.html new file mode 100644 index 0000000000..5a7c20f333 --- /dev/null +++ b/examples/treegrid/treegrid-1.html @@ -0,0 +1,526 @@ + + + + + +Treegrid Email Inbox Example | WAI-ARIA Authoring Practices 1.1 + + + + + + + + + + + + + + + + +
+

Treegrid Email Inbox Example

+

+ NOTE: This example page is not yet complete. + Development of this example is tracked by + issue 132. +

+

+ The following example demonstrates how the + treegrid design pattern + can be used to make an interactive tree that enables users to both navigate the hierarchical structure of email conversations + and also navigate elements that describe each email, such as subject and sender. +

+

Similar examples include:

+ +

Example Useage Options

+

+ This example demonstrates three different ways of implementing the keyboard navigation specified in the treegrid pattern. + The following links change the behavior of the navigation keys : +

+ +

+ Note: A row-only option is not provided. + A treegrid where cells cannot be focused would be implemented as a tree view. + A treeview that has columns in its visual presentation may be appropriate when all the following conditions are present: +

+ +
+

Example

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SubjectSummaryEmail
Treegrids are awesomeWant to learn how to use them?aaron@thegoogle.rocks
re: Treegrids are awesomeI agree with you, they are the shizzlejoe@blahblahblah.blahblah
+
+ + + +
+ +
+

Keyboard Support

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyFunction
+ Right Arrow + +
    +
  • If a row is focused, and it is collapsed, expands the current row.
  • +
  • If a row is focused, and it is expanded, focuses the first cell in the row.
  • +
  • If a cell is focused, moves one cell to the right.
  • +
  • If focus is on the right most cell, focus does not move.
  • +
+
+ Left Arrow + +
    +
  • If a row is focused, and it is expanded, collapses the current row.
  • +
  • If a row is focused, and it is collapsed, moves to the parent row (if there is one).
  • +
  • If a cell in the first column is focused, focuses the row.
  • +
  • If a cell in a different column is focused, moves focus one cell to the left.
  • +
+
+ Down Arrow + +
    +
  • Moves focus one row or one cell down, depending on whether a row or cell is currently focused.
  • +
  • If focus is on the bottom row, focus does not move.
  • +
+
+ Up Arrow + +
    +
  • Moves focus one row or one cell up, depending on whether a row or cell is currently focused.
  • +
  • If focus is on the top row, focus does not move.
  • +
+
+ Tab + +
    +
  • Moves focus to the next interactive cell in the current row.
  • +
  • If there are no more interactive cells in the current row, + moves out of the treegrid.
  • +
+
+ Shift + Tab + +
    +
  • If a cell is focused, moves focus to the previous interactive cell in the current row.
  • +
  • If a row is focused, moves out of the treegrid.
  • +
+
+ Home + +
    +
  • If a row is focused, moves to the first row.
  • +
  • If a cell is focused, moves focus to the row's first cell.
  • +
+
+ End + +
    +
  • If a row is focused, moves to the last row.
  • +
  • If a cell is focused, moves focus to the row's last cell.
  • +
+
+ Control + Home + +
    +
  • Moves focus to the first row.
  • +
  • If a cell was focused, focus is kept in the same column, + otherwise the entire first row is focused.
  • +
+
+ Control + End + +
    +
  • Moves focus to the last row.
  • +
  • If a cell was focused, focus is kept in the same column, + otherwise the entire last row is focused.
  • +
+
+ Enter + +
    +
  • Performs default action on row or cell, e.g. opens message or navigate to link.
  • +
  • If focus is on the cell with the expand/collapse button, and there is no other action, + will toggle expansion of the current row.
  • +
+
+
+ +
+

Role, Property, State, and Tabindex Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
treegridtableIdentifies the element as a treegrid.
+ aria-label="Inbox"tableProvides an accessible name for the treegrid.
rowtr +
    +
  • Identifies the element as a row.
  • +
  • The row role is not an implicit semantic for the tr element when in a treegrid.
  • +
+
tabindex="-1"tr or td +
    +
  • Makes the element focusable without including it in the tab sequence of the page.
  • +
  • All row and gridcell elements are focusable, but only one is included in the tab sequence.
  • +
+
tabindex="0"tr or td +
    +
  • Includes the element in the tab sequence.
  • +
  • Only one row or gridcell in the treegrid has tabindex="0".
  • +
  • In this implementation, the first row in the treegrid is included in the tab sequence when the page loads.
  • +
  • + When the user moves focus in the treegrid, the element included in the tab sequence changes to the element with focus as described in the section on + roving tabindex. +
  • +
+
+ aria-expanded="false"tr or td +
    +
  • Applied only to parent row or first cell of parent row, i.e., next set of rows are responses to the message in this row
  • +
  • Indicates the parent row is closed, i.e., the descendant rows are not visible.
  • +
  • The visual indication of the collapsed state is synchronized by a CSS attribute selector.
  • +
  • When the treegrid is configured to support focus on rows aria-expanded is on the tr elements, but when the treegrid is configured to support focus on cells only, aria-expanded is on the first td element contained in each row.
  • +
+
+ aria-expanded="true"tr or td +
    +
  • Applied only to parent rows or the first cell in parent rows, i.e., next set of rows are responses to the message in this row
  • +
  • Indicates the parent row is open, i.e., the descendant rows are visible.
  • +
  • The visual indication of the open state is synchronized by a CSS attribute selector.
  • +
  • When the treegrid is configured to support focus on rows aria-expanded is on the tr elements, but when the treegrid is configured to support focus on cells only, aria-expanded is on the first td element contained in each row.
  • +
+
+ aria-level="number"tr +
    +
  • Defines the level of the row in the hierarchical treegrid structure.
  • +
  • Counting is one-based.
  • +
  • Root rows have aria-level=“1”.
  • +
+
+ aria-setsize="number"tr + Defines the number of rows in the set of rows that are in the same branch and at the same level within the hierarchy. +
+ aria-posinset="number"tr +
    +
  • Defines the position of the row within the set of other rows that are in the same branch and at the same level within the hierarchy.
  • +
  • Counting is one-based, not zero-based.
  • +
+
gridcelltd +
    +
  • Identifies the element as a gridcell.
  • +
  • The gridcell role is not an implicit semantic for the td element when in a treegrid.
  • +
+
+

+ NOTE: Due to an error in the ARIA 1.1 specification, the aria-posinset and aria-setsize properties are not supported on row elements. + This is being corrected in ARIA 1.2. + Consequently, when validating the HTML in this example, errors are generated. +

+
+ +
+

Javascript and CSS Source Code

+ + +
+ +
+

HTML Source Code

+ +
+ + + +
+
+ + +