From 8d66142dd5813a40f8a8d60a11c0ad38be9e9bc6 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Wed, 22 Nov 2017 09:41:41 -0600 Subject: [PATCH] refactor(toggle): use native input type=checkbox --- packages/core/src/components.d.ts | 1 + .../core/src/components/checkbox/checkbox.tsx | 1 + .../components/radio-group/radio-group.tsx | 1 + packages/core/src/components/radio/radio.tsx | 1 + packages/core/src/components/toggle/readme.md | 25 +++ .../core/src/components/toggle/toggle.scss | 22 ++ .../core/src/components/toggle/toggle.tsx | 211 ++++++++++-------- 7 files changed, 167 insertions(+), 95 deletions(-) diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index 58058f2bc3..9cd5451510 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -3246,6 +3246,7 @@ declare global { color?: string, mode?: 'ios' | 'md', + name?: string, checked?: boolean, disabled?: boolean, value?: string diff --git a/packages/core/src/components/checkbox/checkbox.tsx b/packages/core/src/components/checkbox/checkbox.tsx index 270ef88ee3..6063a927b5 100644 --- a/packages/core/src/components/checkbox/checkbox.tsx +++ b/packages/core/src/components/checkbox/checkbox.tsx @@ -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; diff --git a/packages/core/src/components/radio-group/radio-group.tsx b/packages/core/src/components/radio-group/radio-group.tsx index 5ecf276fbb..760cdf3416 100644 --- a/packages/core/src/components/radio-group/radio-group.tsx +++ b/packages/core/src/components/radio-group/radio-group.tsx @@ -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; diff --git a/packages/core/src/components/radio/radio.tsx b/packages/core/src/components/radio/radio.tsx index eb419c06ab..8b495493cd 100644 --- a/packages/core/src/components/radio/radio.tsx +++ b/packages/core/src/components/radio/radio.tsx @@ -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; diff --git a/packages/core/src/components/toggle/readme.md b/packages/core/src/components/toggle/readme.md index 9f1406cf79..a51be6d764 100644 --- a/packages/core/src/components/toggle/readme.md +++ b/packages/core/src/components/toggle/readme.md @@ -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 + + + + + Pepperoni + + + + + Sausage + + + + + Mushrooms + + + + +``` diff --git a/packages/core/src/components/toggle/toggle.scss b/packages/core/src/components/toggle/toggle.scss index 544caf8ee4..0ba8a067a0 100644 --- a/packages/core/src/components/toggle/toggle.scss +++ b/packages/core/src/components/toggle/toggle.scss @@ -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; +} diff --git a/packages/core/src/components/toggle/toggle.tsx b/packages/core/src/components/toggle/toggle.tsx index 5d683df76a..f4fabee47d 100644 --- a/packages/core/src/components/toggle/toggle.tsx +++ b/packages/core/src/components/toggle/toggle.tsx @@ -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; + @State() activated = false; - /** - * @output {Event} Emitted when the styles change. - */ - @Event() ionStyle: EventEmitter; - - /** - * @output {Event} Emitted when the toggle has focus. - */ - @Event() ionFocus: EventEmitter; - - /** - * @output {Event} Emitted when the toggle loses focus. - */ - @Event() ionBlur: EventEmitter; + @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; + + /** + * @output {Event} Emitted when the toggle has focus. + */ + @Event() ionFocus: EventEmitter; + + /** + * @output {Event} Emitted when the toggle loses focus. + */ + @Event() ionBlur: EventEmitter; + + /** + * @output {Event} Emitted when the styles change. + */ + @Event() ionStyle: EventEmitter; + + 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 [ + enabled={!this.disabled} tabIndex={-1}>
-
+
- - - ); +
+ , + this.nativeInput = (r as any)}/> + ]; } } @@ -220,3 +239,5 @@ function shouldToggle(checked: boolean, deltaX: number, margin: number): boolean (isRTL && (margin > deltaX)); } } + +let toggleIds = 0;