Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(select): Move focus handling to surface element for focus shade #1803

Merged
merged 2 commits into from
Dec 20, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 4 additions & 4 deletions demos/select.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@
<main>
<div class="mdc-toolbar-fixed-adjust"></div>
<section class="hero">
<div id="hero-js-select" class="mdc-select" role="listbox" tabindex="0">
<div class="mdc-select__surface">
<div id="hero-js-select" class="mdc-select" role="listbox">
<div class="mdc-select__surface" tabindex="0">
<div class="mdc-select__label">Pick a Food Group</div>
<div class="mdc-select__selected-text"></div>
<div class="mdc-select__bottom-line"></div>
Expand Down Expand Up @@ -99,8 +99,8 @@
<section class="example">
<h2 class="mdc-typography--title">Fully-Featured Component</h2>
<section id="box-demo-wrapper">
<div id="js-select-box" class="mdc-select" role="listbox" tabindex="0">
<div class="mdc-select__surface">
<div id="js-select-box" class="mdc-select" role="listbox">
<div class="mdc-select__surface" tabindex="0">
<div class="mdc-select__label">Food Group</div>
<div class="mdc-select__selected-text"></div>
<div class="mdc-select__bottom-line"></div>
Expand Down
4 changes: 2 additions & 2 deletions demos/theme/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,8 @@ <h2 class="mdc-typography--headline demo-component-section__heading">
<a href="#select" class="demo-component-section__permalink" title="Permalink to the theme demo for the select component">#</a>
</h2>

<div class="mdc-select" role="listbox" tabindex="0">
<div class="mdc-select__surface">
<div class="mdc-select" role="listbox">
<div class="mdc-select__surface" tabindex="0">
<div class="mdc-select__label">Pick a food group</div>
<div class="mdc-select__selected-text"></div>
<div class="mdc-select__bottom-line"></div>
Expand Down
32 changes: 16 additions & 16 deletions packages/mdc-select/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ npm install --save @material/select
### Using the full-fidelity JS component

```html
<div class="mdc-select" role="listbox" tabindex="0">
<div class="mdc-select__surface">
<div class="mdc-select" role="listbox">
<div class="mdc-select__surface" tabindex="0">
<div class="mdc-select__label">Pick a Food Group</div>
<div class="mdc-select__selected-text"></div>
<div class="mdc-select__bottom-line"></div>
Expand Down Expand Up @@ -98,8 +98,8 @@ style dependencies for both the mdc-list and mdc-menu for this component to func
#### Select with pre-selected option

```html
<div class="mdc-select" role="listbox" tabindex="0">
<div class="mdc-select__surface">
<div class="mdc-select" role="listbox">
<div class="mdc-select__surface" tabindex="0">
<div class="mdc-select__label">Pick a Food Group</div>
<div class="mdc-select__selected-text"></div>
<div class="mdc-select__bottom-line"></div>
Expand Down Expand Up @@ -132,8 +132,8 @@ style dependencies for both the mdc-list and mdc-menu for this component to func
#### Disabled select

