diff --git a/ionic/components/checkbox/checkbox.scss b/ionic/components/checkbox/checkbox.scss index 30dabefa0b..8653bfbe95 100644 --- a/ionic/components/checkbox/checkbox.scss +++ b/ionic/components/checkbox/checkbox.scss @@ -5,7 +5,6 @@ .checkbox { position: relative; - display: block; cursor: pointer; @include user-select-none(); } @@ -21,6 +20,10 @@ @include appearance(none); } +.checkbox .input-label { + max-width: 100%; +} + .checkbox[aria-disabled=true] { opacity: 0.5; color: gray; diff --git a/ionic/components/checkbox/checkbox.ts b/ionic/components/checkbox/checkbox.ts index a705ea12a2..99c816731d 100644 --- a/ionic/components/checkbox/checkbox.ts +++ b/ionic/components/checkbox/checkbox.ts @@ -18,7 +18,7 @@ import {Icon} from '../icon/icon'; @IonicComponent({ selector: 'ion-checkbox', host: { - '[class.item]': 'item', + 'class': 'item', '[attr.aria-checked]': 'input.checked' } }) @@ -46,8 +46,6 @@ export class Checkbox extends IonInputItem { this.cd = cd; cd.valueAccessor = this; - - this.item = true; } onInit() { diff --git a/ionic/components/form/label.scss b/ionic/components/form/label.scss index 6f951f44da..01fb3c600f 100644 --- a/ionic/components/form/label.scss +++ b/ionic/components/form/label.scss @@ -2,14 +2,12 @@ // Label // -------------------------------------------------- -$input-label-color: #444 !default; +$input-label-color: #888 !default; .input-label { display: block; max-width: 200px; - //width: 30%; - //min-width: 100px; color: $input-label-color; font-size: inherit; white-space: nowrap; diff --git a/ionic/components/form/label.ts b/ionic/components/form/label.ts index a89b651a5b..4086ddeada 100644 --- a/ionic/components/form/label.ts +++ b/ionic/components/form/label.ts @@ -5,6 +5,7 @@ import * as dom from '../../util/dom'; import {Input} from './text-input'; import {Checkbox} from '../checkbox/checkbox'; import {RadioButton} from '../radio/radio'; +import {Switch} from '../switch/switch'; @Directive({ @@ -23,9 +24,10 @@ export class Label { @Optional() @Parent() textContainer: Input, @Optional() @Parent() checkboxContainer: Checkbox, @Optional() @Parent() radioContainer: RadioButton, + @Optional() @Parent() switchContainer: Switch, config: IonicConfig ) { - this.container = textContainer || checkboxContainer || radioContainer; + this.container = textContainer || checkboxContainer || radioContainer || switchContainer; if (this.container) { this.container.registerLabel(this); diff --git a/ionic/components/form/test/fixed-inline-labels/main.html b/ionic/components/form/test/fixed-inline-labels/main.html index 2e273a83f1..9755bcce34 100644 --- a/ionic/components/form/test/fixed-inline-labels/main.html +++ b/ionic/components/form/test/fixed-inline-labels/main.html @@ -19,6 +19,9 @@ + @@ -27,16 +30,19 @@ + + + @@ -49,6 +55,7 @@ + diff --git a/ionic/components/form/test/inline-labels/main.html b/ionic/components/form/test/inline-labels/main.html index fe8507d7e4..2f1ebfa8bf 100644 --- a/ionic/components/form/test/inline-labels/main.html +++ b/ionic/components/form/test/inline-labels/main.html @@ -19,6 +19,9 @@ + @@ -27,16 +30,19 @@ + + + @@ -44,11 +50,13 @@ + + diff --git a/ionic/components/item/extensions/ios.scss b/ionic/components/item/extensions/ios.scss index ba74243c70..04bc251e04 100644 --- a/ionic/components/item/extensions/ios.scss +++ b/ionic/components/item/extensions/ios.scss @@ -83,6 +83,10 @@ $item-ios-note-color: #999 !default; font-size: 1.3rem; } + .item-input .input + button { + margin-top: $item-ios-padding-media-top; + } + .badge { margin-right: $item-ios-padding-right; } diff --git a/ionic/components/item/item.scss b/ionic/components/item/item.scss index 3683f65d9b..ce92d42e42 100644 --- a/ionic/components/item/item.scss +++ b/ionic/components/item/item.scss @@ -73,6 +73,8 @@ button.item.item { .item-content + .item-media, .item-content + .item-content, icon + .input, + .input + icon, + icon + .input-label, .input-label + .input { margin-left: 0; } diff --git a/ionic/components/radio/radio.ts b/ionic/components/radio/radio.ts index e5777d51c7..f046e5ce6a 100644 --- a/ionic/components/radio/radio.ts +++ b/ionic/components/radio/radio.ts @@ -71,7 +71,7 @@ export class RadioGroup extends Ion { @IonicComponent({ selector: 'ion-radio', host: { - '[class.item]': 'item', + 'class': 'item', '[attr.aria-checked]': 'input.checked', } }) @@ -91,7 +91,6 @@ export class RadioButton extends IonInputItem { config: IonicConfig ) { super(elementRef, config); - this.item = true; this.group = group; } diff --git a/ionic/components/switch/extensions/ios.scss b/ionic/components/switch/extensions/ios.scss index 313197f726..fab87b26ad 100644 --- a/ionic/components/switch/extensions/ios.scss +++ b/ionic/components/switch/extensions/ios.scss @@ -2,37 +2,86 @@ // iOS Switch // -------------------------------------------------- -$switch-ios-width: 52px !default; -$switch-ios-height: 32px !default; -$switch-ios-slider-off-background: #fff !default; -$switch-ios-slider-on-background: #4cd964 !default; -$switch-ios-toggle-on-background: #fff !default; +$switch-ios-width: 51px !default; +$switch-ios-height: 31px !default; +$switch-ios-border-width: 2px !default; +$switch-ios-border-radius: 30px !default; + +$switch-ios-off-bg-color: #fff !default; +$switch-ios-off-border-color: #e6e6e6 !default; + +$switch-ios-on-bg-color: get-color(primary, base) !default; +$switch-ios-on-border-color: $switch-ios-on-bg-color !default; + +$switch-ios-handle-width: $switch-ios-height - ($switch-ios-border-width * 2) !default; +$switch-ios-handle-height: $switch-ios-handle-width !default; +$switch-ios-handle-radius: $switch-ios-handle-width !default; +$switch-ios-handle-dragging-bg-color: darken(#fff, 5%) !default; +$switch-ios-handle-box-shadow: 0 3px 12px rgba(0, 0, 0, 0.16), 0 3px 1px rgba(0, 0, 0, 0.1), 0px 0px 1px rgba(0, 0, 0, 0.15) !default; + +$switch-ios-handle-off-bg-color: #fff !default; +$switch-ios-handle-on-bg-color: #fff !default; + +$switch-ios-hit-area-expansion: 5px !default; .switch[mode="ios"] { - .switch-toggle { + .media-switch { + margin-top: 5px; + margin-bottom: 5px; + } + + .switch-track { + /*@include transition-timing-function(ease-in-out); + @include transition-duration($switch-ios-transition-duration); + @include transition-property((background-color, border));*/ + position: relative; width: $switch-ios-width; height: $switch-ios-height; - border-radius: $switch-ios-height / 2; - - background: #e5e5e5; + border: solid $switch-ios-border-width $switch-ios-off-border-color; + border-radius: $switch-ios-border-radius; + background-color: $switch-ios-off-bg-color; + content: ' '; + cursor: pointer; + pointer-events: none; } - .switch-toggle:before { - background: $switch-ios-slider-off-background; + .switch-handle { + //@include transition($switch-ios-transition-duration cubic-bezier(0, 1.1, 1, 1.1)); + //@include transition-property((background-color, transform)); + + position: absolute; + + width: $switch-ios-handle-width; + height: $switch-ios-handle-height; + border-radius: $switch-ios-handle-radius; + background-color: $switch-ios-handle-off-bg-color; + top: 0; + left: 0; + box-shadow: $switch-ios-handle-box-shadow; + + &:before { + position: absolute; + top: -4px; + left: ( ($switch-ios-handle-width / 2) * -1) - 8; + padding: ($switch-ios-handle-height / 2) + 5 ($switch-ios-handle-width + 7); + content: ''; + } } - .switch-toggle:after { - box-shadow: 0 2px 5px rgba(0,0,0,.4); + &[aria-checked=true] .switch-track { + background-color: $switch-ios-on-bg-color; + border-color: $switch-ios-on-border-color; } - &[aria-checked=true] .switch-toggle { - background: $switch-ios-slider-on-background; + &[aria-checked=true] .switch-handle { + background-color: $switch-ios-handle-on-bg-color; + transform: translate3d($switch-ios-width - $switch-ios-handle-width - ($switch-ios-border-width * 2), 0, 0); } - &[aria-checked=true] .switch-toggle:before { - transform: scale(0); + .input-label { + color: inherit; } } diff --git a/ionic/components/switch/switch.scss b/ionic/components/switch/switch.scss index 8f18ea5b23..4a418bc61c 100644 --- a/ionic/components/switch/switch.scss +++ b/ionic/components/switch/switch.scss @@ -2,73 +2,29 @@ // Switch // -------------------------------------------------- -$switch-padding: 0 15px !default; -$switch-width: 52px !default; -$switch-height: 32px !default; -$switch-border-width: 2px !default; -$switch-slider-off-background: #ccc !default; -$switch-slider-on-background: #387ef5 !default; -$switch-toggle-on-background: #fff !default; - -.switch .item-media { - padding: $switch-padding; -} - -.switch-toggle { +.switch { position: relative; - - width: $switch-width; - height: $switch-height; - border-radius: $switch-height / 2; - - background: $switch-slider-off-background; + cursor: pointer; + @include user-select-none(); } -.switch-toggle:before { +.switch input { position: absolute; - left: $switch-border-width; - top: $switch-border-width; - - width: $switch-width - ($switch-border-width * 2); - height: $switch-height - ($switch-border-width * 2); - border-radius: $switch-height / 2; - - transition-duration: 300ms; - content: ' '; + width: 0; + height: 0; + margin: 0; + padding: 0; + opacity: 0; + border: none; + @include appearance(none); } -.switch[aria-checked=true] .switch-toggle { - background: $switch-slider-on-background; -} - -.switch .switch-toggle:after { - position: absolute; - left: $switch-border-width; - top: $switch-border-width; - - width: $switch-height - ($switch-border-width * 2); - height: $switch-height - ($switch-border-width * 2); - - border-radius: $switch-height - ($switch-border-width * 2); - background: $switch-toggle-on-background; - - transition-duration: 300ms; - - content: ' '; -} - -.switch[aria-checked=true] .switch-toggle:after { - transform: translate3d(20px,0,0); +.switch .input-label { + max-width: 100%; } .switch[aria-disabled=true] { - pointer-events: none; opacity: 0.5; color: gray; } - -.switch .item-media, -.switch .item-content { - pointer-events: none; -} diff --git a/ionic/components/switch/switch.ts b/ionic/components/switch/switch.ts index 5f90bc7eb6..6b7f08ae82 100644 --- a/ionic/components/switch/switch.ts +++ b/ionic/components/switch/switch.ts @@ -1,71 +1,127 @@ -import {View, ElementRef} from 'angular2/angular2'; -import {ControlGroup, ControlDirective} from 'angular2/forms'; +import { + View, + Directive, + ElementRef, + Renderer, + Optional, + Parent, + NgControl +} from 'angular2/angular2'; import {Ion} from '../ion'; +import {IonInputItem} from '../form/input'; import {IonicConfig} from '../../config/config'; -import {IonicComponent} from '../../config/annotations'; +import {IonicComponent, IonicView} from '../../config/annotations'; +import {Icon} from '../icon/icon'; @IonicComponent({ selector: 'ion-switch', - properties: [ - 'checked' - ], host: { - '(click)': 'switchClicked($event)', - 'class': 'item' + 'class': 'item', + //'[attr.aria-checked]': 'input.checked' } }) -@View({ - template: ` -
-
- -
-
-
-
-
` +@IonicView({ + template: + '
' + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' }) -export class Switch extends Ion { +export class Switch extends IonInputItem { constructor( + @Optional() cd: NgControl, + renderer: Renderer, elementRef: ElementRef, - ionicConfig: IonicConfig - //cd: ControlDirective + config: IonicConfig ) { - super(elementRef, ionicConfig) + super(elementRef, config); + this.onChange = (_) => {}; + this.onTouched = (_) => {}; + this.renderer = renderer; + this.elementRef = elementRef; + this.cd = cd; - // this.config = Switch.config.invoke(this) - // this.controlDirective = cd; - // cd.valueAccessor = this; - - // TODO: These rely on the commented-out PropertySetter's above - //setAriaRole('checkbox') - //setInvalid('false') - //setDisabled('false') - //this.setCheckedProperty = setAriaChecked + if(cd) cd.valueAccessor = this; } - /** - * Much like ngModel, this is called from our valueAccessor for the attached - * ControlDirective to update the value internally. - */ - writeValue(value) { - // Convert it to a boolean - this.checked = !!value; + onInit() { + super.onInit(); + console.log("switch onInit") } - set checked(checked) { - this._checked = checked - //this.setCheckedProperty(checked) - this.controlDirective._control().updateValue(this._checked); + onAllChangesDone() { + return + console.log("switch onAllChangesDone") + if (this._checked !== void 0 && this.input.checked != this._checked) { + if (this.input.checked !== void 0) { + console.warn("switch checked is set in view template and Control declaration.\n" + + "Value: " + !!this._checked + " from Control takes precedence"); + } + this.input.checked = !!this._checked; + } + if (this._value !== void 0 && this.input.value != this._value) { + if (this.input.value !== void 0) { + console.warn("switch value is set in view template and Control declaration.\n" + + "Value: " + this._value + " from Control takes precedence"); + } + this.input.value = this._value; + } + if (this.input.value === void 0) { + this.input.value = "on"; + } + if (this.input.checked === void 0) { + this.input.checked = false; + } + //TODO check validity + this.cd.control._value = {"checked": !!this.input.checked, "value": this.input.value}; + + //TODO only want to call this once, we want to set input.checked directly on subsequent + // writeValue's + this.onAllChangesDone = () => {}; + // this.onChange({"checked": this.input.checked, "value": this.input.value}); } - get checked() { - return this._checked + //from clicking the label or selecting with keyboard + //view -> model (Control) + toggle() { + this.input.checked = this._checked = !this.input.checked; + this.onChange({"checked": this.input.checked, "value": this.input.value}); } - switchClicked(ev) { - this.checked = !this.checked; + // Called by the model (Control) to update the view + writeValue(modelValue) { + let type = typeof modelValue; + switch (type) { + case "boolean": + // don't set input.value here, do it in onAllChangesDone + // because they might have set it in the view + this._checked = modelValue; break; + case "object": + if (modelValue.checked !== void 0) this._checked = !!modelValue.checked; + if (modelValue.value !== void 0) this._value = modelValue.value.toString(); + break; + default: + // don't set input.checked here, do it in onAllChangesDone + // because they might have set it in the view + this._value = modelValue.toString(); + } + + //TODO we want to set input.checked directly after the first time + console.log("writeValue, " + this.input.id + " checked: " + this._checked); + console.log("writeValue " + this.input.id + " value: " + this._value); + + // this.cd.control._value = {"checked": this.input.checked, "value": this.input.value}; } + + // Used by the view to update the model (Control) + // Up to us to call it in update() + registerOnChange(fn) { this.onChange = fn; } + + registerOnTouched(fn) { this.onTouched = fn; } } diff --git a/ionic/components/switch/test/basic/e2e.ts b/ionic/components/switch/test/basic/e2e.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ionic/components/switch/test/basic/index.ts b/ionic/components/switch/test/basic/index.ts index 99de26004d..e906bafacf 100644 --- a/ionic/components/switch/test/basic/index.ts +++ b/ionic/components/switch/test/basic/index.ts @@ -1,24 +1,32 @@ -import {FormBuilder, Validators} from 'angular2/forms'; - import {App} from 'ionic/ionic'; - +import { + Control, + ControlGroup, + NgForm, + formDirectives, + Validators, + NgControl, + ControlValueAccessor, + NgControlName, + NgFormModel, + FormBuilder +} from 'angular2/forms'; @App({ templateUrl: 'main.html' }) class IonicApp { constructor() { - - var fb = new FormBuilder(); - this.form = fb.group({ - enableFun: ['', Validators.required], - enableIceCream: [false, Validators.required], - enablePizza: [true, Validators.required] + this.fruitsForm = new ControlGroup({ + "appleCtrl": new Control({"checked": false, "value": "apple"}), + "bananaCtrl": new Control(true), + "cherryCtrl": new Control({"checked": false, "value": 12}), + "grapeCtrl": new Control("grape") }); } doSubmit(ev) { - console.log('Submitting form', this.form.value); - ev.preventDefault(); + console.log('Submitting form', this.fruitsForm.value); + event.preventDefault(); } } diff --git a/ionic/components/switch/test/basic/main.html b/ionic/components/switch/test/basic/main.html index bdb5c1cb63..494af7bb30 100644 --- a/ionic/components/switch/test/basic/main.html +++ b/ionic/components/switch/test/basic/main.html @@ -1,25 +1,35 @@ + +Switches + + -
+ + + + + + + + + + + + + + + + + + + + + + + -
Some Switches
- - Enable Fun? - - - Enable Ice Cream? - - - Enable Pizza? - -
- Is fun enabled? {{form.controls.enableFun.value}} -
- Is ice cream enabled? {{form.controls.enableIceCream.value}} -
- Is pizza enabled? {{form.controls.enablePizza.value}}
diff --git a/ionic/config/annotations.ts b/ionic/config/annotations.ts index c623c26a36..6beddb1f4d 100644 --- a/ionic/config/annotations.ts +++ b/ionic/config/annotations.ts @@ -65,7 +65,7 @@ export const IonicDirectives = [ forwardRef(() => Checkbox), forwardRef(() => RadioGroup), forwardRef(() => RadioButton), - //Switch + forwardRef(() => Switch), //SearchBar, // Input