From 86c6bf868d5bc20dd4a576b012460e493c2b69cf Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sat, 9 Jan 2016 21:55:28 +0100 Subject: [PATCH 1/3] feat(switch): add switch component and drag service - Improved Dragging as in Material 1, due Outter Drag - NgModel Support / NgControl - RTL Ported from Material 1 --- ember-cli-build.js | 2 +- src/components/switch/switch.html | 8 ++ src/components/switch/switch.scss | 220 ++++++++++++++++++++++++++++++ src/components/switch/switch.ts | 114 ++++++++++++++++ src/core/services/drag/drag.ts | 114 ++++++++++++++++ src/core/style/_mixins.scss | 21 +++ src/core/style/_variables.scss | 4 + src/demo-app/demo-app.html | 3 + src/demo-app/demo-app.ts | 4 +- src/index.html | 4 + 10 files changed, 492 insertions(+), 2 deletions(-) create mode 100644 src/components/switch/switch.html create mode 100644 src/components/switch/switch.scss create mode 100644 src/components/switch/switch.ts create mode 100644 src/core/services/drag/drag.ts create mode 100644 src/core/style/_mixins.scss diff --git a/ember-cli-build.js b/ember-cli-build.js index c76c8afe39b3..0cac710cd89a 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -16,7 +16,7 @@ module.exports = function(defaults) { return mergeTrees([ angularAppTree.toTree(), componentCssTree, - demoAppCssTree, + demoAppCssTree ]); }; diff --git a/src/components/switch/switch.html b/src/components/switch/switch.html new file mode 100644 index 000000000000..5ce0617ad0ed --- /dev/null +++ b/src/components/switch/switch.html @@ -0,0 +1,8 @@ +
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/src/components/switch/switch.scss b/src/components/switch/switch.scss new file mode 100644 index 000000000000..37b0dd332002 --- /dev/null +++ b/src/components/switch/switch.scss @@ -0,0 +1,220 @@ +@import "variables"; +@import "shadows"; +@import "mixins"; + +//TODO Temporary Theme +@import "default-theme"; + +$switch-width: 36px !default; +$switch-height: 8px * 3 !default; +$switch-bar-height: 14px !default; +$switch-thumb-size: 20px !default; +$switch-margin: 16px !default; + +.md-inline-form.md-switch { + margin-top: 18px; + margin-bottom: 19px; +} + +md-switch { + margin: $switch-margin 0; + white-space: nowrap; + cursor: pointer; + outline: none; + user-select: none; + height: 30px; + line-height: 28px; + align-items: center; + display: flex; + + @include rtl(margin-left, inherit, $switch-margin); + @include rtl(margin-right, $switch-margin, inherit); + + &:last-of-type { + @include rtl(margin-left, inherit, 0); + @include rtl(margin-right, 0, inherit); + } + + &[disabled] { + cursor: default; + + .md-container { + cursor: default; + } + } + + .md-container { + cursor: grab; + width: $switch-width; + height: $switch-height; + position: relative; + user-select: none; + margin-right: 8px; + float: left; + } + + &:not([disabled]) { + .md-dragging, + &.md-dragging .md-container { + cursor: grabbing; + } + } + + &.md-focused:not([disabled]) { + .md-thumb:before { + left: -8px; + top: -8px; + right: -8px; + bottom: -8px; + } + + &:not(.md-checked).md-thumb:before { + background-color: rgba(0, 0, 0, 0.12); + } + } + + .md-label { + border: 0 transparent; + float: left; + } + + .md-bar { + left: 1px; + width: $switch-width - 2px; + top: $switch-height / 2 - $switch-bar-height / 2; + height: $switch-bar-height; + border-radius: 8px; + position: absolute; + } + + .md-thumb-container { + top: $switch-height / 2 - $switch-thumb-size / 2; + left: 0; + width: $switch-width - $switch-thumb-size; + position: absolute; + transform: translate3d(0,0,0); + z-index: 1; + } + &.md-checked .md-thumb-container { + transform: translate3d(100%,0,0); + } + + .md-thumb { + position: absolute; + margin: 0; + left: 0; + top: 0; + outline: none; + height: $switch-thumb-size; + width: $switch-thumb-size; + border-radius: 50%; + box-shadow: $md-shadow-bottom-z-1; + + &:before { + background-color: transparent; + border-radius: 50%; + content: ''; + position: absolute; + display: block; + height: auto; + left: 0; + top: 0; + right: 0; + bottom: 0; + transition: all 0.5s; + width: auto; + } + } + + &:not(.md-dragging) { + .md-bar, + .md-thumb-container, + .md-thumb { + transition: $swift-linear; + transition-property: transform, background-color; + } + + .md-bar, + .md-thumb { + transition-delay: 0.05s; + } + } + + // COLOR THEMING + .md-thumb { + background-color: md-color($md-background, 50); + } + .md-bar { + background-color: md-color($md-background, 500); + } + + &.md-checked { + .md-ink-ripple { + color: md-color($md-accent); + } + + .md-thumb { + background-color: md-color($md-accent); + } + + .md-bar { + background-color: md-color($md-accent, 0.5); + } + + &.md-focused .md-thumb:before { + background-color: md-color($md-accent, 0.26); + } + + &.md-primary { + .md-ink-ripple { + color: md-color($md-primary); + } + .md-thumb { + background-color: md-color($md-primary); + } + .md-bar { + background-color: md-color($md-primary, 0.5); + } + &.md-focused .md-thumb:before { + background-color: md-color($md-primary, 0.26); + } + } + + &.md-warn { + .md-ink-ripple { + color: md-color($md-warn); + } + .md-thumb { + background-color: md-color($md-warn); + } + .md-bar { + background-color: md-color($md-warn, 0.5); + } + &.md-focused .md-thumb:before { + background-color: md-color($md-warn, 0.26); + } + } + } + + &[disabled] { + .md-thumb { + background-color: md-color($md-background, 400); + } + .md-bar { + background-color: md-color($md-foreground, 'divider'); + } + } +} + +@media screen and (-ms-high-contrast: active) { + md-switch.md-default-theme .md-bar { + background-color: #666; + } + md-switch.md-default-theme.md-checked .md-bar { + background-color: #9E9E9E; + } + md-switch.md-default-theme .md-thumb { + background-color: #fff; + } +} + diff --git a/src/components/switch/switch.ts b/src/components/switch/switch.ts new file mode 100644 index 000000000000..3873edccfc22 --- /dev/null +++ b/src/components/switch/switch.ts @@ -0,0 +1,114 @@ +import {Component, ViewEncapsulation, ElementRef} from 'angular2/core'; +import {MdDrag} from '../../core/services/drag/drag'; +import {ControlValueAccessor} from "angular2/common"; +import {NgControl} from "angular2/common"; +import {Optional} from "angular2/core"; + +@Component({ + selector: 'md-switch', + host: { + '(click)': 'onClick()' + }, + templateUrl: './components/switch/switch.html', + styleUrls: ['./components/switch/switch.css'], + encapsulation: ViewEncapsulation.None, +}) +export class MdSwitch implements ControlValueAccessor { + + elementRef: ElementRef; + componentElement: HTMLElement; + switchContainer: HTMLElement; + thumbContainer: HTMLElement; + + dragData: any; + dragClick = false; + + // Accessor Values + onChange = (_:any) => {}; + onTouched = () => {}; + + // Model Values + value: boolean; + + constructor(private _elementRef: ElementRef, @Optional() ngControl: NgControl) { + this.elementRef = _elementRef; + + if (ngControl) { + ngControl.valueAccessor = this; + } + } + + ngOnInit() { + this.componentElement = this.elementRef.nativeElement; + this.switchContainer = this.componentElement.querySelector('.md-container'); + this.thumbContainer = this.componentElement.querySelector('.md-thumb-container'); + + MdDrag.register(this.switchContainer); + + this.switchContainer.addEventListener('$md.dragstart', (ev: CustomEvent) => this.onDragStart(ev)); + this.switchContainer.addEventListener('$md.drag', (ev: CustomEvent) => this.onDrag(ev)); + this.switchContainer.addEventListener('$md.dragend', (ev: CustomEvent) => this.onDragEnd(ev)); + } + + + onDragStart(event: CustomEvent) { + this.componentElement.classList.add('md-dragging'); + + this.dragData = { + width: this.thumbContainer.offsetWidth + }; + + this.componentElement.classList.remove('transition') + } + + onDrag(event: CustomEvent) { + let percent = event.detail.pointer.distanceX / this.dragData.width; + + let translate = this.value ? 1 + percent : percent; + translate = Math.max(0, Math.min(1, translate)); + + this.thumbContainer.style.transform = 'translate3d(' + (100 * translate) + '%,0,0)'; + this.dragData.translate = translate; + } + + onDragEnd(event: CustomEvent) { + this.componentElement.classList.remove('md-dragging'); + this.thumbContainer.style.transform = null; + + + var isChanged = this.value ? this.dragData.translate < 0.5 : this.dragData.translate > 0.5; + if (isChanged || !this.dragData.translate) { + this.changeValue(!this.value); + } + + this.dragData = null; + + // Wait for incoming mouseup click + this.dragClick = true; + setTimeout(() => this.dragClick = false, 1); + } + + onClick() { + if (!this.dragClick) this.changeValue(!this.value); + } + + changeValue(newValue: boolean) { + this.onChange(newValue); + this.writeValue(newValue); + } + + writeValue(value: any): void { + this.value = !!value; + + // Apply Checked Class + this.componentElement.classList.toggle('md-checked', this.value); + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } +} \ No newline at end of file diff --git a/src/core/services/drag/drag.ts b/src/core/services/drag/drag.ts new file mode 100644 index 000000000000..967b24872898 --- /dev/null +++ b/src/core/services/drag/drag.ts @@ -0,0 +1,114 @@ +import {DOM} from "angular2/src/platform/dom/dom_adapter"; +import {Json} from "angular2/src/facade/lang"; + +export class MdDrag { + + private static START_EVENTS = ['mousedown', 'touchstart', 'pointerdown']; + private static MOVE_EVENTS = ['mousemove', 'touchmove', 'pointermove']; + private static END_EVENTS = ['mouseup', 'mouseleave', 'touchend', 'touchcancel', 'pointerup', 'pointercancel']; + + private static handlers: any[] = []; + private static currentItem: any; + + public static init() { + this.registerEvents(); + } + + public static terminate() { + this.START_EVENTS.forEach(entry => document.removeEventListener(entry, (ev: PointerEvent) => this.onStartDrag(ev))); + this.MOVE_EVENTS.forEach(entry => document.removeEventListener(entry, (ev: PointerEvent) => this.onMoveDrag(ev))); + this.END_EVENTS.forEach(entry => document.removeEventListener(entry, (ev: PointerEvent) => this.onStopDrag(ev))); + } + + public static register(element: any) { + element.$mdDrag = true; + this.handlers.push({ + element: element + }); + } + + private static registerEvents() { + this.START_EVENTS.forEach(entry => document.addEventListener(entry, (ev:PointerEvent) => this.onStartDrag(ev))); + this.MOVE_EVENTS.forEach(entry => document.addEventListener(entry, (ev:PointerEvent) => this.onMoveDrag(ev))); + this.END_EVENTS.forEach(entry => document.addEventListener(entry, (ev:PointerEvent) => this.onStopDrag(ev))); + } + private static createPointer(event: PointerEvent): any { + return { + startX: event.pageX, + startY: event.pageY, + distanceX: 0, + distanceY: 0 + }; + } + + private static updatePointer(event: PointerEvent, pointer: any): any { + pointer.distanceX = event.pageX - pointer.startX; + pointer.distanceY = event.pageY - pointer.startY; + return pointer; + } + + private static triggerEvent(element: Node, suffix: string, pointer: any) { + element.dispatchEvent(new CustomEvent('$md.' + suffix, { + detail: { + pointer: pointer + } + })); + } + + private static onStartDrag(event: PointerEvent) { + var element = this.getNearestParent(event.srcElement); + if (!element || this.handlers.indexOf(element) != -1) return; + event.preventDefault(); + + var item = this.findElement(this.handlers, 'element', element); + item.pointer = this.createPointer(event); + + this.triggerEvent(element, 'dragstart', item.pointer); + + this.currentItem = item; + } + + private static onMoveDrag(event: PointerEvent) { + if (!this.currentItem) return; + event.preventDefault(); + + this.currentItem.pointer = this.updatePointer(event, this.currentItem.pointer); + + this.triggerEvent(this.currentItem.element, 'drag', this.currentItem.pointer); + } + + private static onStopDrag(event: PointerEvent) { + if (!this.currentItem) return; + event.preventDefault(); + + this.currentItem.pointer = this.updatePointer(event, this.currentItem.pointer); + + this.triggerEvent(this.currentItem.element, 'dragend', this.currentItem.pointer); + + this.currentItem = null; + } + + //TODO Should be accesible in a util class + private static getNearestParent(node: any): Node { + var current = node; + while (current) { + if (current.$mdDrag) { + return current; + } + current = current.parentNode; + } + return null; + } + + private static findElement(arr: any[], propName: string, propValue: any): any { + for (var i = 0; i < arr.length; i++) { + if (arr[i][propName] == propValue) { + return arr[i]; + } + } + } + +} + +// Init Class +MdDrag.init(); \ No newline at end of file diff --git a/src/core/style/_mixins.scss b/src/core/style/_mixins.scss new file mode 100644 index 000000000000..ae3a85e57dfd --- /dev/null +++ b/src/core/style/_mixins.scss @@ -0,0 +1,21 @@ +@mixin rtl($prop, $value, $rtl-value) { + #{$prop}: $value; + + html[dir=rtl] & { + #{$prop}: $rtl-value; + unicode-bidi: embed; + } + body[dir=rtl] & { + #{$prop}: $rtl-value; + unicode-bidi: embed; + } + + bdo[dir=rtl] { + direction: rtl; + unicode-bidi: bidi-override; + } + bdo[dir=ltr] { + direction: ltr; + unicode-bidi: bidi-override; + } +} \ No newline at end of file diff --git a/src/core/style/_variables.scss b/src/core/style/_variables.scss index 880a00913366..a98e27665e73 100644 --- a/src/core/style/_variables.scss +++ b/src/core/style/_variables.scss @@ -20,3 +20,7 @@ $swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !defa $swift-ease-in-out-duration: 0.5s !default; $swift-ease-in-out-timing-function: cubic-bezier(0.35, 0, 0.25, 1) !default; $swift-ease-in-out: all $swift-ease-in-out-duration $swift-ease-in-out-timing-function !default; + +$swift-linear-duration: 0.08s !default; +$swift-linear-timing-function: linear !default; +$swift-linear: all $swift-linear-duration $swift-linear-timing-function !default; \ No newline at end of file diff --git a/src/demo-app/demo-app.html b/src/demo-app/demo-app.html index 8cfb86efe370..79f13fe18d1d 100644 --- a/src/demo-app/demo-app.html +++ b/src/demo-app/demo-app.html @@ -4,4 +4,7 @@ + + Toggle Angular Version + Value: {{ switchValue }}

