refactor(checkbox): use native input type=checkbox

This commit is contained in:
Adam Bradley
2017-11-21 22:31:27 -06:00
parent 7a5682fbbe
commit fece34beb0
7 changed files with 153 additions and 88 deletions

View File

@ -594,6 +594,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

@ -123,6 +123,27 @@ $checkbox-ios-item-end-margin-start: 0 !default;
} }
// iOS Checkbox Keyboard Focus
// -----------------------------------------
.checkbox-key .checkbox-icon::after {
position: absolute;
top: -9px;
left: -9px;
display: block;
width: 36px;
height: 36px;
background: #86A8DF;
opacity: .3;
border-radius: 50%;
content: '';
}
// iOS Checkbox Within An Item // iOS Checkbox Within An Item
// ----------------------------------------- // -----------------------------------------

View File

@ -142,6 +142,27 @@ $checkbox-md-item-end-margin-start: 0 !default;
} }
// Material Design Checkbox Keyboard Focus
// -----------------------------------------
.checkbox-key .checkbox-icon::after {
position: absolute;
top: -12px;
left: -12px;
display: block;
width: 36px;
height: 36px;
background: #86A8DF;
opacity: .3;
border-radius: 50%;
content: '';
}
// Material Design Checkbox Within An Item // Material Design Checkbox Within An Item
// ----------------------------------------- // -----------------------------------------

View File

@ -9,7 +9,7 @@ ion-checkbox {
display: inline-block; display: inline-block;
} }
.checkbox-cover { ion-checkbox input {
@include position(0, null, null, 0); @include position(0, null, null, 0);
position: absolute; position: absolute;
@ -19,4 +19,8 @@ ion-checkbox {
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
} }

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, CssClassMap, Event, EventEmitter, Listen, Prop, PropDidChange } from '@stencil/core'; import { Component, CssClassMap, Event, EventEmitter, Prop, PropDidChange, State } from '@stencil/core';
@Component({ @Component({
@ -13,10 +13,46 @@ import { Component, CssClassMap, Event, EventEmitter, Listen, Prop, PropDidChang
} }
}) })
export class Checkbox implements CheckboxInput { export class Checkbox implements CheckboxInput {
private checkboxId: string; private didLoad: boolean;
private labelId: string; private inputId: string;
private nativeInput: HTMLInputElement;
private styleTmr: any; private styleTmr: any;
@State() keyFocus: boolean;
/**
* @input {string} The color to use from your Sass `$colors` map.
* Default options are: `"primary"`, `"secondary"`, `"danger"`, `"light"`, and `"dark"`.
* For more information, see [Theming your App](/docs/theming/theming-your-app).
*/
@Prop() color: string;
/**
* @input {string} The mode determines which platform styles to use.
* Possible values are: `"ios"` or `"md"`.
* For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
*/
@Prop() mode: 'ios' | 'md';
/**
*/
@Prop() name: string;
/**
* @input {boolean} If true, the checkbox is selected. Defaults to `false`.
*/
@Prop({ mutable: true }) checked = false;
/*
* @input {boolean} If true, the user cannot interact with the checkbox. Default false.
*/
@Prop() disabled = false;
/**
* @input {string} the value of the checkbox.
*/
@Prop({ mutable: true }) value: string;
/** /**
* @output {Event} Emitted when the checked property has changed. * @output {Event} Emitted when the checked property has changed.
*/ */
@ -37,55 +73,51 @@ export class Checkbox implements CheckboxInput {
*/ */
@Event() ionStyle: EventEmitter<StyleEvent>; @Event() ionStyle: EventEmitter<StyleEvent>;
/**
* @input {string} The color to use from your Sass `$colors` map.
* Default options are: `"primary"`, `"secondary"`, `"danger"`, `"light"`, and `"dark"`.
* For more information, see [Theming your App](/docs/theming/theming-your-app).
*/
@Prop() color: string;
/**
* @input {string} The mode determines which platform styles to use.
* Possible values are: `"ios"` or `"md"`.
* For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
*/
@Prop() mode: 'ios' | 'md';
/**
* @input {boolean} If true, the checkbox is selected. Defaults to `false`.
*/
@Prop({ mutable: true }) checked: boolean = false;
/*
* @input {boolean} If true, the user cannot interact with the checkbox. Default false.
*/
@Prop() disabled: boolean = false;
/**
* @input {string} the value of the checkbox.
*/
@Prop({ mutable: true }) value: string;
componentWillLoad() { componentWillLoad() {
this.inputId = 'ion-cb-' + (checkboxIds++);
if (this.value === undefined) {
this.value = this.inputId;
}
this.emitStyle(); this.emitStyle();
} }
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);
}
}
}
@PropDidChange('checked') @PropDidChange('checked')
checkedChanged(isChecked: boolean) { checkedChanged(isChecked: boolean) {
this.ionChange.emit({ if (this.nativeInput.checked !== isChecked) {
checked: isChecked, // keep the checked value and native input `nync
value: this.value this.nativeInput.checked = isChecked;
}); }
if (this.didLoad) {
this.ionChange.emit({
checked: isChecked,
value: this.value
});
}
this.emitStyle(); this.emitStyle();
} }
@PropDidChange('disabled') @PropDidChange('disabled')
disabledChanged() { disabledChanged(isDisabled: boolean) {
this.nativeInput.disabled = isDisabled;
this.emitStyle(); this.emitStyle();
} }
private emitStyle() { emitStyle() {
clearTimeout(this.styleTmr); clearTimeout(this.styleTmr);
this.styleTmr = setTimeout(() => { this.styleTmr = setTimeout(() => {
@ -96,22 +128,29 @@ export class Checkbox implements CheckboxInput {
}); });
} }
@Listen('keydown.space') onChange() {
onSpace(ev: KeyboardEvent) { this.checked = !this.checked;
this.toggle();
ev.stopPropagation();
ev.preventDefault();
} }
toggle() { onKeyUp() {
this.checked = !this.checked; this.keyFocus = true;
}
onFocus() {
this.ionFocus.emit();
}
onBlur() {
this.keyFocus = false;
this.ionBlur.emit();
} }
hostData() { hostData() {
return { return {
class: { class: {
'checkbox-checked': this.checked, 'checkbox-checked': this.checked,
'checkbox-disabled': this.disabled 'checkbox-disabled': this.disabled,
'checkbox-key': this.keyFocus
} }
}; };
} }
@ -126,16 +165,19 @@ export class Checkbox implements CheckboxInput {
<div class={checkboxClasses}> <div class={checkboxClasses}>
<div class='checkbox-inner'></div> <div class='checkbox-inner'></div>
</div>, </div>,
<button <input
class='checkbox-cover' type='checkbox'
onClick={() => this.toggle()} onChange={this.onChange.bind(this)}
id={this.checkboxId} onFocus={this.onFocus.bind(this)}
aria-checked={this.checked ? 'true' : false} onBlur={this.onBlur.bind(this)}
aria-disabled={this.disabled ? 'true' : false} onKeyUp={this.onKeyUp.bind(this)}
aria-labelledby={this.labelId} id={this.inputId}
role='checkbox' name={this.name}
tabIndex={0} value={this.value}
/> disabled={this.disabled}
ref={r => this.nativeInput = (r as any)}/>
]; ];
} }
} }
let checkboxIds = 0;

