ARIA relationships via selector (Was: Agenda: March 24, 2014 WAI-PF ARIA Caucus)

On Mar 25, 2014, at 4:40 PM, Dominic Mazzoni <dmazzoni@google.com> wrote:

> On Mon, Mar 24, 2014 at 12:04 AM, James Craig <jcraig@apple.com> wrote:
>> Rich wrote:
>>> Dominic wants to form an effort to deal with ID references from DOM to WebComponent (http://www.w3.org/TR/components-intro/) subdom. How to handle (process-wise).   e.g.: You can't do use aria-activedescendant to reference an id in a descendant of a web component.
> 
> I don't necessarily want to form a new effort, but my question was what would be the best home for this. I can see this as something that could be solved in this group, with a bit of help from components, or it be solved by that group. My worry was about timeline - web components is already shipping, and I'm worried that without a fix we won't have a good way to solve a bunch of real accessibility problems we're encountering.
>  
>> I’ve been thinking about the (long-term) possibility of leveraging document.querySelector for this and other attributes that are currently IDREF-based. It would make a lot of things easier, but it would be more difficult to have a reflected content attribute, or to connect this in declarative markup.
>> 
>> You could still use IDREFs.
>> 
>>     tree.ariaActiveDescendant = tree.querySelector('#foo');
>> 
>> But it’d be possible to point to elements that did not have a defined ID.
>> 
>>     tree.ariaActiveDescendant = tree.querySelector('[aria-selected="true"]');
> 
> I hadn't thought about it this way. I was assuming that we'd have to solve this with declarative markup, so I was assuming we'd do something like <input aria-activedescendant="querySelector('[aria-selected="true"]')">, but we certainly don't have to. Considering that most of the cases where we'd need this are in dynamic content and not static, it's less important to expose ARIA in an attribute.

A declarative approach assumes you either want the querySelector context to be global (document.querySelector) or on the current element (this.querySelector), and I don't think we should assume that. Short of those two contexts, there'd be no way to keep or declare the scope consistently. For example, what if I want to set the relationship to the value of a property maintained by some instance of a view controller? aria-owns="this.viewController.someElement"? To do that, we'd either have to use the DOM nodes to hold application state, or resolve the execution scope some other way. I feel a new declarative syntax for computed values might be a step backwards, like specifying a new standard to rely on inline event handlers or eval().

I'd rather go all the way and just make these non-reflected properties, or make the reflection be that the content attributes are removed or otherwise unset when you assign a relationship. Developer tools are constantly improving, so I think it'd be okay for these to be missing from content attributes if they can be accessed as DOM attributes or methods.

>> I’m not sure we should open up a property on Element for every ARIA attribute though.
> 
> Why not?

If the concept of assigning relationships is shared between a variety of attributes (controls, owns, labelledby, describedby, flowto, etc.) then I'd prefer not to duplicate that interface in properties. A single method with an attribute argument would suffice. As with all topics, I'm definitely open to changing my opinion if there's a compelling reason for separate properties.

> I've heard this idea discussed before, and I'm not sure I understand the objections. For cleanliness of design, perhaps Element could inherit from a new AccessibleElement interface with ARIA attributes defined there - that way the Element idl can remain small.
> 
>> Maybe something more general like a setReference method:
>> 
>>     // Dialog is labelled by the first heading inside it plus the value of the global element with class username.
>>     dialog.setReference("aria-labelledby", [
>>         dialog.querySelector('*:role(heading)’),
>>         document.querySelector('.username’)
>>     ]);
> 
> How about setAccessibleRelation?

This concept shouldn't have to be limited to accessibility contexts. This could be useful for any attribute that accepts IDREF or IDREFS values.

Spit-balling:

myElement.setRelationship("aria-controls", [ document.QuerySelectorAll(".results"), myElement.querySelector(":role(region)") ]);
myElement.getRelationship("aria-controls"); // returns Element Collection

> How did you want to handle multiple relations, like if an element is labeled by two or more elements?

Both preceding examples use an array ([]) as the second argument. A single-item array would be equivalent to an IDREF value, and a multi-item array would be equivalent to an IDREFS value.

>> The main drawback I see is that there’d be no way to have a reflected DOM attribute, so it’d be a little harder for authors to see and debug. Dev tools could help with that, though.
>> 
>>         <dialog aria-labelledby="???”>…</dialog>
> 
> This would certainly work. I think I could live with not having the reflected DOM attribute if we reach consensus on this.
> 
> However, let's consider the alternative. Suppose we redefine the spec such that anywhere we have an IDREF in an attribute, you're allowed to specify some selector syntax. I'm proposing something like "querySelector(#foo)" or something shorter like "select(#foo)". In theory this could break backwards compatibility, but I think the odds that someone was using an id with the name "select(#foo)" are pretty slim.

I'm unconvinced that we can do this in a declarative fashion, but even if we can, I'd rather have a separate mutually exclusive attribute. e.g. An element would be allowed either @aria-owns or @aria-owns-selector, but not both. 

> The advantage of this approach is that the selector would update automatically, with no JavaScript required. For example, I could implement a listbox like this:
> 
> <div role="listbox" aria-activedescendant="select([tabindex="0"])">
>   <div role="option" tabindex=0>Apple</div>
>   <div role="option" tabindex=-1>Orange</div>
>   <div role="option" tabindex=-1>Banana</div>
> </div>

I don't see a lot of benefit to avoid JavaScript for the active descendant, since you're already forced to change the roaming tabindex with JavaScript. It's just one extra line in the JavaScript using a well-established convention that works in all browsers today. The declarative approach seems like a neat shorthand, but also like you're trying to avoid one line of JavaScript for not a lot of benefit.

> Now when the selected item changes, all I need to do is update the tabindex, and the activedescendant changes automatically. The browser would implement this using its existing style recalculation logic, which already knows when a document might have changed in such a way that a selector has changed.
> 
> We may have to add separate syntax for a query selector relative to the current element, vs relative to the document.
> 
>> I forgot to connect the dots to Web Components, but this could use the cat/hat selectors or author-exposed pseudos to interact with the shadow DOM.
> 
> Yes, that's exactly the problem we were facing and what I had in mind.
> 
> To bring everyone else into the loop, the problem we're facing is when you need to express an ARIA relationship between something in the embedding document and something inside the shadow dom of a web component. You can't use ids, because the shadow dom is scoped - but there is special querySelector syntax (cat/hat) that lets you "jump inside" shadow dom and reference elements that way.
> 
> I can present some specific examples of composite controls where we're hitting this problem.
> 
> - Dominic
> 

Received on Wednesday, 26 March 2014 07:32:33 UTC