Skip to content
This repository has been archived by the owner on Nov 6, 2019. It is now read-only.

Steps to adding accessibility to Phosphor #392

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
646204a
Adds the menubar role.
zorkow May 15, 2019
2f8511d
Adds initial set of aria roles to the menu.
zorkow May 15, 2019
bd603a6
Refactors Aria Attributes into separate type.
zorkow May 15, 2019
9138f6d
Adds Aria attributes to be treated special.
zorkow May 15, 2019
e3d046a
Adds roles and popup to all menu items. Corrects separator.
zorkow May 15, 2019
7ee92fb
Change to seperator role.
zorkow May 16, 2019
780f296
Add all ARIA attributes from the standard.
jasongrout May 16, 2019
94b7612
Add tab and tablist ARIA attributes for tabs.
jasongrout May 16, 2019
83cad0f
Add tab aria attributes in constructor.
jasongrout May 16, 2019
22ae364
Initial draft of adding tabpanel aria data for tabpanel and dockpanel.
jasongrout May 16, 2019
bc3c009
Add aria-label and aria-selected to tab bars.
jasongrout May 16, 2019
6b85946
Merge pull request #2 from jasongrout/aria_roles
jasongrout May 16, 2019
50ee3c3
Remove aria-controls.
jasongrout May 16, 2019
0721062
Add tab bar names, and default to “Activities <number>” for dockpanel.
jasongrout May 16, 2019
370ff66
Merge branch 'aria_roles' of github.com:diagram-codesprint/phosphor i…
jasongrout May 16, 2019
7f0cb21
Keep application-specific things out of phosphor.
jasongrout May 16, 2019
9761aee
Clean up tab panel adding widget ids and assuming tab bars are rendered.
jasongrout May 17, 2019
eb3b6a2
Add isToggleable command state
jasongrout May 17, 2019
fe9eabf
Fix typo
jasongrout May 17, 2019
953f2f0
Fix formatting and variable names.
jasongrout May 17, 2019
8abf54a
Revert changes to default tab renderer and tab type parameters.
jasongrout May 17, 2019
5a6b022
Add documentation for createTabKey.
jasongrout May 17, 2019
c6cae4a
Always set the aria attributes for a tab panel.
jasongrout May 17, 2019
fd4f2b1
Add two TODO notes about where the tab aria-selected state might need…
jasongrout May 17, 2019
025074d
Merge pull request #3 from diagram-codesprint/aria_roles
diagram-codesprint Jun 14, 2019
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
31 changes: 31 additions & 0 deletions packages/commands/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,21 @@ class CommandRegistry {
return cmd ? cmd.isToggled.call(undefined, args) : false;
}

/**
* Test whether a specific command is toggleable.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns A boolean indicating whether the command is toggleable,
* or `false` if the command is not registered.
*/
isToggleable(id: string, args: ReadonlyJSONObject = JSONExt.emptyObject): boolean {
let cmd = this._commands[id];
return cmd ? cmd.isToggleable : false;
}

/**
* Test whether a specific command is visible.
*
Expand Down Expand Up @@ -759,6 +774,20 @@ namespace CommandRegistry {
*/
isToggled?: CommandFunc<boolean>;

/**
* A function which indicates whether the command is toggleable.
*
* #### Notes
* Visual representations may use this value to display a toggled command in
* a different form, such as a check box for a menu item or a depressed
* state for a toggle button. This attribute also allows for accessible
* interfaces to notify the user that the command corresponds to some state.
*
* The default value is `true` if an `isToggled` function is given, `false`
* otherwise.
*/
isToggleable?: boolean;

