refactor(toggle): use native input type=checkbox

This commit is contained in:
Adam Bradley
2017-11-22 09:41:41 -06:00
parent 5ffb31f49a
commit 8d66142dd5
7 changed files with 167 additions and 95 deletions

View File

@ -3246,6 +3246,7 @@ declare global {
color?: string, color?: string,
mode?: 'ios' | 'md', mode?: 'ios' | 'md',
name?: string,
checked?: boolean, checked?: boolean,
disabled?: boolean, disabled?: boolean,
value?: string value?: string

View File

@ -35,6 +35,7 @@ export class Checkbox implements CheckboxInput {
@Prop() mode: 'ios' | 'md'; @Prop() mode: 'ios' | 'md';
/** /**
* The name of the control, which is submitted with the form data.
*/ */
@Prop() name: string; @Prop() name: string;

View File

@ -24,6 +24,7 @@ export class RadioGroup implements ComponentDidLoad, RadioGroupInput {
@Prop({ mutable: true }) disabled = false; @Prop({ mutable: true }) disabled = false;
/** /**
* The name of the control, which is submitted with the form data.
*/ */
@Prop({ mutable: true }) name: string; @Prop({ mutable: true }) name: string;

View File

@ -37,6 +37,7 @@ export class Radio implements RadioButtonInput, ComponentDidLoad, ComponentDidUn
@Prop() mode: 'ios' | 'md'; @Prop() mode: 'ios' | 'md';
/** /**
* The name of the control, which is submitted with the form data.
*/ */
@Prop() name: string; @Prop() name: string;

View File

@ -1,5 +1,30 @@
# ion-toggle # ion-toggle
A toggle is largely the same thing as an [checkbox](../checkbox),
however, it also adds the ability to use gestures to swipe the
toggle on and off.
```html
<ion-list>
<ion-item>
<ion-label>Pepperoni</ion-label>
<ion-toggle value="pepperoni" checked></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Sausage</ion-label>
<ion-toggle value="sausage" disabled></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Mushrooms</ion-label>
<ion-toggle value="mushrooms"></ion-toggle>
</ion-item>
</ion-list>
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -41,3 +41,25 @@ ion-toggle ion-gesture {
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
} }
ion-toggle input {
@include position(0, null, null, 0);
position: absolute;
width: 100%;
height: 100%;
background: transparent;
cursor: pointer;
pointer-events: none;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
.toggle-key input {
border: 2px solid #5E9ED6;
}

View File

@ -1,5 +1,5 @@
import { BlurEvent, CheckboxInput, CheckedInputChangeEvent, FocusEvent, StyleEvent } from '../../utils/input-interfaces'; import { BlurEvent, CheckboxInput, CheckedInputChangeEvent, FocusEvent, StyleEvent } from '../../utils/input-interfaces';
import { Component, Event, EventEmitter, Listen, Method, Prop, PropDidChange, State } from '@stencil/core'; import { Component, Event, EventEmitter, Prop, PropDidChange, State } from '@stencil/core';
import { GestureDetail } from '../../index'; import { GestureDetail } from '../../index';
import { hapticSelection } from '../../utils/haptic'; import { hapticSelection } from '../../utils/haptic';
@ -15,34 +15,17 @@ import { hapticSelection } from '../../utils/haptic';
} }
}) })
export class Toggle implements CheckboxInput { export class Toggle implements CheckboxInput {
private toggleId: string; private didLoad: boolean;
private labelId: string;
private styleTmr: any;
private gestureConfig: any; private gestureConfig: any;
private inputId: string;
private nativeInput: HTMLInputElement;
private pivotX: number; private pivotX: number;
private styleTmr: any;
hasFocus: boolean = false;
@State() activated: boolean = false; @State() activated = false;
/**
* @output {Event} Emitted when the value property has changed.
*/
@Event() ionChange: EventEmitter<CheckedInputChangeEvent>;
/** @State() keyFocus: boolean;
* @output {Event} Emitted when the styles change.
*/
@Event() ionStyle: EventEmitter<StyleEvent>;
/**
* @output {Event} Emitted when the toggle has focus.
*/
@Event() ionFocus: EventEmitter<FocusEvent>;
/**
* @output {Event} Emitted when the toggle loses focus.
*/
@Event() ionBlur: EventEmitter<BlurEvent>;
/** /**
* @input {string} The color to use from your Sass `$colors` map. * @input {string} The color to use from your Sass `$colors` map.
@ -58,35 +41,47 @@ export class Toggle implements CheckboxInput {
*/ */
@Prop() mode: 'ios' | 'md'; @Prop() mode: 'ios' | 'md';
/**
* The name of the control, which is submitted with the form data.
*/
@Prop() name: string;
/** /**
* @input {boolean} If true, the toggle is selected. Defaults to `false`. * @input {boolean} If true, the toggle is selected. Defaults to `false`.
*/ */
@Prop({ mutable: true }) checked: boolean = false; @Prop({ mutable: true }) checked: boolean = false;
@PropDidChange('checked')
checkedChanged(isChecked: boolean) {
this.ionChange.emit({
checked: isChecked,
value: this.value
});
this.emitStyle();
}
/* /*
* @input {boolean} If true, the user cannot interact with the toggle. Default false. * @input {boolean} If true, the user cannot interact with the toggle. Default false.
*/ */
@Prop({ mutable: true }) disabled: boolean = false; @Prop({ mutable: true }) disabled: boolean = false;
@PropDidChange('disabled')
disabledChanged() {
this.emitStyle();
}
/** /**
* @input {string} the value of the toggle. * @input {string} the value of the toggle.
*/ */
@Prop({ mutable: true }) value: string; @Prop({ mutable: true }) value: string;
/**
* @output {Event} Emitted when the value property has changed.
*/
@Event() ionChange: EventEmitter<CheckedInputChangeEvent>;
/**
* @output {Event} Emitted when the toggle has focus.
*/
@Event() ionFocus: EventEmitter<FocusEvent>;
/**
* @output {Event} Emitted when the toggle loses focus.
*/
@Event() ionBlur: EventEmitter<BlurEvent>;
/**
* @output {Event} Emitted when the styles change.
*/
@Event() ionStyle: EventEmitter<StyleEvent>;
constructor() { constructor() {
this.gestureConfig = { this.gestureConfig = {
'onStart': this.onDragStart.bind(this), 'onStart': this.onDragStart.bind(this),
@ -102,36 +97,69 @@ export class Toggle implements CheckboxInput {
} }
componentWillLoad() { componentWillLoad() {
this.inputId = 'ion-tg-' + (toggleIds++);
if (this.value === undefined) {
this.value = this.inputId;
}
this.emitStyle(); this.emitStyle();
} }
@Listen('keydown.space') componentDidLoad() {
onSpace(ev: KeyboardEvent) { this.nativeInput.checked = this.checked;
this.toggle(); this.didLoad = true;
ev.stopPropagation();
ev.preventDefault(); const parentItem = this.nativeInput.closest('ion-item');
if (parentItem) {
const itemLabel = parentItem.querySelector('ion-label');
if (itemLabel) {
itemLabel.id = this.inputId + '-lbl';
this.nativeInput.setAttribute('aria-labelledby', itemLabel.id);
}
}
} }
@Method() @PropDidChange('checked')
toggle() { checkedChanged(isChecked: boolean) {
if (!this.disabled) { if (this.nativeInput.checked !== isChecked) {
this.checked = !this.checked; // keep the checked value and native input `nync
this.fireFocus(); this.nativeInput.checked = isChecked;
} }
return this.checked; if (this.didLoad) {
this.ionChange.emit({
checked: isChecked,
value: this.value
});
}
this.emitStyle();
}
@PropDidChange('disabled')
disabledChanged(isDisabled: boolean) {
this.nativeInput.disabled = isDisabled;
this.emitStyle();
}
emitStyle() {
clearTimeout(this.styleTmr);
this.styleTmr = setTimeout(() => {
this.ionStyle.emit({
'toggle-disabled': this.disabled,
'toggle-checked': this.checked,
'toggle-activated': this.activated
});
});
} }
private onDragStart(detail: GestureDetail) { private onDragStart(detail: GestureDetail) {
this.pivotX = detail.currentX; this.pivotX = detail.currentX;
this.activated = true; this.activated = true;
this.fireFocus();
} }
private onDragMove(detail: GestureDetail) { private onDragMove(detail: GestureDetail) {
const currentX = detail.currentX; const currentX = detail.currentX;
const checked = this.checked; if (shouldToggle(this.checked, currentX - this.pivotX, -15)) {
if (shouldToggle(checked, currentX - this.pivotX, -15)) { this.checked = !this.checked;
this.checked = !checked;
this.pivotX = currentX; this.pivotX = currentX;
hapticSelection(); hapticSelection();
} }
@ -139,43 +167,30 @@ export class Toggle implements CheckboxInput {
private onDragEnd(detail: GestureDetail) { private onDragEnd(detail: GestureDetail) {
const delta = detail.currentX - this.pivotX; const delta = detail.currentX - this.pivotX;
const checked = this.checked; if (shouldToggle(this.checked, delta, 4)) {
if (shouldToggle(checked, delta, 4)) { this.checked = !this.checked;
this.checked = !checked;
hapticSelection(); hapticSelection();
} }
this.activated = false; this.activated = false;
this.fireBlur(); this.nativeInput.focus();
} }
private emitStyle() { onChange() {
clearTimeout(this.styleTmr); this.checked = !this.checked;
this.styleTmr = setTimeout(() => {
this.ionStyle.emit({
'toggle-disabled': this.disabled,
'toggle-checked': this.checked,
'toggle-activated': this.activated,
'toggle-focus': this.hasFocus
});
});
} }
fireFocus() { onKeyUp() {
if (!this.hasFocus) { this.keyFocus = true;
this.hasFocus = true; }
onFocus() {
this.ionFocus.emit(); this.ionFocus.emit();
this.emitStyle();
}
} }
fireBlur() { onBlur() {
if (this.hasFocus) { this.keyFocus = false;
this.hasFocus = false;
this.ionBlur.emit(); this.ionBlur.emit();
this.emitStyle();
}
} }
hostData() { hostData() {
@ -183,29 +198,33 @@ export class Toggle implements CheckboxInput {
class: { class: {
'toggle-activated': this.activated, 'toggle-activated': this.activated,
'toggle-checked': this.checked, 'toggle-checked': this.checked,
'toggle-disabled': this.disabled 'toggle-disabled': this.disabled,
'toggle-key': this.keyFocus
} }
}; };
} }
render() { render() {
return ( return [
<ion-gesture {...this.gestureConfig} <ion-gesture {...this.gestureConfig}
enabled={!this.disabled}> enabled={!this.disabled} tabIndex={-1}>
<div class='toggle-icon'> <div class='toggle-icon'>
<div class='toggle-inner'></div> <div class='toggle-inner'/>
</div> </div>
<div <div class='toggle-cover'/>
class='toggle-cover' </ion-gesture>,
id={this.toggleId} <input
aria-checked={this.checked ? 'true' : false} type='checkbox'
aria-disabled={this.disabled ? 'true' : false} onChange={this.onChange.bind(this)}
aria-labelledby={this.labelId} onFocus={this.onFocus.bind(this)}
role='checkbox' onBlur={this.onBlur.bind(this)}
tabIndex={0}> onKeyUp={this.onKeyUp.bind(this)}
</div> id={this.inputId}
</ion-gesture> name={this.name}
); value={this.value}
disabled={this.disabled}
ref={r => this.nativeInput = (r as any)}/>
];
} }
} }
@ -220,3 +239,5 @@ function shouldToggle(checked: boolean, deltaX: number, margin: number): boolean
(isRTL && (margin > deltaX)); (isRTL && (margin > deltaX));
} }
} }
let toggleIds = 0;