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,
mode?: 'ios' | 'md',
name?: string,
checked?: boolean,
disabled?: boolean,
value?: string

View File

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

View File

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

View File

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

View File

@ -1,5 +1,30 @@
# 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 -->

View File

@ -41,3 +41,25 @@ ion-toggle ion-gesture {
background: transparent;
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 { 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 { hapticSelection } from '../../utils/haptic';
@ -15,34 +15,17 @@ import { hapticSelection } from '../../utils/haptic';
}
})
export class Toggle implements CheckboxInput {
private toggleId: string;
private labelId: string;
private styleTmr: any;
private didLoad: boolean;
private gestureConfig: any;
private inputId: string;
private nativeInput: HTMLInputElement;
private pivotX: number;
private styleTmr: any;
hasFocus: boolean = false;
@State() activated: boolean = false;
/**
* @output {Event} Emitted when the value property has changed.
*/
@Event() ionChange: EventEmitter<CheckedInputChangeEvent>;
@State() activated = false;
/**
* @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>;
@State() keyFocus: boolean;
/**
* @input {string} The color to use from your Sass `$colors` map.
@ -58,35 +41,47 @@ export class Toggle implements CheckboxInput {
*/
@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`.
*/
@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.
*/
@Prop({ mutable: true }) disabled: boolean = false;
@PropDidChange('disabled')
disabledChanged() {
this.emitStyle();
}
/**
* @input {string} the value of the toggle.
*/
@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() {
this.gestureConfig = {
'onStart': this.onDragStart.bind(this),
@ -102,36 +97,69 @@ export class Toggle implements CheckboxInput {
}
componentWillLoad() {
this.inputId = 'ion-tg-' + (toggleIds++);
if (this.value === undefined) {
this.value = this.inputId;
}
this.emitStyle();
}
@Listen('keydown.space')
onSpace(ev: KeyboardEvent) {
this.toggle();
ev.stopPropagation();
ev.preventDefault();
componentDidLoad() {
this.nativeInput.checked = this.checked;
this.didLoad = true;
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()
toggle() {
if (!this.disabled) {
this.checked = !this.checked;
this.fireFocus();
@PropDidChange('checked')
checkedChanged(isChecked: boolean) {
if (this.nativeInput.checked !== isChecked) {
// keep the checked value and native input `nync
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) {
this.pivotX = detail.currentX;
this.activated = true;
this.fireFocus();
}
private onDragMove(detail: GestureDetail) {
const currentX = detail.currentX;
const checked = this.checked;
if (shouldToggle(checked, currentX - this.pivotX, -15)) {
this.checked = !checked;
if (shouldToggle(this.checked, currentX - this.pivotX, -15)) {
this.checked = !this.checked;
this.pivotX = currentX;
hapticSelection();
}
@ -139,43 +167,30 @@ export class Toggle implements CheckboxInput {
private onDragEnd(detail: GestureDetail) {
const delta = detail.currentX - this.pivotX;
const checked = this.checked;
if (shouldToggle(checked, delta, 4)) {
this.checked = !checked;
if (shouldToggle(this.checked, delta, 4)) {
this.checked = !this.checked;
hapticSelection();
}
this.activated = false;
this.fireBlur();
this.nativeInput.focus();
}
private emitStyle() {
clearTimeout(this.styleTmr);
this.styleTmr = setTimeout(() => {
this.ionStyle.emit({
'toggle-disabled': this.disabled,
'toggle-checked': this.checked,
'toggle-activated': this.activated,
'toggle-focus': this.hasFocus
});
});
onChange() {
this.checked = !this.checked;
}
fireFocus() {
if (!this.hasFocus) {
this.hasFocus = true;
this.ionFocus.emit();
this.emitStyle();
}
onKeyUp() {
this.keyFocus = true;
}
fireBlur() {
if (this.hasFocus) {
this.hasFocus = false;
this.ionBlur.emit();
this.emitStyle();
}
onFocus() {
this.ionFocus.emit();
}
onBlur() {
this.keyFocus = false;
this.ionBlur.emit();
}
hostData() {
@ -183,29 +198,33 @@ export class Toggle implements CheckboxInput {
class: {
'toggle-activated': this.activated,
'toggle-checked': this.checked,
'toggle-disabled': this.disabled
'toggle-disabled': this.disabled,
'toggle-key': this.keyFocus
}
};
}
render() {
return (
return [
<ion-gesture {...this.gestureConfig}
enabled={!this.disabled}>
enabled={!this.disabled} tabIndex={-1}>
<div class='toggle-icon'>
<div class='toggle-inner'></div>
<div class='toggle-inner'/>
</div>
<div
class='toggle-cover'
id={this.toggleId}
aria-checked={this.checked ? 'true' : false}
aria-disabled={this.disabled ? 'true' : false}
aria-labelledby={this.labelId}
role='checkbox'
tabIndex={0}>
</div>
</ion-gesture>
);
<div class='toggle-cover'/>
</ion-gesture>,
<input
type='checkbox'
onChange={this.onChange.bind(this)}
onFocus={this.onFocus.bind(this)}
onBlur={this.onBlur.bind(this)}
onKeyUp={this.onKeyUp.bind(this)}
id={this.inputId}
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));
}
}
let toggleIds = 0;