/**
* A function which indicates whether the command is visible.
*
Expand Down Expand Up @@ -1147,6 +1176,7 @@ namespace Private {
readonly dataset: CommandFunc<Dataset>;
readonly isEnabled: CommandFunc<boolean>;
readonly isToggled: CommandFunc<boolean>;
readonly isToggleable: boolean;
readonly isVisible: CommandFunc<boolean>;
}

Expand All @@ -1167,6 +1197,7 @@ namespace Private {
dataset: asFunc(options.dataset, emptyDatasetFunc),
isEnabled: options.isEnabled || trueFunc,
isToggled: options.isToggled || falseFunc,
isToggleable: options.isToggleable || !!options.isToggled,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? It seems like it should only be !!options.isToggled and never use the options.isToggleable.

isVisible: options.isVisible || trueFunc
};
}
Expand Down
77 changes: 75 additions & 2 deletions packages/virtualdom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,66 @@ type ElementAttrNames = (
);


/**
* The names of ARIA attributes for HTML elements.
*
* The attribute names are collected from
* https://www.w3.org/TR/html5/infrastructure.html#element-attrdef-aria-role
*/
export
type ARIAAttrNames = (
'aria-activedescendant' |
'aria-atomic' |
'aria-autocomplete' |
'aria-busy' |
'aria-checked' |
'aria-colcount' |
'aria-colindex' |
'aria-colspan' |
'aria-controls' |
'aria-current' |
'aria-describedby' |
'aria-details' |
'aria-dialog' |
'aria-disabled' |
'aria-dropeffect' |
'aria-errormessage' |
'aria-expanded' |
'aria-flowto' |
'aria-grabbed' |
'aria-haspopup' |
'aria-hidden' |
'aria-invalid' |
'aria-keyshortcuts' |
'aria-label' |
'aria-labelledby' |
'aria-level' |
'aria-live' |
'aria-multiline' |
'aria-multiselectable' |
'aria-orientation' |
'aria-owns' |
'aria-placeholder' |
'aria-posinset' |
'aria-pressed' |
'aria-readonly' |
'aria-relevant' |
'aria-required' |
'aria-roledescription' |
'aria-rowcount' |
'aria-rowindex' |
'aria-rowspan' |
'aria-selected' |
'aria-setsize' |
'aria-sort' |
'aria-valuemax' |
'aria-valuemin' |
'aria-valuenow' |
'aria-valuetext' |
'role'
);


/**
* The names of the supported HTML5 CSS property names.
*
Expand Down Expand Up @@ -599,6 +659,18 @@ type ElementBaseAttrs = {
readonly [T in ElementAttrNames]?: string;
};

/**
* The ARIA attributes for a virtual element node.
*
* These are the attributes which are applied to a real DOM element via
* `element.setAttribute()`. The supported attribute names are defined
* by the `ARIAAttrNames` type.
*/
export
type ElementARIAAttrs = {
readonly [T in ARIAAttrNames]?: string;
};


/**
* The inline event listener attributes for a virtual element node.
Expand Down Expand Up @@ -655,12 +727,13 @@ type ElementSpecialAttrs = {
/**
* The full set of attributes supported by a virtual element node.
*
* This is the combination of the base element attributes, the inline
* element event listeners, and the special element attributes.
* This is the combination of the base element attributes, the ARIA attributes,
* the inline element event listeners, and the special element attributes.
*/
export
type ElementAttrs = (
ElementBaseAttrs &
ElementARIAAttrs &
ElementEventAttrs &
ElementSpecialAttrs
);
Expand Down
19 changes: 19 additions & 0 deletions packages/widgets/src/docklayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,8 @@ class DockLayout extends Layout {
return;
}

Private.removeAria(widget);

