mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
refactor(toggle): use native input type=checkbox
This commit is contained in:
1
packages/core/src/components.d.ts
vendored
1
packages/core/src/components.d.ts
vendored
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 -->
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user