From a7ba9d0b39dbe1eeb09730a7decde7bad1eba904 Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Mon, 13 Jun 2016 23:01:17 -0400 Subject: [PATCH 01/17] Initial mdInkRipple implementation. --- src/demo-app/demo-app/demo-app.html | 1 + src/demo-app/demo-app/demo-app.ts | 2 ++ src/demo-app/system-config.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/src/demo-app/demo-app/demo-app.html b/src/demo-app/demo-app/demo-app.html index b7ec903b17f0..72b04006b711 100644 --- a/src/demo-app/demo-app/demo-app.html +++ b/src/demo-app/demo-app/demo-app.html @@ -16,6 +16,7 @@ Progress Circle Progress Bar Radio + Ripple Sidenav Slide Toggle Toolbar diff --git a/src/demo-app/demo-app/demo-app.ts b/src/demo-app/demo-app/demo-app.ts index 50c07f4298c9..2131909f5db7 100644 --- a/src/demo-app/demo-app/demo-app.ts +++ b/src/demo-app/demo-app/demo-app.ts @@ -27,6 +27,7 @@ import {GesturesDemo} from '../gestures/gestures-demo'; import {GridListDemo} from '../grid-list/grid-list-demo'; import {TabsDemo} from '../tabs/tab-group-demo'; import {SlideToggleDemo} from '../slide-toggle/slide-toggle-demo'; +import {RippleDemo} from '../ripple/ripple-demo'; import {ButtonToggleDemo} from '../button-toggle/button-toggle-demo'; @Component({ @@ -75,6 +76,7 @@ export class Home {} new Route({path: '/gestures', component: GesturesDemo}), new Route({path: '/grid-list', component: GridListDemo}), new Route({path: '/tabs', component: TabsDemo}), + new Route({path: '/ripple', component: RippleDemo}), new Route({path: '/button-toggle', component: ButtonToggleDemo}), new Route({path: '/baseline', component: BaselineDemo}) diff --git a/src/demo-app/system-config.ts b/src/demo-app/system-config.ts index 8184dc7181e6..ca653f688aae 100644 --- a/src/demo-app/system-config.ts +++ b/src/demo-app/system-config.ts @@ -16,6 +16,7 @@ const components = [ 'progress-bar', 'progress-circle', 'radio', + 'ripple', 'sidenav', 'slide-toggle', 'button-toggle', From 551a99986d27a4f357bfd449019e3a0f98a82473 Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Mon, 13 Jun 2016 23:03:24 -0400 Subject: [PATCH 02/17] Add missing files. --- src/components/ripple/README.md | 25 +++ src/components/ripple/package.json | 26 +++ src/components/ripple/ripple.scss | 71 ++++++ src/components/ripple/ripple.spec.ts | 323 +++++++++++++++++++++++++++ src/components/ripple/ripple.ts | 288 ++++++++++++++++++++++++ src/demo-app/ripple/ripple-demo.html | 47 ++++ src/demo-app/ripple/ripple-demo.scss | 35 +++ src/demo-app/ripple/ripple-demo.ts | 47 ++++ 8 files changed, 862 insertions(+) create mode 100644 src/components/ripple/README.md create mode 100644 src/components/ripple/package.json create mode 100644 src/components/ripple/ripple.scss create mode 100644 src/components/ripple/ripple.spec.ts create mode 100644 src/components/ripple/ripple.ts create mode 100644 src/demo-app/ripple/ripple-demo.html create mode 100644 src/demo-app/ripple/ripple-demo.scss create mode 100644 src/demo-app/ripple/ripple-demo.ts diff --git a/src/components/ripple/README.md b/src/components/ripple/README.md new file mode 100644 index 000000000000..3c0bde16614b --- /dev/null +++ b/src/components/ripple/README.md @@ -0,0 +1,25 @@ +# md-ink-ripple + +`md-ink-ripple` defines an area in which a ripple animates, usually in response to user action. + +By default, an `md-ink-ripple` component is activated when its parent element receives mouse or touch events. On a mousedown or touch start, the ripple background fades in. When the click event complets, a circular foreground ripple fades in and expands from the event location to cover the component bounds. + +Ripples can also be triggered programatically by getting a reference to the MdInkRipple component and calling its `start` and `end` methods. + + +### Upcoming work + +Ripples will be added to `md-button`, `md-radio-button`, and `md-checkbox` components. + +### API Summary + +Properties: + +| Name | Type | Description | +| --- | --- | --- | +| `trigger` | Element | The DOM element that triggers the ripple when clicked. Defaults to the parent of the `md-ink-ripple`. +| `centered` | boolean | If true, the ripple animation originates from the center of the `md-ink-ripple` bounds rather than from the location of the click event. +| `focused` | boolean | If true, the background ripple is shown using the current theme's accent color to indicate focus. +| `color` | string | Custom color for foreground ripples +| `backgroundColor` | string | Custom color for the ripple background +| `disabled` | boolean | If true, click events on the trigger element will not activate ripples. The `start` and `end` methods can still be called to programatically create ripples. diff --git a/src/components/ripple/package.json b/src/components/ripple/package.json new file mode 100644 index 000000000000..1550ff4b08de --- /dev/null +++ b/src/components/ripple/package.json @@ -0,0 +1,26 @@ +{ + "name": "@angular2-material/ripple", + "version": "2.0.0-alpha.5-2", + "description": "Angular 2 Material ripple", + "main": "./ripple.js", + "typings": "./ripple.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/angular/material2.git" + }, + "keywords": [ + "angular", + "material", + "material design", + "components", + "ripple" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/material2/issues" + }, + "homepage": "https://github.com/angular/material2#readme", + "peerDependencies": { + "@angular2-material/core": "2.0.0-alpha.5-2" + } +} \ No newline at end of file diff --git a/src/components/ripple/ripple.scss b/src/components/ripple/ripple.scss new file mode 100644 index 000000000000..021d12c6f00b --- /dev/null +++ b/src/components/ripple/ripple.scss @@ -0,0 +1,71 @@ +@import "default-theme"; +@import "theme-functions"; + +$md-ink-ripple-focused-opacity: 0.1; +$md-ink-ripple-background-fade-duration: 0.3s; +$md-ink-ripple-background-default-color: rgba(34, 34, 34, 0.1); +$md-ink-ripple-foreground-initial-opacity: 0.25; +$md-ink-ripple-foreground-default-color: rgba(34, 34, 34, 0.1); + +/** + * An component should always have a position of "absolute" or "relative", + * so that the ripple divs it creates inside itself are correctly positioned. + */ +md-ink-ripple { + overflow: hidden; + // The default trigger for the ripple is the parent element, but we need to + // disable mouse events here to prevent the event from bubbling up to the + // parent. The can have larger bounds than its parent, and + // we don't want a click outside the parent to trigger the ripple. + pointer-events: none; +} + +/** + * Style that can be used by parent elements to specify that the + * should have the same bounds as its parent. + */ +md-ink-ripple.md-ripple-fit-parent { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} + +.md-ripple-background { + background-color: $md-ink-ripple-background-default-color; + opacity: 0; + transition: opacity $md-ink-ripple-background-fade-duration linear; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} + +.md-ripple-background.active { + opacity: 1; +} + +md-ink-ripple.focused .md-ripple-background { + background-color: md-color($md-accent, $md-ink-ripple-focused-opacity); + opacity: 1; +} + +.md-ripple-foreground { + background-color: $md-ink-ripple-foreground-default-color; + border-radius: 50%; + pointer-events: none; + opacity: $md-ink-ripple-foreground-initial-opacity; + position: absolute; + // The transition duration is manually set based on the ripple size. + transition: all 0s cubic-bezier(0, 0, 0.2, 1); +} + +.md-ripple-foreground.fade-in { + opacity: 1; +} + +.md-ripple-foreground.fade-out { + opacity: 0; +} diff --git a/src/components/ripple/ripple.spec.ts b/src/components/ripple/ripple.spec.ts new file mode 100644 index 000000000000..7439201d3d5b --- /dev/null +++ b/src/components/ripple/ripple.spec.ts @@ -0,0 +1,323 @@ +import { + describe, + it, + beforeEach, + inject, + async, + expect, +} from '@angular/core/testing'; +import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing'; +import {Component, ViewChild} from '@angular/core'; +import {MdInkRipple} from './ripple'; + +/** Creates a DOM event to indicate that a CSS transition for the given property ended. */ +const createTransitionEndEvent = (propertyName: string) => { + // The "new" TransitionEvent constructor isn't available in anything except Firefox: + // https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent + // So we just try to create a base event, and IE11 doesn't support that so we have to use + // the deprecated initTransitionEvent. + try { + const event = new Event('transitionend'); + (event).propertyName = propertyName; + return event; + } catch (e) { + const event = document.createEvent('TransitionEvent'); + event.initTransitionEvent('transitionend', + false, /* canBubble */ + false, /* cancelable */ + propertyName, + 0 /* elapsedTime */); + return event; + } +}; + +/** Creates a DOM mouse event. */ +const createMouseEvent = (eventType: string, dict: any = {}) => { + // Ideally this would just be "return new MouseEvent(eventType, dict)". But IE11 doesn't support + // the MouseEvent constructor, and Edge inexplicably divides clientX and clientY by 100 to get + // pageX and pageY. (Really. After "e = new MouseEvent('click', {clientX: 200, clientY: 300})", + // e.clientX is 200, e.pageX is 2, e.clientY is 300, and e.pageY is 3.) + // So instead we use the deprecated createEvent/initMouseEvent APIs, which works everywhere. + const event = document.createEvent('MouseEvents'); + event.initMouseEvent(eventType, + false, /* canBubble */ + false, /* cancelable */ + window, /* view */ + 0, /* detail */ + dict.screenX || 0, + dict.screenY || 0, + dict.clientX || 0, + dict.clientY || 0, + false, /* ctrlKey */ + false, /* altKey */ + false, /* shiftKey */ + false, /* metaKey */ + 0, /* button */ + null /* relatedTarget */); + return event; +}; + +/** Extracts the numeric value of a pixel size string like '123px'. */ +const pxStringToFloat = (s: string) => { + return parseFloat(s.replace('px', '')); +}; + +describe('MdInkRipple', () => { + let builder: TestComponentBuilder; + let fixture: ComponentFixture; + let container: HTMLElement; + let rippleElement: Element; + let rippleBackground: Element; + + beforeEach(() => { + // Clear body margin so it doesn't mess up position calculations. + document.body.style.margin = '0'; + }); + + beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + builder = tcb; + })); + + describe('basic ripple', () => { + beforeEach(async(() => { + builder.createAsync(BasicRippleContainer).then(f => { + fixture = f; + fixture.detectChanges(); + + container = fixture.debugElement.nativeElement.querySelector('#container'); + rippleElement = container.querySelector('md-ink-ripple'); + rippleBackground = rippleElement.querySelector('.md-ripple-background'); + expect(rippleBackground).toBeTruthy(); + }); + })); + + it('shows background when parent receives mousedown event', () => { + expect(rippleBackground.classList).not.toContain('active'); + const mouseDown = createMouseEvent('mousedown'); + // mousedown on the ripple element itself does nothing. + rippleElement.dispatchEvent(mouseDown); + expect(rippleBackground.classList).not.toContain('active'); + // mousedown on the container activates the background ripple. + container.dispatchEvent(mouseDown); + expect(rippleBackground.classList).toContain('active'); + // mouseleave on the container removes the background ripple. + const mouseLeave = createMouseEvent('mouseleave'); + container.dispatchEvent(mouseLeave); + expect(rippleBackground.classList).not.toContain('active'); + }); + + it('creates foreground ripples on click', () => { + container.click(); + expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); + // Second click should create another ripple. + container.click(); + const ripples = rippleElement.querySelectorAll('.md-ripple-foreground'); + expect(ripples.length).toBe(2); + expect(ripples[0].classList).toContain('fade-in'); + expect(ripples[1].classList).toContain('fade-in'); + // Signal the end of the first ripple's expansion. The second ripple should be unaffected. + const opacityTransitionEnd = createTransitionEndEvent('opacity'); + ripples[0].dispatchEvent(opacityTransitionEnd); + expect(ripples[0].classList).not.toContain('fade-in'); + expect(ripples[0].classList).toContain('fade-out'); + expect(ripples[1].classList).toContain('fade-in'); + expect(ripples[1].classList).not.toContain('fade-out'); + // Signal the end of the first ripple's fade out. The ripple should be removed from the DOM. + ripples[0].dispatchEvent(opacityTransitionEnd); + expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); + expect(rippleElement.querySelectorAll('.md-ripple-foreground')[0]).toBe(ripples[1]); + // Finish the second ripple. + ripples[1].dispatchEvent(opacityTransitionEnd); + expect(ripples[1].classList).not.toContain('fade-in'); + expect(ripples[1].classList).toContain('fade-out'); + ripples[1].dispatchEvent(opacityTransitionEnd); + expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(0); + }); + + it('creates ripples when manually triggered', () => { + const rippleComponent = fixture.debugElement.componentInstance.ripple; + // start() should show the background, but no foreground ripple yet. + rippleComponent.start(); + expect(rippleBackground.classList).toContain('active'); + expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(0); + // end() should deactivate the background and show the foreground ripple. + rippleComponent.end(0, 0); + expect(rippleBackground.classList).not.toContain('active'); + expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); + }); + + it('sizes ripple to cover element', () => { + // Click the container 50 px to the right and 75px down from its upper left. + const elementRect = container.getBoundingClientRect(); + const clickEvent = createMouseEvent('click', + {clientX: elementRect.left + 50, clientY: elementRect.top + 75}); + container.dispatchEvent(clickEvent); + // At this point the foreground ripple should be created with a div centered at the click + // location, and large enough to reach the furthest corner, which is 250px to the right + // and 125px down relative to the click position. + const expectedRadius = Math.sqrt(250 * 250 + 125 * 125); + const expectedLeft = elementRect.left + 50 - expectedRadius; + const expectedTop = elementRect.top + 75 - expectedRadius; + const ripple = rippleElement.querySelector('.md-ripple-foreground'); + // Note: getBoundingClientRect won't work because there's a transform applied to make the + // ripple start out tiny. + expect(pxStringToFloat(ripple.style.left)).toBeCloseTo(expectedLeft, 1); + expect(pxStringToFloat(ripple.style.top)).toBeCloseTo(expectedTop, 1); + expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * expectedRadius, 1); + expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * expectedRadius, 1); + }); + + it('expands ripple from center on click event triggered by keyboard', () => { + const elementRect = container.getBoundingClientRect(); + // Simulate a keyboard-triggered click by setting event coordinates to 0. + const clickEvent = createMouseEvent('click', + {clientX: 0, clientY: 0, screenX: 0, screenY: 0}); + container.dispatchEvent(clickEvent); + // The foreground ripple should be centered in the middle of the bounding rect, and large + // enough to reach the corners, which are all 150px horizontally and 100px vertically away. + const expectedRadius = Math.sqrt(150 * 150 + 100 * 100); + const expectedLeft = elementRect.left + (elementRect.width / 2) - expectedRadius; + const expectedTop = elementRect.top + (elementRect.height / 2) - expectedRadius; + const ripple = rippleElement.querySelector('.md-ripple-foreground'); + // Note: getBoundingClientRect won't work because there's a transform applied to make the + // ripple start out tiny. + expect(pxStringToFloat(ripple.style.left)).toBeCloseTo(expectedLeft, 1); + expect(pxStringToFloat(ripple.style.top)).toBeCloseTo(expectedTop, 1); + expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * expectedRadius, 1); + expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * expectedRadius, 1); + }); + }); + + describe('configuring behavior', () => { + let controller: RippleContainerWithInputBindings; + let rippleComponent: MdInkRipple; + + beforeEach(async(() => { + builder.createAsync(RippleContainerWithInputBindings).then(f => { + fixture = f; + fixture.detectChanges(); + + controller = fixture.debugElement.componentInstance; + rippleComponent = controller.ripple; + container = fixture.debugElement.nativeElement.querySelector('#container'); + rippleElement = container.querySelector('md-ink-ripple'); + rippleBackground = rippleElement.querySelector('.md-ripple-background'); + expect(rippleBackground).toBeTruthy(); + }); + })); + + it('sets ripple background color', () => { + // This depends on the exact color format that getComputedStyle returns; for example, alpha + // values are quantized to increments of 1/255, so 0.1 becomes 0.0980392. 0.2 is ok. + const color = 'rgba(22, 44, 66, 0.8)'; + controller.backgroundColor = color; + fixture.detectChanges(); + rippleComponent.start(); + expect(window.getComputedStyle(rippleBackground).backgroundColor).toBe(color); + }); + + it('sets ripple foreground color', () => { + const color = 'rgba(12, 34, 56, 0.8)'; + controller.color = color; + fixture.detectChanges(); + container.click(); + const ripple = rippleElement.querySelector('.md-ripple-foreground'); + expect(window.getComputedStyle(ripple).backgroundColor).toBe(color); + }); + + it('does not respond to events when disabled input is set', () => { + controller.disabled = true; + fixture.detectChanges(); + const mouseDown = createMouseEvent('mousedown'); + // The background ripple should not respond to mouseDown, and no foreground ripple should be + // created on a click. + container.dispatchEvent(mouseDown); + expect(rippleBackground.classList).not.toContain('active'); + container.click(); + expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(0); + // Calling start() and end() should still create a ripple. + rippleComponent.start(); + expect(rippleBackground.classList).toContain('active'); + rippleComponent.end(0, 0); + expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); + }); + + it('allows specifying custom trigger element', () => { + // Events on the other div don't do anything by default. + const alternateTrigger = + fixture.debugElement.nativeElement.querySelector('.alternateTrigger'); + const mouseDown = createMouseEvent('mousedown'); + alternateTrigger.dispatchEvent(mouseDown); + expect(rippleBackground.classList).not.toContain('active'); + alternateTrigger.click(); + expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(0); + + // Reassign the trigger element, and now events should create ripples. + controller.trigger = alternateTrigger; + fixture.detectChanges(); + alternateTrigger.dispatchEvent(mouseDown); + expect(rippleBackground.classList).toContain('active'); + alternateTrigger.click(); + expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); + }); + + it('expands ripple from center if centered input is set', () => { + controller.centered = true; + fixture.detectChanges(); + // Click the container 50 px to the right and 75px down from its upper left. + const elementRect = container.getBoundingClientRect(); + const clickEvent = createMouseEvent('click', + {clientX: elementRect.left + 50, clientY: elementRect.top + 75}); + container.dispatchEvent(clickEvent); + // Because the centered input is true, the center of the ripple should be the midpoint of the + // bounding rect. The ripple should expand to cover the rect corners, which are 150px + // horizontally and 100px vertically from the midpoint. + const expectedRadius = Math.sqrt(150 * 150 + 100 * 100); + const expectedLeft = elementRect.left + (elementRect.width / 2) - expectedRadius; + const expectedTop = elementRect.top + (elementRect.height / 2) - expectedRadius; + const ripple = rippleElement.querySelector('.md-ripple-foreground'); + + expect(pxStringToFloat(ripple.style.left)).toBeCloseTo(expectedLeft, 1); + expect(pxStringToFloat(ripple.style.top)).toBeCloseTo(expectedTop, 1); + expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * expectedRadius, 1); + expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * expectedRadius, 1); + }); + }); +}); + +@Component({ + directives: [MdInkRipple], + template: ` +
+ + +
+ `, +}) +class BasicRippleContainer { + @ViewChild(MdInkRipple) ripple: MdInkRipple; +} + +@Component({ + directives: [MdInkRipple], + template: ` +
+ +
+
+ `, +}) +class RippleContainerWithInputBindings { + trigger: HTMLElement = null; + centered = false; + disabled = false; + color = ''; + backgroundColor = ''; + @ViewChild(MdInkRipple) ripple: MdInkRipple; +} diff --git a/src/components/ripple/ripple.ts b/src/components/ripple/ripple.ts new file mode 100644 index 000000000000..f3c698de8f61 --- /dev/null +++ b/src/components/ripple/ripple.ts @@ -0,0 +1,288 @@ +import { + ChangeDetectionStrategy, + Component, + ElementRef, + HostBinding, + Input, + OnChanges, + OnDestroy, + OnInit, + Renderer, + SimpleChange, + ViewEncapsulation, +} from '@angular/core'; + +enum RippleState { + NEW, + EXPANDING, + FADING_OUT, +} + +class Ripple { + state = RippleState.NEW; + constructor(public element: Element) {} +} + +const RIPPLE_SPEED_PX_PER_SECOND = 500; +const MIN_RIPPLE_FILL_TIME_SECONDS = 0.2; +const MAX_RIPPLE_FILL_TIME_SECONDS = 0.6; + +const sqr = (x: number) => x * x; + +const distanceToFurthestCorner = (x: number, y: number, parentRect: any) => { + const x1 = parentRect.left; + const x2 = x1 + parentRect.width; + const y1 = parentRect.top; + const y2 = y1 + parentRect.height; + const maxSquaredDistance = Math.max( + sqr(x - x1) + sqr(y - y1), + sqr(x2 - x) + sqr(y - y1), + sqr(x - x1) + sqr(y2 - y), + sqr(x2 - x) + sqr(y2 - y)); + return Math.sqrt(maxSquaredDistance); +}; + + +@Component({ + moduleId: module.id, + template: `
`, + selector: 'md-ink-ripple', + styleUrls: ['ripple.css'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MdInkRipple implements OnInit, OnDestroy, OnChanges { + /** + * The element that triggers the ripple when click events are received. Defaults to the parent + * of the . + */ + @Input('trigger') _trigger: Element; + /** + * If true, the ripple always originates from the center of the bounds rather + * than originating from the location of the click event. + */ + @Input('centered') _centered: boolean; + /** + * If true, click events will not trigger the ripple. It can still be triggered by manually + * calling start() and end(). + */ + @Input('disabled') _disabled: boolean; + /** + * Custom color for ripples. + */ + @Input('color') _color: string; + /** + * Custom color for the ripple background. + */ + @Input('backgroundColor') _backgroundColor: string; + /** + * If set, the normal duration of ripple animations is divided by this value. For example, + * setting it to 0.5 will cause the animations to take twice as long. + */ + @Input('speedFactor') _speedFactor: number = 1; + + /** + * If true, the ripple background will be highlighted to indicated a focused state. + */ + @HostBinding('class.focused') @Input('focused') _focused: boolean; + + private _element: Element; + private _triggerElement: Element; + private _mouseDownHandler = (event: MouseEvent) => this.mouseDown(event); + private _clickHandler = (event: MouseEvent) => this.click(event); + private _mouseLeaveHandler = (event: MouseEvent) => this.mouseLeave(event); + + private _rippleContainer: Element; + private _rippleBackground: Element; + private _rippleManager: MdInkRippleManager; + + constructor( + _elementRef: ElementRef, + _renderer: Renderer) { + this._element = _elementRef.nativeElement; + this._rippleManager = new MdInkRippleManager(_renderer); + } + + /** TODO: internal */ + ngOnInit() { + this._rippleBackground = this._element.querySelector('.md-ripple-background'); + // If no trigger element was explicity set, use our parent. + if (!this._triggerElement) { + this._updateTriggerElement(this._element.parentElement); + } + } + + /** TODO: internal */ + ngOnDestroy() { + // Remove event listeners on the trigger element. + this._updateTriggerElement(null); + } + + /** TODO: internal */ + ngOnChanges(changes: { [propertyName: string]: SimpleChange }) { + // If the trigger element changed (or is being initially set), add event listeners to it. + const changedInputs = Object.keys(changes); + if (changedInputs.indexOf('_trigger') !== -1) { + const newTrigger = this._trigger || this._element.parentElement; + this._updateTriggerElement(newTrigger); + } + } + + private _updateTriggerElement(newTrigger: Element) { + if (this._triggerElement !== newTrigger) { + if (this._triggerElement) { + this._triggerElement.removeEventListener('mousedown', this._mouseDownHandler); + this._triggerElement.removeEventListener('click', this._clickHandler); + this._triggerElement.removeEventListener('mouseleave', this._mouseLeaveHandler); + } + this._triggerElement = newTrigger; + if (this._triggerElement) { + this._triggerElement.addEventListener('mousedown', this._mouseDownHandler); + this._triggerElement.addEventListener('click', this._clickHandler); + this._triggerElement.addEventListener('mouseleave', this._mouseLeaveHandler); + } + } + } + + /** + * Responds to the start of a ripple animation trigger by fading the background in. + */ + start() { + this._rippleManager.showRippleBackground(this._rippleBackground, this._backgroundColor); + } + + /** + * Responds to the end of a ripple animation trigger by fading the background out, and creating a + * foreground ripple that expands from the event location (or from the center of the element if + * the "centered" property is set or forceCenter is true). + */ + end(left: number, top: number, forceCenter = true) { + this._rippleManager.createForegroundRipple( + this._element, + left, + top, + this._color, + this._centered || forceCenter, + this._speedFactor, + (ripple: Ripple, event: TransitionEvent) => this._rippleTransitionEnded(ripple, event)); + // Fade out the highlighted background. + this._rippleManager.hideRippleBackground(this._rippleBackground); + } + + private _rippleTransitionEnded(ripple: Ripple, event: TransitionEvent) { + if (event.propertyName === 'opacity') { + // If the ripple finished expanding, start fading it out. If it finished fading out, + // remove it from the DOM. + switch (ripple.state) { + case RippleState.EXPANDING: + this._rippleManager.fadeOutForegroundRipple(ripple.element); + ripple.state = RippleState.FADING_OUT; + break; + case RippleState.FADING_OUT: + this._rippleManager.removeRippleFromDom(ripple.element); + break; + } + } + } + + mouseDown(event: MouseEvent) { + if (!this._disabled && event.button === 0) { + this.start(); + } + } + + click(event: MouseEvent) { + if (!this._disabled && event.button === 0) { + // If screen and page positions are all 0, this was probably triggered by a keypress. + // In that case, use the center of the bounding rect as the ripple origin. + // FIXME: This fails on IE11, which still sets pageX/Y and screenX/Y on keyboard clicks. + const isKeyEvent = + (event.screenX === 0 && event.screenY === 0 && event.pageX === 0 && event.pageY === 0); + this.end(event.pageX, event.pageY, isKeyEvent); + } + } + + mouseLeave(event: MouseEvent) { + // We can always fade out the background here; It's a no-op if it was already inactive. + this._rippleManager.hideRippleBackground(this._rippleBackground); + } + + // TODO: Reactivate the background div if the user drags out and back in. +} + +/** + * Helper service that performs DOM manipulations. Not intended to be used outside this module. + */ +class MdInkRippleManager { + constructor(private _renderer: Renderer) {} + + createForegroundRipple( + container: Element, + rippleOriginLeft: number, + rippleOriginTop: number, + color: string, + centered: boolean, + speedFactor: number, + transitionEndCallback: (r: Ripple, e: TransitionEvent) => void) { + const parentRect = container.getBoundingClientRect(); + // Create a foreground ripple div with the size and position of the fully expanded ripple. + // When the div is created, it's given a transform style that causes the ripple to be displayed + // small and centered on the event location (or the center of the bounding rect if the centered + // input is true). Removing that transform causes the ripple to animate to its natural size. + const startX = centered ? (parentRect.left + parentRect.width / 2) : rippleOriginLeft; + const startY = centered ? (parentRect.top + parentRect.height / 2) : rippleOriginTop; + const offsetX = startX - parentRect.left; + const offsetY = startY - parentRect.top; + const maxRadius = distanceToFurthestCorner(startX, startY, parentRect); + + const rippleDiv = this._renderer.createElement(container, 'div', null); + this._renderer.setElementClass(rippleDiv, 'md-ripple-foreground', true); + this._renderer.setElementStyle(rippleDiv, 'left', (offsetX - maxRadius) + 'px'); + this._renderer.setElementStyle(rippleDiv, 'top', (offsetY - maxRadius) + 'px'); + this._renderer.setElementStyle(rippleDiv, 'width', (2 * maxRadius) + 'px'); + this._renderer.setElementStyle(rippleDiv, 'height', (2 * maxRadius) + 'px'); + // If color input is not set, this will default to the background color defined in CSS. + this._renderer.setElementStyle(rippleDiv, 'background-color', color); + + const translateX = offsetX - parentRect.width / 2; + const translateY = offsetY - parentRect.height / 2; + this._renderer.setElementStyle(rippleDiv, + 'transform', `scale(0.01) translate(${translateX}px, ${translateY}px)`); + + const fadeInSeconds = (1 / (speedFactor || 1)) * Math.max( + MIN_RIPPLE_FILL_TIME_SECONDS, + Math.min(MAX_RIPPLE_FILL_TIME_SECONDS, maxRadius / RIPPLE_SPEED_PX_PER_SECOND)); + this._renderer.setElementStyle(rippleDiv, 'transition-duration', `${fadeInSeconds}s`); + + // https://timtaubert.de/blog/2012/09/css-transitions-for-dynamically-created-dom-elements/ + window.getComputedStyle(rippleDiv).opacity; + + this._renderer.setElementClass(rippleDiv, 'fade-in', true); + // Clearing the transform property causes the ripple to animate to its full size. + this._renderer.setElementStyle(rippleDiv, 'transform', ''); + const ripple = new Ripple(rippleDiv); + ripple.state = RippleState.EXPANDING; + + this._renderer.listen(rippleDiv, 'transitionend', + (event: TransitionEvent) => transitionEndCallback(ripple, event)); + } + + fadeOutForegroundRipple(ripple: Element) { + this._renderer.setElementClass(ripple, 'fade-in', false); + this._renderer.setElementClass(ripple, 'fade-out', true); + } + + removeRippleFromDom(ripple: Element) { + ripple.parentElement.removeChild(ripple); + } + + showRippleBackground(rippleBackground: Element, color: string) { + this._renderer.setElementClass(rippleBackground, 'active', true); + // If color is not set, this will default to the background color defined in CSS. + this._renderer.setElementStyle(rippleBackground, 'background-color', color); + } + + hideRippleBackground(rippleBackground: Element) { + this._renderer.setElementClass(rippleBackground, 'active', false); + } +} diff --git a/src/demo-app/ripple/ripple-demo.html b/src/demo-app/ripple/ripple-demo.html new file mode 100644 index 000000000000..8c1ee5edd72d --- /dev/null +++ b/src/demo-app/ripple/ripple-demo.html @@ -0,0 +1,47 @@ + +
+
+ + + + +
+ +
+
Centered
+
Disabled
+
Rounded container (flaky in Firefox)
+
+ +
+ Speed + + Slow + Normal + Fast + +
+
+ + +
+
+
+ + Click me +
+
+ {{centered.checked}} {{rounded.checked}} +
diff --git a/src/demo-app/ripple/ripple-demo.scss b/src/demo-app/ripple/ripple-demo.scss new file mode 100644 index 000000000000..5fe5da3f21e3 --- /dev/null +++ b/src/demo-app/ripple/ripple-demo.scss @@ -0,0 +1,35 @@ +.demo-ripple { + button, a { + margin: 8px; + text-transform: uppercase; + } + + .manual-ripple-container { + box-shadow: rgba(0, 0, 0, 0.118) 0 10px 15px, rgba(0, 0, 0, 0.239) 0 5px 10px; + cursor: pointer; + font-size: 24px; + height: 150px; + line-height: 150px; + position: relative; + text-align: center; + transition: all 0.2s linear; + width: 200px; + user-select: none; + + &.disabled { + color: rgba(32, 32, 32, 0.4); + } + + &.rounded { + border-radius: 50%; + overflow: hidden; + // z-index needed to make clipping to border-radius work correctly. + // http://stackoverflow.com/questions/20001515/chrome-bug-border-radius-not-clipping-contents-when-combined-with-css-transiti + z-index: 1; + } + } + + .checkbox-option { + margin: 10px 0; + } +} \ No newline at end of file diff --git a/src/demo-app/ripple/ripple-demo.ts b/src/demo-app/ripple/ripple-demo.ts new file mode 100644 index 000000000000..7b1e7b2f36f4 --- /dev/null +++ b/src/demo-app/ripple/ripple-demo.ts @@ -0,0 +1,47 @@ +import { + Component, + ViewChild, +} from '@angular/core'; +import {MdButton} from '@angular2-material/button/button'; +import {MdCard} from '@angular2-material/card/card'; +import {MdCheckbox} from '@angular2-material/checkbox/checkbox'; +import {MdIcon} from '@angular2-material/icon/icon'; +import {MdInkRipple} from '@angular2-material/ripple/ripple'; +import {MdInput} from '@angular2-material/input/input'; +import {MdRadioButton, MdRadioGroup} from '@angular2-material/radio/radio'; +import { + MdUniqueSelectionDispatcher +} from '@angular2-material/core/coordination/unique-selection-dispatcher'; + +@Component({ + moduleId: module.id, + selector: 'ripple-demo', + templateUrl: 'ripple-demo.html', + styleUrls: ['ripple-demo.css'], + providers: [MdUniqueSelectionDispatcher], + directives: [ + MdButton, MdCard, MdCheckbox, MdIcon, MdInput, MdRadioButton, MdRadioGroup, MdInkRipple, + ], +}) +export class RippleDemo { + @ViewChild('manualRipple') manualRipple: MdInkRipple; + + centered: boolean = false; + disabled: boolean = false; + rounded: boolean = false; + rippleSpeed: number = 1; + rippleColor: string = ''; + rippleBackgroundColor: string = ''; + + doManualRipple() { + if (this.manualRipple) { + window.setTimeout(() => this.manualRipple.start(), 10); + window.setTimeout(() => this.manualRipple.end(0, 0), 500); + } + } + + wtf(arg: any): any { + console.log(arg); + return ''; + } +} From b2f45b49b0bf550bc73b36aed0041618681b1597 Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Mon, 13 Jun 2016 23:08:30 -0400 Subject: [PATCH 03/17] Remove unused code. --- src/demo-app/ripple/ripple-demo.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/demo-app/ripple/ripple-demo.ts b/src/demo-app/ripple/ripple-demo.ts index 7b1e7b2f36f4..2e2257d5de3c 100644 --- a/src/demo-app/ripple/ripple-demo.ts +++ b/src/demo-app/ripple/ripple-demo.ts @@ -39,9 +39,4 @@ export class RippleDemo { window.setTimeout(() => this.manualRipple.end(0, 0), 500); } } - - wtf(arg: any): any { - console.log(arg); - return ''; - } } From dad7a4451c98ffe9b149932bf78801cf56094fad Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Mon, 13 Jun 2016 23:24:47 -0400 Subject: [PATCH 04/17] Fix stylelint errors. --- src/components/ripple/ripple.scss | 8 ++++---- src/demo-app/ripple/ripple-demo.scss | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ripple/ripple.scss b/src/components/ripple/ripple.scss index 021d12c6f00b..9adb170d7c12 100644 --- a/src/components/ripple/ripple.scss +++ b/src/components/ripple/ripple.scss @@ -1,8 +1,8 @@ -@import "default-theme"; -@import "theme-functions"; +@import 'default-theme'; +@import 'theme-functions'; $md-ink-ripple-focused-opacity: 0.1; -$md-ink-ripple-background-fade-duration: 0.3s; +$md-ink-ripple-background-fade-duration: 300ms; $md-ink-ripple-background-default-color: rgba(34, 34, 34, 0.1); $md-ink-ripple-foreground-initial-opacity: 0.25; $md-ink-ripple-foreground-default-color: rgba(34, 34, 34, 0.1); @@ -59,7 +59,7 @@ md-ink-ripple.focused .md-ripple-background { opacity: $md-ink-ripple-foreground-initial-opacity; position: absolute; // The transition duration is manually set based on the ripple size. - transition: all 0s cubic-bezier(0, 0, 0.2, 1); + transition: all 0ms cubic-bezier(0, 0, 0.2, 1); } .md-ripple-foreground.fade-in { diff --git a/src/demo-app/ripple/ripple-demo.scss b/src/demo-app/ripple/ripple-demo.scss index 5fe5da3f21e3..c8f0928df804 100644 --- a/src/demo-app/ripple/ripple-demo.scss +++ b/src/demo-app/ripple/ripple-demo.scss @@ -12,7 +12,7 @@ line-height: 150px; position: relative; text-align: center; - transition: all 0.2s linear; + transition: all 200ms linear; width: 200px; user-select: none; From 7f720ccc1fcd451a7068857a338f742547fe3993 Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Wed, 15 Jun 2016 19:07:45 -0400 Subject: [PATCH 05/17] In-progress updates for PR comments. --- src/components/ripple/ripple.scss | 10 ++--- src/components/ripple/ripple.spec.ts | 36 +++++++-------- src/components/ripple/ripple.ts | 66 ++++++++++++++-------------- src/demo-app/ripple/ripple-demo.html | 18 ++++---- src/demo-app/ripple/ripple-demo.scss | 8 ++-- 5 files changed, 70 insertions(+), 68 deletions(-) diff --git a/src/components/ripple/ripple.scss b/src/components/ripple/ripple.scss index 9adb170d7c12..ec97d6d71f9a 100644 --- a/src/components/ripple/ripple.scss +++ b/src/components/ripple/ripple.scss @@ -43,11 +43,11 @@ md-ink-ripple.md-ripple-fit-parent { bottom: 0; } -.md-ripple-background.active { +.md-ripple-background.md-ripple-active { opacity: 1; } -md-ink-ripple.focused .md-ripple-background { +md-ink-ripple.md-ripple-focused .md-ripple-background { background-color: md-color($md-accent, $md-ink-ripple-focused-opacity); opacity: 1; } @@ -59,13 +59,13 @@ md-ink-ripple.focused .md-ripple-background { opacity: $md-ink-ripple-foreground-initial-opacity; position: absolute; // The transition duration is manually set based on the ripple size. - transition: all 0ms cubic-bezier(0, 0, 0.2, 1); + transition: opacity,transform 0ms cubic-bezier(0, 0, 0.2, 1); } -.md-ripple-foreground.fade-in { +.md-ripple-foreground.md-ripple-fade-in { opacity: 1; } -.md-ripple-foreground.fade-out { +.md-ripple-foreground.md-ripple-fade-out { opacity: 0; } diff --git a/src/components/ripple/ripple.spec.ts b/src/components/ripple/ripple.spec.ts index 7439201d3d5b..34279d537105 100644 --- a/src/components/ripple/ripple.spec.ts +++ b/src/components/ripple/ripple.spec.ts @@ -92,18 +92,18 @@ describe('MdInkRipple', () => { })); it('shows background when parent receives mousedown event', () => { - expect(rippleBackground.classList).not.toContain('active'); + expect(rippleBackground.classList).not.toContain('md-ripple-active'); const mouseDown = createMouseEvent('mousedown'); // mousedown on the ripple element itself does nothing. rippleElement.dispatchEvent(mouseDown); - expect(rippleBackground.classList).not.toContain('active'); + expect(rippleBackground.classList).not.toContain('md-ripple-active'); // mousedown on the container activates the background ripple. container.dispatchEvent(mouseDown); - expect(rippleBackground.classList).toContain('active'); + expect(rippleBackground.classList).toContain('md-ripple-active'); // mouseleave on the container removes the background ripple. const mouseLeave = createMouseEvent('mouseleave'); container.dispatchEvent(mouseLeave); - expect(rippleBackground.classList).not.toContain('active'); + expect(rippleBackground.classList).not.toContain('md-ripple-active'); }); it('creates foreground ripples on click', () => { @@ -113,23 +113,23 @@ describe('MdInkRipple', () => { container.click(); const ripples = rippleElement.querySelectorAll('.md-ripple-foreground'); expect(ripples.length).toBe(2); - expect(ripples[0].classList).toContain('fade-in'); - expect(ripples[1].classList).toContain('fade-in'); + expect(ripples[0].classList).toContain('md-ripple-fade-in'); + expect(ripples[1].classList).toContain('md-ripple-fade-in'); // Signal the end of the first ripple's expansion. The second ripple should be unaffected. const opacityTransitionEnd = createTransitionEndEvent('opacity'); ripples[0].dispatchEvent(opacityTransitionEnd); - expect(ripples[0].classList).not.toContain('fade-in'); - expect(ripples[0].classList).toContain('fade-out'); - expect(ripples[1].classList).toContain('fade-in'); - expect(ripples[1].classList).not.toContain('fade-out'); + expect(ripples[0].classList).not.toContain('md-ripple-fade-in'); + expect(ripples[0].classList).toContain('md-ripple-fade-out'); + expect(ripples[1].classList).toContain('md-ripple-fade-in'); + expect(ripples[1].classList).not.toContain('md-ripple-fade-out'); // Signal the end of the first ripple's fade out. The ripple should be removed from the DOM. ripples[0].dispatchEvent(opacityTransitionEnd); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); expect(rippleElement.querySelectorAll('.md-ripple-foreground')[0]).toBe(ripples[1]); // Finish the second ripple. ripples[1].dispatchEvent(opacityTransitionEnd); - expect(ripples[1].classList).not.toContain('fade-in'); - expect(ripples[1].classList).toContain('fade-out'); + expect(ripples[1].classList).not.toContain('md-ripple-fade-in'); + expect(ripples[1].classList).toContain('md-ripple-fade-out'); ripples[1].dispatchEvent(opacityTransitionEnd); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(0); }); @@ -138,11 +138,11 @@ describe('MdInkRipple', () => { const rippleComponent = fixture.debugElement.componentInstance.ripple; // start() should show the background, but no foreground ripple yet. rippleComponent.start(); - expect(rippleBackground.classList).toContain('active'); + expect(rippleBackground.classList).toContain('md-ripple-active'); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(0); // end() should deactivate the background and show the foreground ripple. rippleComponent.end(0, 0); - expect(rippleBackground.classList).not.toContain('active'); + expect(rippleBackground.classList).not.toContain('md-ripple-active'); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); }); @@ -232,12 +232,12 @@ describe('MdInkRipple', () => { // The background ripple should not respond to mouseDown, and no foreground ripple should be // created on a click. container.dispatchEvent(mouseDown); - expect(rippleBackground.classList).not.toContain('active'); + expect(rippleBackground.classList).not.toContain('md-ripple-active'); container.click(); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(0); // Calling start() and end() should still create a ripple. rippleComponent.start(); - expect(rippleBackground.classList).toContain('active'); + expect(rippleBackground.classList).toContain('md-ripple-active'); rippleComponent.end(0, 0); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); }); @@ -248,7 +248,7 @@ describe('MdInkRipple', () => { fixture.debugElement.nativeElement.querySelector('.alternateTrigger'); const mouseDown = createMouseEvent('mousedown'); alternateTrigger.dispatchEvent(mouseDown); - expect(rippleBackground.classList).not.toContain('active'); + expect(rippleBackground.classList).not.toContain('md-ripple-active'); alternateTrigger.click(); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(0); @@ -256,7 +256,7 @@ describe('MdInkRipple', () => { controller.trigger = alternateTrigger; fixture.detectChanges(); alternateTrigger.dispatchEvent(mouseDown); - expect(rippleBackground.classList).toContain('active'); + expect(rippleBackground.classList).toContain('md-ripple-active'); alternateTrigger.click(); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); }); diff --git a/src/components/ripple/ripple.ts b/src/components/ripple/ripple.ts index f3c698de8f61..f005e75e0621 100644 --- a/src/components/ripple/ripple.ts +++ b/src/components/ripple/ripple.ts @@ -29,16 +29,12 @@ const MAX_RIPPLE_FILL_TIME_SECONDS = 0.6; const sqr = (x: number) => x * x; -const distanceToFurthestCorner = (x: number, y: number, parentRect: any) => { - const x1 = parentRect.left; - const x2 = x1 + parentRect.width; - const y1 = parentRect.top; - const y2 = y1 + parentRect.height; +const distanceToFurthestCorner = (x: number, y: number, rect: ClientRect) => { const maxSquaredDistance = Math.max( - sqr(x - x1) + sqr(y - y1), - sqr(x2 - x) + sqr(y - y1), - sqr(x - x1) + sqr(y2 - y), - sqr(x2 - x) + sqr(y2 - y)); + sqr(x - rect.left) + sqr(y - rect.top), + sqr(rect.right - x) + sqr(y - rect.top), + sqr(x - rect.left) + sqr(rect.bottom - y), + sqr(rect.right - x) + sqr(rect.bottom - y)); return Math.sqrt(maxSquaredDistance); }; @@ -56,37 +52,42 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { * The element that triggers the ripple when click events are received. Defaults to the parent * of the . */ - @Input('trigger') _trigger: Element; + @Input('trigger') trigger: Element; /** - * If true, the ripple always originates from the center of the bounds rather + * Whether the ripple always originates from the center of the bounds rather * than originating from the location of the click event. */ - @Input('centered') _centered: boolean; + @Input('centered') centered: boolean; /** - * If true, click events will not trigger the ripple. It can still be triggered by manually + * Whether click events will not trigger the ripple. It can still be triggered by manually * calling start() and end(). */ - @Input('disabled') _disabled: boolean; + @Input('disabled') disabled: boolean; /** * Custom color for ripples. */ - @Input('color') _color: string; + @Input('color') color: string; /** * Custom color for the ripple background. */ - @Input('backgroundColor') _backgroundColor: string; + @Input('backgroundColor') backgroundColor: string; /** * If set, the normal duration of ripple animations is divided by this value. For example, * setting it to 0.5 will cause the animations to take twice as long. */ - @Input('speedFactor') _speedFactor: number = 1; + @Input('speedFactor') speedFactor: number = 1; /** - * If true, the ripple background will be highlighted to indicated a focused state. + * Whether the ripple background will be highlighted to indicated a focused state. */ - @HostBinding('class.focused') @Input('focused') _focused: boolean; + @HostBinding('class.md-ripple-focused') @Input('focused') focused: boolean; private _element: Element; + /** + * _triggerElement is the actual element that will cause a ripple to be created when clicked. + * If the trigger input is set then it is that element, otherwise it is the parent element of + * the . + */ private _triggerElement: Element; private _mouseDownHandler = (event: MouseEvent) => this.mouseDown(event); private _clickHandler = (event: MouseEvent) => this.click(event); @@ -122,8 +123,8 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { ngOnChanges(changes: { [propertyName: string]: SimpleChange }) { // If the trigger element changed (or is being initially set), add event listeners to it. const changedInputs = Object.keys(changes); - if (changedInputs.indexOf('_trigger') !== -1) { - const newTrigger = this._trigger || this._element.parentElement; + if (changedInputs.indexOf('trigger') !== -1) { + const newTrigger = this.trigger || this._element.parentElement; this._updateTriggerElement(newTrigger); } } @@ -148,7 +149,7 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { * Responds to the start of a ripple animation trigger by fading the background in. */ start() { - this._rippleManager.showRippleBackground(this._rippleBackground, this._backgroundColor); + this._rippleManager.showRippleBackground(this._rippleBackground, this.backgroundColor); } /** @@ -161,9 +162,9 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { this._element, left, top, - this._color, - this._centered || forceCenter, - this._speedFactor, + this.color, + this.centered || forceCenter, + this.speedFactor, (ripple: Ripple, event: TransitionEvent) => this._rippleTransitionEnded(ripple, event)); // Fade out the highlighted background. this._rippleManager.hideRippleBackground(this._rippleBackground); @@ -186,13 +187,13 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { } mouseDown(event: MouseEvent) { - if (!this._disabled && event.button === 0) { + if (!this.disabled && event.button === 0) { this.start(); } } click(event: MouseEvent) { - if (!this._disabled && event.button === 0) { + if (!this.disabled && event.button === 0) { // If screen and page positions are all 0, this was probably triggered by a keypress. // In that case, use the center of the bounding rect as the ripple origin. // FIXME: This fails on IE11, which still sets pageX/Y and screenX/Y on keyboard clicks. @@ -212,6 +213,7 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { /** * Helper service that performs DOM manipulations. Not intended to be used outside this module. + * This will eventually become a custom renderer once Angular support exists. */ class MdInkRippleManager { constructor(private _renderer: Renderer) {} @@ -257,7 +259,7 @@ class MdInkRippleManager { // https://timtaubert.de/blog/2012/09/css-transitions-for-dynamically-created-dom-elements/ window.getComputedStyle(rippleDiv).opacity; - this._renderer.setElementClass(rippleDiv, 'fade-in', true); + this._renderer.setElementClass(rippleDiv, 'md-ripple-fade-in', true); // Clearing the transform property causes the ripple to animate to its full size. this._renderer.setElementStyle(rippleDiv, 'transform', ''); const ripple = new Ripple(rippleDiv); @@ -268,8 +270,8 @@ class MdInkRippleManager { } fadeOutForegroundRipple(ripple: Element) { - this._renderer.setElementClass(ripple, 'fade-in', false); - this._renderer.setElementClass(ripple, 'fade-out', true); + this._renderer.setElementClass(ripple, 'md-ripple-fade-in', false); + this._renderer.setElementClass(ripple, 'md-ripple-fade-out', true); } removeRippleFromDom(ripple: Element) { @@ -277,12 +279,12 @@ class MdInkRippleManager { } showRippleBackground(rippleBackground: Element, color: string) { - this._renderer.setElementClass(rippleBackground, 'active', true); + this._renderer.setElementClass(rippleBackground, 'md-ripple-active', true); // If color is not set, this will default to the background color defined in CSS. this._renderer.setElementStyle(rippleBackground, 'background-color', color); } hideRippleBackground(rippleBackground: Element) { - this._renderer.setElementClass(rippleBackground, 'active', false); + this._renderer.setElementClass(rippleBackground, 'md-ripple-active', false); } } diff --git a/src/demo-app/ripple/ripple-demo.html b/src/demo-app/ripple/ripple-demo.html index 8c1ee5edd72d..872a5bc01972 100644 --- a/src/demo-app/ripple/ripple-demo.html +++ b/src/demo-app/ripple/ripple-demo.html @@ -12,17 +12,17 @@
-
Centered
-
Disabled
-
Rounded container (flaky in Firefox)
+
Centered
+
Disabled
+
Rounded container (flaky in Firefox)
Speed - Slow - Normal - Fast + Slow + Normal + Fast
@@ -30,9 +30,9 @@
-
+
Date: Thu, 16 Jun 2016 16:23:45 -0400 Subject: [PATCH 06/17] More PR comments. --- src/components/ripple/package.json | 2 +- src/components/ripple/ripple-manager.ts | 170 ++++++++++++++++++ src/components/ripple/ripple.scss | 2 +- src/components/ripple/ripple.ts | 218 +++++------------------- 4 files changed, 218 insertions(+), 174 deletions(-) create mode 100644 src/components/ripple/ripple-manager.ts diff --git a/src/components/ripple/package.json b/src/components/ripple/package.json index 1550ff4b08de..519e183a48f4 100644 --- a/src/components/ripple/package.json +++ b/src/components/ripple/package.json @@ -23,4 +23,4 @@ "peerDependencies": { "@angular2-material/core": "2.0.0-alpha.5-2" } -} \ No newline at end of file +} diff --git a/src/components/ripple/ripple-manager.ts b/src/components/ripple/ripple-manager.ts new file mode 100644 index 000000000000..b32c1dc9217b --- /dev/null +++ b/src/components/ripple/ripple-manager.ts @@ -0,0 +1,170 @@ +import { + ElementRef, +} from '@angular/core'; + +export enum ForegroundRippleState { + NEW, + EXPANDING, + FADING_OUT, +} + +/** Wrapper for a foreground ripple DOM element and its animation state. */ +export class ForegroundRipple { + state = ForegroundRippleState.NEW; + constructor(public rippleElement: Element) {} +} + +const RIPPLE_SPEED_PX_PER_SECOND = 500; +const MIN_RIPPLE_FILL_TIME_SECONDS = 0.2; +const MAX_RIPPLE_FILL_TIME_SECONDS = 0.6; + +const sqr = (x: number) => x * x; + +/** + * Returns the distance from the point (x, y) to the furthest corner of a rectangle. + */ +const distanceToFurthestCorner = (x: number, y: number, rect: ClientRect) => { + const maxSquaredDistance = Math.max( + sqr(x - rect.left) + sqr(y - rect.top), + sqr(rect.right - x) + sqr(y - rect.top), + sqr(x - rect.left) + sqr(rect.bottom - y), + sqr(rect.right - x) + sqr(rect.bottom - y)); + return Math.sqrt(maxSquaredDistance); +}; + +/** + * Helper service that performs DOM manipulations. Not intended to be used outside this module. + * The constructor takes a reference to the element and a map of DOM event handlers + * to be installed on the element that triggers ripple animations. + * This will eventually become a custom renderer once Angular support exists. + */ +export class MdInkRippleManager { + private _rippleElement: Element; + private _triggerElement: Element; + + constructor(_elementRef: ElementRef, private _eventHandlers: Map void>) { + this._rippleElement = _elementRef.nativeElement; + } + + /** + * Installs event handlers on the given trigger element, and removes event handlers from the + * previous trigger if needed. + */ + setTriggerElement(newTrigger: Element) { + if (this._triggerElement !== newTrigger) { + if (this._triggerElement) { + this._eventHandlers.forEach((eventHandler, eventName) => { + this._triggerElement.removeEventListener(eventName, eventHandler); + }); + } + this._triggerElement = newTrigger; + if (this._triggerElement) { + this._eventHandlers.forEach((eventHandler, eventName) => { + this._triggerElement.addEventListener(eventName, eventHandler); + }); + } + } + } + + /** + * Installs event handlers on the parent of the element. + */ + setTriggerElementToParent() { + this.setTriggerElement(this._rippleElement.parentElement); + } + + /** + * Removes event handlers from the current trigger element if needed. + */ + clearTriggerElement() { + this.setTriggerElement(null); + } + + /** + * Creates a foreground ripple and sets its animation to expand and fade in from the position + * given by rippleOriginLeft and rippleOriginTop (or from the center of the + * bounding rect if centered is true). + */ + createForegroundRipple( + rippleOriginLeft: number, + rippleOriginTop: number, + color: string, + centered: boolean, + speedFactor: number, + transitionEndCallback: (r: ForegroundRipple, e: TransitionEvent) => void) { + const parentRect = this._rippleElement.getBoundingClientRect(); + // Create a foreground ripple div with the size and position of the fully expanded ripple. + // When the div is created, it's given a transform style that causes the ripple to be displayed + // small and centered on the event location (or the center of the bounding rect if the centered + // argument is true). Removing that transform causes the ripple to animate to its natural size. + const startX = centered ? (parentRect.left + parentRect.width / 2) : rippleOriginLeft; + const startY = centered ? (parentRect.top + parentRect.height / 2) : rippleOriginTop; + const offsetX = startX - parentRect.left; + const offsetY = startY - parentRect.top; + const maxRadius = distanceToFurthestCorner(startX, startY, parentRect); + + const rippleDiv = document.createElement('div'); + this._rippleElement.appendChild(rippleDiv); + rippleDiv.classList.add('md-ripple-foreground'); + rippleDiv.style.left = (offsetX - maxRadius) + 'px'; + rippleDiv.style.top = (offsetY - maxRadius) + 'px'; + rippleDiv.style.width = (2 * maxRadius) + 'px'; + rippleDiv.style.height = rippleDiv.style.width; + // If color input is not set, this will default to the background color defined in CSS. + rippleDiv.style.backgroundColor = color; + + const translateX = offsetX - parentRect.width / 2; + const translateY = offsetY - parentRect.height / 2; + rippleDiv.style.transform = `scale(0.01) translate(${translateX}px, ${translateY}px)`; + + const fadeInSeconds = (1 / (speedFactor || 1)) * Math.max( + MIN_RIPPLE_FILL_TIME_SECONDS, + Math.min(MAX_RIPPLE_FILL_TIME_SECONDS, maxRadius / RIPPLE_SPEED_PX_PER_SECOND)); + rippleDiv.style.transitionDuration = `${fadeInSeconds}s`; + + // https://timtaubert.de/blog/2012/09/css-transitions-for-dynamically-created-dom-elements/ + window.getComputedStyle(rippleDiv).opacity; + + rippleDiv.classList.add('md-ripple-fade-in'); + // Clearing the transform property causes the ripple to animate to its full size. + rippleDiv.style.transform = ''; + const ripple = new ForegroundRipple(rippleDiv); + ripple.state = ForegroundRippleState.EXPANDING; + + rippleDiv.addEventListener('transitionend', + (event: TransitionEvent) => transitionEndCallback(ripple, event)); + } + + /** + * Fades out a foreground ripple after it has fully expanded and faded in. + */ + fadeOutForegroundRipple(ripple: Element) { + ripple.classList.remove('md-ripple-fade-in'); + ripple.classList.add('md-ripple-fade-out'); + } + + /** + * Removes a foreground ripple from the DOM after it has faded out. + */ + removeRippleFromDom(ripple: Element) { + ripple.parentElement.removeChild(ripple); + } + + /** + * Fades in the background. + */ + fadeInRippleBackground(color: string) { + const background = this._rippleElement.querySelector('.md-ripple-background'); + background.classList.add('md-ripple-active'); + // If color is not set, this will default to the background color defined in CSS. + background.style.backgroundColor = color; + } + + /** + * Fades out the background. + */ + fadeOutRippleBackground() { + const background = this._rippleElement.querySelector('.md-ripple-background'); + background.classList.remove('md-ripple-active'); + } +} diff --git a/src/components/ripple/ripple.scss b/src/components/ripple/ripple.scss index ec97d6d71f9a..82658778f36c 100644 --- a/src/components/ripple/ripple.scss +++ b/src/components/ripple/ripple.scss @@ -59,7 +59,7 @@ md-ink-ripple.md-ripple-focused .md-ripple-background { opacity: $md-ink-ripple-foreground-initial-opacity; position: absolute; // The transition duration is manually set based on the ripple size. - transition: opacity,transform 0ms cubic-bezier(0, 0, 0.2, 1); + transition: 'opacity, transform' 0ms cubic-bezier(0, 0, 0.2, 1); } .md-ripple-foreground.md-ripple-fade-in { diff --git a/src/components/ripple/ripple.ts b/src/components/ripple/ripple.ts index f005e75e0621..9f96905db432 100644 --- a/src/components/ripple/ripple.ts +++ b/src/components/ripple/ripple.ts @@ -7,36 +7,14 @@ import { OnChanges, OnDestroy, OnInit, - Renderer, SimpleChange, ViewEncapsulation, } from '@angular/core'; - -enum RippleState { - NEW, - EXPANDING, - FADING_OUT, -} - -class Ripple { - state = RippleState.NEW; - constructor(public element: Element) {} -} - -const RIPPLE_SPEED_PX_PER_SECOND = 500; -const MIN_RIPPLE_FILL_TIME_SECONDS = 0.2; -const MAX_RIPPLE_FILL_TIME_SECONDS = 0.6; - -const sqr = (x: number) => x * x; - -const distanceToFurthestCorner = (x: number, y: number, rect: ClientRect) => { - const maxSquaredDistance = Math.max( - sqr(x - rect.left) + sqr(y - rect.top), - sqr(rect.right - x) + sqr(y - rect.top), - sqr(x - rect.left) + sqr(rect.bottom - y), - sqr(rect.right - x) + sqr(rect.bottom - y)); - return Math.sqrt(maxSquaredDistance); -}; +import { + MdInkRippleManager, + ForegroundRipple, + ForegroundRippleState, +} from './ripple-manager'; @Component({ @@ -54,7 +32,7 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { */ @Input('trigger') trigger: Element; /** - * Whether the ripple always originates from the center of the bounds rather + * Whether the ripple always originates from the center of the bounds, rather * than originating from the location of the click event. */ @Input('centered') centered: boolean; @@ -63,60 +41,42 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { * calling start() and end(). */ @Input('disabled') disabled: boolean; - /** - * Custom color for ripples. - */ - @Input('color') color: string; - /** - * Custom color for the ripple background. - */ - @Input('backgroundColor') backgroundColor: string; /** * If set, the normal duration of ripple animations is divided by this value. For example, * setting it to 0.5 will cause the animations to take twice as long. */ @Input('speedFactor') speedFactor: number = 1; + /** Custom color for ripples. */ + @Input('color') color: string; + /** Custom color for the ripple background. */ + @Input('backgroundColor') backgroundColor: string; - /** - * Whether the ripple background will be highlighted to indicated a focused state. - */ + /** Whether the ripple background will be highlighted to indicated a focused state. */ @HostBinding('class.md-ripple-focused') @Input('focused') focused: boolean; - private _element: Element; - /** - * _triggerElement is the actual element that will cause a ripple to be created when clicked. - * If the trigger input is set then it is that element, otherwise it is the parent element of - * the . - */ - private _triggerElement: Element; - private _mouseDownHandler = (event: MouseEvent) => this.mouseDown(event); - private _clickHandler = (event: MouseEvent) => this.click(event); - private _mouseLeaveHandler = (event: MouseEvent) => this.mouseLeave(event); - - private _rippleContainer: Element; - private _rippleBackground: Element; private _rippleManager: MdInkRippleManager; - constructor( - _elementRef: ElementRef, - _renderer: Renderer) { - this._element = _elementRef.nativeElement; - this._rippleManager = new MdInkRippleManager(_renderer); + constructor(_elementRef: ElementRef) { + // These event handlers are attached to the element that triggers the ripple animations. + const eventHandlers = new Map void>(); + eventHandlers.set('mousedown', (event: MouseEvent) => this._mouseDown(event)); + eventHandlers.set('click', (event: MouseEvent) => this._click(event)); + eventHandlers.set('mouseleave', (event: MouseEvent) => this._mouseLeave(event)); + this._rippleManager = new MdInkRippleManager(_elementRef, eventHandlers); } /** TODO: internal */ ngOnInit() { - this._rippleBackground = this._element.querySelector('.md-ripple-background'); // If no trigger element was explicity set, use our parent. - if (!this._triggerElement) { - this._updateTriggerElement(this._element.parentElement); + if (!this.trigger) { + this._rippleManager.setTriggerElementToParent(); } } /** TODO: internal */ ngOnDestroy() { // Remove event listeners on the trigger element. - this._updateTriggerElement(null); + this._rippleManager.clearTriggerElement(); } /** TODO: internal */ @@ -124,24 +84,7 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { // If the trigger element changed (or is being initially set), add event listeners to it. const changedInputs = Object.keys(changes); if (changedInputs.indexOf('trigger') !== -1) { - const newTrigger = this.trigger || this._element.parentElement; - this._updateTriggerElement(newTrigger); - } - } - - private _updateTriggerElement(newTrigger: Element) { - if (this._triggerElement !== newTrigger) { - if (this._triggerElement) { - this._triggerElement.removeEventListener('mousedown', this._mouseDownHandler); - this._triggerElement.removeEventListener('click', this._clickHandler); - this._triggerElement.removeEventListener('mouseleave', this._mouseLeaveHandler); - } - this._triggerElement = newTrigger; - if (this._triggerElement) { - this._triggerElement.addEventListener('mousedown', this._mouseDownHandler); - this._triggerElement.addEventListener('click', this._clickHandler); - this._triggerElement.addEventListener('mouseleave', this._mouseLeaveHandler); - } + this._rippleManager.setTriggerElement(this.trigger); } } @@ -149,7 +92,7 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { * Responds to the start of a ripple animation trigger by fading the background in. */ start() { - this._rippleManager.showRippleBackground(this._rippleBackground, this.backgroundColor); + this._rippleManager.fadeInRippleBackground(this.backgroundColor); } /** @@ -159,40 +102,46 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { */ end(left: number, top: number, forceCenter = true) { this._rippleManager.createForegroundRipple( - this._element, left, top, this.color, this.centered || forceCenter, this.speedFactor, - (ripple: Ripple, event: TransitionEvent) => this._rippleTransitionEnded(ripple, event)); - // Fade out the highlighted background. - this._rippleManager.hideRippleBackground(this._rippleBackground); + (ripple: ForegroundRipple, e: TransitionEvent) => this._rippleTransitionEnded(ripple, e)); + this._rippleManager.fadeOutRippleBackground(); } - private _rippleTransitionEnded(ripple: Ripple, event: TransitionEvent) { + private _rippleTransitionEnded(ripple: ForegroundRipple, event: TransitionEvent) { if (event.propertyName === 'opacity') { // If the ripple finished expanding, start fading it out. If it finished fading out, // remove it from the DOM. switch (ripple.state) { - case RippleState.EXPANDING: - this._rippleManager.fadeOutForegroundRipple(ripple.element); - ripple.state = RippleState.FADING_OUT; + case ForegroundRippleState.EXPANDING: + this._rippleManager.fadeOutForegroundRipple(ripple.rippleElement); + ripple.state = ForegroundRippleState.FADING_OUT; break; - case RippleState.FADING_OUT: - this._rippleManager.removeRippleFromDom(ripple.element); + case ForegroundRippleState.FADING_OUT: + this._rippleManager.removeRippleFromDom(ripple.rippleElement); break; } } } - mouseDown(event: MouseEvent) { + /** + * Called when the trigger element receives a mousedown event. Starts the ripple animation by + * fading in the background. + */ + private _mouseDown(event: MouseEvent) { if (!this.disabled && event.button === 0) { this.start(); } } - click(event: MouseEvent) { + /** + * Called when the trigger element receives a click event. Creates a foreground ripple and + * runs its animation. + */ + private _click(event: MouseEvent) { if (!this.disabled && event.button === 0) { // If screen and page positions are all 0, this was probably triggered by a keypress. // In that case, use the center of the bounding rect as the ripple origin. @@ -203,88 +152,13 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { } } - mouseLeave(event: MouseEvent) { + /** + * Called when the trigger element receives a mouseleave event. Fades out the background. + */ + private _mouseLeave(event: MouseEvent) { // We can always fade out the background here; It's a no-op if it was already inactive. - this._rippleManager.hideRippleBackground(this._rippleBackground); + this._rippleManager.fadeOutRippleBackground(); } // TODO: Reactivate the background div if the user drags out and back in. } - -/** - * Helper service that performs DOM manipulations. Not intended to be used outside this module. - * This will eventually become a custom renderer once Angular support exists. - */ -class MdInkRippleManager { - constructor(private _renderer: Renderer) {} - - createForegroundRipple( - container: Element, - rippleOriginLeft: number, - rippleOriginTop: number, - color: string, - centered: boolean, - speedFactor: number, - transitionEndCallback: (r: Ripple, e: TransitionEvent) => void) { - const parentRect = container.getBoundingClientRect(); - // Create a foreground ripple div with the size and position of the fully expanded ripple. - // When the div is created, it's given a transform style that causes the ripple to be displayed - // small and centered on the event location (or the center of the bounding rect if the centered - // input is true). Removing that transform causes the ripple to animate to its natural size. - const startX = centered ? (parentRect.left + parentRect.width / 2) : rippleOriginLeft; - const startY = centered ? (parentRect.top + parentRect.height / 2) : rippleOriginTop; - const offsetX = startX - parentRect.left; - const offsetY = startY - parentRect.top; - const maxRadius = distanceToFurthestCorner(startX, startY, parentRect); - - const rippleDiv = this._renderer.createElement(container, 'div', null); - this._renderer.setElementClass(rippleDiv, 'md-ripple-foreground', true); - this._renderer.setElementStyle(rippleDiv, 'left', (offsetX - maxRadius) + 'px'); - this._renderer.setElementStyle(rippleDiv, 'top', (offsetY - maxRadius) + 'px'); - this._renderer.setElementStyle(rippleDiv, 'width', (2 * maxRadius) + 'px'); - this._renderer.setElementStyle(rippleDiv, 'height', (2 * maxRadius) + 'px'); - // If color input is not set, this will default to the background color defined in CSS. - this._renderer.setElementStyle(rippleDiv, 'background-color', color); - - const translateX = offsetX - parentRect.width / 2; - const translateY = offsetY - parentRect.height / 2; - this._renderer.setElementStyle(rippleDiv, - 'transform', `scale(0.01) translate(${translateX}px, ${translateY}px)`); - - const fadeInSeconds = (1 / (speedFactor || 1)) * Math.max( - MIN_RIPPLE_FILL_TIME_SECONDS, - Math.min(MAX_RIPPLE_FILL_TIME_SECONDS, maxRadius / RIPPLE_SPEED_PX_PER_SECOND)); - this._renderer.setElementStyle(rippleDiv, 'transition-duration', `${fadeInSeconds}s`); - - // https://timtaubert.de/blog/2012/09/css-transitions-for-dynamically-created-dom-elements/ - window.getComputedStyle(rippleDiv).opacity; - - this._renderer.setElementClass(rippleDiv, 'md-ripple-fade-in', true); - // Clearing the transform property causes the ripple to animate to its full size. - this._renderer.setElementStyle(rippleDiv, 'transform', ''); - const ripple = new Ripple(rippleDiv); - ripple.state = RippleState.EXPANDING; - - this._renderer.listen(rippleDiv, 'transitionend', - (event: TransitionEvent) => transitionEndCallback(ripple, event)); - } - - fadeOutForegroundRipple(ripple: Element) { - this._renderer.setElementClass(ripple, 'md-ripple-fade-in', false); - this._renderer.setElementClass(ripple, 'md-ripple-fade-out', true); - } - - removeRippleFromDom(ripple: Element) { - ripple.parentElement.removeChild(ripple); - } - - showRippleBackground(rippleBackground: Element, color: string) { - this._renderer.setElementClass(rippleBackground, 'md-ripple-active', true); - // If color is not set, this will default to the background color defined in CSS. - this._renderer.setElementStyle(rippleBackground, 'background-color', color); - } - - hideRippleBackground(rippleBackground: Element) { - this._renderer.setElementClass(rippleBackground, 'md-ripple-active', false); - } -} From cd3d4350f486adee5cc5403e0a427c59ef57f2ac Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Thu, 16 Jun 2016 21:05:44 -0400 Subject: [PATCH 07/17] Fix tests, use @internal. --- src/components/ripple/ripple-manager.ts | 7 ++++++- src/core/overlay/position/viewport-ruler.spec.ts | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/ripple/ripple-manager.ts b/src/components/ripple/ripple-manager.ts index b32c1dc9217b..56c506998282 100644 --- a/src/components/ripple/ripple-manager.ts +++ b/src/components/ripple/ripple-manager.ts @@ -2,13 +2,17 @@ import { ElementRef, } from '@angular/core'; +/** @internal */ export enum ForegroundRippleState { NEW, EXPANDING, FADING_OUT, } -/** Wrapper for a foreground ripple DOM element and its animation state. */ +/** + * Wrapper for a foreground ripple DOM element and its animation state. + * @internal + */ export class ForegroundRipple { state = ForegroundRippleState.NEW; constructor(public rippleElement: Element) {} @@ -37,6 +41,7 @@ const distanceToFurthestCorner = (x: number, y: number, rect: ClientRect) => { * The constructor takes a reference to the element and a map of DOM event handlers * to be installed on the element that triggers ripple animations. * This will eventually become a custom renderer once Angular support exists. + * @internal */ export class MdInkRippleManager { private _rippleElement: Element; diff --git a/src/core/overlay/position/viewport-ruler.spec.ts b/src/core/overlay/position/viewport-ruler.spec.ts index de1702f5ec70..f9ac8d9bce19 100644 --- a/src/core/overlay/position/viewport-ruler.spec.ts +++ b/src/core/overlay/position/viewport-ruler.spec.ts @@ -46,6 +46,7 @@ describe('ViewportRuler', () => { // successfully constrain its size. As such, skip assertions in environments where the // window size has changed since the start of the test. if (window.innerWidth > startingWindowWidth || window.innerHeight > startingWindowHeight) { + document.body.removeChild(veryLargeElement); return; } @@ -78,6 +79,7 @@ describe('ViewportRuler', () => { // successfully constrain its size. As such, skip assertions in environments where the // window size has changed since the start of the test. if (window.innerWidth > startingWindowWidth || window.innerHeight > startingWindowHeight) { + document.body.removeChild(veryLargeElement); return; } From 12053e39188ba65442ab2613cd1c3248ea86ce75 Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Thu, 16 Jun 2016 21:33:39 -0400 Subject: [PATCH 08/17] Restore original body margin after tests. --- src/components/ripple/ripple.spec.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/ripple/ripple.spec.ts b/src/components/ripple/ripple.spec.ts index 34279d537105..f1c966c832a9 100644 --- a/src/components/ripple/ripple.spec.ts +++ b/src/components/ripple/ripple.spec.ts @@ -2,6 +2,7 @@ import { describe, it, beforeEach, + afterEach, inject, async, expect, @@ -68,12 +69,18 @@ describe('MdInkRipple', () => { let container: HTMLElement; let rippleElement: Element; let rippleBackground: Element; + let originalBodyMargin: string; beforeEach(() => { - // Clear body margin so it doesn't mess up position calculations. + // Set body margin to 0 during tests so it doesn't mess up position calculations. + originalBodyMargin = document.body.style.margin; document.body.style.margin = '0'; }); + afterEach(() => { + document.body.style.margin = originalBodyMargin; + }); + beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { builder = tcb; })); From 0ff965e396d180654f2bf75c71b8f9eaa5309085 Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Wed, 22 Jun 2016 23:41:03 -0400 Subject: [PATCH 09/17] Add "unbounded" and "max-radius" bindings. --- src/components/ripple/README.md | 6 ++++-- src/components/ripple/ripple-manager.ts | 3 ++- src/components/ripple/ripple.scss | 8 ++++++++ src/components/ripple/ripple.spec.ts | 27 ++++++++++++++++++++++--- src/components/ripple/ripple.ts | 9 +++++++++ src/demo-app/ripple/ripple-demo.html | 8 +++++++- src/demo-app/ripple/ripple-demo.ts | 14 +++++++------ 7 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/components/ripple/README.md b/src/components/ripple/README.md index 3c0bde16614b..39a1ca38ba6d 100644 --- a/src/components/ripple/README.md +++ b/src/components/ripple/README.md @@ -18,8 +18,10 @@ Properties: | Name | Type | Description | | --- | --- | --- | | `trigger` | Element | The DOM element that triggers the ripple when clicked. Defaults to the parent of the `md-ink-ripple`. -| `centered` | boolean | If true, the ripple animation originates from the center of the `md-ink-ripple` bounds rather than from the location of the click event. -| `focused` | boolean | If true, the background ripple is shown using the current theme's accent color to indicate focus. | `color` | string | Custom color for foreground ripples | `backgroundColor` | string | Custom color for the ripple background +| `centered` | boolean | If true, the ripple animation originates from the center of the `md-ink-ripple` bounds rather than from the location of the click event. +| `max-radius` | number | Optional fixed radius of foreground ripples when fully expanded. Mainly used in conjunction with `unbounded` attribute. If not set, ripples will expand from their origin to the most distant corner of the component's bounding rectangle. +| `unbounded` | boolean | If true, foreground ripples will be visible outside the component's bounds. +| `focused` | boolean | If true, the background ripple is shown using the current theme's accent color to indicate focus. | `disabled` | boolean | If true, click events on the trigger element will not activate ripples. The `start` and `end` methods can still be called to programatically create ripples. diff --git a/src/components/ripple/ripple-manager.ts b/src/components/ripple/ripple-manager.ts index 56c506998282..26a2902a3dd7 100644 --- a/src/components/ripple/ripple-manager.ts +++ b/src/components/ripple/ripple-manager.ts @@ -95,6 +95,7 @@ export class MdInkRippleManager { rippleOriginTop: number, color: string, centered: boolean, + radius: number, speedFactor: number, transitionEndCallback: (r: ForegroundRipple, e: TransitionEvent) => void) { const parentRect = this._rippleElement.getBoundingClientRect(); @@ -106,7 +107,7 @@ export class MdInkRippleManager { const startY = centered ? (parentRect.top + parentRect.height / 2) : rippleOriginTop; const offsetX = startX - parentRect.left; const offsetY = startY - parentRect.top; - const maxRadius = distanceToFurthestCorner(startX, startY, parentRect); + const maxRadius = radius > 0 ? radius : distanceToFurthestCorner(startX, startY, parentRect); const rippleDiv = document.createElement('div'); this._rippleElement.appendChild(rippleDiv); diff --git a/src/components/ripple/ripple.scss b/src/components/ripple/ripple.scss index 82658778f36c..2deb3e26ab6f 100644 --- a/src/components/ripple/ripple.scss +++ b/src/components/ripple/ripple.scss @@ -20,6 +20,10 @@ md-ink-ripple { pointer-events: none; } +md-ink-ripple.md-ripple-unbounded { + overflow: visible; +} + /** * Style that can be used by parent elements to specify that the * should have the same bounds as its parent. @@ -43,6 +47,10 @@ md-ink-ripple.md-ripple-fit-parent { bottom: 0; } +md-ink-ripple.md-ripple-unbounded .md-ripple-background { + display: none; +} + .md-ripple-background.md-ripple-active { opacity: 1; } diff --git a/src/components/ripple/ripple.spec.ts b/src/components/ripple/ripple.spec.ts index f1c966c832a9..1b33f1d068d5 100644 --- a/src/components/ripple/ripple.spec.ts +++ b/src/components/ripple/ripple.spec.ts @@ -38,7 +38,7 @@ const createMouseEvent = (eventType: string, dict: any = {}) => { // the MouseEvent constructor, and Edge inexplicably divides clientX and clientY by 100 to get // pageX and pageY. (Really. After "e = new MouseEvent('click', {clientX: 200, clientY: 300})", // e.clientX is 200, e.pageX is 2, e.clientY is 300, and e.pageY is 3.) - // So instead we use the deprecated createEvent/initMouseEvent APIs, which works everywhere. + // So instead we use the deprecated createEvent/initMouseEvent API, which works everywhere. const event = document.createEvent('MouseEvents'); event.initMouseEvent(eventType, false, /* canBubble */ @@ -185,9 +185,9 @@ describe('MdInkRipple', () => { const expectedRadius = Math.sqrt(150 * 150 + 100 * 100); const expectedLeft = elementRect.left + (elementRect.width / 2) - expectedRadius; const expectedTop = elementRect.top + (elementRect.height / 2) - expectedRadius; - const ripple = rippleElement.querySelector('.md-ripple-foreground'); // Note: getBoundingClientRect won't work because there's a transform applied to make the // ripple start out tiny. + const ripple = rippleElement.querySelector('.md-ripple-foreground'); expect(pxStringToFloat(ripple.style.left)).toBeCloseTo(expectedLeft, 1); expect(pxStringToFloat(ripple.style.top)).toBeCloseTo(expectedTop, 1); expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * expectedRadius, 1); @@ -282,13 +282,32 @@ describe('MdInkRipple', () => { const expectedRadius = Math.sqrt(150 * 150 + 100 * 100); const expectedLeft = elementRect.left + (elementRect.width / 2) - expectedRadius; const expectedTop = elementRect.top + (elementRect.height / 2) - expectedRadius; - const ripple = rippleElement.querySelector('.md-ripple-foreground'); + const ripple = rippleElement.querySelector('.md-ripple-foreground'); expect(pxStringToFloat(ripple.style.left)).toBeCloseTo(expectedLeft, 1); expect(pxStringToFloat(ripple.style.top)).toBeCloseTo(expectedTop, 1); expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * expectedRadius, 1); expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * expectedRadius, 1); }); + + it('uses custom radius if set', () => { + const customRadius = 42; + controller.maxRadius = customRadius; + fixture.detectChanges(); + // Click the container 50 px to the right and 75px down from its upper left. + const elementRect = container.getBoundingClientRect(); + const clickEvent = createMouseEvent('click', + {clientX: elementRect.left + 50, clientY: elementRect.top + 75}); + container.dispatchEvent(clickEvent); + const expectedLeft = elementRect.left + 50 - customRadius; + const expectedTop = elementRect.top + 75 - customRadius; + + const ripple = rippleElement.querySelector('.md-ripple-foreground'); + expect(pxStringToFloat(ripple.style.left)).toBeCloseTo(expectedLeft, 1); + expect(pxStringToFloat(ripple.style.top)).toBeCloseTo(expectedTop, 1); + expect(pxStringToFloat(ripple.style.width)).toBeCloseTo(2 * customRadius, 1); + expect(pxStringToFloat(ripple.style.height)).toBeCloseTo(2 * customRadius, 1); + }); }); }); @@ -312,6 +331,7 @@ class BasicRippleContainer { this._rippleTransitionEnded(ripple, e)); this._rippleManager.fadeOutRippleBackground(); diff --git a/src/demo-app/ripple/ripple-demo.html b/src/demo-app/ripple/ripple-demo.html index 872a5bc01972..2bcd069c068e 100644 --- a/src/demo-app/ripple/ripple-demo.html +++ b/src/demo-app/ripple/ripple-demo.html @@ -13,10 +13,10 @@
Centered
+
Unbounded
Disabled
Rounded container (flaky in Firefox)
-
Speed @@ -26,9 +26,13 @@
+
+
+ +
Date: Thu, 23 Jun 2016 12:13:50 -0400 Subject: [PATCH 10/17] Tweaking ripple color and speed. --- src/components/ripple/ripple-manager.ts | 6 +++--- src/components/ripple/ripple.scss | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ripple/ripple-manager.ts b/src/components/ripple/ripple-manager.ts index 26a2902a3dd7..f47043bb03e7 100644 --- a/src/components/ripple/ripple-manager.ts +++ b/src/components/ripple/ripple-manager.ts @@ -18,9 +18,9 @@ export class ForegroundRipple { constructor(public rippleElement: Element) {} } -const RIPPLE_SPEED_PX_PER_SECOND = 500; -const MIN_RIPPLE_FILL_TIME_SECONDS = 0.2; -const MAX_RIPPLE_FILL_TIME_SECONDS = 0.6; +const RIPPLE_SPEED_PX_PER_SECOND = 1000; +const MIN_RIPPLE_FILL_TIME_SECONDS = 0.1; +const MAX_RIPPLE_FILL_TIME_SECONDS = 0.3; const sqr = (x: number) => x * x; diff --git a/src/components/ripple/ripple.scss b/src/components/ripple/ripple.scss index 2deb3e26ab6f..bedd57a13001 100644 --- a/src/components/ripple/ripple.scss +++ b/src/components/ripple/ripple.scss @@ -3,9 +3,9 @@ $md-ink-ripple-focused-opacity: 0.1; $md-ink-ripple-background-fade-duration: 300ms; -$md-ink-ripple-background-default-color: rgba(34, 34, 34, 0.1); +$md-ink-ripple-background-default-color: rgba(0, 0, 0, 0.0588); $md-ink-ripple-foreground-initial-opacity: 0.25; -$md-ink-ripple-foreground-default-color: rgba(34, 34, 34, 0.1); +$md-ink-ripple-foreground-default-color: rgba(0, 0, 0, 0.0588); /** * An component should always have a position of "absolute" or "relative", From b862c5f99c83af56b55dab0da049779541159dc9 Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Thu, 23 Jun 2016 12:48:42 -0400 Subject: [PATCH 11/17] Fix ripple scaling. --- src/components/ripple/ripple-manager.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/ripple/ripple-manager.ts b/src/components/ripple/ripple-manager.ts index f47043bb03e7..025b938b4708 100644 --- a/src/components/ripple/ripple-manager.ts +++ b/src/components/ripple/ripple-manager.ts @@ -118,10 +118,8 @@ export class MdInkRippleManager { rippleDiv.style.height = rippleDiv.style.width; // If color input is not set, this will default to the background color defined in CSS. rippleDiv.style.backgroundColor = color; - - const translateX = offsetX - parentRect.width / 2; - const translateY = offsetY - parentRect.height / 2; - rippleDiv.style.transform = `scale(0.01) translate(${translateX}px, ${translateY}px)`; + // Start the ripple tiny. + rippleDiv.style.transform = `scale(0.001)`; const fadeInSeconds = (1 / (speedFactor || 1)) * Math.max( MIN_RIPPLE_FILL_TIME_SECONDS, From ff53c5cb0d36a9387bdf42dff600b5f80b00171c Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Fri, 8 Jul 2016 00:37:25 -0400 Subject: [PATCH 12/17] In-progress updates for PR comments. --- src/components/ripple/README.md | 2 +- .../{ripple-manager.ts => ripple-renderer.ts} | 14 +++---- src/components/ripple/ripple.ts | 42 ++++++++++--------- src/demo-app/ripple/ripple-demo.html | 4 +- src/demo-app/ripple/ripple-demo.ts | 22 ++++++---- 5 files changed, 46 insertions(+), 38 deletions(-) rename src/components/ripple/{ripple-manager.ts => ripple-renderer.ts} (96%) diff --git a/src/components/ripple/README.md b/src/components/ripple/README.md index 39a1ca38ba6d..6235887d017f 100644 --- a/src/components/ripple/README.md +++ b/src/components/ripple/README.md @@ -21,7 +21,7 @@ Properties: | `color` | string | Custom color for foreground ripples | `backgroundColor` | string | Custom color for the ripple background | `centered` | boolean | If true, the ripple animation originates from the center of the `md-ink-ripple` bounds rather than from the location of the click event. -| `max-radius` | number | Optional fixed radius of foreground ripples when fully expanded. Mainly used in conjunction with `unbounded` attribute. If not set, ripples will expand from their origin to the most distant corner of the component's bounding rectangle. +| `maxRadius` | number | Optional fixed radius of foreground ripples when fully expanded. Mainly used in conjunction with `unbounded` attribute. If not set, ripples will expand from their origin to the most distant corner of the component's bounding rectangle. | `unbounded` | boolean | If true, foreground ripples will be visible outside the component's bounds. | `focused` | boolean | If true, the background ripple is shown using the current theme's accent color to indicate focus. | `disabled` | boolean | If true, click events on the trigger element will not activate ripples. The `start` and `end` methods can still be called to programatically create ripples. diff --git a/src/components/ripple/ripple-manager.ts b/src/components/ripple/ripple-renderer.ts similarity index 96% rename from src/components/ripple/ripple-manager.ts rename to src/components/ripple/ripple-renderer.ts index 025b938b4708..e86210af5025 100644 --- a/src/components/ripple/ripple-manager.ts +++ b/src/components/ripple/ripple-renderer.ts @@ -2,7 +2,7 @@ import { ElementRef, } from '@angular/core'; -/** @internal */ +/** TODO: internal */ export enum ForegroundRippleState { NEW, EXPANDING, @@ -11,7 +11,7 @@ export enum ForegroundRippleState { /** * Wrapper for a foreground ripple DOM element and its animation state. - * @internal + * TODO: internal */ export class ForegroundRipple { state = ForegroundRippleState.NEW; @@ -41,9 +41,9 @@ const distanceToFurthestCorner = (x: number, y: number, rect: ClientRect) => { * The constructor takes a reference to the element and a map of DOM event handlers * to be installed on the element that triggers ripple animations. * This will eventually become a custom renderer once Angular support exists. - * @internal + * TODO: internal */ -export class MdInkRippleManager { +export class RippleRenderer { private _rippleElement: Element; private _triggerElement: Element; @@ -112,9 +112,9 @@ export class MdInkRippleManager { const rippleDiv = document.createElement('div'); this._rippleElement.appendChild(rippleDiv); rippleDiv.classList.add('md-ripple-foreground'); - rippleDiv.style.left = (offsetX - maxRadius) + 'px'; - rippleDiv.style.top = (offsetY - maxRadius) + 'px'; - rippleDiv.style.width = (2 * maxRadius) + 'px'; + rippleDiv.style.left = `${offsetX - maxRadius}px`; + rippleDiv.style.top = `${offsetY - maxRadius}px`; + rippleDiv.style.width = `${2 * maxRadius}px`; rippleDiv.style.height = rippleDiv.style.width; // If color input is not set, this will default to the background color defined in CSS. rippleDiv.style.backgroundColor = color; diff --git a/src/components/ripple/ripple.ts b/src/components/ripple/ripple.ts index 07bbc1374914..2a48e3aee949 100644 --- a/src/components/ripple/ripple.ts +++ b/src/components/ripple/ripple.ts @@ -11,10 +11,10 @@ import { ViewEncapsulation, } from '@angular/core'; import { - MdInkRippleManager, + RippleRenderer, ForegroundRipple, ForegroundRippleState, -} from './ripple-manager'; +} from './ripple-renderer'; @Component({ @@ -30,39 +30,39 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { * The element that triggers the ripple when click events are received. Defaults to the parent * of the . */ - @Input('trigger') trigger: Element; + @Input() trigger: Element; /** * Whether the ripple always originates from the center of the bounds, rather * than originating from the location of the click event. */ - @Input('centered') centered: boolean; + @Input() centered: boolean; /** * Whether click events will not trigger the ripple. It can still be triggered by manually * calling start() and end(). */ - @Input('disabled') disabled: boolean; + @Input() disabled: boolean; /** * If set, the radius in pixels of foreground ripples when fully expanded. If unset, the radius * will be the distance from the center of the ripple to the furthest corner of the element's * bounding rectangle. */ - @Input('max-radius') maxRadius: number = 0; + @Input() maxRadius: number = 0; /** * If set, the normal duration of ripple animations is divided by this value. For example, * setting it to 0.5 will cause the animations to take twice as long. */ - @Input('speedFactor') speedFactor: number = 1; + @Input() speedFactor: number = 1; /** Custom color for ripples. */ - @Input('color') color: string; + @Input() color: string; /** Custom color for the ripple background. */ - @Input('backgroundColor') backgroundColor: string; + @Input() backgroundColor: string; /** Whether the ripple background will be highlighted to indicated a focused state. */ @HostBinding('class.md-ripple-focused') @Input('focused') focused: boolean; /** Whether foreground ripples should be visible outside the component's bounds. */ @HostBinding('class.md-ripple-unbounded') @Input('unbounded') unbounded: boolean; - private _rippleManager: MdInkRippleManager; + private _rippleRenderer: RippleRenderer; constructor(_elementRef: ElementRef) { // These event handlers are attached to the element that triggers the ripple animations. @@ -70,21 +70,21 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { eventHandlers.set('mousedown', (event: MouseEvent) => this._mouseDown(event)); eventHandlers.set('click', (event: MouseEvent) => this._click(event)); eventHandlers.set('mouseleave', (event: MouseEvent) => this._mouseLeave(event)); - this._rippleManager = new MdInkRippleManager(_elementRef, eventHandlers); + this._rippleRenderer = new RippleRenderer(_elementRef, eventHandlers); } /** TODO: internal */ ngOnInit() { // If no trigger element was explicity set, use our parent. if (!this.trigger) { - this._rippleManager.setTriggerElementToParent(); + this._rippleRenderer.setTriggerElementToParent(); } } /** TODO: internal */ ngOnDestroy() { // Remove event listeners on the trigger element. - this._rippleManager.clearTriggerElement(); + this._rippleRenderer.clearTriggerElement(); } /** TODO: internal */ @@ -92,7 +92,7 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { // If the trigger element changed (or is being initially set), add event listeners to it. const changedInputs = Object.keys(changes); if (changedInputs.indexOf('trigger') !== -1) { - this._rippleManager.setTriggerElement(this.trigger); + this._rippleRenderer.setTriggerElement(this.trigger); } } @@ -100,7 +100,7 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { * Responds to the start of a ripple animation trigger by fading the background in. */ start() { - this._rippleManager.fadeInRippleBackground(this.backgroundColor); + this._rippleRenderer.fadeInRippleBackground(this.backgroundColor); } /** @@ -109,7 +109,7 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { * the "centered" property is set or forceCenter is true). */ end(left: number, top: number, forceCenter = true) { - this._rippleManager.createForegroundRipple( + this._rippleRenderer.createForegroundRipple( left, top, this.color, @@ -117,7 +117,7 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { this.maxRadius, this.speedFactor, (ripple: ForegroundRipple, e: TransitionEvent) => this._rippleTransitionEnded(ripple, e)); - this._rippleManager.fadeOutRippleBackground(); + this._rippleRenderer.fadeOutRippleBackground(); } private _rippleTransitionEnded(ripple: ForegroundRipple, event: TransitionEvent) { @@ -126,11 +126,11 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { // remove it from the DOM. switch (ripple.state) { case ForegroundRippleState.EXPANDING: - this._rippleManager.fadeOutForegroundRipple(ripple.rippleElement); + this._rippleRenderer.fadeOutForegroundRipple(ripple.rippleElement); ripple.state = ForegroundRippleState.FADING_OUT; break; case ForegroundRippleState.FADING_OUT: - this._rippleManager.removeRippleFromDom(ripple.rippleElement); + this._rippleRenderer.removeRippleFromDom(ripple.rippleElement); break; } } @@ -166,8 +166,10 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { */ private _mouseLeave(event: MouseEvent) { // We can always fade out the background here; It's a no-op if it was already inactive. - this._rippleManager.fadeOutRippleBackground(); + this._rippleRenderer.fadeOutRippleBackground(); } // TODO: Reactivate the background div if the user drags out and back in. } + +export const MD_RIPPLE_DIRECTIVES = [MdInkRipple]; \ No newline at end of file diff --git a/src/demo-app/ripple/ripple-demo.html b/src/demo-app/ripple/ripple-demo.html index 2bcd069c068e..7565a31511d7 100644 --- a/src/demo-app/ripple/ripple-demo.html +++ b/src/demo-app/ripple/ripple-demo.html @@ -20,7 +20,7 @@
Speed - Slow + Slow Normal Fast @@ -41,7 +41,7 @@ [centered]="centered" [disabled]="disabled" [unbounded]="unbounded" - [max-radius]="maxRadius" + [maxRadius]="maxRadius" [color]="rippleColor" [backgroundColor]="rippleBackgroundColor" [speedFactor]="rippleSpeed" diff --git a/src/demo-app/ripple/ripple-demo.ts b/src/demo-app/ripple/ripple-demo.ts index 1bced59a8a94..29913879e79f 100644 --- a/src/demo-app/ripple/ripple-demo.ts +++ b/src/demo-app/ripple/ripple-demo.ts @@ -2,13 +2,13 @@ import { Component, ViewChild, } from '@angular/core'; -import {MdButton} from '@angular2-material/button/button'; -import {MdCard} from '@angular2-material/card/card'; -import {MdCheckbox} from '@angular2-material/checkbox/checkbox'; -import {MdIcon} from '@angular2-material/icon/icon'; -import {MdInkRipple} from '@angular2-material/ripple/ripple'; -import {MdInput} from '@angular2-material/input/input'; -import {MdRadioButton, MdRadioGroup} from '@angular2-material/radio/radio'; +import {MD_BUTTON_DIRECTIVES} from '@angular2-material/button/button'; +import {MD_CARD_DIRECTIVES} from '@angular2-material/card/card'; +import {MD_CHECKBOX_DIRECTIVES} from '@angular2-material/checkbox/checkbox'; +import {MD_ICON_DIRECTIVES} from '@angular2-material/icon/icon'; +import {MD_RIPPLE_DIRECTIVES, MdInkRipple} from '@angular2-material/ripple/ripple'; +import {MD_INPUT_DIRECTIVES} from '@angular2-material/input/input'; +import {MD_RADIO_DIRECTIVES} from '@angular2-material/radio/radio'; import { MdUniqueSelectionDispatcher } from '@angular2-material/core/coordination/unique-selection-dispatcher'; @@ -20,7 +20,13 @@ import { styleUrls: ['ripple-demo.css'], providers: [MdUniqueSelectionDispatcher], directives: [ - MdButton, MdCard, MdCheckbox, MdIcon, MdInput, MdRadioButton, MdRadioGroup, MdInkRipple, + MD_BUTTON_DIRECTIVES, + MD_CARD_DIRECTIVES, + MD_CHECKBOX_DIRECTIVES, + MD_ICON_DIRECTIVES, + MD_RIPPLE_DIRECTIVES, + MD_INPUT_DIRECTIVES, + MD_RADIO_DIRECTIVES, ], }) export class RippleDemo { From 64cbaf28ed0fba5e2ddd613523ef1b1fd39d1cff Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Fri, 8 Jul 2016 00:53:15 -0400 Subject: [PATCH 13/17] PR comments --- src/components/ripple/README.md | 8 ++++---- src/components/ripple/ripple-renderer.ts | 11 +++-------- src/components/ripple/ripple.ts | 2 +- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/components/ripple/README.md b/src/components/ripple/README.md index 6235887d017f..83715954d986 100644 --- a/src/components/ripple/README.md +++ b/src/components/ripple/README.md @@ -2,14 +2,14 @@ `md-ink-ripple` defines an area in which a ripple animates, usually in response to user action. -By default, an `md-ink-ripple` component is activated when its parent element receives mouse or touch events. On a mousedown or touch start, the ripple background fades in. When the click event complets, a circular foreground ripple fades in and expands from the event location to cover the component bounds. +By default, an `md-ink-ripple` component is activated when its parent element receives mouse or touch events. On a mousedown or touch start, the ripple background fades in. When the click event completes, a circular foreground ripple fades in and expands from the event location to cover the component bounds. -Ripples can also be triggered programatically by getting a reference to the MdInkRipple component and calling its `start` and `end` methods. +Ripples can also be triggered programmatically by getting a reference to the MdInkRipple component and calling its `start` and `end` methods. ### Upcoming work -Ripples will be added to `md-button`, `md-radio-button`, and `md-checkbox` components. +Ripples will be added to the `md-button`, `md-radio-button`, `md-checkbox`, and `md-nav-list` components. ### API Summary @@ -24,4 +24,4 @@ Properties: | `maxRadius` | number | Optional fixed radius of foreground ripples when fully expanded. Mainly used in conjunction with `unbounded` attribute. If not set, ripples will expand from their origin to the most distant corner of the component's bounding rectangle. | `unbounded` | boolean | If true, foreground ripples will be visible outside the component's bounds. | `focused` | boolean | If true, the background ripple is shown using the current theme's accent color to indicate focus. -| `disabled` | boolean | If true, click events on the trigger element will not activate ripples. The `start` and `end` methods can still be called to programatically create ripples. +| `disabled` | boolean | If true, click events on the trigger element will not activate ripples. The `start` and `end` methods can still be called to programmatically create ripples. diff --git a/src/components/ripple/ripple-renderer.ts b/src/components/ripple/ripple-renderer.ts index e86210af5025..fb00d1307c97 100644 --- a/src/components/ripple/ripple-renderer.ts +++ b/src/components/ripple/ripple-renderer.ts @@ -22,18 +22,13 @@ const RIPPLE_SPEED_PX_PER_SECOND = 1000; const MIN_RIPPLE_FILL_TIME_SECONDS = 0.1; const MAX_RIPPLE_FILL_TIME_SECONDS = 0.3; -const sqr = (x: number) => x * x; - /** * Returns the distance from the point (x, y) to the furthest corner of a rectangle. */ const distanceToFurthestCorner = (x: number, y: number, rect: ClientRect) => { - const maxSquaredDistance = Math.max( - sqr(x - rect.left) + sqr(y - rect.top), - sqr(rect.right - x) + sqr(y - rect.top), - sqr(x - rect.left) + sqr(rect.bottom - y), - sqr(rect.right - x) + sqr(rect.bottom - y)); - return Math.sqrt(maxSquaredDistance); + const distX = Math.max(Math.abs(x - rect.left), Math.abs(x - rect.right)); + const distY = Math.max(Math.abs(y - rect.top), Math.abs(y - rect.bottom)); + return Math.sqrt(distX * distX + distY * distY); }; /** diff --git a/src/components/ripple/ripple.ts b/src/components/ripple/ripple.ts index 2a48e3aee949..1aa806b1018d 100644 --- a/src/components/ripple/ripple.ts +++ b/src/components/ripple/ripple.ts @@ -172,4 +172,4 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { // TODO: Reactivate the background div if the user drags out and back in. } -export const MD_RIPPLE_DIRECTIVES = [MdInkRipple]; \ No newline at end of file +export const MD_RIPPLE_DIRECTIVES = [MdInkRipple]; From ebddf7350da542a1c0c19aea64e570fc81697341 Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Fri, 8 Jul 2016 01:16:37 -0400 Subject: [PATCH 14/17] Fix maxRadius binding in tests. --- src/components/ripple/ripple.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ripple/ripple.spec.ts b/src/components/ripple/ripple.spec.ts index 1b33f1d068d5..d52725b2a97d 100644 --- a/src/components/ripple/ripple.spec.ts +++ b/src/components/ripple/ripple.spec.ts @@ -331,7 +331,7 @@ class BasicRippleContainer { Date: Sun, 10 Jul 2016 00:07:58 -0400 Subject: [PATCH 15/17] Simplify ripple demo @ViewChild. --- src/demo-app/ripple/ripple-demo.html | 2 +- src/demo-app/ripple/ripple-demo.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/demo-app/ripple/ripple-demo.html b/src/demo-app/ripple/ripple-demo.html index 7565a31511d7..e221746a49ea 100644 --- a/src/demo-app/ripple/ripple-demo.html +++ b/src/demo-app/ripple/ripple-demo.html @@ -37,7 +37,7 @@
- Date: Thu, 21 Jul 2016 23:18:25 -0400 Subject: [PATCH 16/17] Switch to attribute directive (
instead of ) and move to core. --- src/components/ripple/README.md | 27 -------- src/components/ripple/package.json | 26 ------- src/core/core.ts | 3 + src/core/ripple/README.md | 27 ++++++++ .../ripple/ripple-renderer.ts | 28 +++++--- src/{components => core}/ripple/ripple.scss | 0 .../ripple/ripple.spec.ts | 69 ++++++++----------- src/{components => core}/ripple/ripple.ts | 35 ++++------ src/core/style/_ripple.scss | 62 +++++++++++++++++ src/core/style/core.scss | 2 + src/demo-app/index.html | 1 + src/demo-app/ripple/ripple-demo.html | 19 +++-- src/demo-app/ripple/ripple-demo.ts | 4 +- src/demo-app/system-config.ts | 1 - 14 files changed, 167 insertions(+), 137 deletions(-) delete mode 100644 src/components/ripple/README.md delete mode 100644 src/components/ripple/package.json create mode 100644 src/core/ripple/README.md rename src/{components => core}/ripple/ripple-renderer.ts (86%) rename src/{components => core}/ripple/ripple.scss (100%) rename src/{components => core}/ripple/ripple.spec.ts (88%) rename src/{components => core}/ripple/ripple.ts (86%) create mode 100644 src/core/style/_ripple.scss diff --git a/src/components/ripple/README.md b/src/components/ripple/README.md deleted file mode 100644 index 83715954d986..000000000000 --- a/src/components/ripple/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# md-ink-ripple - -`md-ink-ripple` defines an area in which a ripple animates, usually in response to user action. - -By default, an `md-ink-ripple` component is activated when its parent element receives mouse or touch events. On a mousedown or touch start, the ripple background fades in. When the click event completes, a circular foreground ripple fades in and expands from the event location to cover the component bounds. - -Ripples can also be triggered programmatically by getting a reference to the MdInkRipple component and calling its `start` and `end` methods. - - -### Upcoming work - -Ripples will be added to the `md-button`, `md-radio-button`, `md-checkbox`, and `md-nav-list` components. - -### API Summary - -Properties: - -| Name | Type | Description | -| --- | --- | --- | -| `trigger` | Element | The DOM element that triggers the ripple when clicked. Defaults to the parent of the `md-ink-ripple`. -| `color` | string | Custom color for foreground ripples -| `backgroundColor` | string | Custom color for the ripple background -| `centered` | boolean | If true, the ripple animation originates from the center of the `md-ink-ripple` bounds rather than from the location of the click event. -| `maxRadius` | number | Optional fixed radius of foreground ripples when fully expanded. Mainly used in conjunction with `unbounded` attribute. If not set, ripples will expand from their origin to the most distant corner of the component's bounding rectangle. -| `unbounded` | boolean | If true, foreground ripples will be visible outside the component's bounds. -| `focused` | boolean | If true, the background ripple is shown using the current theme's accent color to indicate focus. -| `disabled` | boolean | If true, click events on the trigger element will not activate ripples. The `start` and `end` methods can still be called to programmatically create ripples. diff --git a/src/components/ripple/package.json b/src/components/ripple/package.json deleted file mode 100644 index 3084f25696e7..000000000000 --- a/src/components/ripple/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "@angular2-material/ripple", - "version": "2.0.0-alpha.6", - "description": "Angular 2 Material ripple", - "main": "./ripple.js", - "typings": "./ripple.d.ts", - "repository": { - "type": "git", - "url": "https://github.com/angular/material2.git" - }, - "keywords": [ - "angular", - "material", - "material design", - "components", - "ripple" - ], - "license": "MIT", - "bugs": { - "url": "https://github.com/angular/material2/issues" - }, - "homepage": "https://github.com/angular/material2#readme", - "peerDependencies": { - "@angular2-material/core": "2.0.0-alpha.6" - } -} diff --git a/src/core/core.ts b/src/core/core.ts index cffa1091703f..43d9c5e98821 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -29,6 +29,9 @@ export { // Gestures export {MdGestureConfig} from './gestures/MdGestureConfig'; +// Ripple +export {MD_RIPPLE_DIRECTIVES, MdInkRipple} from './ripple/ripple'; + // a11y export { AriaLivePoliteness, diff --git a/src/core/ripple/README.md b/src/core/ripple/README.md new file mode 100644 index 000000000000..42dadc9fc147 --- /dev/null +++ b/src/core/ripple/README.md @@ -0,0 +1,27 @@ +# md-ink-ripple + +`md-ink-ripple` defines an area in which a ripple animates, usually in response to user action. It is used as an attribute directive, for example `
...
`. + +By default, a ripple is activated when the host element of the `md-ink-ripple` directive receives mouse or touch events. On a mousedown or touch start, the ripple background fades in. When the click event completes, a circular foreground ripple fades in and expands from the event location to cover the host element bounds. + +Ripples can also be triggered programmatically by getting a reference to the MdInkRipple directive and calling its `start` and `end` methods. + + +### Upcoming work + +Ripples will be added to the `md-button`, `md-radio-button`, `md-checkbox`, and `md-nav-list` components. + +### API Summary + +Properties: + +| Name | Type | Description | +| --- | --- | --- | +| `md-ink-ripple-trigger` | Element | The DOM element that triggers the ripple when clicked. Defaults to the parent of the `md-ink-ripple`. +| `md-ink-ripple-color` | string | Custom color for foreground ripples +| `md-ink-ripple-background-color` | string | Custom color for the ripple background +| `md-ink-ripple-centered` | boolean | If true, the ripple animation originates from the center of the `md-ink-ripple` bounds rather than from the location of the click event. +| `md-ink-ripple-max-radius` | number | Optional fixed radius of foreground ripples when fully expanded. Mainly used in conjunction with `unbounded` attribute. If not set, ripples will expand from their origin to the most distant corner of the component's bounding rectangle. +| `md-ink-ripple-unbounded` | boolean | If true, foreground ripples will be visible outside the component's bounds. +| `md-ink-ripple-focused` | boolean | If true, the background ripple is shown using the current theme's accent color to indicate focus. +| `md-ink-ripple-disabled` | boolean | If true, click events on the trigger element will not activate ripples. The `start` and `end` methods can still be called to programmatically create ripples. diff --git a/src/components/ripple/ripple-renderer.ts b/src/core/ripple/ripple-renderer.ts similarity index 86% rename from src/components/ripple/ripple-renderer.ts rename to src/core/ripple/ripple-renderer.ts index fb00d1307c97..89b84f522c6f 100644 --- a/src/components/ripple/ripple-renderer.ts +++ b/src/core/ripple/ripple-renderer.ts @@ -39,18 +39,24 @@ const distanceToFurthestCorner = (x: number, y: number, rect: ClientRect) => { * TODO: internal */ export class RippleRenderer { - private _rippleElement: Element; - private _triggerElement: Element; + private _backgroundDiv: HTMLElement; + private _rippleElement: HTMLElement; + private _triggerElement: HTMLElement; constructor(_elementRef: ElementRef, private _eventHandlers: Map void>) { this._rippleElement = _elementRef.nativeElement; + // It might be nice to delay creating the background until it's needed, but doing this in + // fadeInRippleBackground causes the first click event to not be handled reliably. + this._backgroundDiv = document.createElement('div'); + this._backgroundDiv.classList.add('md-ripple-background'); + this._rippleElement.appendChild(this._backgroundDiv); } /** * Installs event handlers on the given trigger element, and removes event handlers from the * previous trigger if needed. */ - setTriggerElement(newTrigger: Element) { + setTriggerElement(newTrigger: HTMLElement) { if (this._triggerElement !== newTrigger) { if (this._triggerElement) { this._eventHandlers.forEach((eventHandler, eventName) => { @@ -67,10 +73,10 @@ export class RippleRenderer { } /** - * Installs event handlers on the parent of the element. + * Installs event handlers on the host element of the md-ink-ripple directive. */ - setTriggerElementToParent() { - this.setTriggerElement(this._rippleElement.parentElement); + setTriggerElementToHost() { + this.setTriggerElement(this._rippleElement); } /** @@ -153,17 +159,17 @@ export class RippleRenderer { * Fades in the background. */ fadeInRippleBackground(color: string) { - const background = this._rippleElement.querySelector('.md-ripple-background'); - background.classList.add('md-ripple-active'); + this._backgroundDiv.classList.add('md-ripple-active'); // If color is not set, this will default to the background color defined in CSS. - background.style.backgroundColor = color; + this._backgroundDiv.style.backgroundColor = color; } /** * Fades out the background. */ fadeOutRippleBackground() { - const background = this._rippleElement.querySelector('.md-ripple-background'); - background.classList.remove('md-ripple-active'); + if (this._backgroundDiv) { + this._backgroundDiv.classList.remove('md-ripple-active'); + } } } diff --git a/src/components/ripple/ripple.scss b/src/core/ripple/ripple.scss similarity index 100% rename from src/components/ripple/ripple.scss rename to src/core/ripple/ripple.scss diff --git a/src/components/ripple/ripple.spec.ts b/src/core/ripple/ripple.spec.ts similarity index 88% rename from src/components/ripple/ripple.spec.ts rename to src/core/ripple/ripple.spec.ts index d52725b2a97d..5c09180e783f 100644 --- a/src/components/ripple/ripple.spec.ts +++ b/src/core/ripple/ripple.spec.ts @@ -66,8 +66,7 @@ const pxStringToFloat = (s: string) => { describe('MdInkRipple', () => { let builder: TestComponentBuilder; let fixture: ComponentFixture; - let container: HTMLElement; - let rippleElement: Element; + let rippleElement: HTMLElement; let rippleBackground: Element; let originalBodyMargin: string; @@ -91,8 +90,7 @@ describe('MdInkRipple', () => { fixture = f; fixture.detectChanges(); - container = fixture.debugElement.nativeElement.querySelector('#container'); - rippleElement = container.querySelector('md-ink-ripple'); + rippleElement = fixture.debugElement.nativeElement.querySelector('[md-ink-ripple]'); rippleBackground = rippleElement.querySelector('.md-ripple-background'); expect(rippleBackground).toBeTruthy(); }); @@ -101,23 +99,20 @@ describe('MdInkRipple', () => { it('shows background when parent receives mousedown event', () => { expect(rippleBackground.classList).not.toContain('md-ripple-active'); const mouseDown = createMouseEvent('mousedown'); - // mousedown on the ripple element itself does nothing. + // mousedown on the ripple element activates the background ripple. rippleElement.dispatchEvent(mouseDown); - expect(rippleBackground.classList).not.toContain('md-ripple-active'); - // mousedown on the container activates the background ripple. - container.dispatchEvent(mouseDown); expect(rippleBackground.classList).toContain('md-ripple-active'); // mouseleave on the container removes the background ripple. const mouseLeave = createMouseEvent('mouseleave'); - container.dispatchEvent(mouseLeave); + rippleElement.dispatchEvent(mouseLeave); expect(rippleBackground.classList).not.toContain('md-ripple-active'); }); it('creates foreground ripples on click', () => { - container.click(); + rippleElement.click(); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(1); // Second click should create another ripple. - container.click(); + rippleElement.click(); const ripples = rippleElement.querySelectorAll('.md-ripple-foreground'); expect(ripples.length).toBe(2); expect(ripples[0].classList).toContain('md-ripple-fade-in'); @@ -154,11 +149,11 @@ describe('MdInkRipple', () => { }); it('sizes ripple to cover element', () => { - // Click the container 50 px to the right and 75px down from its upper left. - const elementRect = container.getBoundingClientRect(); + // Click the ripple element 50 px to the right and 75px down from its upper left. + const elementRect = rippleElement.getBoundingClientRect(); const clickEvent = createMouseEvent('click', {clientX: elementRect.left + 50, clientY: elementRect.top + 75}); - container.dispatchEvent(clickEvent); + rippleElement.dispatchEvent(clickEvent); // At this point the foreground ripple should be created with a div centered at the click // location, and large enough to reach the furthest corner, which is 250px to the right // and 125px down relative to the click position. @@ -175,11 +170,11 @@ describe('MdInkRipple', () => { }); it('expands ripple from center on click event triggered by keyboard', () => { - const elementRect = container.getBoundingClientRect(); + const elementRect = rippleElement.getBoundingClientRect(); // Simulate a keyboard-triggered click by setting event coordinates to 0. const clickEvent = createMouseEvent('click', {clientX: 0, clientY: 0, screenX: 0, screenY: 0}); - container.dispatchEvent(clickEvent); + rippleElement.dispatchEvent(clickEvent); // The foreground ripple should be centered in the middle of the bounding rect, and large // enough to reach the corners, which are all 150px horizontally and 100px vertically away. const expectedRadius = Math.sqrt(150 * 150 + 100 * 100); @@ -206,8 +201,7 @@ describe('MdInkRipple', () => { controller = fixture.debugElement.componentInstance; rippleComponent = controller.ripple; - container = fixture.debugElement.nativeElement.querySelector('#container'); - rippleElement = container.querySelector('md-ink-ripple'); + rippleElement = fixture.debugElement.nativeElement.querySelector('[md-ink-ripple]'); rippleBackground = rippleElement.querySelector('.md-ripple-background'); expect(rippleBackground).toBeTruthy(); }); @@ -227,7 +221,7 @@ describe('MdInkRipple', () => { const color = 'rgba(12, 34, 56, 0.8)'; controller.color = color; fixture.detectChanges(); - container.click(); + rippleElement.click(); const ripple = rippleElement.querySelector('.md-ripple-foreground'); expect(window.getComputedStyle(ripple).backgroundColor).toBe(color); }); @@ -238,9 +232,9 @@ describe('MdInkRipple', () => { const mouseDown = createMouseEvent('mousedown'); // The background ripple should not respond to mouseDown, and no foreground ripple should be // created on a click. - container.dispatchEvent(mouseDown); + rippleElement.dispatchEvent(mouseDown); expect(rippleBackground.classList).not.toContain('md-ripple-active'); - container.click(); + rippleElement.click(); expect(rippleElement.querySelectorAll('.md-ripple-foreground').length).toBe(0); // Calling start() and end() should still create a ripple. rippleComponent.start(); @@ -271,11 +265,11 @@ describe('MdInkRipple', () => { it('expands ripple from center if centered input is set', () => { controller.centered = true; fixture.detectChanges(); - // Click the container 50 px to the right and 75px down from its upper left. - const elementRect = container.getBoundingClientRect(); + // Click the ripple element 50 px to the right and 75px down from its upper left. + const elementRect = rippleElement.getBoundingClientRect(); const clickEvent = createMouseEvent('click', {clientX: elementRect.left + 50, clientY: elementRect.top + 75}); - container.dispatchEvent(clickEvent); + rippleElement.dispatchEvent(clickEvent); // Because the centered input is true, the center of the ripple should be the midpoint of the // bounding rect. The ripple should expand to cover the rect corners, which are 150px // horizontally and 100px vertically from the midpoint. @@ -294,11 +288,11 @@ describe('MdInkRipple', () => { const customRadius = 42; controller.maxRadius = customRadius; fixture.detectChanges(); - // Click the container 50 px to the right and 75px down from its upper left. - const elementRect = container.getBoundingClientRect(); + // Click the ripple element 50 px to the right and 75px down from its upper left. + const elementRect = rippleElement.getBoundingClientRect(); const clickEvent = createMouseEvent('click', {clientX: elementRect.left + 50, clientY: elementRect.top + 75}); - container.dispatchEvent(clickEvent); + rippleElement.dispatchEvent(clickEvent); const expectedLeft = elementRect.left + 50 - customRadius; const expectedTop = elementRect.top + 75 - customRadius; @@ -314,9 +308,7 @@ describe('MdInkRipple', () => { @Component({ directives: [MdInkRipple], template: ` -
- - +
`, }) @@ -327,15 +319,14 @@ class BasicRippleContainer { @Component({ directives: [MdInkRipple], template: ` -
- +
`, diff --git a/src/components/ripple/ripple.ts b/src/core/ripple/ripple.ts similarity index 86% rename from src/components/ripple/ripple.ts rename to src/core/ripple/ripple.ts index 1aa806b1018d..2b80f754e19f 100644 --- a/src/components/ripple/ripple.ts +++ b/src/core/ripple/ripple.ts @@ -1,6 +1,5 @@ import { - ChangeDetectionStrategy, - Component, + Directive, ElementRef, HostBinding, Input, @@ -8,7 +7,6 @@ import { OnDestroy, OnInit, SimpleChange, - ViewEncapsulation, } from '@angular/core'; import { RippleRenderer, @@ -17,50 +15,45 @@ import { } from './ripple-renderer'; -@Component({ - moduleId: module.id, - template: `
`, - selector: 'md-ink-ripple', - styleUrls: ['ripple.css'], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, +@Directive({ + selector: '[md-ink-ripple]', }) export class MdInkRipple implements OnInit, OnDestroy, OnChanges { /** * The element that triggers the ripple when click events are received. Defaults to the parent * of the . */ - @Input() trigger: Element; + @Input('md-ink-ripple-trigger') trigger: HTMLElement; /** * Whether the ripple always originates from the center of the bounds, rather * than originating from the location of the click event. */ - @Input() centered: boolean; + @Input('md-ink-ripple-centered') centered: boolean; /** * Whether click events will not trigger the ripple. It can still be triggered by manually * calling start() and end(). */ - @Input() disabled: boolean; + @Input('md-ink-ripple-disabled') disabled: boolean; /** * If set, the radius in pixels of foreground ripples when fully expanded. If unset, the radius * will be the distance from the center of the ripple to the furthest corner of the element's * bounding rectangle. */ - @Input() maxRadius: number = 0; + @Input('md-ink-ripple-max-radius') maxRadius: number = 0; /** * If set, the normal duration of ripple animations is divided by this value. For example, * setting it to 0.5 will cause the animations to take twice as long. */ - @Input() speedFactor: number = 1; + @Input('md-ink-ripple-speed-factor') speedFactor: number = 1; /** Custom color for ripples. */ - @Input() color: string; + @Input('md-ink-ripple-color') color: string; /** Custom color for the ripple background. */ - @Input() backgroundColor: string; + @Input('md-ink-ripple-background-color') backgroundColor: string; /** Whether the ripple background will be highlighted to indicated a focused state. */ - @HostBinding('class.md-ripple-focused') @Input('focused') focused: boolean; + @HostBinding('class.md-ripple-focused') @Input('md-ink-ripple-focused') focused: boolean; /** Whether foreground ripples should be visible outside the component's bounds. */ - @HostBinding('class.md-ripple-unbounded') @Input('unbounded') unbounded: boolean; + @HostBinding('class.md-ripple-unbounded') @Input('md-ink-ripple-unbounded') unbounded: boolean; private _rippleRenderer: RippleRenderer; @@ -75,9 +68,9 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { /** TODO: internal */ ngOnInit() { - // If no trigger element was explicity set, use our parent. + // If no trigger element was explicity set, use the host element if (!this.trigger) { - this._rippleRenderer.setTriggerElementToParent(); + this._rippleRenderer.setTriggerElementToHost(); } } diff --git a/src/core/style/_ripple.scss b/src/core/style/_ripple.scss new file mode 100644 index 000000000000..30b20b027f14 --- /dev/null +++ b/src/core/style/_ripple.scss @@ -0,0 +1,62 @@ +@import 'default-theme'; +@import 'theme-functions'; + +$md-ink-ripple-focused-opacity: 0.1; +$md-ink-ripple-background-fade-duration: 300ms; +$md-ink-ripple-background-default-color: rgba(0, 0, 0, 0.0588); +$md-ink-ripple-foreground-initial-opacity: 0.25; +$md-ink-ripple-foreground-default-color: rgba(0, 0, 0, 0.0588); + +/** + * The host element of an md-ink-ripple directive should always have a position of "absolute" or + * "relative" so that the ripple divs it creates inside itself are correctly positioned. + */ +[md-ink-ripple] { + overflow: hidden; +} + +[md-ink-ripple].md-ripple-unbounded { + overflow: visible; +} + +.md-ripple-background { + background-color: $md-ink-ripple-background-default-color; + opacity: 0; + transition: opacity $md-ink-ripple-background-fade-duration linear; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} + +.md-ripple-unbounded .md-ripple-background { + display: none; +} + +.md-ripple-background.md-ripple-active { + opacity: 1; +} + +.md-ripple-focused .md-ripple-background { + background-color: md-color($md-accent, $md-ink-ripple-focused-opacity); + opacity: 1; +} + +.md-ripple-foreground { + background-color: $md-ink-ripple-foreground-default-color; + border-radius: 50%; + pointer-events: none; + opacity: $md-ink-ripple-foreground-initial-opacity; + position: absolute; + // The transition duration is manually set based on the ripple size. + transition: 'opacity, transform' 0ms cubic-bezier(0, 0, 0.2, 1); +} + +.md-ripple-foreground.md-ripple-fade-in { + opacity: 1; +} + +.md-ripple-foreground.md-ripple-fade-out { + opacity: 0; +} diff --git a/src/core/style/core.scss b/src/core/style/core.scss index 1725802f542e..b0027c1c4658 100644 --- a/src/core/style/core.scss +++ b/src/core/style/core.scss @@ -10,3 +10,5 @@ @include md-elevation($zValue); } } + +@import 'ripple'; diff --git a/src/demo-app/index.html b/src/demo-app/index.html index dcc54e5d0d55..d2ee30b106b5 100644 --- a/src/demo-app/index.html +++ b/src/demo-app/index.html @@ -14,6 +14,7 @@ + diff --git a/src/demo-app/ripple/ripple-demo.html b/src/demo-app/ripple/ripple-demo.html index e221746a49ea..e476e176d086 100644 --- a/src/demo-app/ripple/ripple-demo.html +++ b/src/demo-app/ripple/ripple-demo.html @@ -36,16 +36,15 @@
- + [class.demo-ripple-rounded]="rounded" + md-ink-ripple + [md-ink-ripple-centered]="centered" + [md-ink-ripple-disabled]="disabled" + [md-ink-ripple-unbounded]="unbounded" + [md-ink-ripple-max-radius]="maxRadius" + [md-ink-ripple-color]="rippleColor" + [md-ink-ripple-background-color]="rippleBackgroundColor" + [md-ink-ripple-speed-factor]="rippleSpeed"> Click me
diff --git a/src/demo-app/ripple/ripple-demo.ts b/src/demo-app/ripple/ripple-demo.ts index 445d41044fe1..ae673b2931d6 100644 --- a/src/demo-app/ripple/ripple-demo.ts +++ b/src/demo-app/ripple/ripple-demo.ts @@ -6,12 +6,12 @@ import {MD_BUTTON_DIRECTIVES} from '@angular2-material/button/button'; import {MD_CARD_DIRECTIVES} from '@angular2-material/card/card'; import {MD_CHECKBOX_DIRECTIVES} from '@angular2-material/checkbox/checkbox'; import {MD_ICON_DIRECTIVES} from '@angular2-material/icon/icon'; -import {MD_RIPPLE_DIRECTIVES, MdInkRipple} from '@angular2-material/ripple/ripple'; import {MD_INPUT_DIRECTIVES} from '@angular2-material/input/input'; import {MD_RADIO_DIRECTIVES} from '@angular2-material/radio/radio'; import { MdUniqueSelectionDispatcher } from '@angular2-material/core/coordination/unique-selection-dispatcher'; +import {MD_RIPPLE_DIRECTIVES, MdInkRipple} from '@angular2-material/core/core'; @Component({ moduleId: module.id, @@ -24,9 +24,9 @@ import { MD_CARD_DIRECTIVES, MD_CHECKBOX_DIRECTIVES, MD_ICON_DIRECTIVES, - MD_RIPPLE_DIRECTIVES, MD_INPUT_DIRECTIVES, MD_RADIO_DIRECTIVES, + MD_RIPPLE_DIRECTIVES, ], }) export class RippleDemo { diff --git a/src/demo-app/system-config.ts b/src/demo-app/system-config.ts index 4c7f91e6683f..a5b3dad5f39e 100644 --- a/src/demo-app/system-config.ts +++ b/src/demo-app/system-config.ts @@ -15,7 +15,6 @@ const components = [ 'progress-bar', 'progress-circle', 'radio', - 'ripple', 'sidenav', 'slider', 'slide-toggle', From 39b850bb372ddd59b697a869189fddbf4c044c28 Mon Sep 17 00:00:00 2001 From: Brian Nenninger Date: Fri, 22 Jul 2016 19:34:22 -0400 Subject: [PATCH 17/17] Change MdInkRipple identifiers to MdRipple, remove duplicate CSS file. --- src/core/core.ts | 2 +- src/core/ripple/README.md | 24 ++++----- src/core/ripple/ripple-renderer.ts | 12 ++--- src/core/ripple/ripple.scss | 79 ---------------------------- src/core/ripple/ripple.spec.ts | 34 ++++++------ src/core/ripple/ripple.ts | 32 +++++------ src/core/style/_ripple.scss | 26 ++++----- src/demo-app/ripple/ripple-demo.html | 16 +++--- src/demo-app/ripple/ripple-demo.ts | 4 +- 9 files changed, 75 insertions(+), 154 deletions(-) delete mode 100644 src/core/ripple/ripple.scss diff --git a/src/core/core.ts b/src/core/core.ts index 43d9c5e98821..004a65431b0a 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -30,7 +30,7 @@ export { export {MdGestureConfig} from './gestures/MdGestureConfig'; // Ripple -export {MD_RIPPLE_DIRECTIVES, MdInkRipple} from './ripple/ripple'; +export {MD_RIPPLE_DIRECTIVES, MdRipple} from './ripple/ripple'; // a11y export { diff --git a/src/core/ripple/README.md b/src/core/ripple/README.md index 42dadc9fc147..48f6dd628268 100644 --- a/src/core/ripple/README.md +++ b/src/core/ripple/README.md @@ -1,10 +1,10 @@ -# md-ink-ripple +# md-ripple -`md-ink-ripple` defines an area in which a ripple animates, usually in response to user action. It is used as an attribute directive, for example `
...
`. +`md-ripple` defines an area in which a ripple animates, usually in response to user action. It is used as an attribute directive, for example `
...
`. -By default, a ripple is activated when the host element of the `md-ink-ripple` directive receives mouse or touch events. On a mousedown or touch start, the ripple background fades in. When the click event completes, a circular foreground ripple fades in and expands from the event location to cover the host element bounds. +By default, a ripple is activated when the host element of the `md-ripple` directive receives mouse or touch events. On a mousedown or touch start, the ripple background fades in. When the click event completes, a circular foreground ripple fades in and expands from the event location to cover the host element bounds. -Ripples can also be triggered programmatically by getting a reference to the MdInkRipple directive and calling its `start` and `end` methods. +Ripples can also be triggered programmatically by getting a reference to the MdRipple directive and calling its `start` and `end` methods. ### Upcoming work @@ -17,11 +17,11 @@ Properties: | Name | Type | Description | | --- | --- | --- | -| `md-ink-ripple-trigger` | Element | The DOM element that triggers the ripple when clicked. Defaults to the parent of the `md-ink-ripple`. -| `md-ink-ripple-color` | string | Custom color for foreground ripples -| `md-ink-ripple-background-color` | string | Custom color for the ripple background -| `md-ink-ripple-centered` | boolean | If true, the ripple animation originates from the center of the `md-ink-ripple` bounds rather than from the location of the click event. -| `md-ink-ripple-max-radius` | number | Optional fixed radius of foreground ripples when fully expanded. Mainly used in conjunction with `unbounded` attribute. If not set, ripples will expand from their origin to the most distant corner of the component's bounding rectangle. -| `md-ink-ripple-unbounded` | boolean | If true, foreground ripples will be visible outside the component's bounds. -| `md-ink-ripple-focused` | boolean | If true, the background ripple is shown using the current theme's accent color to indicate focus. -| `md-ink-ripple-disabled` | boolean | If true, click events on the trigger element will not activate ripples. The `start` and `end` methods can still be called to programmatically create ripples. +| `md-ripple-trigger` | Element | The DOM element that triggers the ripple when clicked. Defaults to the parent of the `md-ripple`. +| `md-ripple-color` | string | Custom color for foreground ripples +| `md-ripple-background-color` | string | Custom color for the ripple background +| `md-ripple-centered` | boolean | If true, the ripple animation originates from the center of the `md-ripple` bounds rather than from the location of the click event. +| `md-ripple-max-radius` | number | Optional fixed radius of foreground ripples when fully expanded. Mainly used in conjunction with `unbounded` attribute. If not set, ripples will expand from their origin to the most distant corner of the component's bounding rectangle. +| `md-ripple-unbounded` | boolean | If true, foreground ripples will be visible outside the component's bounds. +| `md-ripple-focused` | boolean | If true, the background ripple is shown using the current theme's accent color to indicate focus. +| `md-ripple-disabled` | boolean | If true, click events on the trigger element will not activate ripples. The `start` and `end` methods can still be called to programmatically create ripples. diff --git a/src/core/ripple/ripple-renderer.ts b/src/core/ripple/ripple-renderer.ts index 89b84f522c6f..396c630c8cb9 100644 --- a/src/core/ripple/ripple-renderer.ts +++ b/src/core/ripple/ripple-renderer.ts @@ -33,8 +33,8 @@ const distanceToFurthestCorner = (x: number, y: number, rect: ClientRect) => { /** * Helper service that performs DOM manipulations. Not intended to be used outside this module. - * The constructor takes a reference to the element and a map of DOM event handlers - * to be installed on the element that triggers ripple animations. + * The constructor takes a reference to the ripple directive's host element and a map of DOM + * event handlers to be installed on the element that triggers ripple animations. * This will eventually become a custom renderer once Angular support exists. * TODO: internal */ @@ -73,7 +73,7 @@ export class RippleRenderer { } /** - * Installs event handlers on the host element of the md-ink-ripple directive. + * Installs event handlers on the host element of the md-ripple directive. */ setTriggerElementToHost() { this.setTriggerElement(this._rippleElement); @@ -88,7 +88,7 @@ export class RippleRenderer { /** * Creates a foreground ripple and sets its animation to expand and fade in from the position - * given by rippleOriginLeft and rippleOriginTop (or from the center of the + * given by rippleOriginLeft and rippleOriginTop (or from the center of the * bounding rect if centered is true). */ createForegroundRipple( @@ -156,7 +156,7 @@ export class RippleRenderer { } /** - * Fades in the background. + * Fades in the ripple background. */ fadeInRippleBackground(color: string) { this._backgroundDiv.classList.add('md-ripple-active'); @@ -165,7 +165,7 @@ export class RippleRenderer { } /** - * Fades out the background. + * Fades out the ripple background. */ fadeOutRippleBackground() { if (this._backgroundDiv) { diff --git a/src/core/ripple/ripple.scss b/src/core/ripple/ripple.scss deleted file mode 100644 index bedd57a13001..000000000000 --- a/src/core/ripple/ripple.scss +++ /dev/null @@ -1,79 +0,0 @@ -@import 'default-theme'; -@import 'theme-functions'; - -$md-ink-ripple-focused-opacity: 0.1; -$md-ink-ripple-background-fade-duration: 300ms; -$md-ink-ripple-background-default-color: rgba(0, 0, 0, 0.0588); -$md-ink-ripple-foreground-initial-opacity: 0.25; -$md-ink-ripple-foreground-default-color: rgba(0, 0, 0, 0.0588); - -/** - * An component should always have a position of "absolute" or "relative", - * so that the ripple divs it creates inside itself are correctly positioned. - */ -md-ink-ripple { - overflow: hidden; - // The default trigger for the ripple is the parent element, but we need to - // disable mouse events here to prevent the event from bubbling up to the - // parent. The can have larger bounds than its parent, and - // we don't want a click outside the parent to trigger the ripple. - pointer-events: none; -} - -md-ink-ripple.md-ripple-unbounded { - overflow: visible; -} - -/** - * Style that can be used by parent elements to specify that the - * should have the same bounds as its parent. - */ -md-ink-ripple.md-ripple-fit-parent { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; -} - -.md-ripple-background { - background-color: $md-ink-ripple-background-default-color; - opacity: 0; - transition: opacity $md-ink-ripple-background-fade-duration linear; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; -} - -md-ink-ripple.md-ripple-unbounded .md-ripple-background { - display: none; -} - -.md-ripple-background.md-ripple-active { - opacity: 1; -} - -md-ink-ripple.md-ripple-focused .md-ripple-background { - background-color: md-color($md-accent, $md-ink-ripple-focused-opacity); - opacity: 1; -} - -.md-ripple-foreground { - background-color: $md-ink-ripple-foreground-default-color; - border-radius: 50%; - pointer-events: none; - opacity: $md-ink-ripple-foreground-initial-opacity; - position: absolute; - // The transition duration is manually set based on the ripple size. - transition: 'opacity, transform' 0ms cubic-bezier(0, 0, 0.2, 1); -} - -.md-ripple-foreground.md-ripple-fade-in { - opacity: 1; -} - -.md-ripple-foreground.md-ripple-fade-out { - opacity: 0; -} diff --git a/src/core/ripple/ripple.spec.ts b/src/core/ripple/ripple.spec.ts index 5c09180e783f..c0c6b34e3a18 100644 --- a/src/core/ripple/ripple.spec.ts +++ b/src/core/ripple/ripple.spec.ts @@ -9,7 +9,7 @@ import { } from '@angular/core/testing'; import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing'; import {Component, ViewChild} from '@angular/core'; -import {MdInkRipple} from './ripple'; +import {MdRipple} from './ripple'; /** Creates a DOM event to indicate that a CSS transition for the given property ended. */ const createTransitionEndEvent = (propertyName: string) => { @@ -63,7 +63,7 @@ const pxStringToFloat = (s: string) => { return parseFloat(s.replace('px', '')); }; -describe('MdInkRipple', () => { +describe('MdRipple', () => { let builder: TestComponentBuilder; let fixture: ComponentFixture; let rippleElement: HTMLElement; @@ -90,7 +90,7 @@ describe('MdInkRipple', () => { fixture = f; fixture.detectChanges(); - rippleElement = fixture.debugElement.nativeElement.querySelector('[md-ink-ripple]'); + rippleElement = fixture.debugElement.nativeElement.querySelector('[md-ripple]'); rippleBackground = rippleElement.querySelector('.md-ripple-background'); expect(rippleBackground).toBeTruthy(); }); @@ -192,7 +192,7 @@ describe('MdInkRipple', () => { describe('configuring behavior', () => { let controller: RippleContainerWithInputBindings; - let rippleComponent: MdInkRipple; + let rippleComponent: MdRipple; beforeEach(async(() => { builder.createAsync(RippleContainerWithInputBindings).then(f => { @@ -201,7 +201,7 @@ describe('MdInkRipple', () => { controller = fixture.debugElement.componentInstance; rippleComponent = controller.ripple; - rippleElement = fixture.debugElement.nativeElement.querySelector('[md-ink-ripple]'); + rippleElement = fixture.debugElement.nativeElement.querySelector('[md-ripple]'); rippleBackground = rippleElement.querySelector('.md-ripple-background'); expect(rippleBackground).toBeTruthy(); }); @@ -306,27 +306,27 @@ describe('MdInkRipple', () => { }); @Component({ - directives: [MdInkRipple], + directives: [MdRipple], template: ` -
+
`, }) class BasicRippleContainer { - @ViewChild(MdInkRipple) ripple: MdInkRipple; + @ViewChild(MdRipple) ripple: MdRipple; } @Component({ - directives: [MdInkRipple], + directives: [MdRipple], template: `
+ md-ripple + [md-ripple-trigger]="trigger" + [md-ripple-centered]="centered" + [md-ripple-max-radius]="maxRadius" + [md-ripple-disabled]="disabled" + [md-ripple-color]="color" + [md-ripple-background-color]="backgroundColor">
`, @@ -338,5 +338,5 @@ class RippleContainerWithInputBindings { maxRadius = 0; color = ''; backgroundColor = ''; - @ViewChild(MdInkRipple) ripple: MdInkRipple; + @ViewChild(MdRipple) ripple: MdRipple; } diff --git a/src/core/ripple/ripple.ts b/src/core/ripple/ripple.ts index 2b80f754e19f..04e4c7afd8b0 100644 --- a/src/core/ripple/ripple.ts +++ b/src/core/ripple/ripple.ts @@ -16,44 +16,44 @@ import { @Directive({ - selector: '[md-ink-ripple]', + selector: '[md-ripple]', }) -export class MdInkRipple implements OnInit, OnDestroy, OnChanges { +export class MdRipple implements OnInit, OnDestroy, OnChanges { /** - * The element that triggers the ripple when click events are received. Defaults to the parent - * of the . + * The element that triggers the ripple when click events are received. Defaults to the + * directive's host element. */ - @Input('md-ink-ripple-trigger') trigger: HTMLElement; + @Input('md-ripple-trigger') trigger: HTMLElement; /** - * Whether the ripple always originates from the center of the bounds, rather + * Whether the ripple always originates from the center of the host element's bounds, rather * than originating from the location of the click event. */ - @Input('md-ink-ripple-centered') centered: boolean; + @Input('md-ripple-centered') centered: boolean; /** * Whether click events will not trigger the ripple. It can still be triggered by manually * calling start() and end(). */ - @Input('md-ink-ripple-disabled') disabled: boolean; + @Input('md-ripple-disabled') disabled: boolean; /** * If set, the radius in pixels of foreground ripples when fully expanded. If unset, the radius - * will be the distance from the center of the ripple to the furthest corner of the element's + * will be the distance from the center of the ripple to the furthest corner of the host element's * bounding rectangle. */ - @Input('md-ink-ripple-max-radius') maxRadius: number = 0; + @Input('md-ripple-max-radius') maxRadius: number = 0; /** * If set, the normal duration of ripple animations is divided by this value. For example, * setting it to 0.5 will cause the animations to take twice as long. */ - @Input('md-ink-ripple-speed-factor') speedFactor: number = 1; + @Input('md-ripple-speed-factor') speedFactor: number = 1; /** Custom color for ripples. */ - @Input('md-ink-ripple-color') color: string; + @Input('md-ripple-color') color: string; /** Custom color for the ripple background. */ - @Input('md-ink-ripple-background-color') backgroundColor: string; + @Input('md-ripple-background-color') backgroundColor: string; /** Whether the ripple background will be highlighted to indicated a focused state. */ - @HostBinding('class.md-ripple-focused') @Input('md-ink-ripple-focused') focused: boolean; + @HostBinding('class.md-ripple-focused') @Input('md-ripple-focused') focused: boolean; /** Whether foreground ripples should be visible outside the component's bounds. */ - @HostBinding('class.md-ripple-unbounded') @Input('md-ink-ripple-unbounded') unbounded: boolean; + @HostBinding('class.md-ripple-unbounded') @Input('md-ripple-unbounded') unbounded: boolean; private _rippleRenderer: RippleRenderer; @@ -165,4 +165,4 @@ export class MdInkRipple implements OnInit, OnDestroy, OnChanges { // TODO: Reactivate the background div if the user drags out and back in. } -export const MD_RIPPLE_DIRECTIVES = [MdInkRipple]; +export const MD_RIPPLE_DIRECTIVES = [MdRipple]; diff --git a/src/core/style/_ripple.scss b/src/core/style/_ripple.scss index 30b20b027f14..61355366f2e7 100644 --- a/src/core/style/_ripple.scss +++ b/src/core/style/_ripple.scss @@ -1,28 +1,28 @@ @import 'default-theme'; @import 'theme-functions'; -$md-ink-ripple-focused-opacity: 0.1; -$md-ink-ripple-background-fade-duration: 300ms; -$md-ink-ripple-background-default-color: rgba(0, 0, 0, 0.0588); -$md-ink-ripple-foreground-initial-opacity: 0.25; -$md-ink-ripple-foreground-default-color: rgba(0, 0, 0, 0.0588); +$md-ripple-focused-opacity: 0.1; +$md-ripple-background-fade-duration: 300ms; +$md-ripple-background-default-color: rgba(0, 0, 0, 0.0588); +$md-ripple-foreground-initial-opacity: 0.25; +$md-ripple-foreground-default-color: rgba(0, 0, 0, 0.0588); /** - * The host element of an md-ink-ripple directive should always have a position of "absolute" or + * The host element of an md-ripple directive should always have a position of "absolute" or * "relative" so that the ripple divs it creates inside itself are correctly positioned. */ -[md-ink-ripple] { +[md-ripple] { overflow: hidden; } -[md-ink-ripple].md-ripple-unbounded { +[md-ripple].md-ripple-unbounded { overflow: visible; } .md-ripple-background { - background-color: $md-ink-ripple-background-default-color; + background-color: $md-ripple-background-default-color; opacity: 0; - transition: opacity $md-ink-ripple-background-fade-duration linear; + transition: opacity $md-ripple-background-fade-duration linear; position: absolute; left: 0; top: 0; @@ -39,15 +39,15 @@ $md-ink-ripple-foreground-default-color: rgba(0, 0, 0, 0.0588); } .md-ripple-focused .md-ripple-background { - background-color: md-color($md-accent, $md-ink-ripple-focused-opacity); + background-color: md-color($md-accent, $md-ripple-focused-opacity); opacity: 1; } .md-ripple-foreground { - background-color: $md-ink-ripple-foreground-default-color; + background-color: $md-ripple-foreground-default-color; border-radius: 50%; pointer-events: none; - opacity: $md-ink-ripple-foreground-initial-opacity; + opacity: $md-ripple-foreground-initial-opacity; position: absolute; // The transition duration is manually set based on the ripple size. transition: 'opacity, transform' 0ms cubic-bezier(0, 0, 0.2, 1); diff --git a/src/demo-app/ripple/ripple-demo.html b/src/demo-app/ripple/ripple-demo.html index e476e176d086..9f09de717d09 100644 --- a/src/demo-app/ripple/ripple-demo.html +++ b/src/demo-app/ripple/ripple-demo.html @@ -37,14 +37,14 @@
+ md-ripple + [md-ripple-centered]="centered" + [md-ripple-disabled]="disabled" + [md-ripple-unbounded]="unbounded" + [md-ripple-max-radius]="maxRadius" + [md-ripple-color]="rippleColor" + [md-ripple-background-color]="rippleBackgroundColor" + [md-ripple-speed-factor]="rippleSpeed"> Click me
diff --git a/src/demo-app/ripple/ripple-demo.ts b/src/demo-app/ripple/ripple-demo.ts index ae673b2931d6..e2be9f254852 100644 --- a/src/demo-app/ripple/ripple-demo.ts +++ b/src/demo-app/ripple/ripple-demo.ts @@ -11,7 +11,7 @@ import {MD_RADIO_DIRECTIVES} from '@angular2-material/radio/radio'; import { MdUniqueSelectionDispatcher } from '@angular2-material/core/coordination/unique-selection-dispatcher'; -import {MD_RIPPLE_DIRECTIVES, MdInkRipple} from '@angular2-material/core/core'; +import {MD_RIPPLE_DIRECTIVES, MdRipple} from '@angular2-material/core/core'; @Component({ moduleId: module.id, @@ -30,7 +30,7 @@ import {MD_RIPPLE_DIRECTIVES, MdInkRipple} from '@angular2-material/core/core'; ], }) export class RippleDemo { - @ViewChild(MdInkRipple) manualRipple: MdInkRipple; + @ViewChild(MdRipple) manualRipple: MdRipple; centered = false; disabled = false;