diff --git a/src/demo-app/demo-app.ts b/src/demo-app/demo-app.ts index 9589221d781d..70c0bdcffae2 100644 --- a/src/demo-app/demo-app.ts +++ b/src/demo-app/demo-app.ts @@ -1,12 +1,14 @@ import {Component} from 'angular2/core'; import {MdButton} from '../components/button/button'; +import {MdSwitch} from '../components/switch/switch'; +import {FORM_DIRECTIVES} from "angular2/common"; @Component({ selector: 'demo-app', providers: [], templateUrl: 'demo-app/demo-app.html', - directives: [MdButton], + directives: [MdButton, MdSwitch, FORM_DIRECTIVES], pipes: [] }) export class DemoApp { } diff --git a/src/index.html b/src/index.html index c89b6cfc4383..9d4179b5ad0c 100644 --- a/src/index.html +++ b/src/index.html @@ -23,6 +23,10 @@ format: 'register', defaultExtension: 'js' }, + 'core': { + format: 'register', + defaultExtension: 'js' + }, 'components': { format: 'register', defaultExtension: 'js' From 08f0ca4ae23dafab75dcc453eec48c66cf42889c Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sun, 10 Jan 2016 13:55:26 +0100 Subject: [PATCH 2/3] test(switch): add basic test --- karma-test-shim.js | 14 ++++++- karma.conf.js | 2 +- src/components/switch/switch.spec.ts | 59 ++++++++++++++++++++++++++++ src/core/services/drag/drag.ts | 1 - 4 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 src/components/switch/switch.spec.ts diff --git a/karma-test-shim.js b/karma-test-shim.js index 6cca7c7f829f..536922f266fb 100644 --- a/karma-test-shim.js +++ b/karma-test-shim.js @@ -8,10 +8,11 @@ __karma__.loaded = function() {}; /** * Gets map of module alias to location or package. * @param dir Directory name under `src/` for create a map for. + * @param core Is that a map of the core files */ -function getPathsMap(dir) { +function getPathsMap(dir, core) { return Object.keys(window.__karma__.files) - .filter(isComponentsFile) + .filter(!!core ? isCoreFile : isComponentsFile) .reduce(function(pathsMapping, appPath) { var pathToReplace = new RegExp('^/base/dist/' + dir + '/'); var moduleName = appPath.replace(pathToReplace, './').replace(/\.js$/, ''); @@ -26,6 +27,11 @@ System.config({ defaultExtension: false, format: 'register', map: getPathsMap('components') + }, + 'base/dist/core': { + defaultExtension: false, + format: 'register', + map: getPathsMap('core', true) } } }); @@ -46,6 +52,10 @@ System.import('angular2/platform/browser').then(function(browser_adapter) { __karma__.error(error.stack || error); }); +function isCoreFile(filePath) { + return /^\/base\/dist\/core\/(?!spec)([a-z0-9-_\/]+)\.js$/.test(filePath); +} + function isComponentsFile(filePath) { return /^\/base\/dist\/components\/(?!spec)([a-z0-9-_\/]+)\.js$/.test(filePath); } diff --git a/karma.conf.js b/karma.conf.js index 2858b845050a..063e9d90d04a 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -31,7 +31,7 @@ module.exports = function(config) { proxies: { // required for component assests fetched by Angular's compiler "/demo-app/": "/base/dist/demo-app/", - "/components/": "/base/dist/components/", + "/components/": "/base/dist/components/" }, exclude: [], preprocessors: {}, diff --git a/src/components/switch/switch.spec.ts b/src/components/switch/switch.spec.ts new file mode 100644 index 000000000000..be87b80d9664 --- /dev/null +++ b/src/components/switch/switch.spec.ts @@ -0,0 +1,59 @@ +import { + it, + iit, + describe, + ddescribe, + expect, + inject, + injectAsync, + TestComponentBuilder, + beforeEachProviders, + beforeEach, +} from 'angular2/testing'; +import {provide, Component} from 'angular2/core'; +import {DebugElement} from "angular2/core"; +import {MdSwitch} from './switch'; +import {AsyncTestFn} from "angular2/testing"; +import {FORM_DIRECTIVES} from "angular2/common"; +import {Input} from "angular2/core"; + + +describe('MdSwitch', () => { + let builder: TestComponentBuilder; + + beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { builder = tcb; })); + + describe('md-switch', () => { + it('should change the model value', (done: () => void) => { + return builder.createAsync(TestApp).then((fixture) => { + let testComponent = fixture.debugElement.componentInstance; + let switchElement = getChildDebugElement(fixture.debugElement, 'md-switch'); + + expect(switchElement.nativeElement.classList.contains('md-checked')).toBe(false); + + testComponent.testSwitch = true; + + fixture.detectChanges(); + + expect(switchElement.nativeElement.classList.contains('md-checked')).toBe(true); + done(); + }); + }); + }); +}); + +/** Gets a child DebugElement by tag name. */ +function getChildDebugElement(parent: DebugElement, tagName: string): DebugElement { + return parent.query(debugEl => debugEl.nativeElement.tagName.toLowerCase() == tagName); +} + +/** Test component that contains an MdSwitch. */ +@Component({ + selector: 'test-app', + directives: [MdSwitch, FORM_DIRECTIVES], + template: + 'Test Switch', +}) +class TestApp { + testSwitch = false; +} diff --git a/src/core/services/drag/drag.ts b/src/core/services/drag/drag.ts index 967b24872898..e5a26f1a472c 100644 --- a/src/core/services/drag/drag.ts +++ b/src/core/services/drag/drag.ts @@ -1,5 +1,4 @@ import {DOM} from "angular2/src/platform/dom/dom_adapter"; -import {Json} from "angular2/src/facade/lang"; export class MdDrag { From 622357428acf4f6f84b2ab2fb13f8944084ddb25 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sun, 10 Jan 2016 20:28:31 +0100 Subject: [PATCH 3/3] feature(switch): add disabled input and test --- ember-cli-build.js | 2 +- src/components/switch/switch.spec.ts | 31 +++++++++---- src/components/switch/switch.ts | 65 +++++++++++++++++++++------- src/demo-app/demo-app.ts | 1 + src/index.html | 6 +++ 5 files changed, 79 insertions(+), 26 deletions(-) diff --git a/ember-cli-build.js b/ember-cli-build.js index 0cac710cd89a..95e39df7194a 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -9,7 +9,7 @@ var broccoliAutoprefixer = require('broccoli-autoprefixer'); var autoprefixerOptions = require('./build/autoprefixer-options'); module.exports = function(defaults) { - var demoAppCssTree = new BroccoliSass(['src/demo-app'], './demo-app.scss', 'demo-app.css'); + var demoAppCssTree = new BroccoliSass(['src/demo-app'], './demo-app.scss', 'demo-app/demo-app.css'); var componentCssTree = getComponentsCssTree(); var angularAppTree = new Angular2App(defaults); diff --git a/src/components/switch/switch.spec.ts b/src/components/switch/switch.spec.ts index be87b80d9664..3e450e0dc515 100644 --- a/src/components/switch/switch.spec.ts +++ b/src/components/switch/switch.spec.ts @@ -16,7 +16,7 @@ import {MdSwitch} from './switch'; import {AsyncTestFn} from "angular2/testing"; import {FORM_DIRECTIVES} from "angular2/common"; import {Input} from "angular2/core"; - +import {By} from 'angular2/platform/browser'; describe('MdSwitch', () => { let builder: TestComponentBuilder; @@ -24,10 +24,10 @@ describe('MdSwitch', () => { beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { builder = tcb; })); describe('md-switch', () => { - it('should change the model value', (done: () => void) => { + it('should change the model value', (done:() => void) => { return builder.createAsync(TestApp).then((fixture) => { let testComponent = fixture.debugElement.componentInstance; - let switchElement = getChildDebugElement(fixture.debugElement, 'md-switch'); + let switchElement = fixture.debugElement.query(By.css('md-switch')); expect(switchElement.nativeElement.classList.contains('md-checked')).toBe(false); @@ -39,21 +39,34 @@ describe('MdSwitch', () => { done(); }); }); + + it('should not change the model if disabled', (done:() => void) => { + return builder.createAsync(TestApp).then((fixture) => { + let testComponent = fixture.debugElement.componentInstance; + let switchElement = fixture.debugElement.query(By.css('md-switch')); + + expect(switchElement.nativeElement.classList.contains('md-checked')).toBe(false); + + testComponent.isDisabled = true; + fixture.detectChanges(); + + switchElement.nativeElement.click(); + + expect(switchElement.nativeElement.classList.contains('md-checked')).toBe(false); + done(); + }); + }); }); }); -/** Gets a child DebugElement by tag name. */ -function getChildDebugElement(parent: DebugElement, tagName: string): DebugElement { - return parent.query(debugEl => debugEl.nativeElement.tagName.toLowerCase() == tagName); -} - /** Test component that contains an MdSwitch. */ @Component({ selector: 'test-app', directives: [MdSwitch, FORM_DIRECTIVES], template: - 'Test Switch', + 'Test Switch', }) class TestApp { testSwitch = false; + isDisabled = false; } diff --git a/src/components/switch/switch.ts b/src/components/switch/switch.ts index 3873edccfc22..473d72289c93 100644 --- a/src/components/switch/switch.ts +++ b/src/components/switch/switch.ts @@ -3,10 +3,13 @@ import {MdDrag} from '../../core/services/drag/drag'; import {ControlValueAccessor} from "angular2/common"; import {NgControl} from "angular2/common"; import {Optional} from "angular2/core"; +import {Renderer} from "angular2/core"; @Component({ selector: 'md-switch', + inputs: ['disabled'], host: { + '[attr.aria-disabled]': 'disabled', '(click)': 'onClick()' }, templateUrl: './components/switch/switch.html', @@ -27,10 +30,12 @@ export class MdSwitch implements ControlValueAccessor { onChange = (_:any) => {}; onTouched = () => {}; - // Model Values - value: boolean; + // storage values + checked_: any; + disabled_: boolean; - constructor(private _elementRef: ElementRef, @Optional() ngControl: NgControl) { + constructor(private _elementRef: ElementRef, private _renderer: Renderer, @Optional() ngControl: NgControl) { + this.componentElement = _elementRef.nativeElement; this.elementRef = _elementRef; if (ngControl) { @@ -39,7 +44,6 @@ export class MdSwitch implements ControlValueAccessor { } ngOnInit() { - this.componentElement = this.elementRef.nativeElement; this.switchContainer = this.componentElement.querySelector('.md-container'); this.thumbContainer = this.componentElement.querySelector('.md-thumb-container'); @@ -48,10 +52,13 @@ export class MdSwitch implements ControlValueAccessor { this.switchContainer.addEventListener('$md.dragstart', (ev: CustomEvent) => this.onDragStart(ev)); this.switchContainer.addEventListener('$md.drag', (ev: CustomEvent) => this.onDrag(ev)); this.switchContainer.addEventListener('$md.dragend', (ev: CustomEvent) => this.onDragEnd(ev)); + } onDragStart(event: CustomEvent) { + if (this.disabled) return; + this.componentElement.classList.add('md-dragging'); this.dragData = { @@ -62,9 +69,11 @@ export class MdSwitch implements ControlValueAccessor { } onDrag(event: CustomEvent) { + if (this.disabled) return; + let percent = event.detail.pointer.distanceX / this.dragData.width; - let translate = this.value ? 1 + percent : percent; + let translate = this.checked ? 1 + percent : percent; translate = Math.max(0, Math.min(1, translate)); this.thumbContainer.style.transform = 'translate3d(' + (100 * translate) + '%,0,0)'; @@ -72,13 +81,15 @@ export class MdSwitch implements ControlValueAccessor { } onDragEnd(event: CustomEvent) { + if (this.disabled) return; + this.componentElement.classList.remove('md-dragging'); this.thumbContainer.style.transform = null; - var isChanged = this.value ? this.dragData.translate < 0.5 : this.dragData.translate > 0.5; + var isChanged = this.checked ? this.dragData.translate < 0.5 : this.dragData.translate > 0.5; if (isChanged || !this.dragData.translate) { - this.changeValue(!this.value); + this.checked = !this.checked; } this.dragData = null; @@ -89,19 +100,14 @@ export class MdSwitch implements ControlValueAccessor { } onClick() { - if (!this.dragClick) this.changeValue(!this.value); + if (!this.dragClick && !this.disabled) { + this.checked = !this.checked; + } } - changeValue(newValue: boolean) { - this.onChange(newValue); - this.writeValue(newValue); - } writeValue(value: any): void { - this.value = !!value; - - // Apply Checked Class - this.componentElement.classList.toggle('md-checked', this.value); + this.checked = value; } registerOnChange(fn: any): void { @@ -111,4 +117,31 @@ export class MdSwitch implements ControlValueAccessor { registerOnTouched(fn: any): void { this.onTouched = fn; } + + get disabled(): string|boolean { + return this.disabled_; + } + + set disabled(value: string|boolean) { + if (typeof value == 'string') { + this.disabled_ = (value === 'true' || value === ''); + } else { + this.disabled_ = value; + } + + this._renderer.setElementAttribute(this._elementRef, 'disabled', this.disabled_ ? 'true' : undefined); + } + + get checked() { + return !!this.checked_; + } + + set checked(value) { + this.checked_ = !!value; + this.onChange(this.checked_); + + this._renderer.setElementAttribute(this._elementRef, 'aria-checked', this.checked_); + this.componentElement.classList.toggle('md-checked', this.checked); + } + } \ No newline at end of file diff --git a/src/demo-app/demo-app.ts b/src/demo-app/demo-app.ts index 70c0bdcffae2..9edcb5dd2092 100644 --- a/src/demo-app/demo-app.ts +++ b/src/demo-app/demo-app.ts @@ -8,6 +8,7 @@ import {FORM_DIRECTIVES} from "angular2/common"; selector: 'demo-app', providers: [], templateUrl: 'demo-app/demo-app.html', + styleUrls: ['demo-app/demo-app.css'], directives: [MdButton, MdSwitch, FORM_DIRECTIVES], pipes: [] }) diff --git a/src/index.html b/src/index.html index 9d4179b5ad0c..a0b6f07ac945 100644 --- a/src/index.html +++ b/src/index.html @@ -6,6 +6,12 @@ {{content-for 'head'}} + +