Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

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