// If there are multiple tabs, just remove the widget's tab.
if (tabNode.tabBar.titles.length > 1) {
tabNode.tabBar.removeTab(widget.title);
Expand Down Expand Up @@ -764,6 +766,7 @@ class DockLayout extends Layout {
let tabNode = new Private.TabLayoutNode(this._createTabBar());
tabNode.tabBar.addTab(widget.title);
this._root = tabNode;
Private.addAria(widget, tabNode.tabBar);
return;
}

Expand All @@ -789,6 +792,7 @@ class DockLayout extends Layout {

// Insert the widget's tab relative to the target index.
refNode.tabBar.insertTab(index + (after ? 1 : 0), widget.title);
Private.addAria(widget, refNode.tabBar);
}

/**
Expand All @@ -809,6 +813,7 @@ class DockLayout extends Layout {
// Create the tab layout node to hold the widget.
let tabNode = new Private.TabLayoutNode(this._createTabBar());
tabNode.tabBar.addTab(widget.title);
Private.addAria(widget, tabNode.tabBar);

// Set the root if it does not exist.
if (!this._root) {
Expand Down Expand Up @@ -1976,6 +1981,19 @@ namespace Private {
}
}

export
async function addAria(widget: Widget, tabBar: TabBar<Widget>) {
let tabId = tabBar.renderer.createTabKey({title: widget.title, current: false, zIndex: 0});
widget.node.setAttribute('role', 'tabpanel');
widget.node.setAttribute('aria-labelledby', tabId);
}

export
async function removeAria(widget: Widget) {
widget.node.removeAttribute('role');
widget.node.removeAttribute('aria-labelledby');
}

/**
* Normalize a tab area config and collect the visited widgets.
*/
Expand Down Expand Up @@ -2065,6 +2083,7 @@ namespace Private {
each(config.widgets, widget => {
widget.hide();
tabBar.addTab(widget.title);
Private.addAria(widget, tabBar);
});

// Set the current index of the tab bar.
Expand Down
21 changes: 19 additions & 2 deletions packages/widgets/src/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
} from '@phosphor/signaling';

import {
ElementDataset, VirtualDOM, VirtualElement, h
ARIAAttrNames, ElementARIAAttrs, ElementDataset, VirtualDOM, VirtualElement, h
} from '@phosphor/virtualdom';

import {
Expand Down Expand Up @@ -1143,8 +1143,9 @@ namespace Menu {
renderItem(data: IRenderData): VirtualElement {
let className = this.createItemClass(data);
let dataset = this.createItemDataset(data);
let aria = this.createItemARIA(data);
return (
h.li({ className, dataset },
h.li({ className, dataset, ...aria },
this.renderIcon(data),
this.renderLabel(data),
this.renderShortcut(data),
Expand Down Expand Up @@ -1269,6 +1270,21 @@ namespace Menu {
return extra ? `${name} ${extra}` : name;
}

createItemARIA(data: IRenderData): ElementARIAAttrs {
let aria: {[T in ARIAAttrNames]?: string} = {};
switch (data.item.type) {
case 'separator':
aria.role = 'presentation';
break;
case 'submenu':
aria['aria-haspopup'] = 'true';
break;
default:
aria.role = 'menuitem';
}
return aria;
}

/**
* Create the render content for the label node.
*
Expand Down Expand Up @@ -1342,6 +1358,7 @@ namespace Private {
let node = document.createElement('div');
let content = document.createElement('ul');
content.className = 'p-Menu-content';
content.setAttribute('role', 'menu');
node.appendChild(content);
node.tabIndex = -1;
return node;
Expand Down
10 changes: 8 additions & 2 deletions packages/widgets/src/menubar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '@phosphor/messaging';

import {
ElementDataset, VirtualDOM, VirtualElement, h
ElementARIAAttrs, ElementDataset, VirtualDOM, VirtualElement, h
} from '@phosphor/virtualdom';

import {
Expand Down Expand Up @@ -747,8 +747,9 @@ namespace MenuBar {
renderItem(data: IRenderData): VirtualElement {
let className = this.createItemClass(data);
let dataset = this.createItemDataset(data);
let aria = this.createItemARIA(data);
return (
h.li({ className, dataset },
h.li({ className, dataset, ...aria},
this.renderIcon(data),
this.renderLabel(data)
)
Expand Down Expand Up @@ -808,6 +809,10 @@ namespace MenuBar {
return data.title.dataset;
}

createItemARIA(data: IRenderData): ElementARIAAttrs {
return {role: 'menuitem', 'aria-haspopup': 'true'};
}

/**
* Create the class name for the menu bar item icon.
*
Expand Down Expand Up @@ -870,6 +875,7 @@ namespace Private {
let node = document.createElement('div');
let content = document.createElement('ul');
content.className = 'p-MenuBar-content';
content.setAttribute('role', 'menubar');
node.appendChild(content);
node.tabIndex = -1;
return node;
Expand Down
Loading