From 048a0cc42a5f3ee575d8205f0a70cfdcff3b34bb Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Tue, 21 Nov 2017 08:00:50 -0600 Subject: [PATCH] refactor(radio): native input type=radio --- packages/core/src/components.d.ts | 18 +- .../core/src/components/checkbox/checkbox.tsx | 18 +- packages/core/src/components/item/item.tsx | 122 +---------- .../components/radio-group/radio-group.tsx | 150 ++++++++++++++ .../core/src/components/radio-group/readme.md | 46 ++++ .../radio-group/test/form/index.html | 105 ++++++++++ .../core/src/components/radio/radio-group.tsx | 183 ---------------- packages/core/src/components/radio/radio.scss | 26 ++- packages/core/src/components/radio/radio.tsx | 196 ++++++++++-------- packages/core/src/components/radio/readme.md | 40 ++++ .../core/src/components/toggle/toggle.tsx | 15 +- packages/core/src/index.d.ts | 4 +- packages/core/src/utils/input-interfaces.d.ts | 131 ++++++++++-- packages/core/src/utils/input-interfaces.ts | 122 ----------- 14 files changed, 629 insertions(+), 547 deletions(-) create mode 100644 packages/core/src/components/radio-group/radio-group.tsx create mode 100644 packages/core/src/components/radio-group/readme.md create mode 100644 packages/core/src/components/radio-group/test/form/index.html delete mode 100644 packages/core/src/components/radio/radio-group.tsx create mode 100644 packages/core/src/components/radio/readme.md delete mode 100644 packages/core/src/utils/input-interfaces.ts diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index 5f0ee9b10f..8704692fbd 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -8,7 +8,7 @@ import 'ionicons'; import { ActionSheetButton, -} from './components/action-sheet/action-sheet.js'; +} from './components/action-sheet/action-sheet'; import { AnimationBuilder, PickerOptions, @@ -18,7 +18,7 @@ import { import { AlertButton, AlertInput, -} from './components/alert/alert.js'; +} from './components/alert/alert'; import { ElementRef, Side, @@ -26,20 +26,20 @@ import { import { GestureCallback, GestureDetail, -} from './components/gesture/gesture.js'; +} from './components/gesture/gesture'; import { PickerButton, PickerColumn as PickerColumn2, -} from './components/picker/picker.js'; +} from './components/picker/picker'; import { Event, } from '@stencil/core'; import { ScrollCallback, -} from './components/scroll/scroll.js'; +} from './components/scroll/scroll'; import { SelectPopoverOption, -} from './components/select/select-popover.js'; +} from './components/select/select-popover'; import { ActionSheetController as IonActionSheetController @@ -2159,7 +2159,7 @@ declare global { import { RadioGroup as IonRadioGroup -} from './components/radio/radio-group'; +} from './components/radio-group/radio-group'; declare global { interface HTMLIonRadioGroupElement extends IonRadioGroup, HTMLElement { @@ -2184,6 +2184,7 @@ declare global { allowEmptySelection?: boolean, disabled?: boolean, + name?: string, value?: string } } @@ -2217,8 +2218,9 @@ declare global { color?: string, mode?: 'ios' | 'md', - checked?: boolean, + name?: string, disabled?: boolean, + checked?: boolean, value?: string } } diff --git a/packages/core/src/components/checkbox/checkbox.tsx b/packages/core/src/components/checkbox/checkbox.tsx index 35d8666bf5..4161683ae2 100644 --- a/packages/core/src/components/checkbox/checkbox.tsx +++ b/packages/core/src/components/checkbox/checkbox.tsx @@ -1,4 +1,4 @@ -import { BlurEvent, BooleanInput, BooleanInputChangeEvent, 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'; /** @@ -76,7 +76,7 @@ import { Component, CssClassMap, Event, EventEmitter, Listen, Prop, PropDidChang theme: 'checkbox' } }) -export class Checkbox implements BooleanInput { +export class Checkbox implements CheckboxInput { private checkboxId: string; private labelId: string; private styleTmr: any; @@ -84,7 +84,7 @@ export class Checkbox implements BooleanInput { /** * @output {Event} Emitted when the checked property has changed. */ - @Event() ionChange: EventEmitter; + @Event() ionChange: EventEmitter; /** * @output {Event} Emitted when the toggle has focus. @@ -123,7 +123,7 @@ export class Checkbox implements BooleanInput { /* * @input {boolean} If true, the user cannot interact with the checkbox. Default false. */ - @Prop({ mutable: true }) disabled: boolean = false; + @Prop() disabled: boolean = false; /** * @input {string} the value of the checkbox. @@ -136,13 +136,16 @@ export class Checkbox implements BooleanInput { } @PropDidChange('checked') - protected checkedChanged(val: boolean) { - this.ionChange.emit({ checked: val }); + checkedChanged(isChecked: boolean) { + this.ionChange.emit({ + checked: isChecked, + value: this.value + }); this.emitStyle(); } @PropDidChange('disabled') - protected disabledChanged() { + disabledChanged() { this.emitStyle(); } @@ -157,7 +160,6 @@ export class Checkbox implements BooleanInput { }); } - @Listen('keydown.space') onSpace(ev: KeyboardEvent) { this.toggle(); diff --git a/packages/core/src/components/item/item.tsx b/packages/core/src/components/item/item.tsx index 1ce7506c5d..791ca60a8c 100644 --- a/packages/core/src/components/item/item.tsx +++ b/packages/core/src/components/item/item.tsx @@ -1,9 +1,8 @@ -import { Component, Element, Listen, Method, Prop } from '@stencil/core'; - +import { Component, Element, Listen, Prop, State } from '@stencil/core'; import { createThemedClasses } from '../../utils/theme'; - import { CssClassMap } from '../../index'; + @Component({ tag: 'ion-item', styleUrls: { @@ -12,15 +11,12 @@ import { CssClassMap } from '../../index'; } }) export class Item { - private ids: number = -1; - private itemId: string; - private inputs: any = []; - - private itemStyles: { [key: string]: CssClassMap } = Object.create(null); - private label: any; + private itemStyles: { [key: string]: CssClassMap } = {}; @Element() private el: HTMLElement; + @State() hasStyleChange: boolean; + /** * @input {string} The color to use from your Sass `$colors` map. * Default options are: `"primary"`, `"secondary"`, `"danger"`, `"light"`, and `"dark"`. @@ -60,27 +56,9 @@ export class Item { this.itemStyles[tagName] = updatedStyles; - // returning true tells the renderer to queue an update - return hasChildStyleChange; - } - - // TODO? this loads after radio group - // @Listen('ionRadioDidLoad') - // protected radioDidLoad(ev: RadioEvent) { - // const radio = ev.detail.radio; - // // register the input inside of the item - // // reset to the item's id instead of the radiogroup id - // radio.id = 'rb-' + this.registerInput('radio'); - // radio.labelId = 'lbl-' + this.itemId; - // } - - @Method() - getLabelText(): string { - return this.label ? this.label.getText() : ''; - } - - componentWillLoad() { - this.itemId = (++itemId).toString(); + if (hasChildStyleChange) { + this.hasStyleChange = true; + } } componentDidLoad() { @@ -89,34 +67,6 @@ export class Item { for (var i = 0; i < buttons.length; i++) { buttons[i].itemButton = true; } - - this.label = this.el.querySelector('ion-label'); - - // if (label) { - // this.label = label; - // this.labelId = label.id = ('lbl-' + this.itemId); - // if (label.type) { - // this.setElementClass('item-label-' + label.type, true); - // } - // this.viewLabel = false; - // } - - // if (this._viewLabel && this.inputs.length) { - // let labelText = this.getLabelText().trim(); - // this._viewLabel = (labelText.length > 0); - // } - - // if (this.inputs.length > 1) { - // this.setElementClass('item-multiple-inputs', true); - // } - } - - /** - * @hidden - */ - registerInput(type: string) { - this.inputs.push(type); - return this.itemId + '-' + (++this.ids); } render() { @@ -132,6 +82,8 @@ export class Item { 'item-block': true }; + this.hasStyleChange = false; + // TODO add support for button items const TagType = this.href ? 'a' : 'div'; @@ -149,58 +101,4 @@ export class Item { ); } - - // constructor() { - // this._setName(elementRef); - // this._hasReorder = !!reorder; - // this.itemId = form.nextId().toString(); - - // // auto add "tappable" attribute to ion-item components that have a click listener - // if (!(renderer).orgListen) { - // (renderer).orgListen = renderer.listen; - // renderer.listen = function(renderElement: HTMLElement, name: string, callback: Function): Function { - // if (name === 'click' && renderElement.setAttribute) { - // renderElement.setAttribute('tappable', ''); - // } - // return (renderer).orgListen(renderElement, name, callback); - // }; - // } - // } - - // /** - // * @hidden - // */ - // @ContentChild(Label) - // set contentLabel(label: Label) { - // if (label) { - // this._label = label; - // this.labelId = label.id = ('lbl-' + this.itemId); - // if (label.type) { - // this.setElementClass('item-label-' + label.type, true); - // } - // this._viewLabel = false; - // } - // } - - // /** - // * @hidden - // */ - // @ViewChild(Label) - // set viewLabel(label: Label) { - // if (!this._label) { - // this._label = label; - // } - // } - - // /** - // * @hidden - // */ - // @ContentChildren(Icon) - // set _icons(icons: QueryList) { - // icons.forEach(icon => { - // icon.setElementClass('item-icon', true); - // }); - // } } - -var itemId = -1; diff --git a/packages/core/src/components/radio-group/radio-group.tsx b/packages/core/src/components/radio-group/radio-group.tsx new file mode 100644 index 0000000000..3e36f31714 --- /dev/null +++ b/packages/core/src/components/radio-group/radio-group.tsx @@ -0,0 +1,150 @@ +import { Component, ComponentDidLoad, Element, Event, EventEmitter, Listen, Prop, PropDidChange, State } from '@stencil/core'; +import { HTMLIonRadioElementEvent } from '../radio/radio'; +import { RadioGroupInput, TextInputChangeEvent } from '../../utils/input-interfaces'; + + +@Component({ + tag: 'ion-radio-group' +}) +export class RadioGroup implements ComponentDidLoad, RadioGroupInput { + radios: HTMLIonRadioElement[] = []; + + @Element() el: HTMLElement; + + @State() labelId: string; + + /* + * @input {boolean} If true, the radios can be deselected. Default false. + */ + @Prop() allowEmptySelection = false; + + /* + * @input {boolean} If true, the user cannot interact with the radio group. Default false. + */ + @Prop({ mutable: true }) disabled = false; + + /** + */ + @Prop({ mutable: true }) name: string; + + /** + * @input {string} the value of the radio group. + */ + @Prop({ mutable: true }) value: string; + + @PropDidChange('value') + valueChanged() { + // this radio group's value just changed + // double check the button with this value is checked + if (this.value === undefined) { + // set to undefined + // ensure all that are checked become unchecked + this.radios.filter(r => r.checked).forEach(radio => { + radio.checked = false; + }); + + } else { + let hasChecked = false; + + this.radios.forEach(radio => { + if (radio.value === this.value) { + if (!radio.checked && !hasChecked) { + // correct value for this radio + // but this radio isn't checked yet + // and we haven't found a checked yet + // so CHECK IT! + radio.checked = true; + + } else if (hasChecked && radio.checked) { + // somehow we've got multiple radios + // with the same value, but only one can be checked + radio.checked = false; + } + + // remember we've got a checked radio button now + hasChecked = true; + + } else if (radio.checked) { + // this radio doesn't have the correct value + // and it's also checked, so let's uncheck it + radio.checked = false; + } + }); + } + + // emit the new value + this.ionChange.emit({ value: this.value }); + } + + /** + * @output {Event} Emitted when the value has changed. + */ + @Event() ionChange: EventEmitter; + + @Listen('ionRadioDidLoad') + onRadioDidLoad(ev: HTMLIonRadioElementEvent) { + const radio = ev.target; + this.radios.push(radio); + radio.name = this.name; + + if (radio.checked && !this.value) { + // set the initial value from the check radio's value + this.value = radio.value; + } + } + + @Listen('ionRadioDidUnload') + onRadioDidUnload(ev: HTMLIonRadioElementEvent) { + const index = this.radios.indexOf(ev.target); + if (index > -1) { + this.radios.splice(index, 1); + } + } + + @Listen('ionSelect') + onRadioSelect(ev: HTMLIonRadioElementEvent) { + // ionSelect only come from the checked radio button + this.radios.forEach(radio => { + if (radio === ev.target) { + this.value = radio.value; + } else { + radio.checked = false; + } + }); + } + + componentWillLoad() { + this.name = this.name || 'ion-rg-' + (radioGroupIds++); + } + + componentDidLoad() { + // Get the list header if it exists and set the id + // this is used to set aria-labelledby + let header = this.el.querySelector('ion-list-header'); + if (!header) { + header = this.el.querySelector('ion-item-divider'); + } + if (header) { + const label = header.querySelector('ion-label'); + if (label) { + this.labelId = label.id = this.name + '-lbl'; + } + } + } + + hostData() { + const hostAttrs: any = { + 'role': 'radiogroup' + }; + if (this.labelId) { + hostAttrs['aria-labelledby'] = this.labelId; + } + return hostAttrs; + } + + render() { + return ; + } +} + +let radioGroupIds = 0; diff --git a/packages/core/src/components/radio-group/readme.md b/packages/core/src/components/radio-group/readme.md new file mode 100644 index 0000000000..0ff20b6c91 --- /dev/null +++ b/packages/core/src/components/radio-group/readme.md @@ -0,0 +1,46 @@ +# ion-radio-group + +A radio group is a group of [radio buttons](../radio). It allows +a user to select at most one radio button from a set. Checking one radio +button that belongs to a radio group unchecks any previous checked +radio button within the same group. + + +```html + + + + + + Auto Manufacturers + + + + Cord + + + + + Duesenberg + + + + + Hudson + + + + + Packard + + + + + Studebaker + + + + + + +``` diff --git a/packages/core/src/components/radio-group/test/form/index.html b/packages/core/src/components/radio-group/test/form/index.html new file mode 100644 index 0000000000..9e2cb9a45e --- /dev/null +++ b/packages/core/src/components/radio-group/test/form/index.html @@ -0,0 +1,105 @@ + + + + + Radio Group - Form + + + + + + + + + + Radio Group - Form + + + + +
+ + + + + + + Luckiest Man On Earth + + + + Biff + + + + + Griff + + + + + Buford + + + + + George + + + + + Submit + + + + + + +
+ +

+ Value: +

+ +

+ Changes: 0 +

+ +
+ +
+ + + +
+ + + + + diff --git a/packages/core/src/components/radio/radio-group.tsx b/packages/core/src/components/radio/radio-group.tsx deleted file mode 100644 index 92d628aa6a..0000000000 --- a/packages/core/src/components/radio/radio-group.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { Component, Element, Event, EventEmitter, Listen, Prop, PropDidChange, State} from '@stencil/core'; - -import { isCheckedProperty } from '../../utils/helpers'; - -import { Radio, RadioEvent } from './radio'; - - -/** - * @name RadioGroup - * @description - * A radio group is a group of [radio buttons](../RadioButton). It allows - * a user to select at most one radio button from a set. Checking one radio - * button that belongs to a radio group unchecks any previous checked - * radio button within the same group. - * - * See the [Angular Forms Docs](https://angular.io/docs/ts/latest/guide/forms.html) - * for more information on forms and inputs. - * - * @usage - * ```html - * - * - * - * Auto Manufacturers - * - * - * - * Cord - * - * - * - * - * Duesenberg - * - * - * - * - * Hudson - * - * - * - * - * Packard - * - * - * - * - * Studebaker - * - * - * - * - * ``` - * - * @demo /docs/demos/src/radio/ - * @see {@link /docs/components#radio Radio Component Docs} - * @see {@link ../RadioButton RadioButton API Docs} -*/ -@Component({ - tag: 'ion-radio-group' -}) -export class RadioGroup { - radios: Radio[] = []; - radioGroupId: number; - ids = 0; - - @Element() private el: HTMLElement; - - @State() activeId: string; - @State() headerId: string; - - - /** - * @output {Event} Emitted when the value has changed. - */ - @Event() ionChange: EventEmitter; - - /* - * @input {boolean} If true, the radios can be deselected. Default false. - */ - @Prop() allowEmptySelection: boolean = false; - - /* - * @input {boolean} If true, the user cannot interact with the radio group. Default false. - */ - @Prop({ mutable: true }) disabled: boolean = false; - - /** - * @input {string} the value of the radio group. - */ - @Prop({ mutable: true }) value: string; - - @PropDidChange('value') - protected valueChanged() { - this.update(); - this.ionChange.emit(this); - } - - @Listen('ionRadioDidLoad') - protected radioDidLoad(ev: RadioEvent) { - const radio = ev.detail.radio; - this.radios.push(radio); - radio.radioId = 'rb-' + this.radioGroupId + '-' + (++this.ids); - - // if the value is not defined then use its unique id - radio.value = !radio.value ? radio.radioId : radio.value; - - if (radio.checked && !this.value) { - this.value = radio.value; - } - } - - @Listen('ionRadioCheckedDidChange') - protected radioCheckedDidChange(ev: RadioEvent) { - const radio = ev.detail.radio; - - // TODO shouldn't be able to set radio checked to false - // if allowEmptySelection is false - if (radio.checked && this.value !== radio.value) { - this.value = radio.checked ? radio.value : ''; - } - } - - @Listen('ionRadioDidToggle') - protected radioDidToggle(ev: RadioEvent) { - const radio = ev.detail.radio; - - // If the group does not allow empty selection then checked - // should be true, otherwise leave it as is - radio.checked = this.allowEmptySelection ? radio.checked : true; - this.value = radio.checked ? radio.value : ''; - } - - componentWillLoad() { - this.radioGroupId = ++radioGroupIds; - - // Get the list header if it exists and set the id - const header = this.el.querySelector('ion-list-header'); - - if (header) { - if (!header.id) { - header.id = 'rg-hdr-' + this.radioGroupId; - } - this.headerId = header.id; - } - - } - - - /** - * @hidden - */ - update() { - // loop through each of the radios - let hasChecked = false; - this.radios.forEach((radio: Radio) => { - - // Check the radio if the value is the same as the group value - radio.checked = isCheckedProperty(this.value, radio.value) && !hasChecked; - - if (radio.checked) { - // if this button is checked, then set it as - // the radiogroup's active descendant - this.activeId = radio.radioId; - hasChecked = true; - } - }); - } - - hostData() { - return { - 'role': 'radiogroup', - 'aria-activedescendant': this.activeId, - 'aria-describedby': this.headerId - }; - } - - render() { - return ; - } -} - -let radioGroupIds = -1; diff --git a/packages/core/src/components/radio/radio.scss b/packages/core/src/components/radio/radio.scss index e434802a29..5370390b52 100644 --- a/packages/core/src/components/radio/radio.scss +++ b/packages/core/src/components/radio/radio.scss @@ -9,7 +9,7 @@ ion-radio { display: inline-block; } -.radio-cover { +ion-radio input { @include position(0, null, null, 0); position: absolute; @@ -19,4 +19,28 @@ ion-radio { background: transparent; cursor: pointer; + + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.radio-outline { + display: none; +} + +.radio-key .radio-outline { + position: absolute; + top: 5px; + left: 10px; + + display: block; + + width: 36px; + height: 36px; + + background: #86A8DF; + opacity: .3; + + border-radius: 50%; } diff --git a/packages/core/src/components/radio/radio.tsx b/packages/core/src/components/radio/radio.tsx index 53f5a9bf49..38cee67124 100644 --- a/packages/core/src/components/radio/radio.tsx +++ b/packages/core/src/components/radio/radio.tsx @@ -1,43 +1,8 @@ -import { Component, CssClassMap, Event, EventEmitter, Listen, Prop, PropDidChange, State } from '@stencil/core'; - +import { BlurEvent, CheckedInputChangeEvent, FocusEvent, RadioButtonInput, StyleEvent } from '../../utils/input-interfaces'; +import { Component, ComponentDidLoad, ComponentDidUnload, ComponentWillLoad, CssClassMap, Event, EventEmitter, Listen, Prop, PropDidChange, State } from '@stencil/core'; import { createThemedClasses } from '../../utils/theme'; -/** - * @description - * A radio button is a button that can be either checked or unchecked. A user can tap - * the button to check or uncheck it. It can also be checked from the template using - * the `checked` property. - * - * Use an element with a `radio-group` attribute to group a set of radio buttons. When - * radio buttons are inside a [radio group](../RadioGroup), exactly one radio button - * in the group can be checked at any time. If a radio button is not placed in a group, - * they will all have the ability to be checked at the same time. - * - * See the [Angular Forms Docs](https://angular.io/docs/ts/latest/guide/forms.html) for - * more information on forms and input. - * - * @usage - * ```html - * - * - * Friends - * - * - * - * Family - * - * - * - * Enemies - * - * - * - * ``` - * @demo /docs/demos/src/radio/ - * @see {@link /docs/components#radio Radio Component Docs} - * @see {@link ../RadioGroup RadioGroup API Docs} - */ @Component({ tag: 'ion-radio', styleUrls: { @@ -48,13 +13,14 @@ import { createThemedClasses } from '../../utils/theme'; theme: 'radio' } }) -export class Radio { - labelId: string; +export class Radio implements RadioButtonInput, ComponentDidLoad, ComponentDidUnload, ComponentWillLoad { + didLoad: boolean; + inputId: string; + nativeInput: HTMLInputElement; styleTmr: any; - @State() radioId: string; - @State() activated: boolean; + @State() keyFocus: boolean; /** * @output {RadioEvent} Emitted when the radio loads. @@ -66,25 +32,25 @@ export class Radio { */ @Event() ionRadioDidUnload: EventEmitter; - /** - * @output {RadioEvent} Emitted when the radio is toggled. - */ - @Event() ionRadioDidToggle: EventEmitter; - - /** - * @output {RadioEvent} Emitted when the radio checked property is changed. - */ - @Event() ionRadioCheckedDidChange: EventEmitter; - /** * @output {Event} Emitted when the styles change. */ - @Event() ionStyle: EventEmitter; + @Event() ionStyle: EventEmitter; /** - * @output {Event} Emitted when the radio is selected. + * @output {Event} Emitted when the radio button is selected. */ - @Event() ionSelect: EventEmitter; + @Event() ionSelect: EventEmitter; + + /** + * @output {Event} Emitted when the radio button has focus. + */ + @Event() ionFocus: EventEmitter; + + /** + * @output {Event} Emitted when the radio button loses focus. + */ + @Event() ionBlur: EventEmitter; /** * @input {string} The color to use from your Sass `$colors` map. @@ -101,26 +67,46 @@ export class Radio { @Prop() mode: 'ios' | 'md'; /** - * @input {boolean} If true, the radio is selected. Defaults to `false`. */ - @Prop({ mutable: true }) checked: boolean = false; + @Prop() name: string; /* * @input {boolean} If true, the user cannot interact with the radio. Default false. */ - @Prop({ mutable: true }) disabled: boolean = false; + @Prop() disabled = false; + + /** + * @input {boolean} If true, the radio is selected. Defaults to `false`. + */ + @Prop({ mutable: true }) checked = false; /** * @input {string} the value of the radio. */ @Prop({ mutable: true }) value: string; + componentWillLoad() { + this.inputId = 'ion-rb-' + (radioButtonIds++); + if (this.value === undefined) { + this.value = this.inputId; + } this.emitStyle(); } componentDidLoad() { this.ionRadioDidLoad.emit({ radio: this }); + 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); + } + } } componentDidUnload() { @@ -128,53 +114,79 @@ export class Radio { } @PropDidChange('color') - protected colorChanged() { + colorChanged() { this.emitStyle(); } @PropDidChange('checked') - protected checkedChanged(val: boolean) { - this.ionRadioCheckedDidChange.emit({ radio: this }); - this.ionSelect.emit({ checked: val }); + checkedChanged(isChecked: boolean) { + if (this.nativeInput.checked !== isChecked) { + // keep the checked value and native input `nync + this.nativeInput.checked = isChecked; + } + if (this.didLoad && isChecked) { + // only emit ionSelect when checked is true + this.ionSelect.emit({ + checked: isChecked, + value: this.value + }); + } this.emitStyle(); } @PropDidChange('disabled') - protected disabledChanged() { + disabledChanged(val: boolean) { + this.nativeInput.disabled = val; this.emitStyle(); } - private emitStyle() { + emitStyle() { clearTimeout(this.styleTmr); this.styleTmr = setTimeout(() => { this.ionStyle.emit({ ...createThemedClasses(this.mode, this.color, 'radio'), 'radio-checked': this.checked, - 'radio-disabled': this.disabled, + 'radio-disabled': this.disabled }); }); } - @Listen('keydown.space') - onSpace(ev: KeyboardEvent) { - this.toggle(); - ev.stopPropagation(); - ev.preventDefault(); + onChange() { + this.onClick(); } - toggle() { - this.checked = !this.checked; - this.ionRadioDidToggle.emit({ radio: this }); + onKeyUp() { + this.keyFocus = true; + } + + onFocus() { + this.ionFocus.emit(); + } + + onBlur() { + this.keyFocus = false; + this.ionBlur.emit(); + } + + @Listen('click') + onClick() { + if (!this.checked && !this.disabled) { + this.checked = true; + this.nativeInput.focus(); + } } hostData() { - return { - class: { + const hostAttrs: any = { + 'class': { 'radio-checked': this.checked, - 'radio-disabled': this.disabled + 'radio-disabled': this.disabled, + 'radio-key': this.keyFocus } }; + + return hostAttrs; } render() { @@ -182,27 +194,29 @@ export class Radio { 'radio-icon': true, 'radio-checked': this.checked }; - return [
-
+
, -