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 {
|
||||
ionCancel: EventEmitter<CustomEvent>;
|
||||
ionChange: EventEmitter<CustomEvent>;
|
||||
ionFocus: EventEmitter<CustomEvent>;
|
||||
ionBlur: EventEmitter<CustomEvent>;
|
||||
ionStyle: EventEmitter<CustomEvent>;
|
||||
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef) {
|
||||
@ -217,7 +219,7 @@ export class Datetime {
|
||||
const el = r.nativeElement;
|
||||
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']);
|
||||
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'> {}
|
||||
@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 {
|
||||
ionInput: EventEmitter<CustomEvent>;
|
||||
ionChange: EventEmitter<CustomEvent>;
|
||||
ionStyle: EventEmitter<CustomEvent>;
|
||||
ionBlur: EventEmitter<CustomEvent>;
|
||||
ionFocus: EventEmitter<CustomEvent>;
|
||||
ionInputDidLoad: EventEmitter<CustomEvent>;
|
||||
ionInputDidUnload: EventEmitter<CustomEvent>;
|
||||
ionStyle: EventEmitter<CustomEvent>;
|
||||
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef) {
|
||||
c.detach();
|
||||
const el = r.nativeElement;
|
||||
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']);
|
||||
proxyOutputs(this, el, ['ionInput', 'ionChange', 'ionStyle', 'ionBlur', 'ionFocus', 'ionInputDidLoad', 'ionInputDidUnload']);
|
||||
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', '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;
|
||||
/**
|
||||
* Emitted when the datetime loses focus.
|
||||
*/
|
||||
'onIonBlur'?: (event: CustomEvent<void>) => void;
|
||||
/**
|
||||
* Emitted when the datetime selection was cancelled.
|
||||
*/
|
||||
'onIonCancel'?: (event: CustomEvent<void>) => void;
|
||||
@ -1296,6 +1300,10 @@ export namespace Components {
|
||||
*/
|
||||
'onIonChange'?: (event: CustomEvent<InputChangeEvent>) => void;
|
||||
/**
|
||||
* Emitted when the datetime has focus.
|
||||
*/
|
||||
'onIonFocus'?: (event: CustomEvent<void>) => void;
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
*/
|
||||
'onIonStyle'?: (event: CustomEvent<StyleEvent>) => void;
|
||||
@ -1696,10 +1704,6 @@ export namespace Components {
|
||||
*/
|
||||
'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()`.
|
||||
*/
|
||||
'setFocus': () => void;
|
||||
@ -1842,10 +1846,6 @@ export namespace Components {
|
||||
*/
|
||||
'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.
|
||||
*/
|
||||
'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.
|
||||
*/
|
||||
'open': (ev?: UIEvent | undefined) => Promise<OverlaySelect>;
|
||||
'open': (ev?: UIEvent | undefined) => Promise<HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPopoverElement | undefined>;
|
||||
/**
|
||||
* The text to display when the select is empty.
|
||||
*/
|
||||
|
@ -323,7 +323,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => this.cbClick(i)}
|
||||
aria-checked={i.checked ? 'true' : null}
|
||||
aria-checked={`${i.checked}`}
|
||||
id={i.id}
|
||||
disabled={i.disabled}
|
||||
tabIndex={0}
|
||||
@ -356,7 +356,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => this.rbClick(i)}
|
||||
aria-checked={i.checked ? 'true' : null}
|
||||
aria-checked={`${i.checked}`}
|
||||
disabled={i.disabled}
|
||||
id={i.id}
|
||||
tabIndex={0}
|
||||
|
@ -143,6 +143,7 @@ export class Button implements ComponentInterface {
|
||||
|
||||
return {
|
||||
'ion-activatable': true,
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
class: {
|
||||
...createColorClasses(color),
|
||||
[buttonType]: true,
|
||||
|
@ -35,7 +35,7 @@
|
||||
--checkmark-color: #{current-color(contrast)};
|
||||
}
|
||||
|
||||
input {
|
||||
button {
|
||||
@include input-cover();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
|
||||
|
||||
import { CheckedInputChangeEvent, Color, Mode, StyleEvent } from '../../interface';
|
||||
import { renderHiddenInput } from '../../utils/helpers';
|
||||
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
@Component({
|
||||
@ -15,7 +15,6 @@ import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
export class Checkbox implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-cb-${checkboxIds++}`;
|
||||
private labelId = `${this.inputId}-lbl`;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@ -74,6 +73,7 @@ export class Checkbox implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
* @internal
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||
|
||||
@ -98,7 +98,7 @@ export class Checkbox implements ComponentInterface {
|
||||
});
|
||||
}
|
||||
|
||||
private onChange = () => {
|
||||
private onClick = () => {
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
|
||||
@ -116,7 +116,16 @@ export class Checkbox implements ComponentInterface {
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(this.el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
return {
|
||||
'role': 'checkbox',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-checked': `${this.checked}`,
|
||||
'aria-labelledby': labelId,
|
||||
class: {
|
||||
...createColorClasses(this.color),
|
||||
'in-item': hostContext('ion-item', this.el),
|
||||
@ -129,7 +138,7 @@ export class Checkbox implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
renderHiddenInput(this.el, this.name, this.value, this.disabled);
|
||||
renderHiddenInput(true, this.el, this.name, (this.checked ? this.value : ''), this.disabled);
|
||||
|
||||
return [
|
||||
<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"/>
|
||||
}
|
||||
</svg>,
|
||||
<input
|
||||
type="checkbox"
|
||||
id={this.inputId}
|
||||
aria-labelledby={this.labelId}
|
||||
onChange={this.onChange}
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.onClick}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onKeyUp={this.onKeyUp}
|
||||
checked={this.checked}
|
||||
name={this.name}
|
||||
value={this.value}
|
||||
disabled={this.disabled}
|
||||
/>
|
||||
>
|
||||
</button>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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 |
|
||||
| `ionChange` | Emitted when the checked property has changed. | CheckedInputChangeEvent |
|
||||
| `ionFocus` | Emitted when the toggle has focus. | void |
|
||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
||||
|
||||
|
||||
## CSS Custom Properties
|
||||
|
@ -40,9 +40,10 @@
|
||||
|
||||
:host(.datetime-disabled) {
|
||||
opacity: .3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.datetime-cover {
|
||||
button {
|
||||
@include input-cover();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
||||
|
||||
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 { 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 {
|
||||
private inputId = `ion-dt-${datetimeIds++}`;
|
||||
private labelId = `${this.inputId}-lbl`;
|
||||
private picker?: HTMLIonPickerElement;
|
||||
private locale: LocaleData = {};
|
||||
private datetimeMin: DatetimeData = {};
|
||||
private datetimeMax: DatetimeData = {};
|
||||
@ -25,7 +23,8 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
@Element() el!: HTMLIonDatetimeElement;
|
||||
|
||||
@State() text?: string | null;
|
||||
@State() isExpanded = false;
|
||||
@State() keyFocus = false;
|
||||
|
||||
@Prop({ connect: 'ion-picker-controller' }) pickerCtrl!: HTMLIonPickerControllerElement;
|
||||
|
||||
@ -207,8 +206,19 @@ export class Datetime implements ComponentInterface {
|
||||
*/
|
||||
@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.
|
||||
* @internal
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||
|
||||
@ -233,13 +243,17 @@ export class Datetime implements ComponentInterface {
|
||||
*/
|
||||
@Method()
|
||||
async open() {
|
||||
if (this.disabled) {
|
||||
if (this.disabled || this.isExpanded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pickerOptions = this.generatePickerOptions();
|
||||
const picker = this.picker = await this.pickerCtrl.create(pickerOptions);
|
||||
await this.validate();
|
||||
const picker = await this.pickerCtrl.create(pickerOptions);
|
||||
this.isExpanded = true;
|
||||
picker.onDidDismiss().then(() => {
|
||||
this.isExpanded = false;
|
||||
});
|
||||
await this.validate(picker);
|
||||
await picker.present();
|
||||
}
|
||||
|
||||
@ -255,7 +269,6 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
private updateDatetimeValue(value: any) {
|
||||
updateDate(this.datetimeValue, value);
|
||||
this.updateText();
|
||||
}
|
||||
|
||||
private generatePickerOptions(): PickerOptions {
|
||||
@ -355,11 +368,11 @@ export class Datetime implements ComponentInterface {
|
||||
return divyColumns(columns);
|
||||
}
|
||||
|
||||
private async validate() {
|
||||
private async validate(picker: HTMLIonPickerElement) {
|
||||
const today = new Date();
|
||||
const minCompareVal = dateDataSortValue(this.datetimeMin);
|
||||
const maxCompareVal = dateDataSortValue(this.datetimeMax);
|
||||
const yearCol = await this.picker!.getColumn('year');
|
||||
const yearCol = await picker.getColumn('year');
|
||||
|
||||
let selectedYear: number = today.getFullYear();
|
||||
if (yearCol) {
|
||||
@ -378,7 +391,7 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
const selectedMonth = await this.validateColumn(
|
||||
const selectedMonth = await this.validateColumn(picker,
|
||||
'month', 1,
|
||||
minCompareVal, maxCompareVal,
|
||||
[selectedYear, 0, 0, 0, 0],
|
||||
@ -386,21 +399,21 @@ export class Datetime implements ComponentInterface {
|
||||
);
|
||||
|
||||
const numDaysInMonth = daysInMonth(selectedMonth, selectedYear);
|
||||
const selectedDay = await this.validateColumn(
|
||||
const selectedDay = await this.validateColumn(picker,
|
||||
'day', 2,
|
||||
minCompareVal, maxCompareVal,
|
||||
[selectedYear, selectedMonth, 0, 0, 0],
|
||||
[selectedYear, selectedMonth, numDaysInMonth, 23, 59]
|
||||
);
|
||||
|
||||
const selectedHour = await this.validateColumn(
|
||||
const selectedHour = await this.validateColumn(picker,
|
||||
'hour', 3,
|
||||
minCompareVal, maxCompareVal,
|
||||
[selectedYear, selectedMonth, selectedDay, 0, 0],
|
||||
[selectedYear, selectedMonth, selectedDay, 23, 59]
|
||||
);
|
||||
|
||||
await this.validateColumn(
|
||||
await this.validateColumn(picker,
|
||||
'minute', 4,
|
||||
minCompareVal, maxCompareVal,
|
||||
[selectedYear, selectedMonth, selectedDay, selectedHour, 0],
|
||||
@ -444,7 +457,7 @@ export class Datetime implements ComponentInterface {
|
||||
min.second = min.second || 0;
|
||||
max.second = max.second || 59;
|
||||
|
||||
// Ensure min/max constraits
|
||||
// Ensure min/max constraints
|
||||
if (min.year > max.year) {
|
||||
console.error('min.year > max.year');
|
||||
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> {
|
||||
const column = await this.picker!.getColumn(name);
|
||||
private async validateColumn(picker: HTMLIonPickerElement, name: string, index: number, min: number, max: number, lowerBounds: number[], upperBounds: number[]): Promise<number> {
|
||||
const column = await picker.getColumn(name);
|
||||
if (!column) {
|
||||
return 0;
|
||||
}
|
||||
@ -497,10 +510,10 @@ export class Datetime implements ComponentInterface {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private updateText() {
|
||||
private getText() {
|
||||
// create the text of the formatted data
|
||||
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 {
|
||||
@ -508,11 +521,39 @@ export class Datetime implements ComponentInterface {
|
||||
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() {
|
||||
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 {
|
||||
'role': 'combobox',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-expanded': `${this.isExpanded}`,
|
||||
'aria-haspopup': 'true',
|
||||
'aria-labelledby': labelId,
|
||||
class: {
|
||||
'datetime-disabled': this.disabled,
|
||||
'datetime-placeholder': addPlaceholderClass,
|
||||
@ -524,24 +565,22 @@ export class Datetime implements ComponentInterface {
|
||||
render() {
|
||||
// If selected text has been passed in, use that first
|
||||
// otherwise use the placeholder
|
||||
let datetimeText = this.text;
|
||||
if (datetimeText == null) {
|
||||
let datetimeText = this.getText();
|
||||
if (datetimeText === undefined) {
|
||||
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 [
|
||||
<div class="datetime-text">{datetimeText}</div>,
|
||||
<button
|
||||
type="button"
|
||||
aria-haspopup="true"
|
||||
aria-labelledby={this.labelId}
|
||||
aria-disabled={this.disabled ? 'true' : null}
|
||||
onClick={this.open.bind(this)}
|
||||
class="datetime-cover"
|
||||
onClick={this.onClick}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
>
|
||||
</button>,
|
||||
<slot></slot>
|
||||
</button>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -233,9 +233,10 @@ dates in JavaScript.
|
||||
|
||||
| Event | Description | Detail |
|
||||
| ----------- | --------------------------------------------------- | ---------------- |
|
||||
| `ionBlur` | Emitted when the datetime loses focus. | void |
|
||||
| `ionCancel` | Emitted when the datetime selection was cancelled. | void |
|
||||
| `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
|
||||
|
@ -94,6 +94,7 @@ export class FabButton implements ComponentInterface {
|
||||
const inList = hostContext('ion-fab-list', this.el);
|
||||
return {
|
||||
'ion-activatable': true,
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
class: {
|
||||
...createColorClasses(this.color),
|
||||
'fab-button-in-list': inList,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
||||
|
||||
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';
|
||||
|
||||
@Component({
|
||||
@ -145,11 +145,6 @@ export class Input implements ComponentInterface {
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@ -181,13 +176,8 @@ export class Input implements ComponentInterface {
|
||||
*/
|
||||
@Watch('value')
|
||||
protected valueChanged() {
|
||||
const inputEl = this.nativeInput;
|
||||
const value = this.getValue();
|
||||
if (inputEl && inputEl.value !== value) {
|
||||
inputEl.value = value;
|
||||
}
|
||||
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>;
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when the input loses focus.
|
||||
*/
|
||||
@ -225,6 +210,12 @@ export class Input implements ComponentInterface {
|
||||
*/
|
||||
@Event() ionInputDidUnload!: EventEmitter<void>;
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
* @internal
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||
|
||||
componentWillLoad() {
|
||||
// By default, password inputs clear after focus when they have content
|
||||
if (this.clearOnEdit === undefined && this.type === 'password') {
|
||||
@ -240,7 +231,6 @@ export class Input implements ComponentInterface {
|
||||
}
|
||||
|
||||
componentDidUnload() {
|
||||
this.nativeInput = undefined;
|
||||
this.ionInputDidUnload.emit();
|
||||
}
|
||||
|
||||
@ -324,6 +314,7 @@ export class Input implements ComponentInterface {
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
class: {
|
||||
...createColorClasses(this.color),
|
||||
'in-item': hostContext('ion-item', this.el),
|
||||
@ -335,19 +326,25 @@ export class Input implements ComponentInterface {
|
||||
|
||||
render() {
|
||||
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 [
|
||||
<input
|
||||
ref={input => this.nativeInput = input as any}
|
||||
aria-disabled={this.disabled ? 'true' : null}
|
||||
class="native-input"
|
||||
ref={input => this.nativeInput = input}
|
||||
aria-labelledby={labelId}
|
||||
disabled={this.disabled}
|
||||
accept={this.accept}
|
||||
autoCapitalize={this.autocapitalize}
|
||||
autoComplete={this.autocomplete}
|
||||
autoCorrect={this.autocorrect}
|
||||
autoFocus={this.autofocus}
|
||||
class="native-input"
|
||||
disabled={this.disabled}
|
||||
inputMode={this.inputmode}
|
||||
min={this.min}
|
||||
max={this.max}
|
||||
@ -357,7 +354,6 @@ export class Input implements ComponentInterface {
|
||||
name={this.name}
|
||||
pattern={this.pattern}
|
||||
placeholder={this.placeholder || ''}
|
||||
results={this.results}
|
||||
readOnly={this.readonly}
|
||||
required={this.required}
|
||||
spellCheck={this.spellcheck}
|
||||
@ -370,7 +366,6 @@ export class Input implements ComponentInterface {
|
||||
onFocus={this.onFocus}
|
||||
onKeyDown={this.onKeydown}
|
||||
/>,
|
||||
<slot></slot>,
|
||||
(this.clearInput && !this.readonly && !this.disabled) && <button
|
||||
type="button"
|
||||
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` |
|
||||
| `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` |
|
||||
| `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` |
|
||||
| `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` |
|
||||
@ -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 |
|
||||
| `ionInputDidLoad` | Emitted when the input has been created. | void |
|
||||
| `ionInputDidUnload` | Emitted when the input has been removed. | void |
|
||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
||||
|
||||
|
||||
## Methods
|
||||
|
@ -16,6 +16,7 @@ export class ItemGroup implements ComponentInterface {
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
'role': 'group',
|
||||
class: {
|
||||
...createThemedClasses(this.mode, 'item-group'),
|
||||
'item': true,
|
||||
|
@ -169,8 +169,6 @@ button, a {
|
||||
|
||||
display: flex;
|
||||
|
||||
position: relative;
|
||||
|
||||
flex: 1;
|
||||
flex-direction: inherit;
|
||||
align-items: inherit;
|
||||
|
@ -128,6 +128,7 @@ export class Item implements ComponentInterface {
|
||||
|
||||
return {
|
||||
'ion-activatable': this.isClickable(),
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
class: {
|
||||
...childStyles,
|
||||
...createColorClasses(this.color),
|
||||
|
@ -1,10 +1,14 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { E2EPage, newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('item: inputs', async () => {
|
||||
const page = await newE2EPage({
|
||||
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
|
||||
let compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
@ -13,13 +17,22 @@ test('item: inputs', async () => {
|
||||
await page.click('#btnDisabled');
|
||||
await page.waitFor(250);
|
||||
|
||||
// check form
|
||||
await page.click('#submit');
|
||||
await checkFormResult(page, '{}');
|
||||
|
||||
// screenshot
|
||||
compare = await page.compareScreenshot('should disable all');
|
||||
expect(compare).toMatchScreenshot();
|
||||
|
||||
// Reenable and set some value
|
||||
await page.click('#btnDisabled');
|
||||
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');
|
||||
expect(compare).toMatchScreenshot();
|
||||
@ -45,3 +58,8 @@ test('item: inputs', async () => {
|
||||
compare = await page.compareScreenshot('should set empty');
|
||||
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-content padding-vertical>
|
||||
<form onsubmit="return onSubmit(event)">
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label>Simple item</ion-label>
|
||||
@ -31,12 +32,12 @@
|
||||
|
||||
<ion-item>
|
||||
<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-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="nes">NES</ion-select-option>
|
||||
<ion-select-option value="n64" selected>Nintendo64</ion-select-option>
|
||||
@ -49,30 +50,34 @@
|
||||
|
||||
<ion-item>
|
||||
<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-label>Input (text)</ion-label>
|
||||
<ion-input id="text"></ion-input>
|
||||
<ion-input name="input" id="text"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<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-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-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-list>
|
||||
|
||||
<ion-button id="submit" type="submit">Submit</ion-button>
|
||||
<p id="form-result"></p>
|
||||
</form>
|
||||
|
||||
<ion-list>
|
||||
<ion-list-header>Controls</ion-list-header>
|
||||
<ion-item-divider>Value Controls</ion-item-divider>
|
||||
@ -181,6 +186,16 @@
|
||||
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>
|
||||
|
||||
</html>
|
||||
|
@ -33,6 +33,7 @@ export class Label implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
* @internal
|
||||
*/
|
||||
@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` |
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Description | Detail |
|
||||
| ---------- | ------------------------------- | ---------- |
|
||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
||||
|
||||
|
||||
## CSS Custom Properties
|
||||
|
||||
| Name | Description |
|
||||
|
@ -14,6 +14,8 @@
|
||||
box-sizing: border-box;
|
||||
|
||||
user-select: none;
|
||||
|
||||
z-index: $z-index-item-input;
|
||||
}
|
||||
|
||||
:host(.radio-disabled) {
|
||||
@ -38,7 +40,7 @@
|
||||
height: var(--inner-height);
|
||||
}
|
||||
|
||||
input {
|
||||
button {
|
||||
@include input-cover();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
|
||||
|
||||
import { CheckedInputChangeEvent, Color, Mode, StyleEvent } from '../../interface';
|
||||
import { findItemLabel } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
@Component({
|
||||
@ -14,7 +15,6 @@ import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
export class Radio implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-rb-${radioButtonIds++}`;
|
||||
private nativeInput!: HTMLInputElement;
|
||||
|
||||
@State() keyFocus = false;
|
||||
|
||||
@ -64,6 +64,7 @@ export class Radio implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
* @internal
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||
|
||||
@ -82,31 +83,6 @@ export class Radio implements ComponentInterface {
|
||||
*/
|
||||
@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')
|
||||
colorChanged() {
|
||||
this.emitStyle();
|
||||
@ -114,11 +90,6 @@ export class Radio implements ComponentInterface {
|
||||
|
||||
@Watch('checked')
|
||||
checkedChanged(isChecked: boolean) {
|
||||
if (this.nativeInput.checked !== isChecked) {
|
||||
// keep the checked value and native input `nync
|
||||
this.nativeInput.checked = isChecked;
|
||||
}
|
||||
|
||||
if (isChecked) {
|
||||
this.ionSelect.emit({
|
||||
checked: true,
|
||||
@ -129,11 +100,25 @@ export class Radio implements ComponentInterface {
|
||||
}
|
||||
|
||||
@Watch('disabled')
|
||||
disabledChanged(isDisabled: boolean) {
|
||||
this.nativeInput.disabled = isDisabled;
|
||||
disabledChanged() {
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
if (this.value == null) {
|
||||
this.value = this.inputId;
|
||||
}
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
this.ionRadioDidLoad.emit();
|
||||
}
|
||||
|
||||
componentDidUnload() {
|
||||
this.ionRadioDidUnload.emit();
|
||||
}
|
||||
|
||||
private emitStyle() {
|
||||
this.ionStyle.emit({
|
||||
'radio-checked': this.checked,
|
||||
@ -142,12 +127,7 @@ export class Radio implements ComponentInterface {
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
this.checkedChanged(true);
|
||||
}
|
||||
|
||||
private onChange = () => {
|
||||
this.checked = true;
|
||||
this.nativeInput.focus();
|
||||
}
|
||||
|
||||
private onKeyUp = () => {
|
||||
@ -164,7 +144,16 @@ export class Radio implements ComponentInterface {
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(this.el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
return {
|
||||
'role': 'radio',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-checked': `${this.checked}`,
|
||||
'aria-labelledby': labelId,
|
||||
class: {
|
||||
...createColorClasses(this.color),
|
||||
'in-item': hostContext('ion-item', this.el),
|
||||
@ -181,19 +170,14 @@ export class Radio implements ComponentInterface {
|
||||
<div class="radio-icon">
|
||||
<div class="radio-inner"/>
|
||||
</div>,
|
||||
<input
|
||||
type="radio"
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.onClick}
|
||||
onChange={this.onChange}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onKeyUp={this.onKeyUp}
|
||||
id={this.inputId}
|
||||
name={this.name}
|
||||
value={this.value}
|
||||
disabled={this.disabled}
|
||||
ref={r => this.nativeInput = (r as any)}
|
||||
/>
|
||||
>
|
||||
</button>,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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 |
|
||||
| `ionRadioDidUnload` | Emitted when the radio unloads. | void |
|
||||
| `ionSelect` | Emitted when the radio button is selected. | CheckedInputChangeEvent |
|
||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
||||
|
||||
|
||||
## CSS Custom Properties
|
||||
|
@ -47,7 +47,7 @@
|
||||
</ion-item-divider>
|
||||
<ion-item>
|
||||
<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>
|
||||
@ -143,6 +143,12 @@
|
||||
</ion-content>
|
||||
|
||||
<script>
|
||||
const radio = document.getElementById('pepperoni-radio');
|
||||
if (radio) {
|
||||
radio.addEventListener('ionSelect', (ev) => {
|
||||
console.log(ev.detail);
|
||||
});
|
||||
}
|
||||
var radioValues = ['fruitRadio', 'pizzaRadio', 'veggiesRadio'];
|
||||
printRadioValues();
|
||||
|
||||
|
@ -28,6 +28,10 @@
|
||||
z-index: $z-index-item-input;
|
||||
}
|
||||
|
||||
:host(.range-disabled) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
::slotted(ion-label) {
|
||||
flex: initial;
|
||||
}
|
||||
@ -36,9 +40,6 @@
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.range-slider {
|
||||
position: relative;
|
||||
|
||||
|
@ -133,6 +133,7 @@ export class Range implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
* @internal
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||
|
||||
@ -417,6 +418,7 @@ export class Range implements ComponentInterface {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
interface RangeKnob {
|
||||
knob: string;
|
||||
value: number;
|
||||
|
@ -40,7 +40,6 @@ left or right of the range.
|
||||
| `ionBlur` | Emitted when the range loses focus. | void |
|
||||
| `ionChange` | Emitted when the value property has changed. | InputChangeEvent |
|
||||
| `ionFocus` | Emitted when the range has focus. | void |
|
||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
||||
|
||||
|
||||
## CSS Custom Properties
|
||||
|
@ -66,6 +66,12 @@ export class RippleEffect implements ComponentInterface {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
role: 'presentation'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function removeRipple(ripple: HTMLElement) {
|
||||
|
@ -20,6 +20,17 @@
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
position: relative;
|
||||
display: block;
|
||||
background: #262626;
|
||||
color: white;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border-radius: 20px;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -45,23 +56,23 @@
|
||||
<p>
|
||||
<ion-button size="large" fill="clear">Large</ion-button>
|
||||
</p>
|
||||
<div class="my-block">
|
||||
<div class="my-block" ion-activatable>
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
This is just a div + effect behind
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
</div>
|
||||
<div class="my-block">
|
||||
<div class="my-block" ion-activatable>
|
||||
This is just a div + effect on top
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</div>
|
||||
|
||||
<div class="my-block">
|
||||
<div class="my-block" ion-activatable>
|
||||
This is just a div + effect
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</div>
|
||||
|
||||
<a class="my-block">
|
||||
<a class="my-block" ion-activatable>
|
||||
This is just a a + effect on top
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
@ -72,6 +83,10 @@
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</button>
|
||||
|
||||
<a class="block" ion-activatable>
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</a>
|
||||
</ion-content>
|
||||
|
||||
</ion-app>
|
||||
|
@ -376,7 +376,7 @@ export class Searchbar implements ComponentInterface {
|
||||
return [
|
||||
<div class="searchbar-input-container">
|
||||
<input
|
||||
ref={el => this.nativeInput = el as HTMLInputElement}
|
||||
ref={el => this.nativeInput = el}
|
||||
class="searchbar-input"
|
||||
onInput={this.onInput}
|
||||
onBlur={this.onBlur}
|
||||
|
@ -69,6 +69,8 @@ export class SegmentButton implements ComponentInterface {
|
||||
const { disabled, checked, color } = this;
|
||||
return {
|
||||
'ion-activatable': 'instant',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
|
||||
class: {
|
||||
...createColorClasses(color),
|
||||
'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 |
|
||||
| `ionChange` | Emitted when the value has changed. | SelectInputChangeEvent |
|
||||
| `ionFocus` | Emitted when the select has focus. | void |
|
||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
||||
|
||||
|
||||
## 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,
|
||||
based in `ion-select` settings.
|
||||
@ -82,7 +81,7 @@ based in `ion-select` settings.
|
||||
|
||||
#### Returns
|
||||
|
||||
Type: `Promise<OverlaySelect>`
|
||||
Type: `Promise<HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPopoverElement | undefined>`
|
||||
|
||||
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
||||
color: var(--placeholder-color);
|
||||
}
|
||||
|
||||
.select-cover {
|
||||
button {
|
||||
@include input-cover();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
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 { renderHiddenInput } from '../../utils/helpers';
|
||||
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { hostContext } from '../../utils/theme';
|
||||
|
||||
@Component({
|
||||
@ -16,7 +16,6 @@ export class Select implements ComponentInterface {
|
||||
|
||||
private childOpts: HTMLIonSelectOptionElement[] = [];
|
||||
private inputId = `ion-sel-${selectIds++}`;
|
||||
private labelId?: string;
|
||||
private overlay?: OverlaySelect;
|
||||
private didInit = false;
|
||||
|
||||
@ -110,6 +109,7 @@ export class Select implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
* @internal
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEvent>;
|
||||
|
||||
@ -141,11 +141,6 @@ export class Select implements ComponentInterface {
|
||||
async componentDidLoad() {
|
||||
await this.loadOptions();
|
||||
|
||||
const label = this.getLabel();
|
||||
if (label) {
|
||||
this.labelId = label.id = this.name + '-lbl';
|
||||
}
|
||||
|
||||
if (this.value === undefined) {
|
||||
if (this.multiple) {
|
||||
// there are no values set at this point
|
||||
@ -170,9 +165,22 @@ export class Select implements ComponentInterface {
|
||||
* based in `ion-select` settings.
|
||||
*/
|
||||
@Method()
|
||||
open(ev?: UIEvent): Promise<OverlaySelect> {
|
||||
let selectInterface = this.interface;
|
||||
async open(ev?: UIEvent): Promise<OverlaySelect | undefined> {
|
||||
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) {
|
||||
console.warn(`Select interface cannot be "${selectInterface}" with a multi-value select. Using the "alert" interface instead.`);
|
||||
selectInterface = 'alert';
|
||||
@ -186,11 +194,9 @@ export class Select implements ComponentInterface {
|
||||
if (selectInterface === 'popover') {
|
||||
return this.openPopover(ev!);
|
||||
}
|
||||
|
||||
if (selectInterface === 'action-sheet') {
|
||||
return this.openActionSheet();
|
||||
}
|
||||
|
||||
return this.openAlert();
|
||||
}
|
||||
|
||||
@ -222,11 +228,7 @@ export class Select implements ComponentInterface {
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const popover = this.overlay = await this.popoverCtrl.create(popoverOpts);
|
||||
await popover.present();
|
||||
this.isExpanded = true;
|
||||
return popover;
|
||||
return this.popoverCtrl.create(popoverOpts);
|
||||
}
|
||||
|
||||
private async openActionSheet() {
|
||||
@ -256,12 +258,7 @@ export class Select implements ComponentInterface {
|
||||
buttons: actionSheetButtons,
|
||||
cssClass: ['select-action-sheet', interfaceOptions.cssClass]
|
||||
};
|
||||
|
||||
const actionSheet = this.overlay = await this.actionSheetCtrl.create(actionSheetOpts);
|
||||
await actionSheet.present();
|
||||
|
||||
this.isExpanded = true;
|
||||
return actionSheet;
|
||||
return this.actionSheetCtrl.create(actionSheetOpts);
|
||||
}
|
||||
|
||||
private async openAlert() {
|
||||
@ -303,12 +300,7 @@ export class Select implements ComponentInterface {
|
||||
cssClass: ['select-alert', interfaceOptions.cssClass,
|
||||
(this.multiple ? 'multiple-select-alert' : 'single-select-alert')]
|
||||
};
|
||||
|
||||
const alert = this.overlay = await this.alertCtrl.create(alertOpts);
|
||||
await alert.present();
|
||||
|
||||
this.isExpanded = true;
|
||||
return alert;
|
||||
return this.alertCtrl.create(alertOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -319,12 +311,7 @@ export class Select implements ComponentInterface {
|
||||
if (!this.overlay) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const overlay = this.overlay;
|
||||
this.overlay = undefined;
|
||||
this.isExpanded = false;
|
||||
|
||||
return overlay.dismiss();
|
||||
return this.overlay.dismiss();
|
||||
}
|
||||
|
||||
private async loadOptions() {
|
||||
@ -349,11 +336,7 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
|
||||
private getLabel() {
|
||||
const item = this.el.closest('ion-item');
|
||||
if (item) {
|
||||
return item.querySelector('ion-label');
|
||||
}
|
||||
return null;
|
||||
return findItemLabel(this.el);
|
||||
}
|
||||
|
||||
private hasValue(): boolean {
|
||||
@ -379,6 +362,10 @@ export class Select implements ComponentInterface {
|
||||
});
|
||||
}
|
||||
|
||||
private onClick = (ev: UIEvent) => {
|
||||
this.open(ev);
|
||||
}
|
||||
|
||||
private onKeyUp = () => {
|
||||
this.keyFocus = true;
|
||||
}
|
||||
@ -393,7 +380,18 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(this.el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
|
||||
return {
|
||||
'role': 'combobox',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-expanded': `${this.isExpanded}`,
|
||||
'aria-haspopup': 'dialog',
|
||||
'aria-labelledby': labelId,
|
||||
class: {
|
||||
'in-item': hostContext('ion-item', this.el),
|
||||
'select-disabled': this.disabled,
|
||||
@ -403,7 +401,13 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
|
||||
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 selectText = this.getText();
|
||||
@ -418,11 +422,7 @@ export class Select implements ComponentInterface {
|
||||
};
|
||||
|
||||
return [
|
||||
<div
|
||||
role="textbox"
|
||||
aria-multiline="false"
|
||||
class={selectTextClasses}
|
||||
>
|
||||
<div class={selectTextClasses}>
|
||||
{selectText}
|
||||
</div>,
|
||||
<div class="select-icon" role="presentation">
|
||||
@ -430,19 +430,11 @@ export class Select implements ComponentInterface {
|
||||
</div>,
|
||||
<button
|
||||
type="button"
|
||||
role="combobox"
|
||||
aria-haspopup="dialog"
|
||||
aria-labelledby={this.labelId}
|
||||
aria-expanded={this.isExpanded ? 'true' : null}
|
||||
aria-disabled={this.disabled ? 'true' : null}
|
||||
onClick={this.open.bind(this)}
|
||||
onClick={this.onClick}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
class="select-cover"
|
||||
>
|
||||
<slot></slot>
|
||||
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
</button>
|
||||
];
|
||||
}
|
||||
|
@ -43,7 +43,6 @@ The textarea component accepts the [native textarea attributes](https://develope
|
||||
| `ionChange` | Emitted when the input value has changed. | TextInputChangeEvent |
|
||||
| `ionFocus` | Emitted when the input has focus. | void |
|
||||
| `ionInput` | Emitted when a keyboard input ocurred. | KeyboardEvent |
|
||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
||||
|
||||
|
||||
## Methods
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
||||
|
||||
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';
|
||||
|
||||
@Component({
|
||||
@ -131,8 +131,8 @@ export class Textarea implements ComponentInterface {
|
||||
protected valueChanged() {
|
||||
const nativeInput = this.nativeInput;
|
||||
const value = this.getValue();
|
||||
if (nativeInput!.value !== value) {
|
||||
nativeInput!.value = value;
|
||||
if (nativeInput && nativeInput.value !== value) {
|
||||
nativeInput.value = value;
|
||||
}
|
||||
this.ionChange.emit({ value });
|
||||
}
|
||||
@ -149,6 +149,7 @@ export class Textarea implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
* @internal
|
||||
*/
|
||||
@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
|
||||
*/
|
||||
@ -251,22 +228,53 @@ export class Textarea implements ComponentInterface {
|
||||
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() {
|
||||
return {
|
||||
class: {
|
||||
...createColorClasses(this.color)
|
||||
}
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
class: createColorClasses(this.color)
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
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 (
|
||||
<textarea
|
||||
class="native-textarea"
|
||||
ref={el => this.nativeInput = el as HTMLTextAreaElement}
|
||||
ref={el => this.nativeInput = el}
|
||||
autoCapitalize={this.autocapitalize}
|
||||
autoFocus={this.autofocus}
|
||||
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 |
|
||||
| `ionChange` | Emitted when the value property has changed. | CheckedInputChangeEvent |
|
||||
| `ionFocus` | Emitted when the toggle has focus. | void |
|
||||
| `ionStyle` | Emitted when the styles change. | StyleEvent |
|
||||
|
||||
|
||||
## CSS Custom Properties
|
||||
|
@ -77,6 +77,9 @@ describe('toggle', () => {
|
||||
// spy on the ionChange event
|
||||
const ionChange = await page.spyOnEvent('ionChange');
|
||||
|
||||
// check aria
|
||||
expect(toggle).toEqualAttribute('aria-checked', 'true');
|
||||
|
||||
// find the hidden input in the light dom
|
||||
const hiddenInput = await page.find('ion-toggle input[type=hidden]');
|
||||
|
||||
@ -86,16 +89,6 @@ describe('toggle', () => {
|
||||
// hidden in put should have aux-input class
|
||||
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
|
||||
toggle.setProperty('checked', true);
|
||||
|
||||
@ -116,7 +109,7 @@ describe('toggle', () => {
|
||||
expect(checkedValue2).toBe(false);
|
||||
|
||||
// hidden input property should no value
|
||||
expect(hiddenInput).toEqualAttribute('value', '');
|
||||
expect(toggle).toEqualAttribute('aria-checked', 'false');
|
||||
|
||||
expect(ionChange).toHaveReceivedEventTimes(1);
|
||||
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
display: inline-block;
|
||||
|
||||
outline: none;
|
||||
|
||||
contain: content;
|
||||
cursor: pointer;
|
||||
touch-action: none;
|
||||
@ -28,10 +30,6 @@
|
||||
border: 2px solid #5e9ed6;
|
||||
}
|
||||
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:host(.toggle-disabled) {
|
||||
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 { hapticSelection } from '../../utils/haptic';
|
||||
import { renderHiddenInput } from '../../utils/helpers';
|
||||
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
@Component({
|
||||
@ -16,7 +16,6 @@ import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
export class Toggle implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-tg-${toggleIds++}`;
|
||||
private nativeInput!: HTMLInputElement;
|
||||
private pivotX = 0;
|
||||
private gesture?: Gesture;
|
||||
|
||||
@ -80,6 +79,7 @@ export class Toggle implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
* @internal
|
||||
*/
|
||||
@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() {
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
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({
|
||||
el: this.el,
|
||||
queue: this.queue,
|
||||
@ -158,24 +170,6 @@ export class Toggle implements ComponentInterface {
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -183,7 +177,19 @@ export class Toggle implements ComponentInterface {
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(this.el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
|
||||
return {
|
||||
'role': 'checkbox',
|
||||
'tabindex': '0',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-checked': `${this.checked}`,
|
||||
'aria-labelledby': labelId,
|
||||
|
||||
class: {
|
||||
...createColorClasses(this.color),
|
||||
'in-item': hostContext('ion-item', this.el),
|
||||
@ -198,27 +204,13 @@ export class Toggle implements ComponentInterface {
|
||||
|
||||
render() {
|
||||
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-inner"/>
|
||||
</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>
|
||||
];
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,16 @@ export function hasShadowDom(el: HTMLElement) {
|
||||
return !!el.shadowRoot && !!(el as any).attachShadow;
|
||||
}
|
||||
|
||||
export function renderHiddenInput(container: HTMLElement, name: string, value: string | undefined | null, disabled: boolean) {
|
||||
if (hasShadowDom(container)) {
|
||||
export function findItemLabel(componentEl: HTMLElement) {
|
||||
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;
|
||||
if (!input) {
|
||||
input = container.ownerDocument!.createElement('input');
|
||||
|
Reference in New Issue
Block a user