mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(select): update select interfaces
This commit is contained in:
8
packages/core/src/components.d.ts
vendored
8
packages/core/src/components.d.ts
vendored
@@ -2493,7 +2493,6 @@ declare global {
|
||||
namespace JSXElements {
|
||||
export interface IonSelectPopoverAttributes extends HTMLAttributes {
|
||||
options?: SelectPopoverOption[];
|
||||
value?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2527,11 +2526,12 @@ declare global {
|
||||
cancelText?: string;
|
||||
okText?: string;
|
||||
placeholder?: string;
|
||||
selectOptions?: any;
|
||||
interface?: string;
|
||||
name?: string;
|
||||
selectedText?: string;
|
||||
multiple?: boolean;
|
||||
value?: string | string[];
|
||||
interface?: string;
|
||||
interfaceOptions?: any;
|
||||
value?: string|string[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ export interface ActionSheetOptions {
|
||||
|
||||
export interface ActionSheetButton {
|
||||
text?: string;
|
||||
role?: 'cancel' | 'destructive' | 'selected';
|
||||
role?: 'cancel' | 'destructive' | 'selected' | string;
|
||||
icon?: string;
|
||||
cssClass?: string;
|
||||
handler?: () => boolean | void;
|
||||
|
||||
@@ -8,8 +8,6 @@ export class PanRecognizer {
|
||||
private threshold: number;
|
||||
private maxCosine: number;
|
||||
private isDirX: boolean;
|
||||
|
||||
private angle = 0;
|
||||
private isPan = 0;
|
||||
|
||||
constructor(direction: string, threshold: number, maxAngle: number) {
|
||||
@@ -22,7 +20,6 @@ export class PanRecognizer {
|
||||
start(x: number, y: number) {
|
||||
this.startX = x;
|
||||
this.startY = y;
|
||||
this.angle = 0;
|
||||
this.isPan = 0;
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Component, ComponentDidLoad, Element, Event, EventEmitter, Listen, Prop, PropDidChange, State } from '@stencil/core';
|
||||
import { HTMLIonRadioElementEvent } from '../radio/radio';
|
||||
import { RadioGroupInput, TextInputChangeEvent } from '../../utils/input-interfaces';
|
||||
import { InputChangeEvent, RadioGroupInput } from '../../utils/input-interfaces';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-radio-group'
|
||||
})
|
||||
export class RadioGroup implements ComponentDidLoad, RadioGroupInput {
|
||||
radios: HTMLIonRadioElement[] = [];
|
||||
private didLoad: boolean;
|
||||
private radios: HTMLIonRadioElement[] = [];
|
||||
|
||||
@Element() el: HTMLElement;
|
||||
|
||||
@@ -73,14 +74,16 @@ export class RadioGroup implements ComponentDidLoad, RadioGroupInput {
|
||||
});
|
||||
}
|
||||
|
||||
// emit the new value
|
||||
this.ionChange.emit({ value: this.value });
|
||||
if (this.didLoad) {
|
||||
// emit the new value
|
||||
this.ionChange.emit({ value: this.value });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @output {Event} Emitted when the value has changed.
|
||||
*/
|
||||
@Event() ionChange: EventEmitter<TextInputChangeEvent>;
|
||||
@Event() ionChange: EventEmitter<InputChangeEvent>;
|
||||
|
||||
@Listen('ionRadioDidLoad')
|
||||
onRadioDidLoad(ev: HTMLIonRadioElementEvent) {
|
||||
@@ -120,7 +123,9 @@ export class RadioGroup implements ComponentDidLoad, RadioGroupInput {
|
||||
// ionSelect only come from the checked radio button
|
||||
this.radios.forEach(radio => {
|
||||
if (radio === ev.target) {
|
||||
this.value = radio.value;
|
||||
if (radio.value !== this.value) {
|
||||
this.value = radio.value;
|
||||
}
|
||||
} else {
|
||||
radio.checked = false;
|
||||
}
|
||||
@@ -144,6 +149,8 @@ export class RadioGroup implements ComponentDidLoad, RadioGroupInput {
|
||||
this.labelId = label.id = this.name + '-lbl';
|
||||
}
|
||||
}
|
||||
|
||||
this.didLoad = true;
|
||||
}
|
||||
|
||||
hostData() {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { createThemedClasses } from '../../utils/theme';
|
||||
}
|
||||
})
|
||||
export class Radio implements RadioButtonInput, ComponentDidLoad, ComponentDidUnload, ComponentWillLoad {
|
||||
private checkedTmr: any;
|
||||
private didLoad: boolean;
|
||||
private inputId: string;
|
||||
private nativeInput: HTMLInputElement;
|
||||
@@ -125,13 +126,18 @@ export class Radio implements RadioButtonInput, ComponentDidLoad, ComponentDidUn
|
||||
// keep the checked value and native input `nync
|
||||
this.nativeInput.checked = isChecked;
|
||||
}
|
||||
if (this.didLoad && isChecked) {
|
||||
|
||||
clearTimeout(this.checkedTmr);
|
||||
this.checkedTmr = setTimeout(() => {
|
||||
// only emit ionSelect when checked is true
|
||||
this.ionSelect.emit({
|
||||
checked: isChecked,
|
||||
value: this.value
|
||||
});
|
||||
}
|
||||
if (this.didLoad && isChecked) {
|
||||
this.ionSelect.emit({
|
||||
checked: isChecked,
|
||||
value: this.value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
@@ -153,6 +159,10 @@ export class Radio implements RadioButtonInput, ComponentDidLoad, ComponentDidUn
|
||||
});
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.checkedChanged(true);
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.checked = true;
|
||||
this.nativeInput.focus();
|
||||
@@ -194,6 +204,7 @@ export class Radio implements RadioButtonInput, ComponentDidLoad, ComponentDidUn
|
||||
</div>,
|
||||
<input
|
||||
type='radio'
|
||||
onClick={this.onClick.bind(this)}
|
||||
onChange={this.onChange.bind(this)}
|
||||
onFocus={this.onFocus.bind(this)}
|
||||
onBlur={this.onBlur.bind(this)}
|
||||
|
||||
@@ -41,12 +41,10 @@ string
|
||||
|
||||
## Events
|
||||
|
||||
#### ionSelect
|
||||
#### ionSelectOptionDidLoad
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
#### getText()
|
||||
#### ionSelectOptionDidUnload
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Element, Event, EventEmitter, Method, Prop } from '@stencil/core';
|
||||
import { Component, Element, Event, EventEmitter, Prop } from '@stencil/core';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -8,31 +8,56 @@ import { Component, Element, Event, EventEmitter, Method, Prop } from '@stencil/
|
||||
}
|
||||
})
|
||||
export class SelectOption {
|
||||
@Element() private el: HTMLElement;
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* @output {SelectOptionEvent} Emitted when the select option is selected.
|
||||
*/
|
||||
@Event() ionSelect: EventEmitter;
|
||||
@Element() el: HTMLElement;
|
||||
|
||||
/**
|
||||
* @input {boolean} If true, the user cannot interact with the select option.
|
||||
*/
|
||||
@Prop() disabled: boolean = false;
|
||||
@Prop() disabled = false;
|
||||
|
||||
/**
|
||||
* @input {boolean} If true, the element is selected.
|
||||
* If true, the element is selected.
|
||||
*/
|
||||
@Prop({ mutable: true }) selected: boolean = false;
|
||||
@Prop({ mutable: true }) selected = false;
|
||||
|
||||
/**
|
||||
* @input {string} The text value of the option.
|
||||
* The text value of the option.
|
||||
*/
|
||||
@Prop() value: string;
|
||||
@Prop({ mutable: true }) value: string;
|
||||
|
||||
@Method()
|
||||
getText(): string {
|
||||
return this.el.textContent || '';
|
||||
/**
|
||||
* Emitted when the select option loads.
|
||||
*/
|
||||
@Event() ionSelectOptionDidLoad: EventEmitter;
|
||||
|
||||
/**
|
||||
* Emitted when the select option unloads.
|
||||
*/
|
||||
@Event() ionSelectOptionDidUnload: EventEmitter;
|
||||
|
||||
constructor() {
|
||||
this.id = 'ion-selopt-' + (selectOptionIds++);
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.value = this.value || this.el.textContent;
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
this.ionSelectOptionDidLoad.emit();
|
||||
}
|
||||
|
||||
componentDidUnload() {
|
||||
this.ionSelectOptionDidUnload.emit();
|
||||
}
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
'role': 'option',
|
||||
'id': this.id
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -40,3 +65,10 @@ export class SelectOption {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export interface HTMLIonSelectOptionElementEvent extends CustomEvent {
|
||||
target: HTMLIonSelectOptionElement;
|
||||
}
|
||||
|
||||
let selectOptionIds = 0;
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
any
|
||||
|
||||
|
||||
#### value
|
||||
|
||||
string
|
||||
|
||||
|
||||
## Attributes
|
||||
|
||||
#### options
|
||||
@@ -24,16 +19,6 @@ string
|
||||
any
|
||||
|
||||
|
||||
#### value
|
||||
|
||||
string
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
#### ionDismiss
|
||||
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
import { Component, Event, EventEmitter, Listen, Prop, PropDidChange } from '@stencil/core';
|
||||
import { Component, Listen, Prop } from '@stencil/core';
|
||||
|
||||
|
||||
export interface SelectPopoverOption {
|
||||
@@ -17,49 +16,20 @@ export interface SelectPopoverOption {
|
||||
}}
|
||||
)
|
||||
export class SelectPopover {
|
||||
mode: string;
|
||||
color: string;
|
||||
private mode: string;
|
||||
|
||||
/**
|
||||
* @output {Event} Emitted when the select popover is dismissed.
|
||||
*/
|
||||
@Event() ionDismiss: EventEmitter;
|
||||
@Prop() options: SelectPopoverOption[] = [];
|
||||
|
||||
@Prop() options: SelectPopoverOption[];
|
||||
|
||||
/**
|
||||
* @input {string} the value of the select popover.
|
||||
*/
|
||||
@Prop({ mutable: true }) value: string;
|
||||
|
||||
@Listen('ionChange')
|
||||
onChange(ev: CustomEvent) {
|
||||
this.value = ev.detail.value;
|
||||
}
|
||||
|
||||
// public get value() {
|
||||
// let checkedOption = this.options.find(option => option.checked);
|
||||
|
||||
// return checkedOption ? checkedOption.value : undefined;
|
||||
// }
|
||||
|
||||
dismiss(value: any) {
|
||||
this.ionDismiss.emit(value);
|
||||
}
|
||||
|
||||
@PropDidChange('value')
|
||||
protected valueChanged(value: string) {
|
||||
let checkedOption = this.options.find(option => option.value === value);
|
||||
if (checkedOption && checkedOption.handler) {
|
||||
checkedOption.handler();
|
||||
}
|
||||
this.dismiss(value);
|
||||
@Listen('ionSelect')
|
||||
onSelect(ev: any) {
|
||||
const option = this.options.find(o => o.value === ev.target.value);
|
||||
option && option.handler && option.handler();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ion-list no-lines={this.mode === 'md'}>
|
||||
<ion-radio-group value={this.value}>
|
||||
<ion-radio-group>
|
||||
{this.options.map(option =>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
|
||||
@@ -22,11 +22,21 @@ boolean
|
||||
string
|
||||
|
||||
|
||||
#### interfaceOptions
|
||||
|
||||
any
|
||||
|
||||
|
||||
#### multiple
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
#### name
|
||||
|
||||
string
|
||||
|
||||
|
||||
#### okText
|
||||
|
||||
string
|
||||
@@ -37,11 +47,6 @@ string
|
||||
string
|
||||
|
||||
|
||||
#### selectOptions
|
||||
|
||||
any
|
||||
|
||||
|
||||
#### selectedText
|
||||
|
||||
string
|
||||
@@ -69,11 +74,21 @@ boolean
|
||||
string
|
||||
|
||||
|
||||
#### interfaceOptions
|
||||
|
||||
any
|
||||
|
||||
|
||||
#### multiple
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
#### name
|
||||
|
||||
string
|
||||
|
||||
|
||||
#### okText
|
||||
|
||||
string
|
||||
@@ -84,11 +99,6 @@ string
|
||||
string
|
||||
|
||||
|
||||
#### selectOptions
|
||||
|
||||
any
|
||||
|
||||
|
||||
#### selectedText
|
||||
|
||||
string
|
||||
@@ -101,9 +111,18 @@ any
|
||||
|
||||
## Events
|
||||
|
||||
#### ionBlur
|
||||
|
||||
|
||||
#### ionCancel
|
||||
|
||||
|
||||
#### ionChange
|
||||
|
||||
|
||||
#### ionFocus
|
||||
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
|
||||
@@ -42,3 +42,11 @@ ion-select {
|
||||
.select-option {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.select-key button {
|
||||
border: 2px solid blue;
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Component, CssClassMap, Element, Event, EventEmitter, Prop, PropDidChange, State } from '@stencil/core';
|
||||
import { Component, CssClassMap, Element, Event, EventEmitter, Listen, Prop, PropDidChange, State } from '@stencil/core';
|
||||
import { HTMLIonSelectOptionElementEvent } from '../select-option/select-option';
|
||||
import { BlurEvent, FocusEvent, SelectInputChangeEvent } from '../../utils/input-interfaces';
|
||||
|
||||
import { deepCopy, isCheckedProperty } from '../../utils/helpers';
|
||||
|
||||
import { ActionSheet } from '../action-sheet/action-sheet';
|
||||
import { Alert } from '../alert/alert';
|
||||
import { Label } from '../label/label';
|
||||
import { Popover } from '../popover/popover';
|
||||
|
||||
import { SelectOption } from '../select-option/select-option';
|
||||
import { ActionSheet, ActionSheetButton, ActionSheetOptions } from '../action-sheet/action-sheet';
|
||||
import { Alert, AlertOptions } from '../alert/alert';
|
||||
import { Popover, PopoverOptions } from '../popover/popover';
|
||||
|
||||
import { ActionSheetController } from '../action-sheet-controller/action-sheet-controller';
|
||||
import { AlertController } from '../alert-controller/alert-controller';
|
||||
import { PopoverController } from '../popover-controller/popover-controller';
|
||||
import { SelectPopoverOption } from '../select-popover/select-popover';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -25,153 +23,382 @@ import { PopoverController } from '../popover-controller/popover-controller';
|
||||
}
|
||||
})
|
||||
export class Select {
|
||||
private childOpts: HTMLIonSelectOptionElement[] = [];
|
||||
private selectId: string;
|
||||
private labelId: string;
|
||||
private overlay: ActionSheet | Alert | Popover;
|
||||
|
||||
// TODO rename this
|
||||
texts: any = [];
|
||||
@Element() private el: HTMLIonSelectElement;
|
||||
|
||||
// TODO typing
|
||||
label: Label;
|
||||
@State() isExpanded = false;
|
||||
|
||||
options: SelectOption[] = [];
|
||||
overlay: ActionSheet | Alert | Popover;
|
||||
@State() keyFocus: boolean;
|
||||
|
||||
@State() text: string;
|
||||
|
||||
@Prop({ connect: 'ion-action-sheet-controller' }) actionSheetCtrl: ActionSheetController;
|
||||
@Prop({ connect: 'ion-alert-controller' }) alertCtrl: AlertController;
|
||||
@Prop({ connect: 'ion-popover-controller' }) popoverCtrl: PopoverController;
|
||||
|
||||
@Element() private el: HTMLElement;
|
||||
|
||||
@State() text: string;
|
||||
|
||||
/**
|
||||
* @input {boolean} If true, the user cannot interact with the select. Defaults to `false`.
|
||||
* If true, the user cannot interact with the select. Defaults to `false`.
|
||||
*/
|
||||
@Prop() disabled: boolean = false;
|
||||
@Prop() disabled = false;
|
||||
|
||||
/**
|
||||
* @input {string} The text to display on the cancel button. Default: `Cancel`.
|
||||
* The text to display on the cancel button. Default: `Cancel`.
|
||||
*/
|
||||
@Prop() cancelText: string = 'Cancel';
|
||||
@Prop() cancelText = 'Cancel';
|
||||
|
||||
/**
|
||||
* @input {string} The text to display on the ok button. Default: `OK`.
|
||||
* The text to display on the ok button. Default: `OK`.
|
||||
*/
|
||||
@Prop() okText: string = 'OK';
|
||||
@Prop() okText = 'OK';
|
||||
|
||||
/**
|
||||
* @input {string} The text to display when the select is empty.
|
||||
* The text to display when the select is empty.
|
||||
*/
|
||||
@Prop() placeholder: string;
|
||||
|
||||
/**
|
||||
* @input {any} Any additional options that the `alert`, `action-sheet` or `popover` interface
|
||||
* The name of the control, which is submitted with the form data.
|
||||
*/
|
||||
@Prop({ mutable: true }) name: string;
|
||||
|
||||
/**
|
||||
* The text to display instead of the selected option's value.
|
||||
*/
|
||||
@Prop() selectedText: string;
|
||||
|
||||
/**
|
||||
* If true, the select can accept multiple values.
|
||||
*/
|
||||
@Prop() multiple: boolean;
|
||||
|
||||
/**
|
||||
* The interface the select should use: `action-sheet`, `popover` or `alert`. Default: `alert`.
|
||||
*/
|
||||
@Prop() interface = 'alert';
|
||||
|
||||
/**
|
||||
* Any additional options that the `alert`, `action-sheet` or `popover` interface
|
||||
* can take. See the [AlertController API docs](../../alert/AlertController/#create), the
|
||||
* [ActionSheetController API docs](../../action-sheet/ActionSheetController/#create) and the
|
||||
* [PopoverController API docs](../../popover/PopoverController/#create) for the
|
||||
* create options for each interface.
|
||||
*/
|
||||
@Prop() selectOptions: any = {};
|
||||
@Prop() interfaceOptions: any = {};
|
||||
|
||||
/**
|
||||
* @input {string} The interface the select should use: `action-sheet`, `popover` or `alert`. Default: `alert`.
|
||||
*
|
||||
*/
|
||||
@Prop() interface: string = 'alert';
|
||||
|
||||
/**
|
||||
* @input {string} The text to display instead of the selected option's value.
|
||||
*/
|
||||
@Prop() selectedText: string;
|
||||
|
||||
/**
|
||||
* @input {boolean} If true, the select can accept multiple values.
|
||||
*/
|
||||
@Prop() multiple: boolean;
|
||||
|
||||
/**
|
||||
* @input {string} the value of the select.
|
||||
*/
|
||||
@Prop({ mutable: true }) value: string | string[];
|
||||
@Prop({ mutable: true }) value: string|string[];
|
||||
|
||||
@PropDidChange('value')
|
||||
protected valueChanged() {
|
||||
this.optionUpdated();
|
||||
valueChanged() {
|
||||
// this select value just changed
|
||||
// double check the select option with this value is checked
|
||||
if (this.value === undefined) {
|
||||
// set to undefined
|
||||
// ensure all that are checked become unchecked
|
||||
this.childOpts.filter(o => o.selected).forEach(selectOption => {
|
||||
selectOption.selected = false;
|
||||
});
|
||||
this.text = '';
|
||||
|
||||
} else {
|
||||
let hasChecked = false;
|
||||
const texts: string[] = [];
|
||||
|
||||
this.childOpts.forEach(selectOption => {
|
||||
if ((Array.isArray(this.value) && this.value.indexOf(selectOption.value) > -1) || (selectOption.value === this.value)) {
|
||||
if (!selectOption.selected && (this.multiple || !hasChecked)) {
|
||||
// correct value for this select option
|
||||
// but this select option isn't checked yet
|
||||
// and we haven't found a checked yet
|
||||
// so CHECK IT!
|
||||
selectOption.selected = true;
|
||||
|
||||
} else if (!this.multiple && hasChecked && selectOption.selected) {
|
||||
// somehow we've got multiple select options
|
||||
// with the same value, but only one can be checked
|
||||
selectOption.selected = false;
|
||||
}
|
||||
|
||||
// remember we've got a checked select option button now
|
||||
hasChecked = true;
|
||||
|
||||
} else if (selectOption.selected) {
|
||||
// this select option doesn't have the correct value
|
||||
// and it's also checked, so let's uncheck it
|
||||
selectOption.selected = false;
|
||||
}
|
||||
|
||||
if (selectOption.selected) {
|
||||
texts.push(selectOption.textContent);
|
||||
}
|
||||
});
|
||||
|
||||
this.text = texts.join(', ');
|
||||
}
|
||||
|
||||
// emit the new value
|
||||
this.ionChange.emit({
|
||||
value: this.value,
|
||||
text: this.text
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @output {Event} Emitted when the selection is cancelled.
|
||||
* Emitted when the value has changed.
|
||||
*/
|
||||
@Event() ionChange: EventEmitter<SelectInputChangeEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when the selection is cancelled.
|
||||
*/
|
||||
@Event() ionCancel: EventEmitter;
|
||||
|
||||
/**
|
||||
* Emitted when the select has focus.
|
||||
*/
|
||||
@Event() ionFocus: EventEmitter<FocusEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when the select loses focus.
|
||||
*/
|
||||
@Event() ionBlur: EventEmitter<BlurEvent>;
|
||||
|
||||
|
||||
@Listen('ionSelectOptionDidLoad')
|
||||
optLoad(ev: HTMLIonSelectOptionElementEvent) {
|
||||
const selectOption = ev.target;
|
||||
this.childOpts.push(selectOption);
|
||||
|
||||
if (this.value !== undefined && selectOption.value === this.value) {
|
||||
// this select has a value and this
|
||||
// radio equals the correct select value
|
||||
// so let's check this select option
|
||||
selectOption.selected = true;
|
||||
|
||||
} else if (this.value === undefined && selectOption.selected) {
|
||||
// this select does not have a value
|
||||
// but this selection option is checked, so let's set the
|
||||
// select's value from the checked select option
|
||||
this.value = selectOption.value;
|
||||
|
||||
} else if (selectOption.selected) {
|
||||
// if it doesn't match one of the above cases, but the
|
||||
// select option is still checked, then we need to uncheck it
|
||||
selectOption.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('ionSelectOptionDidUnload')
|
||||
optUnload(ev: HTMLIonSelectOptionElementEvent) {
|
||||
const index = this.childOpts.indexOf(ev.target);
|
||||
if (index > -1) {
|
||||
this.childOpts.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('ionSelect')
|
||||
onSelect(ev: HTMLIonSelectOptionElementEvent) {
|
||||
// ionSelect only come from the checked select option
|
||||
this.childOpts.forEach(selectOption => {
|
||||
if (selectOption === ev.target) {
|
||||
this.value = selectOption.value;
|
||||
} else {
|
||||
selectOption.selected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.selectId = 'ion-sel-' + (selectIds++);
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
if (!this.value) {
|
||||
this.value = this.multiple ? [] : undefined;
|
||||
}
|
||||
this.name = this.name || this.selectId;
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
// Get the nearest label
|
||||
this.label = this.el.closest('ion-item').querySelector('ion-label');
|
||||
const label = this.getLabel();
|
||||
if (label) {
|
||||
this.labelId = label.id = this.name + '-lbl';
|
||||
}
|
||||
|
||||
this.setOptions();
|
||||
}
|
||||
|
||||
|
||||
setOptions() {
|
||||
// Get the options
|
||||
const options = this.el.querySelectorAll('ion-select-option');
|
||||
|
||||
Array.from(options).forEach(option => {
|
||||
if (!option.value) {
|
||||
option.value = option.getText();
|
||||
}
|
||||
this.options.push(option);
|
||||
});
|
||||
|
||||
const values = this.getValues();
|
||||
|
||||
if (values.length === 0) {
|
||||
if (this.multiple) {
|
||||
// there are no values set at this point
|
||||
// so check to see who should be selected
|
||||
let filtered = this.options.filter(o => o.selected).map(o => o.value);
|
||||
this.value = filtered;
|
||||
} else {
|
||||
this.optionUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
* Update the select options when the value changes
|
||||
*/
|
||||
optionUpdated() {
|
||||
this.texts = [];
|
||||
|
||||
if (this.options) {
|
||||
this.options.forEach(option => {
|
||||
// check this option if the option's value is in the values array
|
||||
option.selected = this.getValues().some(selectValue => {
|
||||
return isCheckedProperty(selectValue, option.value);
|
||||
});
|
||||
|
||||
if (option.selected) {
|
||||
this.texts.push(option.getText());
|
||||
}
|
||||
const checked = this.childOpts.filter(o => o.selected);
|
||||
(this.value as string[]).length = 0;
|
||||
checked.forEach(o => {
|
||||
// doing this instead of map() so we don't
|
||||
// fire off an unecessary change event
|
||||
(this.value as string[]).push(o.value);
|
||||
});
|
||||
}
|
||||
this.text = checked.map(o => o.textContent).join(', ');
|
||||
|
||||
this.text = this.texts.join(', ');
|
||||
} else {
|
||||
const checked = this.childOpts.find(o => o.selected);
|
||||
if (checked) {
|
||||
this.value = checked.value;
|
||||
this.text = checked.textContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
getValues(): any[] {
|
||||
if (!this.value) {
|
||||
return [];
|
||||
getLabel() {
|
||||
const item = this.el.closest('ion-item');
|
||||
if (item) {
|
||||
return item.querySelector('ion-label');
|
||||
}
|
||||
const values = Array.isArray(this.value) ? this.value : [this.value];
|
||||
return values;
|
||||
return null;
|
||||
}
|
||||
|
||||
open(ev: UIEvent) {
|
||||
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';
|
||||
}
|
||||
|
||||
if (selectInterface === 'popover' && !ev) {
|
||||
console.warn('Select interface cannot be a "popover" without passing an event. Using the "alert" interface instead.');
|
||||
selectInterface = 'alert';
|
||||
}
|
||||
|
||||
if (selectInterface === 'popover') {
|
||||
return this.openPopover(ev);
|
||||
}
|
||||
|
||||
if (selectInterface === 'action-sheet') {
|
||||
return this.openActionSheet();
|
||||
}
|
||||
|
||||
return this.openAlert();
|
||||
}
|
||||
|
||||
openPopover(ev: UIEvent) {
|
||||
const popoverOpts: PopoverOptions = {
|
||||
component: 'ion-select-popover',
|
||||
componentProps: {
|
||||
value: this.value,
|
||||
options: this.childOpts.map(o => {
|
||||
return {
|
||||
text: o.textContent,
|
||||
value: o.value,
|
||||
checked: o.selected,
|
||||
disabled: o.disabled,
|
||||
handler: () => {
|
||||
this.value = o.value;
|
||||
this.close();
|
||||
}
|
||||
} as SelectPopoverOption;
|
||||
})
|
||||
},
|
||||
cssClass: 'select-popover ' + (this.interfaceOptions.cssClass ? ' ' + this.interfaceOptions.cssClass : ''),
|
||||
ev: ev
|
||||
};
|
||||
|
||||
const popover = this.popoverCtrl.create(popoverOpts);
|
||||
|
||||
return popover.then(overlay => {
|
||||
this.overlay = overlay;
|
||||
|
||||
return overlay.present().then(() => {
|
||||
this.isExpanded = true;
|
||||
return overlay;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
openActionSheet() {
|
||||
const actionSheetButtons: ActionSheetButton[] = this.childOpts.map(option => {
|
||||
return {
|
||||
role: (option.selected ? 'selected' : ''),
|
||||
text: option.textContent,
|
||||
handler: () => {
|
||||
this.value = option.value;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
actionSheetButtons.push({
|
||||
text: this.cancelText,
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
this.ionCancel.emit(this);
|
||||
}
|
||||
});
|
||||
|
||||
const actionSheetOpts: ActionSheetOptions = {
|
||||
buttons: actionSheetButtons,
|
||||
cssClass: 'select-action-sheet' + (this.interfaceOptions.cssClass ? ' ' + this.interfaceOptions.cssClass : '')
|
||||
};
|
||||
|
||||
const actionSheet = this.actionSheetCtrl.create(actionSheetOpts);
|
||||
return actionSheet.then(overlay => {
|
||||
this.overlay = overlay;
|
||||
return overlay.present().then(() => {
|
||||
this.isExpanded = true;
|
||||
return overlay;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
openAlert() {
|
||||
const label = this.getLabel();
|
||||
let labelText: string = null;
|
||||
if (label) {
|
||||
labelText = label.textContent;
|
||||
}
|
||||
|
||||
const alertOpts: AlertOptions = {
|
||||
title: labelText,
|
||||
inputs: this.childOpts.map(o => {
|
||||
return {
|
||||
type: (this.multiple ? 'checkbox' : 'radio'),
|
||||
label: o.textContent,
|
||||
value: o.value,
|
||||
checked: o.selected,
|
||||
disabled: o.disabled
|
||||
};
|
||||
}),
|
||||
buttons: [
|
||||
{
|
||||
text: this.cancelText,
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
this.ionCancel.emit(this);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: this.okText,
|
||||
handler: (selectedValues) => {
|
||||
this.value = selectedValues;
|
||||
}
|
||||
}
|
||||
],
|
||||
cssClass: 'select-alert ' +
|
||||
(this.multiple ? 'multiple-select-alert' : 'single-select-alert') +
|
||||
(this.interfaceOptions.cssClass ? ' ' + this.interfaceOptions.cssClass : '')
|
||||
};
|
||||
|
||||
const alert = this.alertCtrl.create(alertOpts);
|
||||
return alert.then(overlay => {
|
||||
this.overlay = overlay;
|
||||
return overlay.present().then(() => {
|
||||
this.isExpanded = true;
|
||||
return overlay;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the select interface.
|
||||
@@ -179,192 +406,35 @@ export class Select {
|
||||
close(): Promise<any> | void {
|
||||
// TODO check !this.overlay || !this.isFocus()
|
||||
if (!this.overlay) {
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.overlay.dismiss();
|
||||
const overlay = this.overlay;
|
||||
this.overlay = null;
|
||||
|
||||
this.isExpanded = false;
|
||||
|
||||
return overlay.dismiss();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
getText() {
|
||||
return (this.multiple ? this.texts : this.texts.join());
|
||||
onKeyUp() {
|
||||
this.keyFocus = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
resetInterface(ev: Event): string {
|
||||
let selectInterface = this.interface;
|
||||
|
||||
if ((selectInterface === 'action-sheet' || selectInterface === 'popover') && this.multiple) {
|
||||
console.warn('Interface cannot be "' + selectInterface + '" with a multi-value select. Using the "alert" interface instead.');
|
||||
selectInterface = 'alert';
|
||||
}
|
||||
|
||||
if (selectInterface === 'popover' && !ev) {
|
||||
console.warn('Interface cannot be "' + selectInterface + '" without passing an event. Using the "alert" interface instead.');
|
||||
selectInterface = 'alert';
|
||||
}
|
||||
|
||||
return selectInterface;
|
||||
onFocus() {
|
||||
this.ionFocus.emit();
|
||||
}
|
||||
|
||||
buildAlert(selectOptions: any) {
|
||||
console.debug('Build Select: Alert with', selectOptions, this.options);
|
||||
|
||||
// user cannot provide inputs from selectOptions
|
||||
// alert inputs must be created by ionic from ion-select-options
|
||||
selectOptions.inputs = this.options.map((option: any) => {
|
||||
return {
|
||||
type: (this.multiple ? 'checkbox' : 'radio'),
|
||||
label: option.getText(),
|
||||
value: option.value,
|
||||
checked: option.selected,
|
||||
disabled: option.disabled,
|
||||
handler: (selectedOption: any) => {
|
||||
// Only emit the select event if it is being checked
|
||||
// For multi selects this won't emit when unchecking
|
||||
if (selectedOption.checked) {
|
||||
option.ionSelect.emit(option.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// If multiple is true use checkboxes, else use radio buttons
|
||||
var selectCssClass = 'select-alert ' + this.multiple ? 'multiple-select-alert' : 'single-select-alert';
|
||||
|
||||
// If the user passed a cssClass for the select, add it
|
||||
selectCssClass += selectOptions.cssClass ? ' ' + selectOptions.cssClass : '';
|
||||
|
||||
// Add an ok button to the alert
|
||||
selectOptions.buttons = selectOptions.buttons.concat({
|
||||
text: this.okText,
|
||||
handler: (selectedValues: any) => this.value = selectedValues
|
||||
});
|
||||
|
||||
// create the alert instance from our built up selectOptions
|
||||
const alertOptions = {
|
||||
cssClass: selectCssClass,
|
||||
...selectOptions
|
||||
};
|
||||
|
||||
console.debug('Built Select: Alert with', alertOptions);
|
||||
return this.alertCtrl.create(alertOptions);
|
||||
}
|
||||
|
||||
buildActionSheet(selectOptions: any) {
|
||||
console.debug('Building Select: Action Sheet with', selectOptions, this.options);
|
||||
|
||||
selectOptions.buttons = selectOptions.buttons.concat(this.options.map((option: any) => {
|
||||
return {
|
||||
role: (option.selected ? 'selected' : ''),
|
||||
text: option.getText(),
|
||||
handler: () => {
|
||||
this.value = option.value;
|
||||
option.ionSelect.emit(option.value);
|
||||
}
|
||||
};
|
||||
}));
|
||||
|
||||
var selectCssClass = 'select-action-sheet';
|
||||
|
||||
// If the user passed a cssClass for the select, add it
|
||||
selectCssClass += selectOptions.cssClass ? ' ' + selectOptions.cssClass : '';
|
||||
|
||||
const actionSheetOptions = {
|
||||
cssClass: selectCssClass,
|
||||
...selectOptions
|
||||
};
|
||||
|
||||
console.debug('Built Select: Action Sheet with', actionSheetOptions);
|
||||
return this.actionSheetCtrl.create(actionSheetOptions);
|
||||
}
|
||||
|
||||
buildPopover(selectOptions: any) {
|
||||
console.debug('Building Select: Popover with', selectOptions, this.options);
|
||||
|
||||
selectOptions = this.options.map((option: any) => {
|
||||
return {
|
||||
text: option.getText(),
|
||||
checked: option.selected,
|
||||
disabled: option.disabled,
|
||||
value: option.value,
|
||||
handler: () => {
|
||||
this.value = option.value;
|
||||
option.ionSelect.emit(option.value);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var selectCssClass = 'select-popover';
|
||||
|
||||
// If the user passed a cssClass for the select, add it
|
||||
selectCssClass += selectOptions.cssClass ? ' ' + selectOptions.cssClass : '';
|
||||
|
||||
const popoverOptions = {
|
||||
component: 'ion-select-popover',
|
||||
componentProps: {
|
||||
options: selectOptions
|
||||
},
|
||||
cssClass: selectCssClass,
|
||||
ev: event
|
||||
};
|
||||
|
||||
console.debug('Built Select: Popover with', popoverOptions);
|
||||
return this.popoverCtrl.create(popoverOptions);
|
||||
}
|
||||
|
||||
open(ev: UIEvent) {
|
||||
// the user may have assigned some options specifically for the alert
|
||||
const selectOptions = deepCopy(this.selectOptions);
|
||||
|
||||
// make sure their buttons array is removed from the options
|
||||
// and we create a new array for the alert's two buttons
|
||||
selectOptions.buttons = [{
|
||||
text: this.cancelText,
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
this.ionCancel.emit(this);
|
||||
}
|
||||
}];
|
||||
|
||||
// if the selectOptions didn't provide a title then use the label's text
|
||||
if (!selectOptions.title && this.label) {
|
||||
selectOptions.title = this.label.getText();
|
||||
}
|
||||
|
||||
// If the user passed in an invalid interface we need to reset it to alert
|
||||
let selectInterface = this.resetInterface(ev);
|
||||
|
||||
let controller: Promise<any>;
|
||||
|
||||
if (selectInterface === 'action-sheet') {
|
||||
controller = this.buildActionSheet(selectOptions);
|
||||
} else if (selectInterface === 'popover') {
|
||||
controller = this.buildPopover(selectOptions);
|
||||
} else {
|
||||
controller = this.buildAlert(selectOptions);
|
||||
}
|
||||
|
||||
controller.then((component: any) => {
|
||||
component.present();
|
||||
|
||||
// component.onDidDismiss(() => {
|
||||
// this.overlay = undefined;
|
||||
// })
|
||||
|
||||
// this.overlay = component;
|
||||
});
|
||||
onBlur() {
|
||||
this.keyFocus = false;
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
class: {
|
||||
'select-disabled': this.disabled
|
||||
'select-disabled': this.disabled,
|
||||
'select-key': this.keyFocus
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -372,7 +442,6 @@ export class Select {
|
||||
render() {
|
||||
let addPlaceholderClass = false;
|
||||
|
||||
// If selected text has been passed in, use that first
|
||||
let selectText = this.selectedText || this.text;
|
||||
if (!selectText && this.placeholder) {
|
||||
selectText = this.placeholder;
|
||||
@@ -385,18 +454,30 @@ export class Select {
|
||||
};
|
||||
|
||||
return [
|
||||
<div class={ selectTextClasses }>{ selectText }</div>,
|
||||
<div class='select-icon'>
|
||||
<div
|
||||
role='textbox'
|
||||
aria-multiline='false'
|
||||
class={ selectTextClasses }>{ selectText }
|
||||
</div>,
|
||||
<div class='select-icon' role='presentation'>
|
||||
<div class='select-icon-inner'></div>
|
||||
</div>,
|
||||
<button
|
||||
aria-haspopup='true'
|
||||
id={this.selectId}
|
||||
role='combobox'
|
||||
aria-haspopup='dialog'
|
||||
aria-expanded={this.isExpanded}
|
||||
aria-labelledby={this.labelId}
|
||||
aria-disabled={this.disabled ? 'true' : false}
|
||||
onClick={this.open.bind(this)}
|
||||
onKeyUp={this.onKeyUp.bind(this)}
|
||||
onFocus={this.onFocus.bind(this)}
|
||||
onBlur={this.onBlur.bind(this)}
|
||||
class='item-cover'>
|
||||
</button>
|
||||
<slot></slot>
|
||||
</button>,
|
||||
<input type='hidden' name={this.name} value={this.value}/>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
let selectIds = 0;
|
||||
|
||||
@@ -59,6 +59,10 @@ ion-toggle input {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
ion-toggle :focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.toggle-key input {
|
||||
border: 2px solid #5e9ed6;
|
||||
}
|
||||
|
||||
@@ -223,11 +223,6 @@ export function getOrAppendElement(tagName: string): Element {
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
export function deepCopy(obj: any) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export function getWindow() {
|
||||
return window;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ export interface CheckboxInput extends BaseInput {
|
||||
*/
|
||||
checked: boolean;
|
||||
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The change event is fired when the value of has changed.
|
||||
*/
|
||||
@@ -61,6 +63,8 @@ export interface RadioButtonInput extends BaseInput {
|
||||
*/
|
||||
checked: boolean;
|
||||
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* A single radio button fires an ionSelect event, whereas
|
||||
* a radio group fires an ionChange event. It would be more common
|
||||
@@ -77,33 +81,16 @@ export interface RadioButtonInput extends BaseInput {
|
||||
* Focus on the input element; keystrokes will subsequently go to this element.
|
||||
*/
|
||||
ionFocus: EventEmitter<FocusEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when the styles change. This is useful for parent
|
||||
* components to know how to style themselves depending on the
|
||||
* child input. For example, a disabled ion-toggle may give
|
||||
* its wrapping ion-item a different style.
|
||||
*/
|
||||
ionStyle: EventEmitter<StyleEvent>;
|
||||
}
|
||||
|
||||
|
||||
export interface CheckedInputChangeEvent {
|
||||
export interface InputChangeEvent {
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
||||
export interface CheckedInputChangeEvent extends InputChangeEvent {
|
||||
checked: boolean;
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
||||
export interface TextInputChangeEvent {
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
||||
export interface SelectOptionInput extends BaseInput {
|
||||
/**
|
||||
* Indicates whether the option is currently selected.
|
||||
*/
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +112,8 @@ export interface SelectInput extends BaseInput {
|
||||
*/
|
||||
multiple: boolean;
|
||||
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The change event is fired when the value of has changed.
|
||||
* This is actually more similar to the native "input" event
|
||||
@@ -152,8 +141,14 @@ export interface SelectInput extends BaseInput {
|
||||
}
|
||||
|
||||
|
||||
export interface SelectInputChangeEvent {
|
||||
value: string|string[];
|
||||
text: string;
|
||||
}
|
||||
|
||||
|
||||
export interface RadioGroupInput extends BaseInput {
|
||||
ionChange: EventEmitter<TextInputChangeEvent>;
|
||||
ionChange: EventEmitter<InputChangeEvent>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user