mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 10:01:59 +08:00
fix(inputs): fix aria with shadow-dom (#16329)
This commit is contained in:
@ -210,6 +210,8 @@ export declare interface Datetime extends StencilComponents<'IonDatetime'> {}
|
|||||||
export class Datetime {
|
export class Datetime {
|
||||||
ionCancel: EventEmitter<CustomEvent>;
|
ionCancel: EventEmitter<CustomEvent>;
|
||||||
ionChange: EventEmitter<CustomEvent>;
|
ionChange: EventEmitter<CustomEvent>;
|
||||||
|
ionFocus: EventEmitter<CustomEvent>;
|
||||||
|
ionBlur: EventEmitter<CustomEvent>;
|
||||||
ionStyle: EventEmitter<CustomEvent>;
|
ionStyle: EventEmitter<CustomEvent>;
|
||||||
|
|
||||||
constructor(c: ChangeDetectorRef, r: ElementRef) {
|
constructor(c: ChangeDetectorRef, r: ElementRef) {
|
||||||
@ -217,7 +219,7 @@ export class Datetime {
|
|||||||
const el = r.nativeElement;
|
const el = r.nativeElement;
|
||||||
proxyMethods(this, el, ['open']);
|
proxyMethods(this, el, ['open']);
|
||||||
proxyInputs(this, el, ['mode', 'name', 'disabled', 'min', 'max', 'displayFormat', 'pickerFormat', 'cancelText', 'doneText', 'yearValues', 'monthValues', 'dayValues', 'hourValues', 'minuteValues', 'monthNames', 'monthShortNames', 'dayNames', 'dayShortNames', 'pickerOptions', 'placeholder', 'value']);
|
proxyInputs(this, el, ['mode', 'name', 'disabled', 'min', 'max', 'displayFormat', 'pickerFormat', 'cancelText', 'doneText', 'yearValues', 'monthValues', 'dayValues', 'hourValues', 'minuteValues', 'monthNames', 'monthShortNames', 'dayNames', 'dayShortNames', 'pickerOptions', 'placeholder', 'value']);
|
||||||
proxyOutputs(this, el, ['ionCancel', 'ionChange', 'ionStyle']);
|
proxyOutputs(this, el, ['ionCancel', 'ionChange', 'ionFocus', 'ionBlur', 'ionStyle']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,22 +343,22 @@ export class InfiniteScrollContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export declare interface Input extends StencilComponents<'IonInput'> {}
|
export declare interface Input extends StencilComponents<'IonInput'> {}
|
||||||
@Component({ selector: 'ion-input', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['color', 'mode', 'accept', 'autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'clearInput', 'clearOnEdit', 'debounce', 'disabled', 'inputmode', 'max', 'maxlength', 'min', 'minlength', 'multiple', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'results', 'spellcheck', 'step', 'size', 'type', 'value'] })
|
@Component({ selector: 'ion-input', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['color', 'mode', 'accept', 'autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'clearInput', 'clearOnEdit', 'debounce', 'disabled', 'inputmode', 'max', 'maxlength', 'min', 'minlength', 'multiple', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'spellcheck', 'step', 'size', 'type', 'value'] })
|
||||||
export class Input {
|
export class Input {
|
||||||
ionInput: EventEmitter<CustomEvent>;
|
ionInput: EventEmitter<CustomEvent>;
|
||||||
ionChange: EventEmitter<CustomEvent>;
|
ionChange: EventEmitter<CustomEvent>;
|
||||||
ionStyle: EventEmitter<CustomEvent>;
|
|
||||||
ionBlur: EventEmitter<CustomEvent>;
|
ionBlur: EventEmitter<CustomEvent>;
|
||||||
ionFocus: EventEmitter<CustomEvent>;
|
ionFocus: EventEmitter<CustomEvent>;
|
||||||
ionInputDidLoad: EventEmitter<CustomEvent>;
|
ionInputDidLoad: EventEmitter<CustomEvent>;
|
||||||
ionInputDidUnload: EventEmitter<CustomEvent>;
|
ionInputDidUnload: EventEmitter<CustomEvent>;
|
||||||
|
ionStyle: EventEmitter<CustomEvent>;
|
||||||
|
|
||||||
constructor(c: ChangeDetectorRef, r: ElementRef) {
|
constructor(c: ChangeDetectorRef, r: ElementRef) {
|
||||||
c.detach();
|
c.detach();
|
||||||
const el = r.nativeElement;
|
const el = r.nativeElement;
|
||||||
proxyMethods(this, el, ['setFocus']);
|
proxyMethods(this, el, ['setFocus']);
|
||||||
proxyInputs(this, el, ['color', 'mode', 'accept', 'autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'clearInput', 'clearOnEdit', 'debounce', 'disabled', 'inputmode', 'max', 'maxlength', 'min', 'minlength', 'multiple', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'results', 'spellcheck', 'step', 'size', 'type', 'value']);
|
proxyInputs(this, el, ['color', 'mode', 'accept', 'autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'clearInput', 'clearOnEdit', 'debounce', 'disabled', 'inputmode', 'max', 'maxlength', 'min', 'minlength', 'multiple', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'spellcheck', 'step', 'size', 'type', 'value']);
|
||||||
proxyOutputs(this, el, ['ionInput', 'ionChange', 'ionStyle', 'ionBlur', 'ionFocus', 'ionInputDidLoad', 'ionInputDidUnload']);
|
proxyOutputs(this, el, ['ionInput', 'ionChange', 'ionBlur', 'ionFocus', 'ionInputDidLoad', 'ionInputDidUnload', 'ionStyle']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
core/src/components.d.ts
vendored
18
core/src/components.d.ts
vendored
@ -1288,6 +1288,10 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
'name'?: string;
|
'name'?: string;
|
||||||
/**
|
/**
|
||||||
|
* Emitted when the datetime loses focus.
|
||||||
|
*/
|
||||||
|
'onIonBlur'?: (event: CustomEvent<void>) => void;
|
||||||
|
/**
|
||||||
* Emitted when the datetime selection was cancelled.
|
* Emitted when the datetime selection was cancelled.
|
||||||
*/
|
*/
|
||||||
'onIonCancel'?: (event: CustomEvent<void>) => void;
|
'onIonCancel'?: (event: CustomEvent<void>) => void;
|
||||||
@ -1296,6 +1300,10 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
'onIonChange'?: (event: CustomEvent<InputChangeEvent>) => void;
|
'onIonChange'?: (event: CustomEvent<InputChangeEvent>) => void;
|
||||||
/**
|
/**
|
||||||
|
* Emitted when the datetime has focus.
|
||||||
|
*/
|
||||||
|
'onIonFocus'?: (event: CustomEvent<void>) => void;
|
||||||
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
*/
|
*/
|
||||||
'onIonStyle'?: (event: CustomEvent<StyleEvent>) => void;
|
'onIonStyle'?: (event: CustomEvent<StyleEvent>) => void;
|
||||||
@ -1696,10 +1704,6 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
'required': boolean;
|
'required': boolean;
|
||||||
/**
|
/**
|
||||||
* This is a nonstandard attribute supported by Safari that only applies when the type is `"search"`. Its value should be a nonnegative decimal integer.
|
|
||||||
*/
|
|
||||||
'results'?: number;
|
|
||||||
/**
|
|
||||||
* Sets focus on the specified `ion-input`. Use this method instead of the global `input.focus()`.
|
* Sets focus on the specified `ion-input`. Use this method instead of the global `input.focus()`.
|
||||||
*/
|
*/
|
||||||
'setFocus': () => void;
|
'setFocus': () => void;
|
||||||
@ -1842,10 +1846,6 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
'required'?: boolean;
|
'required'?: boolean;
|
||||||
/**
|
/**
|
||||||
* This is a nonstandard attribute supported by Safari that only applies when the type is `"search"`. Its value should be a nonnegative decimal integer.
|
|
||||||
*/
|
|
||||||
'results'?: number;
|
|
||||||
/**
|
|
||||||
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
|
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
|
||||||
*/
|
*/
|
||||||
'size'?: number;
|
'size'?: number;
|
||||||
@ -4052,7 +4052,7 @@ export namespace Components {
|
|||||||
/**
|
/**
|
||||||
* Opens the select overlay, it could be an alert, action-sheet or popover, based in `ion-select` settings.
|
* Opens the select overlay, it could be an alert, action-sheet or popover, based in `ion-select` settings.
|
||||||
*/
|
*/
|
||||||
'open': (ev?: UIEvent | undefined) => Promise<OverlaySelect>;
|
'open': (ev?: UIEvent | undefined) => Promise<HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPopoverElement | undefined>;
|
||||||
/**
|
/**
|
||||||
* The text to display when the select is empty.
|
* The text to display when the select is empty.
|
||||||
*/
|
*/
|
||||||
|
@ -323,7 +323,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => this.cbClick(i)}
|
onClick={() => this.cbClick(i)}
|
||||||
aria-checked={i.checked ? 'true' : null}
|
aria-checked={`${i.checked}`}
|
||||||
id={i.id}
|
id={i.id}
|
||||||
disabled={i.disabled}
|
disabled={i.disabled}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@ -356,7 +356,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => this.rbClick(i)}
|
onClick={() => this.rbClick(i)}
|
||||||
aria-checked={i.checked ? 'true' : null}
|
aria-checked={`${i.checked}`}
|
||||||
disabled={i.disabled}
|
disabled={i.disabled}
|
||||||
id={i.id}
|
id={i.id}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
@ -143,6 +143,7 @@ export class Button implements ComponentInterface {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'ion-activatable': true,
|
'ion-activatable': true,
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
class: {
|
class: {
|
||||||
...createColorClasses(color),
|
...createColorClasses(color),
|
||||||
[buttonType]: true,
|
[buttonType]: true,
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
--checkmark-color: #{current-color(contrast)};
|
--checkmark-color: #{current-color(contrast)};
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
button {
|
||||||
@include input-cover();
|
@include input-cover();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
|
||||||
|
|
||||||
import { CheckedInputChangeEvent, Color, Mode, StyleEvent } from '../../interface';
|
import { CheckedInputChangeEvent, Color, Mode, StyleEvent } from '../../interface';
|
||||||
import { renderHiddenInput } from '../../utils/helpers';
|
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -15,7 +15,6 @@ import { createColorClasses, hostContext } from '../../utils/theme';
|
|||||||
export class Checkbox implements ComponentInterface {
|
export class Checkbox implements ComponentInterface {
|
||||||
|
|
||||||
private inputId = `ion-cb-${checkboxIds++}`;
|
private inputId = `ion-cb-${checkboxIds++}`;
|
||||||
private labelId = `${this.inputId}-lbl`;
|
|
||||||
|
|
||||||
@Element() el!: HTMLElement;
|
@Element() el!: HTMLElement;
|
||||||
|
|
||||||
@ -74,6 +73,7 @@ export class Checkbox implements ComponentInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ export class Checkbox implements ComponentInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onChange = () => {
|
private onClick = () => {
|
||||||
this.checked = !this.checked;
|
this.checked = !this.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +116,16 @@ export class Checkbox implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
|
const labelId = this.inputId + '-lbl';
|
||||||
|
const label = findItemLabel(this.el);
|
||||||
|
if (label) {
|
||||||
|
label.id = labelId;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
|
'role': 'checkbox',
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
|
'aria-checked': `${this.checked}`,
|
||||||
|
'aria-labelledby': labelId,
|
||||||
class: {
|
class: {
|
||||||
...createColorClasses(this.color),
|
...createColorClasses(this.color),
|
||||||
'in-item': hostContext('ion-item', this.el),
|
'in-item': hostContext('ion-item', this.el),
|
||||||
@ -129,7 +138,7 @@ export class Checkbox implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
renderHiddenInput(this.el, this.name, this.value, this.disabled);
|
renderHiddenInput(true, this.el, this.name, (this.checked ? this.value : ''), this.disabled);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
<svg class="checkbox-icon" viewBox="0 0 24 24">
|
<svg class="checkbox-icon" viewBox="0 0 24 24">
|
||||||
@ -138,19 +147,14 @@ export class Checkbox implements ComponentInterface {
|
|||||||
: <path d="M5.9,12.5l3.8,3.8l8.8-8.8"/>
|
: <path d="M5.9,12.5l3.8,3.8l8.8-8.8"/>
|
||||||
}
|
}
|
||||||
</svg>,
|
</svg>,
|
||||||
<input
|
<button
|
||||||
type="checkbox"
|
type="button"
|
||||||
id={this.inputId}
|
onClick={this.onClick}
|
||||||
aria-labelledby={this.labelId}
|
onKeyUp={this.onKeyUp}
|
||||||
onChange={this.onChange}
|
|
||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
onKeyUp={this.onKeyUp}
|
>
|
||||||
checked={this.checked}
|
</button>
|
||||||
name={this.name}
|
|
||||||
value={this.value}
|
|
||||||
disabled={this.disabled}
|
|
||||||
/>
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ Checkboxes allow the selection of multiple options from a set of options. They a
|
|||||||
| `ionBlur` | Emitted when the toggle loses focus. | void |
|
| `ionBlur` | Emitted when the toggle loses focus. | void |
|
||||||
| `ionChange` | Emitted when the checked property has changed. | CheckedInputChangeEvent |
|
| `ionChange` | Emitted when the checked property has changed. | CheckedInputChangeEvent |
|
||||||
| `ionFocus` | Emitted when the toggle has focus. | void |
|
| `ionFocus` | Emitted when the toggle has focus. | void |
|
||||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
|
||||||
|
|
||||||
|
|
||||||
## CSS Custom Properties
|
## CSS Custom Properties
|
||||||
|
@ -40,9 +40,10 @@
|
|||||||
|
|
||||||
:host(.datetime-disabled) {
|
:host(.datetime-disabled) {
|
||||||
opacity: .3;
|
opacity: .3;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datetime-cover {
|
button {
|
||||||
@include input-cover();
|
@include input-cover();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
||||||
|
|
||||||
import { DatetimeOptions, InputChangeEvent, Mode, PickerColumn, PickerColumnOption, PickerOptions, StyleEvent } from '../../interface';
|
import { DatetimeOptions, InputChangeEvent, Mode, PickerColumn, PickerColumnOption, PickerOptions, StyleEvent } from '../../interface';
|
||||||
import { clamp, renderHiddenInput } from '../../utils/helpers';
|
import { clamp, findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||||
import { hostContext } from '../../utils/theme';
|
import { hostContext } from '../../utils/theme';
|
||||||
|
|
||||||
import { DatetimeData, LocaleData, convertDataToISO, convertFormatToKey, convertToArrayOfNumbers, convertToArrayOfStrings, dateDataSortValue, dateSortValue, dateValueRange, daysInMonth, getValueFromFormat, parseDate, parseTemplate, renderDatetime, renderTextFormat, updateDate } from './datetime-util';
|
import { DatetimeData, LocaleData, convertDataToISO, convertFormatToKey, convertToArrayOfNumbers, convertToArrayOfStrings, dateDataSortValue, dateSortValue, dateValueRange, daysInMonth, getValueFromFormat, parseDate, parseTemplate, renderDatetime, renderTextFormat, updateDate } from './datetime-util';
|
||||||
@ -16,8 +16,6 @@ import { DatetimeData, LocaleData, convertDataToISO, convertFormatToKey, convert
|
|||||||
})
|
})
|
||||||
export class Datetime implements ComponentInterface {
|
export class Datetime implements ComponentInterface {
|
||||||
private inputId = `ion-dt-${datetimeIds++}`;
|
private inputId = `ion-dt-${datetimeIds++}`;
|
||||||
private labelId = `${this.inputId}-lbl`;
|
|
||||||
private picker?: HTMLIonPickerElement;
|
|
||||||
private locale: LocaleData = {};
|
private locale: LocaleData = {};
|
||||||
private datetimeMin: DatetimeData = {};
|
private datetimeMin: DatetimeData = {};
|
||||||
private datetimeMax: DatetimeData = {};
|
private datetimeMax: DatetimeData = {};
|
||||||
@ -25,7 +23,8 @@ export class Datetime implements ComponentInterface {
|
|||||||
|
|
||||||
@Element() el!: HTMLIonDatetimeElement;
|
@Element() el!: HTMLIonDatetimeElement;
|
||||||
|
|
||||||
@State() text?: string | null;
|
@State() isExpanded = false;
|
||||||
|
@State() keyFocus = false;
|
||||||
|
|
||||||
@Prop({ connect: 'ion-picker-controller' }) pickerCtrl!: HTMLIonPickerControllerElement;
|
@Prop({ connect: 'ion-picker-controller' }) pickerCtrl!: HTMLIonPickerControllerElement;
|
||||||
|
|
||||||
@ -207,8 +206,19 @@ export class Datetime implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionChange!: EventEmitter<InputChangeEvent>;
|
@Event() ionChange!: EventEmitter<InputChangeEvent>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when the datetime has focus.
|
||||||
|
*/
|
||||||
|
@Event() ionFocus!: EventEmitter<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when the datetime loses focus.
|
||||||
|
*/
|
||||||
|
@Event() ionBlur!: EventEmitter<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||||
|
|
||||||
@ -233,13 +243,17 @@ export class Datetime implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
async open() {
|
async open() {
|
||||||
if (this.disabled) {
|
if (this.disabled || this.isExpanded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pickerOptions = this.generatePickerOptions();
|
const pickerOptions = this.generatePickerOptions();
|
||||||
const picker = this.picker = await this.pickerCtrl.create(pickerOptions);
|
const picker = await this.pickerCtrl.create(pickerOptions);
|
||||||
await this.validate();
|
this.isExpanded = true;
|
||||||
|
picker.onDidDismiss().then(() => {
|
||||||
|
this.isExpanded = false;
|
||||||
|
});
|
||||||
|
await this.validate(picker);
|
||||||
await picker.present();
|
await picker.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +269,6 @@ export class Datetime implements ComponentInterface {
|
|||||||
|
|
||||||
private updateDatetimeValue(value: any) {
|
private updateDatetimeValue(value: any) {
|
||||||
updateDate(this.datetimeValue, value);
|
updateDate(this.datetimeValue, value);
|
||||||
this.updateText();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private generatePickerOptions(): PickerOptions {
|
private generatePickerOptions(): PickerOptions {
|
||||||
@ -355,11 +368,11 @@ export class Datetime implements ComponentInterface {
|
|||||||
return divyColumns(columns);
|
return divyColumns(columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async validate() {
|
private async validate(picker: HTMLIonPickerElement) {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const minCompareVal = dateDataSortValue(this.datetimeMin);
|
const minCompareVal = dateDataSortValue(this.datetimeMin);
|
||||||
const maxCompareVal = dateDataSortValue(this.datetimeMax);
|
const maxCompareVal = dateDataSortValue(this.datetimeMax);
|
||||||
const yearCol = await this.picker!.getColumn('year');
|
const yearCol = await picker.getColumn('year');
|
||||||
|
|
||||||
let selectedYear: number = today.getFullYear();
|
let selectedYear: number = today.getFullYear();
|
||||||
if (yearCol) {
|
if (yearCol) {
|
||||||
@ -378,7 +391,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedMonth = await this.validateColumn(
|
const selectedMonth = await this.validateColumn(picker,
|
||||||
'month', 1,
|
'month', 1,
|
||||||
minCompareVal, maxCompareVal,
|
minCompareVal, maxCompareVal,
|
||||||
[selectedYear, 0, 0, 0, 0],
|
[selectedYear, 0, 0, 0, 0],
|
||||||
@ -386,21 +399,21 @@ export class Datetime implements ComponentInterface {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const numDaysInMonth = daysInMonth(selectedMonth, selectedYear);
|
const numDaysInMonth = daysInMonth(selectedMonth, selectedYear);
|
||||||
const selectedDay = await this.validateColumn(
|
const selectedDay = await this.validateColumn(picker,
|
||||||
'day', 2,
|
'day', 2,
|
||||||
minCompareVal, maxCompareVal,
|
minCompareVal, maxCompareVal,
|
||||||
[selectedYear, selectedMonth, 0, 0, 0],
|
[selectedYear, selectedMonth, 0, 0, 0],
|
||||||
[selectedYear, selectedMonth, numDaysInMonth, 23, 59]
|
[selectedYear, selectedMonth, numDaysInMonth, 23, 59]
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedHour = await this.validateColumn(
|
const selectedHour = await this.validateColumn(picker,
|
||||||
'hour', 3,
|
'hour', 3,
|
||||||
minCompareVal, maxCompareVal,
|
minCompareVal, maxCompareVal,
|
||||||
[selectedYear, selectedMonth, selectedDay, 0, 0],
|
[selectedYear, selectedMonth, selectedDay, 0, 0],
|
||||||
[selectedYear, selectedMonth, selectedDay, 23, 59]
|
[selectedYear, selectedMonth, selectedDay, 23, 59]
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.validateColumn(
|
await this.validateColumn(picker,
|
||||||
'minute', 4,
|
'minute', 4,
|
||||||
minCompareVal, maxCompareVal,
|
minCompareVal, maxCompareVal,
|
||||||
[selectedYear, selectedMonth, selectedDay, selectedHour, 0],
|
[selectedYear, selectedMonth, selectedDay, selectedHour, 0],
|
||||||
@ -444,7 +457,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
min.second = min.second || 0;
|
min.second = min.second || 0;
|
||||||
max.second = max.second || 59;
|
max.second = max.second || 59;
|
||||||
|
|
||||||
// Ensure min/max constraits
|
// Ensure min/max constraints
|
||||||
if (min.year > max.year) {
|
if (min.year > max.year) {
|
||||||
console.error('min.year > max.year');
|
console.error('min.year > max.year');
|
||||||
min.year = max.year - 100;
|
min.year = max.year - 100;
|
||||||
@ -460,8 +473,8 @@ export class Datetime implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async validateColumn(name: string, index: number, min: number, max: number, lowerBounds: number[], upperBounds: number[]): Promise<number> {
|
private async validateColumn(picker: HTMLIonPickerElement, name: string, index: number, min: number, max: number, lowerBounds: number[], upperBounds: number[]): Promise<number> {
|
||||||
const column = await this.picker!.getColumn(name);
|
const column = await picker.getColumn(name);
|
||||||
if (!column) {
|
if (!column) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -497,10 +510,10 @@ export class Datetime implements ComponentInterface {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateText() {
|
private getText() {
|
||||||
// create the text of the formatted data
|
// create the text of the formatted data
|
||||||
const template = this.displayFormat || this.pickerFormat || DEFAULT_FORMAT;
|
const template = this.displayFormat || this.pickerFormat || DEFAULT_FORMAT;
|
||||||
this.text = renderDatetime(template, this.datetimeValue, this.locale);
|
return renderDatetime(template, this.datetimeValue, this.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasValue(): boolean {
|
private hasValue(): boolean {
|
||||||
@ -508,11 +521,39 @@ export class Datetime implements ComponentInterface {
|
|||||||
return Object.keys(val).length > 0;
|
return Object.keys(val).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onClick = () => {
|
||||||
|
this.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onKeyUp = () => {
|
||||||
|
this.keyFocus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onFocus = () => {
|
||||||
|
this.ionFocus.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onBlur = () => {
|
||||||
|
this.keyFocus = false;
|
||||||
|
this.ionBlur.emit();
|
||||||
|
}
|
||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
const addPlaceholderClass =
|
const addPlaceholderClass =
|
||||||
(this.text == null && this.placeholder != null) ? true : false;
|
(this.getText() === undefined && this.placeholder != null) ? true : false;
|
||||||
|
|
||||||
|
const labelId = this.inputId + '-lbl';
|
||||||
|
const label = findItemLabel(this.el);
|
||||||
|
if (label) {
|
||||||
|
label.id = labelId;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'role': 'combobox',
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
|
'aria-expanded': `${this.isExpanded}`,
|
||||||
|
'aria-haspopup': 'true',
|
||||||
|
'aria-labelledby': labelId,
|
||||||
class: {
|
class: {
|
||||||
'datetime-disabled': this.disabled,
|
'datetime-disabled': this.disabled,
|
||||||
'datetime-placeholder': addPlaceholderClass,
|
'datetime-placeholder': addPlaceholderClass,
|
||||||
@ -524,24 +565,22 @@ export class Datetime implements ComponentInterface {
|
|||||||
render() {
|
render() {
|
||||||
// If selected text has been passed in, use that first
|
// If selected text has been passed in, use that first
|
||||||
// otherwise use the placeholder
|
// otherwise use the placeholder
|
||||||
let datetimeText = this.text;
|
let datetimeText = this.getText();
|
||||||
if (datetimeText == null) {
|
if (datetimeText === undefined) {
|
||||||
datetimeText = this.placeholder != null ? this.placeholder : '';
|
datetimeText = this.placeholder != null ? this.placeholder : '';
|
||||||
}
|
}
|
||||||
renderHiddenInput(this.el, this.name, this.value, this.disabled);
|
renderHiddenInput(true, this.el, this.name, this.value, this.disabled);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
<div class="datetime-text">{datetimeText}</div>,
|
<div class="datetime-text">{datetimeText}</div>,
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-haspopup="true"
|
onClick={this.onClick}
|
||||||
aria-labelledby={this.labelId}
|
onKeyUp={this.onKeyUp}
|
||||||
aria-disabled={this.disabled ? 'true' : null}
|
onFocus={this.onFocus}
|
||||||
onClick={this.open.bind(this)}
|
onBlur={this.onBlur}
|
||||||
class="datetime-cover"
|
|
||||||
>
|
>
|
||||||
</button>,
|
</button>
|
||||||
<slot></slot>
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,9 +233,10 @@ dates in JavaScript.
|
|||||||
|
|
||||||
| Event | Description | Detail |
|
| Event | Description | Detail |
|
||||||
| ----------- | --------------------------------------------------- | ---------------- |
|
| ----------- | --------------------------------------------------- | ---------------- |
|
||||||
|
| `ionBlur` | Emitted when the datetime loses focus. | void |
|
||||||
| `ionCancel` | Emitted when the datetime selection was cancelled. | void |
|
| `ionCancel` | Emitted when the datetime selection was cancelled. | void |
|
||||||
| `ionChange` | Emitted when the value (selected date) has changed. | InputChangeEvent |
|
| `ionChange` | Emitted when the value (selected date) has changed. | InputChangeEvent |
|
||||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
| `ionFocus` | Emitted when the datetime has focus. | void |
|
||||||
|
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
@ -94,6 +94,7 @@ export class FabButton implements ComponentInterface {
|
|||||||
const inList = hostContext('ion-fab-list', this.el);
|
const inList = hostContext('ion-fab-list', this.el);
|
||||||
return {
|
return {
|
||||||
'ion-activatable': true,
|
'ion-activatable': true,
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
class: {
|
class: {
|
||||||
...createColorClasses(this.color),
|
...createColorClasses(this.color),
|
||||||
'fab-button-in-list': inList,
|
'fab-button-in-list': inList,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
||||||
|
|
||||||
import { Color, Mode, StyleEvent, TextFieldTypes, TextInputChangeEvent } from '../../interface';
|
import { Color, Mode, StyleEvent, TextFieldTypes, TextInputChangeEvent } from '../../interface';
|
||||||
import { debounceEvent, renderHiddenInput } from '../../utils/helpers';
|
import { debounceEvent, findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -145,11 +145,6 @@ export class Input implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop() required = false;
|
@Prop() required = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a nonstandard attribute supported by Safari that only applies when the type is `"search"`. Its value should be a nonnegative decimal integer.
|
|
||||||
*/
|
|
||||||
@Prop() results?: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If `true`, the element will have its spelling and grammar checked.
|
* If `true`, the element will have its spelling and grammar checked.
|
||||||
*/
|
*/
|
||||||
@ -181,13 +176,8 @@ export class Input implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Watch('value')
|
@Watch('value')
|
||||||
protected valueChanged() {
|
protected valueChanged() {
|
||||||
const inputEl = this.nativeInput;
|
|
||||||
const value = this.getValue();
|
|
||||||
if (inputEl && inputEl.value !== value) {
|
|
||||||
inputEl.value = value;
|
|
||||||
}
|
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
this.ionChange.emit({ value });
|
this.ionChange.emit({ value: this.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -200,11 +190,6 @@ export class Input implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionChange!: EventEmitter<TextInputChangeEvent>;
|
@Event() ionChange!: EventEmitter<TextInputChangeEvent>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when the styles change.
|
|
||||||
*/
|
|
||||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the input loses focus.
|
* Emitted when the input loses focus.
|
||||||
*/
|
*/
|
||||||
@ -225,6 +210,12 @@ export class Input implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionInputDidUnload!: EventEmitter<void>;
|
@Event() ionInputDidUnload!: EventEmitter<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when the styles change.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||||
|
|
||||||
componentWillLoad() {
|
componentWillLoad() {
|
||||||
// By default, password inputs clear after focus when they have content
|
// By default, password inputs clear after focus when they have content
|
||||||
if (this.clearOnEdit === undefined && this.type === 'password') {
|
if (this.clearOnEdit === undefined && this.type === 'password') {
|
||||||
@ -240,7 +231,6 @@ export class Input implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
componentDidUnload() {
|
||||||
this.nativeInput = undefined;
|
|
||||||
this.ionInputDidUnload.emit();
|
this.ionInputDidUnload.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,6 +314,7 @@ export class Input implements ComponentInterface {
|
|||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
return {
|
return {
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
class: {
|
class: {
|
||||||
...createColorClasses(this.color),
|
...createColorClasses(this.color),
|
||||||
'in-item': hostContext('ion-item', this.el),
|
'in-item': hostContext('ion-item', this.el),
|
||||||
@ -335,19 +326,25 @@ export class Input implements ComponentInterface {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const value = this.getValue();
|
const value = this.getValue();
|
||||||
renderHiddenInput(this.el, this.name, value, this.disabled);
|
renderHiddenInput(false, this.el, this.name, value, this.disabled);
|
||||||
|
|
||||||
|
const labelId = this.inputId + '-lbl';
|
||||||
|
const label = findItemLabel(this.el);
|
||||||
|
if (label) {
|
||||||
|
label.id = labelId;
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
<input
|
<input
|
||||||
ref={input => this.nativeInput = input as any}
|
class="native-input"
|
||||||
aria-disabled={this.disabled ? 'true' : null}
|
ref={input => this.nativeInput = input}
|
||||||
|
aria-labelledby={labelId}
|
||||||
|
disabled={this.disabled}
|
||||||
accept={this.accept}
|
accept={this.accept}
|
||||||
autoCapitalize={this.autocapitalize}
|
autoCapitalize={this.autocapitalize}
|
||||||
autoComplete={this.autocomplete}
|
autoComplete={this.autocomplete}
|
||||||
autoCorrect={this.autocorrect}
|
autoCorrect={this.autocorrect}
|
||||||
autoFocus={this.autofocus}
|
autoFocus={this.autofocus}
|
||||||
class="native-input"
|
|
||||||
disabled={this.disabled}
|
|
||||||
inputMode={this.inputmode}
|
inputMode={this.inputmode}
|
||||||
min={this.min}
|
min={this.min}
|
||||||
max={this.max}
|
max={this.max}
|
||||||
@ -357,7 +354,6 @@ export class Input implements ComponentInterface {
|
|||||||
name={this.name}
|
name={this.name}
|
||||||
pattern={this.pattern}
|
pattern={this.pattern}
|
||||||
placeholder={this.placeholder || ''}
|
placeholder={this.placeholder || ''}
|
||||||
results={this.results}
|
|
||||||
readOnly={this.readonly}
|
readOnly={this.readonly}
|
||||||
required={this.required}
|
required={this.required}
|
||||||
spellCheck={this.spellcheck}
|
spellCheck={this.spellcheck}
|
||||||
@ -370,7 +366,6 @@ export class Input implements ComponentInterface {
|
|||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
onKeyDown={this.onKeydown}
|
onKeyDown={this.onKeydown}
|
||||||
/>,
|
/>,
|
||||||
<slot></slot>,
|
|
||||||
(this.clearInput && !this.readonly && !this.disabled) && <button
|
(this.clearInput && !this.readonly && !this.disabled) && <button
|
||||||
type="button"
|
type="button"
|
||||||
class="input-clear-icon"
|
class="input-clear-icon"
|
||||||
|
@ -34,7 +34,6 @@ It is meant for text `type` inputs only, such as `"text"`, `"password"`, `"email
|
|||||||
| `placeholder` | `placeholder` | Instructional text that shows before the input has a value. | `null \| string \| undefined` | `undefined` |
|
| `placeholder` | `placeholder` | Instructional text that shows before the input has a value. | `null \| string \| undefined` | `undefined` |
|
||||||
| `readonly` | `readonly` | If `true`, the user cannot modify the value. | `boolean` | `false` |
|
| `readonly` | `readonly` | If `true`, the user cannot modify the value. | `boolean` | `false` |
|
||||||
| `required` | `required` | If `true`, the user must fill in a value before submitting a form. | `boolean` | `false` |
|
| `required` | `required` | If `true`, the user must fill in a value before submitting a form. | `boolean` | `false` |
|
||||||
| `results` | `results` | This is a nonstandard attribute supported by Safari that only applies when the type is `"search"`. Its value should be a nonnegative decimal integer. | `number \| undefined` | `undefined` |
|
|
||||||
| `size` | `size` | The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored. | `number \| undefined` | `undefined` |
|
| `size` | `size` | The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored. | `number \| undefined` | `undefined` |
|
||||||
| `spellcheck` | `spellcheck` | If `true`, the element will have its spelling and grammar checked. | `boolean` | `false` |
|
| `spellcheck` | `spellcheck` | If `true`, the element will have its spelling and grammar checked. | `boolean` | `false` |
|
||||||
| `step` | `step` | Works with the min and max attributes to limit the increments at which a value can be set. Possible values are: `"any"` or a positive floating point number. | `string \| undefined` | `undefined` |
|
| `step` | `step` | Works with the min and max attributes to limit the increments at which a value can be set. Possible values are: `"any"` or a positive floating point number. | `string \| undefined` | `undefined` |
|
||||||
@ -52,7 +51,6 @@ It is meant for text `type` inputs only, such as `"text"`, `"password"`, `"email
|
|||||||
| `ionInput` | Emitted when a keyboard input ocurred. | KeyboardEvent |
|
| `ionInput` | Emitted when a keyboard input ocurred. | KeyboardEvent |
|
||||||
| `ionInputDidLoad` | Emitted when the input has been created. | void |
|
| `ionInputDidLoad` | Emitted when the input has been created. | void |
|
||||||
| `ionInputDidUnload` | Emitted when the input has been removed. | void |
|
| `ionInputDidUnload` | Emitted when the input has been removed. | void |
|
||||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
|
||||||
|
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
@ -16,6 +16,7 @@ export class ItemGroup implements ComponentInterface {
|
|||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
return {
|
return {
|
||||||
|
'role': 'group',
|
||||||
class: {
|
class: {
|
||||||
...createThemedClasses(this.mode, 'item-group'),
|
...createThemedClasses(this.mode, 'item-group'),
|
||||||
'item': true,
|
'item': true,
|
||||||
|
@ -169,8 +169,6 @@ button, a {
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: inherit;
|
flex-direction: inherit;
|
||||||
align-items: inherit;
|
align-items: inherit;
|
||||||
|
@ -128,6 +128,7 @@ export class Item implements ComponentInterface {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'ion-activatable': this.isClickable(),
|
'ion-activatable': this.isClickable(),
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
class: {
|
class: {
|
||||||
...childStyles,
|
...childStyles,
|
||||||
...createColorClasses(this.color),
|
...createColorClasses(this.color),
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
import { E2EPage, newE2EPage } from '@stencil/core/testing';
|
||||||
|
|
||||||
test('item: inputs', async () => {
|
test('item: inputs', async () => {
|
||||||
const page = await newE2EPage({
|
const page = await newE2EPage({
|
||||||
url: '/src/components/item/test/inputs?ionic:_testing=true'
|
url: '/src/components/item/test/inputs?ionic:_testing=true'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// check form
|
||||||
|
await page.click('#submit');
|
||||||
|
await checkFormResult(page, '{"date":"","select":"n64","toggle":"","input":"","input2":"","checkbox":""}');
|
||||||
|
|
||||||
// Default case, enabled and no value
|
// Default case, enabled and no value
|
||||||
let compare = await page.compareScreenshot();
|
let compare = await page.compareScreenshot();
|
||||||
expect(compare).toMatchScreenshot();
|
expect(compare).toMatchScreenshot();
|
||||||
@ -13,13 +17,22 @@ test('item: inputs', async () => {
|
|||||||
await page.click('#btnDisabled');
|
await page.click('#btnDisabled');
|
||||||
await page.waitFor(250);
|
await page.waitFor(250);
|
||||||
|
|
||||||
|
// check form
|
||||||
|
await page.click('#submit');
|
||||||
|
await checkFormResult(page, '{}');
|
||||||
|
|
||||||
|
// screenshot
|
||||||
compare = await page.compareScreenshot('should disable all');
|
compare = await page.compareScreenshot('should disable all');
|
||||||
expect(compare).toMatchScreenshot();
|
expect(compare).toMatchScreenshot();
|
||||||
|
|
||||||
// Reenable and set some value
|
// Reenable and set some value
|
||||||
await page.click('#btnDisabled');
|
await page.click('#btnDisabled');
|
||||||
await page.click('#btnSomeValue');
|
await page.click('#btnSomeValue');
|
||||||
await page.waitFor(250);
|
await page.waitFor(100);
|
||||||
|
|
||||||
|
// check form
|
||||||
|
await page.click('#submit');
|
||||||
|
await checkFormResult(page, '{"date":"2016-12-09","select":"nes","toggle":"on","input":"Some text","input2":"Some text","checkbox":"on"}');
|
||||||
|
|
||||||
compare = await page.compareScreenshot('should reenable and set value');
|
compare = await page.compareScreenshot('should reenable and set value');
|
||||||
expect(compare).toMatchScreenshot();
|
expect(compare).toMatchScreenshot();
|
||||||
@ -45,3 +58,8 @@ test('item: inputs', async () => {
|
|||||||
compare = await page.compareScreenshot('should set empty');
|
compare = await page.compareScreenshot('should set empty');
|
||||||
expect(compare).toMatchScreenshot();
|
expect(compare).toMatchScreenshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function checkFormResult(page: E2EPage, content: string) {
|
||||||
|
const div = await page.find('#form-result');
|
||||||
|
expect(div.textContent).toEqual(content);
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content padding-vertical>
|
<ion-content padding-vertical>
|
||||||
|
<form onsubmit="return onSubmit(event)">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Simple item</ion-label>
|
<ion-label>Simple item</ion-label>
|
||||||
@ -31,12 +32,12 @@
|
|||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>DateTime</ion-label>
|
<ion-label>DateTime</ion-label>
|
||||||
<ion-datetime id="datetime" min="1994-03-14" max="2017-12-09" display-format="MM/DD/YYYY"></ion-datetime>
|
<ion-datetime name="date" id="datetime" min="1994-03-14" max="2017-12-09" display-format="MM/DD/YYYY"></ion-datetime>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Select</ion-label>
|
<ion-label>Select</ion-label>
|
||||||
<ion-select id="select">
|
<ion-select name="select" id="select">
|
||||||
<ion-select-option value="">No Game Console</ion-select-option>
|
<ion-select-option value="">No Game Console</ion-select-option>
|
||||||
<ion-select-option value="nes">NES</ion-select-option>
|
<ion-select-option value="nes">NES</ion-select-option>
|
||||||
<ion-select-option value="n64" selected>Nintendo64</ion-select-option>
|
<ion-select-option value="n64" selected>Nintendo64</ion-select-option>
|
||||||
@ -49,30 +50,34 @@
|
|||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Toggle</ion-label>
|
<ion-label>Toggle</ion-label>
|
||||||
<ion-toggle id="toggle" name="Actually" slot="end"></ion-toggle>
|
<ion-toggle name="toggle" id="toggle" name="Actually" slot="end"></ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Input (text)</ion-label>
|
<ion-label>Input (text)</ion-label>
|
||||||
<ion-input id="text"></ion-input>
|
<ion-input name="input" id="text"></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Input (placeholder)</ion-label>
|
<ion-label>Input (placeholder)</ion-label>
|
||||||
<ion-input id="placeholder" placeholder="Placeholder"></ion-input>
|
<ion-input name="input2" id="placeholder" placeholder="Placeholder"></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Checkbox</ion-label>
|
<ion-label>Checkbox</ion-label>
|
||||||
<ion-checkbox id="checkbox" slot="start"></ion-checkbox>
|
<ion-checkbox name="checkbox" id="checkbox" slot="start"></ion-checkbox>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Range</ion-label>
|
<ion-label>Range</ion-label>
|
||||||
<ion-range id="range" value="10"></ion-range>
|
<ion-range name="range" id="range" value="10"></ion-range>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
|
<ion-button id="submit" type="submit">Submit</ion-button>
|
||||||
|
<p id="form-result"></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-list-header>Controls</ion-list-header>
|
<ion-list-header>Controls</ion-list-header>
|
||||||
<ion-item-divider>Value Controls</ion-item-divider>
|
<ion-item-divider>Value Controls</ion-item-divider>
|
||||||
@ -181,6 +186,16 @@
|
|||||||
console.log('CLICK!', ev.target.tagName, ev.target.textContent.trim());
|
console.log('CLICK!', ev.target.tagName, ev.target.textContent.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSubmit(ev) {
|
||||||
|
const data = new FormData(ev.target);
|
||||||
|
const json = {};
|
||||||
|
data.forEach((value, key) => {
|
||||||
|
json[key] = value;
|
||||||
|
})
|
||||||
|
document.getElementById('form-result').textContent = JSON.stringify(json);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -33,6 +33,7 @@ export class Label implements ComponentInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||||
|
|
||||||
|
@ -15,13 +15,6 @@ Label is a wrapper element that can be used in combination with `ion-item`, `ion
|
|||||||
| `position` | `position` | The position determines where and how the label behaves inside an item. | `"fixed" \| "floating" \| "stacked" \| undefined` | `undefined` |
|
| `position` | `position` | The position determines where and how the label behaves inside an item. | `"fixed" \| "floating" \| "stacked" \| undefined` | `undefined` |
|
||||||
|
|
||||||
|
|
||||||
## Events
|
|
||||||
|
|
||||||
| Event | Description | Detail |
|
|
||||||
| ---------- | ------------------------------- | ---------- |
|
|
||||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
|
||||||
|
|
||||||
|
|
||||||
## CSS Custom Properties
|
## CSS Custom Properties
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
z-index: $z-index-item-input;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.radio-disabled) {
|
:host(.radio-disabled) {
|
||||||
@ -38,7 +40,7 @@
|
|||||||
height: var(--inner-height);
|
height: var(--inner-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
button {
|
||||||
@include input-cover();
|
@include input-cover();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
|
||||||
|
|
||||||
import { CheckedInputChangeEvent, Color, Mode, StyleEvent } from '../../interface';
|
import { CheckedInputChangeEvent, Color, Mode, StyleEvent } from '../../interface';
|
||||||
|
import { findItemLabel } from '../../utils/helpers';
|
||||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -14,7 +15,6 @@ import { createColorClasses, hostContext } from '../../utils/theme';
|
|||||||
export class Radio implements ComponentInterface {
|
export class Radio implements ComponentInterface {
|
||||||
|
|
||||||
private inputId = `ion-rb-${radioButtonIds++}`;
|
private inputId = `ion-rb-${radioButtonIds++}`;
|
||||||
private nativeInput!: HTMLInputElement;
|
|
||||||
|
|
||||||
@State() keyFocus = false;
|
@State() keyFocus = false;
|
||||||
|
|
||||||
@ -64,6 +64,7 @@ export class Radio implements ComponentInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||||
|
|
||||||
@ -82,31 +83,6 @@ export class Radio implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionBlur!: EventEmitter<void>;
|
@Event() ionBlur!: EventEmitter<void>;
|
||||||
|
|
||||||
componentWillLoad() {
|
|
||||||
if (this.value == null) {
|
|
||||||
this.value = this.inputId;
|
|
||||||
}
|
|
||||||
this.emitStyle();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidLoad() {
|
|
||||||
this.ionRadioDidLoad.emit();
|
|
||||||
this.nativeInput.checked = this.checked;
|
|
||||||
|
|
||||||
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() {
|
|
||||||
this.ionRadioDidUnload.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Watch('color')
|
@Watch('color')
|
||||||
colorChanged() {
|
colorChanged() {
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
@ -114,11 +90,6 @@ export class Radio implements ComponentInterface {
|
|||||||
|
|
||||||
@Watch('checked')
|
@Watch('checked')
|
||||||
checkedChanged(isChecked: boolean) {
|
checkedChanged(isChecked: boolean) {
|
||||||
if (this.nativeInput.checked !== isChecked) {
|
|
||||||
// keep the checked value and native input `nync
|
|
||||||
this.nativeInput.checked = isChecked;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
this.ionSelect.emit({
|
this.ionSelect.emit({
|
||||||
checked: true,
|
checked: true,
|
||||||
@ -129,11 +100,25 @@ export class Radio implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Watch('disabled')
|
@Watch('disabled')
|
||||||
disabledChanged(isDisabled: boolean) {
|
disabledChanged() {
|
||||||
this.nativeInput.disabled = isDisabled;
|
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillLoad() {
|
||||||
|
if (this.value == null) {
|
||||||
|
this.value = this.inputId;
|
||||||
|
}
|
||||||
|
this.emitStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidLoad() {
|
||||||
|
this.ionRadioDidLoad.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUnload() {
|
||||||
|
this.ionRadioDidUnload.emit();
|
||||||
|
}
|
||||||
|
|
||||||
private emitStyle() {
|
private emitStyle() {
|
||||||
this.ionStyle.emit({
|
this.ionStyle.emit({
|
||||||
'radio-checked': this.checked,
|
'radio-checked': this.checked,
|
||||||
@ -142,12 +127,7 @@ export class Radio implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onClick = () => {
|
private onClick = () => {
|
||||||
this.checkedChanged(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onChange = () => {
|
|
||||||
this.checked = true;
|
this.checked = true;
|
||||||
this.nativeInput.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onKeyUp = () => {
|
private onKeyUp = () => {
|
||||||
@ -164,7 +144,16 @@ export class Radio implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
|
const labelId = this.inputId + '-lbl';
|
||||||
|
const label = findItemLabel(this.el);
|
||||||
|
if (label) {
|
||||||
|
label.id = labelId;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
|
'role': 'radio',
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
|
'aria-checked': `${this.checked}`,
|
||||||
|
'aria-labelledby': labelId,
|
||||||
class: {
|
class: {
|
||||||
...createColorClasses(this.color),
|
...createColorClasses(this.color),
|
||||||
'in-item': hostContext('ion-item', this.el),
|
'in-item': hostContext('ion-item', this.el),
|
||||||
@ -181,19 +170,14 @@ export class Radio implements ComponentInterface {
|
|||||||
<div class="radio-icon">
|
<div class="radio-icon">
|
||||||
<div class="radio-inner"/>
|
<div class="radio-inner"/>
|
||||||
</div>,
|
</div>,
|
||||||
<input
|
<button
|
||||||
type="radio"
|
type="button"
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onChange={this.onChange}
|
onKeyUp={this.onKeyUp}
|
||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
onKeyUp={this.onKeyUp}
|
>
|
||||||
id={this.inputId}
|
</button>,
|
||||||
name={this.name}
|
|
||||||
value={this.value}
|
|
||||||
disabled={this.disabled}
|
|
||||||
ref={r => this.nativeInput = (r as any)}
|
|
||||||
/>
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ An `ion-radio-group` can be used to group a set of radios. When radios are insid
|
|||||||
| `ionRadioDidLoad` | Emitted when the radio loads. | void |
|
| `ionRadioDidLoad` | Emitted when the radio loads. | void |
|
||||||
| `ionRadioDidUnload` | Emitted when the radio unloads. | void |
|
| `ionRadioDidUnload` | Emitted when the radio unloads. | void |
|
||||||
| `ionSelect` | Emitted when the radio button is selected. | CheckedInputChangeEvent |
|
| `ionSelect` | Emitted when the radio button is selected. | CheckedInputChangeEvent |
|
||||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
|
||||||
|
|
||||||
|
|
||||||
## CSS Custom Properties
|
## CSS Custom Properties
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Pepperoni</ion-label>
|
<ion-label>Pepperoni</ion-label>
|
||||||
<ion-radio slot="end" name="pepperoni" checked></ion-radio>
|
<ion-radio slot="end" name="pepperoni" checked id="pepperoni-radio"></ion-radio>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
@ -143,6 +143,12 @@
|
|||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const radio = document.getElementById('pepperoni-radio');
|
||||||
|
if (radio) {
|
||||||
|
radio.addEventListener('ionSelect', (ev) => {
|
||||||
|
console.log(ev.detail);
|
||||||
|
});
|
||||||
|
}
|
||||||
var radioValues = ['fruitRadio', 'pizzaRadio', 'veggiesRadio'];
|
var radioValues = ['fruitRadio', 'pizzaRadio', 'veggiesRadio'];
|
||||||
printRadioValues();
|
printRadioValues();
|
||||||
|
|
||||||
|
@ -28,6 +28,10 @@
|
|||||||
z-index: $z-index-item-input;
|
z-index: $z-index-item-input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host(.range-disabled) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
::slotted(ion-label) {
|
::slotted(ion-label) {
|
||||||
flex: initial;
|
flex: initial;
|
||||||
}
|
}
|
||||||
@ -36,9 +40,6 @@
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.range-slider {
|
.range-slider {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -133,6 +133,7 @@ export class Range implements ComponentInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||||
|
|
||||||
@ -417,6 +418,7 @@ export class Range implements ComponentInterface {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RangeKnob {
|
interface RangeKnob {
|
||||||
knob: string;
|
knob: string;
|
||||||
value: number;
|
value: number;
|
||||||
|
@ -40,7 +40,6 @@ left or right of the range.
|
|||||||
| `ionBlur` | Emitted when the range loses focus. | void |
|
| `ionBlur` | Emitted when the range loses focus. | void |
|
||||||
| `ionChange` | Emitted when the value property has changed. | InputChangeEvent |
|
| `ionChange` | Emitted when the value property has changed. | InputChangeEvent |
|
||||||
| `ionFocus` | Emitted when the range has focus. | void |
|
| `ionFocus` | Emitted when the range has focus. | void |
|
||||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
|
||||||
|
|
||||||
|
|
||||||
## CSS Custom Properties
|
## CSS Custom Properties
|
||||||
|
@ -66,6 +66,12 @@ export class RippleEffect implements ComponentInterface {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hostData() {
|
||||||
|
return {
|
||||||
|
role: 'presentation'
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeRipple(ripple: HTMLElement) {
|
function removeRipple(ripple: HTMLElement) {
|
||||||
|
@ -20,6 +20,17 @@
|
|||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
background: #262626;
|
||||||
|
color: white;
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -45,23 +56,23 @@
|
|||||||
<p>
|
<p>
|
||||||
<ion-button size="large" fill="clear">Large</ion-button>
|
<ion-button size="large" fill="clear">Large</ion-button>
|
||||||
</p>
|
</p>
|
||||||
<div class="my-block">
|
<div class="my-block" ion-activatable>
|
||||||
<ion-ripple-effect></ion-ripple-effect>
|
<ion-ripple-effect></ion-ripple-effect>
|
||||||
This is just a div + effect behind
|
This is just a div + effect behind
|
||||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-block">
|
<div class="my-block" ion-activatable>
|
||||||
This is just a div + effect on top
|
This is just a div + effect on top
|
||||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||||
<ion-ripple-effect></ion-ripple-effect>
|
<ion-ripple-effect></ion-ripple-effect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-block">
|
<div class="my-block" ion-activatable>
|
||||||
This is just a div + effect
|
This is just a div + effect
|
||||||
<ion-ripple-effect></ion-ripple-effect>
|
<ion-ripple-effect></ion-ripple-effect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="my-block">
|
<a class="my-block" ion-activatable>
|
||||||
This is just a a + effect on top
|
This is just a a + effect on top
|
||||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||||
<ion-ripple-effect></ion-ripple-effect>
|
<ion-ripple-effect></ion-ripple-effect>
|
||||||
@ -72,6 +83,10 @@
|
|||||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||||
<ion-ripple-effect></ion-ripple-effect>
|
<ion-ripple-effect></ion-ripple-effect>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<a class="block" ion-activatable>
|
||||||
|
<ion-ripple-effect></ion-ripple-effect>
|
||||||
|
</a>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
</ion-app>
|
</ion-app>
|
||||||
|
@ -376,7 +376,7 @@ export class Searchbar implements ComponentInterface {
|
|||||||
return [
|
return [
|
||||||
<div class="searchbar-input-container">
|
<div class="searchbar-input-container">
|
||||||
<input
|
<input
|
||||||
ref={el => this.nativeInput = el as HTMLInputElement}
|
ref={el => this.nativeInput = el}
|
||||||
class="searchbar-input"
|
class="searchbar-input"
|
||||||
onInput={this.onInput}
|
onInput={this.onInput}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
|
@ -69,6 +69,8 @@ export class SegmentButton implements ComponentInterface {
|
|||||||
const { disabled, checked, color } = this;
|
const { disabled, checked, color } = this;
|
||||||
return {
|
return {
|
||||||
'ion-activatable': 'instant',
|
'ion-activatable': 'instant',
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
|
|
||||||
class: {
|
class: {
|
||||||
...createColorClasses(color),
|
...createColorClasses(color),
|
||||||
'segment-button-disabled': disabled,
|
'segment-button-disabled': disabled,
|
||||||
|
@ -64,12 +64,11 @@ Since select uses the alert, action sheet and popover interfaces, options can be
|
|||||||
| `ionCancel` | Emitted when the selection is cancelled. | void |
|
| `ionCancel` | Emitted when the selection is cancelled. | void |
|
||||||
| `ionChange` | Emitted when the value has changed. | SelectInputChangeEvent |
|
| `ionChange` | Emitted when the value has changed. | SelectInputChangeEvent |
|
||||||
| `ionFocus` | Emitted when the select has focus. | void |
|
| `ionFocus` | Emitted when the select has focus. | void |
|
||||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
|
||||||
|
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
|
||||||
### `open(ev?: UIEvent | undefined) => Promise<OverlaySelect>`
|
### `open(ev?: UIEvent | undefined) => Promise<HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPo...`
|
||||||
|
|
||||||
Opens the select overlay, it could be an alert, action-sheet or popover,
|
Opens the select overlay, it could be an alert, action-sheet or popover,
|
||||||
based in `ion-select` settings.
|
based in `ion-select` settings.
|
||||||
@ -82,7 +81,7 @@ based in `ion-select` settings.
|
|||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
Type: `Promise<OverlaySelect>`
|
Type: `Promise<HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPopoverElement | undefined>`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
color: var(--placeholder-color);
|
color: var(--placeholder-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-cover {
|
button {
|
||||||
@include input-cover();
|
@include input-cover();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Method, Prop, State, Watch } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Method, Prop, State, Watch } from '@stencil/core';
|
||||||
|
|
||||||
import { ActionSheetButton, ActionSheetOptions, AlertOptions, CssClassMap, Mode, OverlaySelect, PopoverOptions, SelectInputChangeEvent, SelectInterface, SelectPopoverOption, StyleEvent } from '../../interface';
|
import { ActionSheetButton, ActionSheetOptions, AlertOptions, CssClassMap, Mode, OverlaySelect, PopoverOptions, SelectInputChangeEvent, SelectInterface, SelectPopoverOption, StyleEvent } from '../../interface';
|
||||||
import { renderHiddenInput } from '../../utils/helpers';
|
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||||
import { hostContext } from '../../utils/theme';
|
import { hostContext } from '../../utils/theme';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -16,7 +16,6 @@ export class Select implements ComponentInterface {
|
|||||||
|
|
||||||
private childOpts: HTMLIonSelectOptionElement[] = [];
|
private childOpts: HTMLIonSelectOptionElement[] = [];
|
||||||
private inputId = `ion-sel-${selectIds++}`;
|
private inputId = `ion-sel-${selectIds++}`;
|
||||||
private labelId?: string;
|
|
||||||
private overlay?: OverlaySelect;
|
private overlay?: OverlaySelect;
|
||||||
private didInit = false;
|
private didInit = false;
|
||||||
|
|
||||||
@ -110,6 +109,7 @@ export class Select implements ComponentInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||||
|
|
||||||
@ -141,11 +141,6 @@ export class Select implements ComponentInterface {
|
|||||||
async componentDidLoad() {
|
async componentDidLoad() {
|
||||||
await this.loadOptions();
|
await this.loadOptions();
|
||||||
|
|
||||||
const label = this.getLabel();
|
|
||||||
if (label) {
|
|
||||||
this.labelId = label.id = this.name + '-lbl';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.value === undefined) {
|
if (this.value === undefined) {
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
// there are no values set at this point
|
// there are no values set at this point
|
||||||
@ -170,9 +165,22 @@ export class Select implements ComponentInterface {
|
|||||||
* based in `ion-select` settings.
|
* based in `ion-select` settings.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
open(ev?: UIEvent): Promise<OverlaySelect> {
|
async open(ev?: UIEvent): Promise<OverlaySelect | undefined> {
|
||||||
let selectInterface = this.interface;
|
if (this.disabled || this.isExpanded) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const overlay = this.overlay = await this.createOverlay(ev);
|
||||||
|
this.isExpanded = true;
|
||||||
|
overlay.onDidDismiss().then(() => {
|
||||||
|
this.overlay = undefined;
|
||||||
|
this.isExpanded = false;
|
||||||
|
});
|
||||||
|
await overlay.present();
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createOverlay(ev?: UIEvent): Promise<OverlaySelect> {
|
||||||
|
let selectInterface = this.interface;
|
||||||
if ((selectInterface === 'action-sheet' || selectInterface === 'popover') && this.multiple) {
|
if ((selectInterface === 'action-sheet' || selectInterface === 'popover') && this.multiple) {
|
||||||
console.warn(`Select interface cannot be "${selectInterface}" with a multi-value select. Using the "alert" interface instead.`);
|
console.warn(`Select interface cannot be "${selectInterface}" with a multi-value select. Using the "alert" interface instead.`);
|
||||||
selectInterface = 'alert';
|
selectInterface = 'alert';
|
||||||
@ -186,11 +194,9 @@ export class Select implements ComponentInterface {
|
|||||||
if (selectInterface === 'popover') {
|
if (selectInterface === 'popover') {
|
||||||
return this.openPopover(ev!);
|
return this.openPopover(ev!);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectInterface === 'action-sheet') {
|
if (selectInterface === 'action-sheet') {
|
||||||
return this.openActionSheet();
|
return this.openActionSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.openAlert();
|
return this.openAlert();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,11 +228,7 @@ export class Select implements ComponentInterface {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
return this.popoverCtrl.create(popoverOpts);
|
||||||
const popover = this.overlay = await this.popoverCtrl.create(popoverOpts);
|
|
||||||
await popover.present();
|
|
||||||
this.isExpanded = true;
|
|
||||||
return popover;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async openActionSheet() {
|
private async openActionSheet() {
|
||||||
@ -256,12 +258,7 @@ export class Select implements ComponentInterface {
|
|||||||
buttons: actionSheetButtons,
|
buttons: actionSheetButtons,
|
||||||
cssClass: ['select-action-sheet', interfaceOptions.cssClass]
|
cssClass: ['select-action-sheet', interfaceOptions.cssClass]
|
||||||
};
|
};
|
||||||
|
return this.actionSheetCtrl.create(actionSheetOpts);
|
||||||
const actionSheet = this.overlay = await this.actionSheetCtrl.create(actionSheetOpts);
|
|
||||||
await actionSheet.present();
|
|
||||||
|
|
||||||
this.isExpanded = true;
|
|
||||||
return actionSheet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async openAlert() {
|
private async openAlert() {
|
||||||
@ -303,12 +300,7 @@ export class Select implements ComponentInterface {
|
|||||||
cssClass: ['select-alert', interfaceOptions.cssClass,
|
cssClass: ['select-alert', interfaceOptions.cssClass,
|
||||||
(this.multiple ? 'multiple-select-alert' : 'single-select-alert')]
|
(this.multiple ? 'multiple-select-alert' : 'single-select-alert')]
|
||||||
};
|
};
|
||||||
|
return this.alertCtrl.create(alertOpts);
|
||||||
const alert = this.overlay = await this.alertCtrl.create(alertOpts);
|
|
||||||
await alert.present();
|
|
||||||
|
|
||||||
this.isExpanded = true;
|
|
||||||
return alert;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -319,12 +311,7 @@ export class Select implements ComponentInterface {
|
|||||||
if (!this.overlay) {
|
if (!this.overlay) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
return this.overlay.dismiss();
|
||||||
const overlay = this.overlay;
|
|
||||||
this.overlay = undefined;
|
|
||||||
this.isExpanded = false;
|
|
||||||
|
|
||||||
return overlay.dismiss();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadOptions() {
|
private async loadOptions() {
|
||||||
@ -349,11 +336,7 @@ export class Select implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getLabel() {
|
private getLabel() {
|
||||||
const item = this.el.closest('ion-item');
|
return findItemLabel(this.el);
|
||||||
if (item) {
|
|
||||||
return item.querySelector('ion-label');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasValue(): boolean {
|
private hasValue(): boolean {
|
||||||
@ -379,6 +362,10 @@ export class Select implements ComponentInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onClick = (ev: UIEvent) => {
|
||||||
|
this.open(ev);
|
||||||
|
}
|
||||||
|
|
||||||
private onKeyUp = () => {
|
private onKeyUp = () => {
|
||||||
this.keyFocus = true;
|
this.keyFocus = true;
|
||||||
}
|
}
|
||||||
@ -393,7 +380,18 @@ export class Select implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
|
const labelId = this.inputId + '-lbl';
|
||||||
|
const label = findItemLabel(this.el);
|
||||||
|
if (label) {
|
||||||
|
label.id = labelId;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'role': 'combobox',
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
|
'aria-expanded': `${this.isExpanded}`,
|
||||||
|
'aria-haspopup': 'dialog',
|
||||||
|
'aria-labelledby': labelId,
|
||||||
class: {
|
class: {
|
||||||
'in-item': hostContext('ion-item', this.el),
|
'in-item': hostContext('ion-item', this.el),
|
||||||
'select-disabled': this.disabled,
|
'select-disabled': this.disabled,
|
||||||
@ -403,7 +401,13 @@ export class Select implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
renderHiddenInput(this.el, this.name, parseValue(this.value), this.disabled);
|
renderHiddenInput(true, this.el, this.name, parseValue(this.value), this.disabled);
|
||||||
|
|
||||||
|
const labelId = this.inputId + '-lbl';
|
||||||
|
const label = findItemLabel(this.el);
|
||||||
|
if (label) {
|
||||||
|
label.id = labelId;
|
||||||
|
}
|
||||||
|
|
||||||
let addPlaceholderClass = false;
|
let addPlaceholderClass = false;
|
||||||
let selectText = this.getText();
|
let selectText = this.getText();
|
||||||
@ -418,11 +422,7 @@ export class Select implements ComponentInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
<div
|
<div class={selectTextClasses}>
|
||||||
role="textbox"
|
|
||||||
aria-multiline="false"
|
|
||||||
class={selectTextClasses}
|
|
||||||
>
|
|
||||||
{selectText}
|
{selectText}
|
||||||
</div>,
|
</div>,
|
||||||
<div class="select-icon" role="presentation">
|
<div class="select-icon" role="presentation">
|
||||||
@ -430,19 +430,11 @@ export class Select implements ComponentInterface {
|
|||||||
</div>,
|
</div>,
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="combobox"
|
onClick={this.onClick}
|
||||||
aria-haspopup="dialog"
|
|
||||||
aria-labelledby={this.labelId}
|
|
||||||
aria-expanded={this.isExpanded ? 'true' : null}
|
|
||||||
aria-disabled={this.disabled ? 'true' : null}
|
|
||||||
onClick={this.open.bind(this)}
|
|
||||||
onKeyUp={this.onKeyUp}
|
onKeyUp={this.onKeyUp}
|
||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
class="select-cover"
|
|
||||||
>
|
>
|
||||||
<slot></slot>
|
|
||||||
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
|
||||||
</button>
|
</button>
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,6 @@ The textarea component accepts the [native textarea attributes](https://develope
|
|||||||
| `ionChange` | Emitted when the input value has changed. | TextInputChangeEvent |
|
| `ionChange` | Emitted when the input value has changed. | TextInputChangeEvent |
|
||||||
| `ionFocus` | Emitted when the input has focus. | void |
|
| `ionFocus` | Emitted when the input has focus. | void |
|
||||||
| `ionInput` | Emitted when a keyboard input ocurred. | KeyboardEvent |
|
| `ionInput` | Emitted when a keyboard input ocurred. | KeyboardEvent |
|
||||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
|
||||||
|
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
||||||
|
|
||||||
import { Color, Mode, StyleEvent, TextInputChangeEvent } from '../../interface';
|
import { Color, Mode, StyleEvent, TextInputChangeEvent } from '../../interface';
|
||||||
import { debounceEvent, renderHiddenInput } from '../../utils/helpers';
|
import { debounceEvent, findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||||
import { createColorClasses } from '../../utils/theme';
|
import { createColorClasses } from '../../utils/theme';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -131,8 +131,8 @@ export class Textarea implements ComponentInterface {
|
|||||||
protected valueChanged() {
|
protected valueChanged() {
|
||||||
const nativeInput = this.nativeInput;
|
const nativeInput = this.nativeInput;
|
||||||
const value = this.getValue();
|
const value = this.getValue();
|
||||||
if (nativeInput!.value !== value) {
|
if (nativeInput && nativeInput.value !== value) {
|
||||||
nativeInput!.value = value;
|
nativeInput.value = value;
|
||||||
}
|
}
|
||||||
this.ionChange.emit({ value });
|
this.ionChange.emit({ value });
|
||||||
}
|
}
|
||||||
@ -149,6 +149,7 @@ export class Textarea implements ComponentInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||||
|
|
||||||
@ -193,30 +194,6 @@ export class Textarea implements ComponentInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onInput = (ev: Event) => {
|
|
||||||
this.value = this.nativeInput!.value;
|
|
||||||
this.emitStyle();
|
|
||||||
this.ionInput.emit(ev as KeyboardEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onFocus = () => {
|
|
||||||
this.hasFocus = true;
|
|
||||||
this.focusChange();
|
|
||||||
|
|
||||||
this.ionFocus.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onBlur = () => {
|
|
||||||
this.hasFocus = false;
|
|
||||||
this.focusChange();
|
|
||||||
|
|
||||||
this.ionBlur.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onKeyDown = () => {
|
|
||||||
this.checkClearOnEdit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if we need to clear the text input if clearOnEdit is enabled
|
* Check if we need to clear the text input if clearOnEdit is enabled
|
||||||
*/
|
*/
|
||||||
@ -251,22 +228,53 @@ export class Textarea implements ComponentInterface {
|
|||||||
return this.value || '';
|
return this.value || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onInput = (ev: Event) => {
|
||||||
|
if (this.nativeInput) {
|
||||||
|
this.value = this.nativeInput.value;
|
||||||
|
}
|
||||||
|
this.emitStyle();
|
||||||
|
this.ionInput.emit(ev as KeyboardEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onFocus = () => {
|
||||||
|
this.hasFocus = true;
|
||||||
|
this.focusChange();
|
||||||
|
|
||||||
|
this.ionFocus.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onBlur = () => {
|
||||||
|
this.hasFocus = false;
|
||||||
|
this.focusChange();
|
||||||
|
|
||||||
|
this.ionBlur.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onKeyDown = () => {
|
||||||
|
this.checkClearOnEdit();
|
||||||
|
}
|
||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
return {
|
return {
|
||||||
class: {
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
...createColorClasses(this.color)
|
class: createColorClasses(this.color)
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const value = this.getValue();
|
const value = this.getValue();
|
||||||
renderHiddenInput(this.el, this.name, value, this.disabled);
|
renderHiddenInput(false, this.el, this.name, value, this.disabled);
|
||||||
|
|
||||||
|
const labelId = this.inputId + '-lbl';
|
||||||
|
const label = findItemLabel(this.el);
|
||||||
|
if (label) {
|
||||||
|
label.id = labelId;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
class="native-textarea"
|
class="native-textarea"
|
||||||
ref={el => this.nativeInput = el as HTMLTextAreaElement}
|
ref={el => this.nativeInput = el}
|
||||||
autoCapitalize={this.autocapitalize}
|
autoCapitalize={this.autocapitalize}
|
||||||
autoFocus={this.autofocus}
|
autoFocus={this.autofocus}
|
||||||
disabled={this.disabled}
|
disabled={this.disabled}
|
||||||
|
@ -26,7 +26,6 @@ Toggles change the state of a single option. Toggles can be switched on or off b
|
|||||||
| `ionBlur` | Emitted when the toggle loses focus. | void |
|
| `ionBlur` | Emitted when the toggle loses focus. | void |
|
||||||
| `ionChange` | Emitted when the value property has changed. | CheckedInputChangeEvent |
|
| `ionChange` | Emitted when the value property has changed. | CheckedInputChangeEvent |
|
||||||
| `ionFocus` | Emitted when the toggle has focus. | void |
|
| `ionFocus` | Emitted when the toggle has focus. | void |
|
||||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
|
||||||
|
|
||||||
|
|
||||||
## CSS Custom Properties
|
## CSS Custom Properties
|
||||||
|
@ -77,6 +77,9 @@ describe('toggle', () => {
|
|||||||
// spy on the ionChange event
|
// spy on the ionChange event
|
||||||
const ionChange = await page.spyOnEvent('ionChange');
|
const ionChange = await page.spyOnEvent('ionChange');
|
||||||
|
|
||||||
|
// check aria
|
||||||
|
expect(toggle).toEqualAttribute('aria-checked', 'true');
|
||||||
|
|
||||||
// find the hidden input in the light dom
|
// find the hidden input in the light dom
|
||||||
const hiddenInput = await page.find('ion-toggle input[type=hidden]');
|
const hiddenInput = await page.find('ion-toggle input[type=hidden]');
|
||||||
|
|
||||||
@ -86,16 +89,6 @@ describe('toggle', () => {
|
|||||||
// hidden in put should have aux-input class
|
// hidden in put should have aux-input class
|
||||||
expect(hiddenInput).toHaveClass('aux-input');
|
expect(hiddenInput).toHaveClass('aux-input');
|
||||||
|
|
||||||
// find the checkbox input in the shadow dom
|
|
||||||
const checkboxInput = await page.find('ion-toggle >>> input[type=checkbox]');
|
|
||||||
|
|
||||||
// checkbox input should have value on
|
|
||||||
expect(checkboxInput).toEqualAttribute('value', 'on');
|
|
||||||
|
|
||||||
// checkbox input should have checked property true
|
|
||||||
const checkedValue = await checkboxInput.getProperty('checked');
|
|
||||||
expect(checkedValue).toBe(true);
|
|
||||||
|
|
||||||
// set checked true again, no actual change
|
// set checked true again, no actual change
|
||||||
toggle.setProperty('checked', true);
|
toggle.setProperty('checked', true);
|
||||||
|
|
||||||
@ -116,7 +109,7 @@ describe('toggle', () => {
|
|||||||
expect(checkedValue2).toBe(false);
|
expect(checkedValue2).toBe(false);
|
||||||
|
|
||||||
// hidden input property should no value
|
// hidden input property should no value
|
||||||
expect(hiddenInput).toEqualAttribute('value', '');
|
expect(toggle).toEqualAttribute('aria-checked', 'false');
|
||||||
|
|
||||||
expect(ionChange).toHaveReceivedEventTimes(1);
|
expect(ionChange).toHaveReceivedEventTimes(1);
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
contain: content;
|
contain: content;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
@ -28,10 +30,6 @@
|
|||||||
border: 2px solid #5e9ed6;
|
border: 2px solid #5e9ed6;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(:focus) {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host(.toggle-disabled) {
|
:host(.toggle-disabled) {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, QueueApi, State, Watch } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Prop, QueueApi, State, Watch } from '@stencil/core';
|
||||||
|
|
||||||
import { CheckedInputChangeEvent, Color, Gesture, GestureDetail, Mode, StyleEvent } from '../../interface';
|
import { CheckedInputChangeEvent, Color, Gesture, GestureDetail, Mode, StyleEvent } from '../../interface';
|
||||||
import { hapticSelection } from '../../utils/haptic';
|
import { hapticSelection } from '../../utils/haptic';
|
||||||
import { renderHiddenInput } from '../../utils/helpers';
|
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -16,7 +16,6 @@ import { createColorClasses, hostContext } from '../../utils/theme';
|
|||||||
export class Toggle implements ComponentInterface {
|
export class Toggle implements ComponentInterface {
|
||||||
|
|
||||||
private inputId = `ion-tg-${toggleIds++}`;
|
private inputId = `ion-tg-${toggleIds++}`;
|
||||||
private nativeInput!: HTMLInputElement;
|
|
||||||
private pivotX = 0;
|
private pivotX = 0;
|
||||||
private gesture?: Gesture;
|
private gesture?: Gesture;
|
||||||
|
|
||||||
@ -80,6 +79,7 @@ export class Toggle implements ComponentInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||||
|
|
||||||
@ -99,20 +99,32 @@ export class Toggle implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Listen('click')
|
||||||
|
onClick() {
|
||||||
|
this.checked = !this.checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listen('keyup')
|
||||||
|
onKeyUp() {
|
||||||
|
this.keyFocus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listen('focus')
|
||||||
|
onFocus() {
|
||||||
|
this.ionFocus.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listen('blur')
|
||||||
|
onBlur() {
|
||||||
|
this.keyFocus = false;
|
||||||
|
this.ionBlur.emit();
|
||||||
|
}
|
||||||
|
|
||||||
componentWillLoad() {
|
componentWillLoad() {
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidLoad() {
|
async componentDidLoad() {
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||||
el: this.el,
|
el: this.el,
|
||||||
queue: this.queue,
|
queue: this.queue,
|
||||||
@ -158,24 +170,6 @@ export class Toggle implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.activated = false;
|
this.activated = false;
|
||||||
this.nativeInput.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onChange = () => {
|
|
||||||
this.checked = !this.checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onKeyUp = () => {
|
|
||||||
this.keyFocus = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onFocus = () => {
|
|
||||||
this.ionFocus.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onBlur = () => {
|
|
||||||
this.keyFocus = false;
|
|
||||||
this.ionBlur.emit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getValue() {
|
private getValue() {
|
||||||
@ -183,7 +177,19 @@ export class Toggle implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
|
const labelId = this.inputId + '-lbl';
|
||||||
|
const label = findItemLabel(this.el);
|
||||||
|
if (label) {
|
||||||
|
label.id = labelId;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'role': 'checkbox',
|
||||||
|
'tabindex': '0',
|
||||||
|
'aria-disabled': this.disabled ? 'true' : null,
|
||||||
|
'aria-checked': `${this.checked}`,
|
||||||
|
'aria-labelledby': labelId,
|
||||||
|
|
||||||
class: {
|
class: {
|
||||||
...createColorClasses(this.color),
|
...createColorClasses(this.color),
|
||||||
'in-item': hostContext('ion-item', this.el),
|
'in-item': hostContext('ion-item', this.el),
|
||||||
@ -198,27 +204,13 @@ export class Toggle implements ComponentInterface {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const value = this.getValue();
|
const value = this.getValue();
|
||||||
renderHiddenInput(this.el, this.name, (this.checked ? value : ''), this.disabled);
|
renderHiddenInput(true, this.el, this.name, (this.checked ? value : ''), this.disabled);
|
||||||
|
|
||||||
return [
|
return (
|
||||||
<div class="toggle-icon">
|
<div class="toggle-icon">
|
||||||
<div class="toggle-inner"/>
|
<div class="toggle-inner"/>
|
||||||
</div>,
|
</div>
|
||||||
<input
|
);
|
||||||
type="checkbox"
|
|
||||||
onChange={this.onChange}
|
|
||||||
onFocus={this.onFocus}
|
|
||||||
onBlur={this.onBlur}
|
|
||||||
onKeyUp={this.onKeyUp}
|
|
||||||
checked={this.checked}
|
|
||||||
id={this.inputId}
|
|
||||||
name={this.name}
|
|
||||||
value={value}
|
|
||||||
disabled={this.disabled}
|
|
||||||
ref={r => this.nativeInput = (r as any)}
|
|
||||||
/>,
|
|
||||||
<slot></slot>
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,16 @@ export function hasShadowDom(el: HTMLElement) {
|
|||||||
return !!el.shadowRoot && !!(el as any).attachShadow;
|
return !!el.shadowRoot && !!(el as any).attachShadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderHiddenInput(container: HTMLElement, name: string, value: string | undefined | null, disabled: boolean) {
|
export function findItemLabel(componentEl: HTMLElement) {
|
||||||
if (hasShadowDom(container)) {
|
const itemEl = componentEl.closest('ion-item');
|
||||||
|
if (itemEl) {
|
||||||
|
return itemEl.querySelector('ion-label');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderHiddenInput(always: boolean, container: HTMLElement, name: string, value: string | undefined | null, disabled: boolean) {
|
||||||
|
if (always || hasShadowDom(container)) {
|
||||||
let input = container.querySelector('input.aux-input') as HTMLInputElement | null;
|
let input = container.querySelector('input.aux-input') as HTMLInputElement | null;
|
||||||
if (!input) {
|
if (!input) {
|
||||||
input = container.ownerDocument!.createElement('input');
|
input = container.ownerDocument!.createElement('input');
|
||||||
|
Reference in New Issue
Block a user