```html
<div class="mdc-select" role="listbox" aria-disabled="true" tabindex="0">
<div class="mdc-select__surface">
<div class="mdc-select" role="listbox" aria-disabled="true">
<div class="mdc-select__surface" tabindex="-1">
<div class="mdc-select__label">Pick a Food Group</div>
<div class="mdc-select__selected-text"></div>
<div class="mdc-select__bottom-line"></div>
Expand Down Expand Up @@ -165,12 +165,12 @@ style dependencies for both the mdc-list and mdc-menu for this component to func

#### Disabled options

When used in components such as MDC Select, `mdc-list-item`'s can be disabled.
When used in components such as MDC Select, `mdc-list-item`s can be disabled.
To disable a list item, set `aria-disabled` to `"true"`, and set `tabindex` to `"-1"`.

```html
<div class="mdc-select" role="listbox" tabindex="0">
<div class="mdc-select__surface">
<div class="mdc-select" role="listbox">
<div class="mdc-select__surface" tabindex="0">
<div class="mdc-select__label">Pick a Food Group</div>
<div class="mdc-select__selected-text"></div>
<div class="mdc-select__bottom-line"></div>
Expand Down Expand Up @@ -358,11 +358,11 @@ within `componentDidUpdate`.
| `setAttr(attr: string, value: string) => void` | Sets attribute `attr` to value `value` on the root element. |
| `rmAttr(attr: string) => void` | Removes attribute `attr` from the root element. |
| `computeBoundingRect() => {left: number, top: number}` | Returns an object with a shape similar to a `ClientRect` object, with a `left` and `top` property specifying the element's position on the page relative to the viewport. The easiest way to achieve this is by calling `getBoundingClientRect()` on the surface element. |
| `registerInteractionHandler(type: string, handler: EventListener) => void` | Adds an event listener `handler` for event type `type` on the root element. |
| `deregisterInteractionHandler(type: string, handler: EventListener) => void` | Removes an event listener `handler` for event type `type` on the root element. |
| `focus() => void` | Focuses the root element |
| `makeTabbable() => void` | Allows the root element to be tab-focused via keyboard. We achieve this by setting the root element's `tabIndex` property to `0`. |
| `makeUntabbable() => void` | Disallows the root element to be tab-focused via keyboard. We achieve this by setting the root element's `tabIndex` property to `-1`. |
| `registerInteractionHandler(type: string, handler: EventListener) => void` | Adds an event listener `handler` for event type `type` on the surface element. |
| `deregisterInteractionHandler(type: string, handler: EventListener) => void` | Removes an event listener `handler` for event type `type` on the surface element. |
| `focus() => void` | Focuses the surface element |
| `makeTabbable() => void` | Allows the surface element to be tab-focused via keyboard. We achieve this by setting the surface element's `tabIndex` property to `0`. |
| `makeUntabbable() => void` | Disallows the surface element from being tab-focused via keyboard. We achieve this by setting the surface element's `tabIndex` property to `-1`. |
| `getComputedStyleValue(propertyName: string) => string` | Get the surface element's computed style value of the given dasherized css property `propertyName`. We achieve this via `getComputedStyle(...).getPropertyValue(propertyName). `|
| `setStyle(propertyName: string, value: string) => void` | Sets a dasherized css property `propertyName` to the value `value` on the surface element. We achieve this via `root.style.setProperty(propertyName, value)`. |
| `create2dRenderingContext() => {font: string, measureText: (string) => {width: number}}` | Returns an object which has the shape of a CanvasRenderingContext2d instance. Namely, it has a string property `font` which is writable, and a method `measureText` which given a string of text returns an object containing a `width` property specifying how wide that text should be rendered in the `font` specified by the font property. An easy way to achieve this is simply `document.createElement('canvas').getContext('2d');`. |
Expand Down Expand Up @@ -426,8 +426,8 @@ First, wrap both a custom select and a native select within a wrapper element, l
```html
<div class="select-manager">
<!-- Custom MDC Select, shown on desktop -->
<div class="mdc-select" role="listbox" tabindex="0">
<div class="mdc-select__surface">
<div class="mdc-select" role="listbox">
<div class="mdc-select__surface" tabindex="0">
<div class="mdc-select__label">Pick One</div>
<div class="mdc-select__selected-text"></div>
<div class="mdc-select__bottom-line"></div>
Expand Down
10 changes: 5 additions & 5 deletions packages/mdc-select/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ export class MDCSelect extends MDCComponent {
setAttr: (attr, value) => this.root_.setAttribute(attr, value),
rmAttr: (attr, value) => this.root_.removeAttribute(attr, value),
computeBoundingRect: () => this.surface_.getBoundingClientRect(),
registerInteractionHandler: (type, handler) => this.root_.addEventListener(type, handler),
deregisterInteractionHandler: (type, handler) => this.root_.removeEventListener(type, handler),
focus: () => this.root_.focus(),
registerInteractionHandler: (type, handler) => this.surface_.addEventListener(type, handler),
deregisterInteractionHandler: (type, handler) => this.surface_.removeEventListener(type, handler),
focus: () => this.surface_.focus(),
makeTabbable: () => {
this.root_.tabIndex = 0;
this.surface_.tabIndex = 0;
},
makeUntabbable: () => {
this.root_.tabIndex = -1;
this.surface_.tabIndex = -1;
},
getComputedStyleValue: (prop) => window.getComputedStyle(this.surface_).getPropertyValue(prop),
setStyle: (propertyName, value) => this.surface_.style.setProperty(propertyName, value),
Expand Down
29 changes: 14 additions & 15 deletions packages/mdc-select/mdc-select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,6 @@ $mdc-select-menu-transition: transform 180ms $mdc-animation-standard-curve-timin
background-position: left 10px center;
}

&:focus {
.mdc-select__bottom-line {
@include mdc-theme-prop(background-color, primary);

transform: scaleY(2);

&::after {
opacity: 1;
}
}
}

@include mdc-theme-dark(".mdc-select") {
@include mdc-select-dd-arrow-svg-bg_("fff", .54);

Expand Down Expand Up @@ -184,17 +172,28 @@ $mdc-select-menu-transition: transform 180ms $mdc-animation-standard-curve-timin
}
}

// Since the CSS only version's .mdc-select__surface element
// is an actual <select> element (and as such gets focus), this
// will only apply to CSS only selects
// JS-enhanced and CSS-only Selects require different selectors for focused bottom-line due to different DOM structure
&__surface:focus .mdc-select__bottom-line,
&__surface:focus ~ .mdc-select__bottom-line {
@include mdc-theme-prop(background-color, primary);

transform: scaleY(2);

&::after {
opacity: 1;
}
}
}

.mdc-select--open {
.mdc-select__surface::before {
opacity: map-get($mdc-ripple-dark-ink-opacities, "focus");

@include mdc-theme-dark(".mdc-select") {
opacity: map-get($mdc-ripple-light-ink-opacities, "focus");
}
}

.mdc-select__selected-text {
transform: translateY(8px);
transition:
Expand Down
42 changes: 20 additions & 22 deletions test/unit/mdc-select/mdc-select.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ class FakeMenu {

function getFixture() {
return bel`
<div class="mdc-select" role="listbox" tabindex="0">
<div class="mdc-select__surface">
<div class="mdc-select" role="listbox">
<div class="mdc-select__surface" tabindex="0">
<div class="mdc-select__label">Pick a Food Group</div>
<div class="mdc-select__selected-text"></div>
<div class="mdc-select__bottom-line"></div>
Expand Down Expand Up @@ -231,47 +231,45 @@ test('adapter#computeBoundingRect returns the result of getBoundingClientRect()
);
});