View File

@ -10,50 +10,22 @@ Placed in an `ion-item` or used as a stand-alone checkbox.
<ion-item> <ion-item>
<ion-label>Pepperoni</ion-label> <ion-label>Pepperoni</ion-label>
<ion-checkbox [(ngModel)]="pepperoni"></ion-checkbox> <ion-checkbox value="pepperoni" checked></ion-checkbox>
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label>Sausage</ion-label> <ion-label>Sausage</ion-label>
<ion-checkbox [(ngModel)]="sausage" disabled="true"></ion-checkbox> <ion-checkbox value="sausage" disabled></ion-checkbox>
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label>Mushrooms</ion-label> <ion-label>Mushrooms</ion-label>
<ion-checkbox [(ngModel)]="mushrooms"></ion-checkbox> <ion-checkbox value="mushrooms"></ion-checkbox>
</ion-item> </ion-item>
</ion-list> </ion-list>
``` ```
@advanced
```html
<!-- Call function when state changes -->
<ion-list>
<ion-item>
<ion-label>Cucumber</ion-label>
<ion-checkbox [(ngModel)]="cucumber" (ionChange)="updateCucumber()"></ion-checkbox>
</ion-item>
</ion-list>
```
```ts
@Component({
templateUrl: 'main.html'
})
class SaladPage {
cucumber: boolean;
updateCucumber() {
console.log('Cucumbers new state:' + this.cucumber);
}
}
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -40,6 +40,10 @@
<ion-label>Dark</ion-label> <ion-label>Dark</ion-label>
<ion-checkbox color="dark" checked></ion-checkbox> <ion-checkbox color="dark" checked></ion-checkbox>
</ion-item> </ion-item>
<ion-item>
<ion-label>Unchecked by Default</ion-label>
<ion-checkbox></ion-checkbox>
</ion-item>
<ion-item> <ion-item>
<ion-label>Disabled</ion-label> <ion-label>Disabled</ion-label>
<ion-checkbox disabled></ion-checkbox> <ion-checkbox disabled></ion-checkbox>