test('adapter#registerInteractionHandler adds an event listener to the root element', () => {
const {component, fixture} = setupTest();
test('adapter#registerInteractionHandler adds an event listener to the surface element', () => {
const {component, surface} = setupTest();
const listener = td.func('eventlistener');
component.getDefaultFoundation().adapter_.registerInteractionHandler('click', listener);
domEvents.emit(fixture, 'click');
domEvents.emit(surface, 'click');
td.verify(listener(td.matchers.anything()));
});

test('adapter#deregisterInteractionHandler removes an event listener from the root element', () => {
const {component, fixture} = setupTest();
test('adapter#deregisterInteractionHandler removes an event listener from the surface element', () => {
const {component, surface} = setupTest();
const listener = td.func('eventlistener');
fixture.addEventListener('click', listener);
surface.addEventListener('click', listener);
component.getDefaultFoundation().adapter_.deregisterInteractionHandler('click', listener);
domEvents.emit(fixture, 'click');
domEvents.emit(surface, 'click');
td.verify(listener(td.matchers.anything()), {times: 0});
});

test('adapter#focus focuses on the root element', () => {
const {component, fixture} = setupTest();
const handler = td.func('fixture focus handler');
fixture.addEventListener('focus', handler);
test('adapter#focus focuses on the surface element', () => {
const {component, fixture, surface} = setupTest();
document.body.appendChild(fixture);

component.getDefaultFoundation().adapter_.focus();
assert.equal(document.activeElement, fixture);
assert.equal(document.activeElement, surface);

document.body.removeChild(fixture);
});

test('adapter#makeTabbable sets the root element\'s tabindex to 0', () => {
const {component, fixture} = setupTest();
fixture.tabIndex = -1;
test('adapter#makeTabbable sets the surface element\'s tabindex to 0', () => {
const {component, surface} = setupTest();
surface.tabIndex = -1;
component.getDefaultFoundation().adapter_.makeTabbable();
assert.equal(fixture.tabIndex, 0);
assert.equal(surface.tabIndex, 0);
});

test('adapter#makeUntabbable sets the root element\'s tabindex to -1', () => {
const {component, fixture} = setupTest();
fixture.tabIndex = 0;
test('adapter#makeUntabbable sets the surface element\'s tabindex to -1', () => {
const {component, surface} = setupTest();
surface.tabIndex = 0;
component.getDefaultFoundation().adapter_.makeUntabbable();
assert.equal(fixture.tabIndex, -1);
assert.equal(surface.tabIndex, -1);
});

test('adapter#getComputedStyleValue gets the computed style value of the prop from the surface element', () => {
Expand Down