mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 04:14:21 +08:00
feat(select): add popover interface as an option
This commit is contained in:

committed by
Brandy Carney

parent
314f7e5e1e
commit
745d808728
@ -65,6 +65,62 @@
|
|||||||
|
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
|
<ion-list>
|
||||||
|
<ion-list-header>Popover Interface Select</ion-list-header>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Gender</ion-label>
|
||||||
|
<ion-select [(ngModel)]="gender" interface="popover">
|
||||||
|
<ion-option value="f">Female</ion-option>
|
||||||
|
<ion-option value="m">Male</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Gaming</ion-label>
|
||||||
|
<ion-select [(ngModel)]="gaming" okText="Okay" cancelText="Dismiss" interface="popover">
|
||||||
|
<ion-option value="nes">NES</ion-option>
|
||||||
|
<ion-option value="n64">Nintendo64</ion-option>
|
||||||
|
<ion-option value="ps">PlayStation</ion-option>
|
||||||
|
<ion-option value="genesis">Sega Genesis</ion-option>
|
||||||
|
<ion-option value="saturn">Sega Saturn</ion-option>
|
||||||
|
<ion-option value="snes">SNES</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Date</ion-label>
|
||||||
|
<ion-select (ionChange)="monthChange($event)" interface="popover">
|
||||||
|
<ion-option value="01">January</ion-option>
|
||||||
|
<ion-option value="02">February</ion-option>
|
||||||
|
<ion-option value="03" selected="true">March</ion-option>
|
||||||
|
<ion-option value="04">April</ion-option>
|
||||||
|
<ion-option value="05">May</ion-option>
|
||||||
|
<ion-option value="06">June</ion-option>
|
||||||
|
<ion-option value="07">July</ion-option>
|
||||||
|
<ion-option value="08">August</ion-option>
|
||||||
|
<ion-option value="09">September</ion-option>
|
||||||
|
<ion-option value="10">October</ion-option>
|
||||||
|
<ion-option value="11">November</ion-option>
|
||||||
|
<ion-option value="12">December</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
<ion-select (ionChange)="yearChange($event)" interface="popover">
|
||||||
|
<ion-option>1989</ion-option>
|
||||||
|
<ion-option>1990</ion-option>
|
||||||
|
<ion-option>1991</ion-option>
|
||||||
|
<ion-option>1992</ion-option>
|
||||||
|
<ion-option>1993</ion-option>
|
||||||
|
<ion-option selected="true">1994</ion-option>
|
||||||
|
<ion-option>1995</ion-option>
|
||||||
|
<ion-option>1996</ion-option>
|
||||||
|
<ion-option>1997</ion-option>
|
||||||
|
<ion-option>1998</ion-option>
|
||||||
|
<ion-option>1999</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-list-header>Multiple Value Select</ion-list-header>
|
<ion-list-header>Multiple Value Select</ion-list-header>
|
||||||
|
|
||||||
|
46
src/components/select/select-popover-component.ts
Normal file
46
src/components/select/select-popover-component.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { NavParams } from '../../navigation/nav-params';
|
||||||
|
import { ViewController } from '../../navigation/view-controller';
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
export interface SelectPopoverOption {
|
||||||
|
text: string;
|
||||||
|
value: string;
|
||||||
|
disabled: boolean;
|
||||||
|
checked: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<ion-list radio-group [(ngModel)]="value">
|
||||||
|
<ion-item *ngFor="let option of options; let i = index">
|
||||||
|
<ion-label>{{option.text}}</ion-label>
|
||||||
|
<ion-radio [checked]="option.checked" [value]="option.value" [disabled]="option.disabled"></ion-radio>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class SelectPopover implements OnInit {
|
||||||
|
|
||||||
|
public get value() {
|
||||||
|
let checkedOption = this.options.find(option => option.checked);
|
||||||
|
|
||||||
|
return checkedOption ? checkedOption.value : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set value(value: any) {
|
||||||
|
this.viewController.dismiss(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private options: SelectPopoverOption[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private navParams: NavParams,
|
||||||
|
private viewController: ViewController
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
this.options = this.navParams.data.options;
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,37 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { Select } from './select';
|
import { Select } from './select';
|
||||||
|
import { SelectPopover } from './select-popover-component';
|
||||||
|
|
||||||
|
import { ItemModule } from '../item/item.module';
|
||||||
|
import { LabelModule } from '../label/label.module';
|
||||||
|
import { ListModule } from '../list/list.module';
|
||||||
|
import { RadioModule } from '../radio/radio.module';
|
||||||
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ItemModule,
|
||||||
|
LabelModule,
|
||||||
|
ListModule,
|
||||||
|
RadioModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
Select
|
Select,
|
||||||
|
SelectPopover
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
Select
|
Select,
|
||||||
|
SelectPopover
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
SelectPopover
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SelectModule {
|
export class SelectModule {
|
||||||
|
@ -3,6 +3,19 @@
|
|||||||
// Select
|
// Select
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
/// @prop - Margin top of the select popover list
|
||||||
|
$select-popover-list-margin-top: -1px !default;
|
||||||
|
|
||||||
|
/// @prop - Margin right of the select popover list
|
||||||
|
$select-popover-list-margin-right: 0 !default;
|
||||||
|
|
||||||
|
/// @prop - Margin bottom of the select popover list
|
||||||
|
$select-popover-list-margin-bottom: -1px !default;
|
||||||
|
|
||||||
|
/// @prop - Margin left of the select popover list
|
||||||
|
$select-popover-list-margin-left: 0 !default;
|
||||||
|
|
||||||
|
|
||||||
ion-select {
|
ion-select {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -32,3 +45,7 @@ ion-select {
|
|||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-popover ion-list {
|
||||||
|
margin: $select-popover-list-margin-top $select-popover-list-margin-right $select-popover-list-margin-bottom $select-popover-list-margin-left;
|
||||||
|
}
|
||||||
|
@ -3,14 +3,17 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|||||||
|
|
||||||
import { ActionSheet } from '../action-sheet/action-sheet';
|
import { ActionSheet } from '../action-sheet/action-sheet';
|
||||||
import { Alert } from '../alert/alert';
|
import { Alert } from '../alert/alert';
|
||||||
|
import { Popover } from '../popover/popover';
|
||||||
import { App } from '../app/app';
|
import { App } from '../app/app';
|
||||||
import { Config } from '../../config/config';
|
import { Config } from '../../config/config';
|
||||||
|
import { DeepLinker } from '../../navigation/deep-linker';
|
||||||
import { Form } from '../../util/form';
|
import { Form } from '../../util/form';
|
||||||
import { BaseInput } from '../../util/base-input';
|
import { BaseInput } from '../../util/base-input';
|
||||||
import { isCheckedProperty, isTrueProperty, deepCopy, deepEqual } from '../../util/util';
|
import { isCheckedProperty, isTrueProperty, deepCopy, deepEqual } from '../../util/util';
|
||||||
import { Item } from '../item/item';
|
import { Item } from '../item/item';
|
||||||
import { NavController } from '../../navigation/nav-controller';
|
import { NavController } from '../../navigation/nav-controller';
|
||||||
import { Option } from '../option/option';
|
import { Option } from '../option/option';
|
||||||
|
import { SelectPopover, SelectPopoverOption } from './select-popover-component';
|
||||||
|
|
||||||
export const SELECT_VALUE_ACCESSOR: any = {
|
export const SELECT_VALUE_ACCESSOR: any = {
|
||||||
provide: NG_VALUE_ACCESSOR,
|
provide: NG_VALUE_ACCESSOR,
|
||||||
@ -38,9 +41,10 @@ export const SELECT_VALUE_ACCESSOR: any = {
|
|||||||
*
|
*
|
||||||
* By default, the `ion-select` uses the {@link ../../alert/AlertController AlertController API}
|
* By default, the `ion-select` uses the {@link ../../alert/AlertController AlertController API}
|
||||||
* to open up the overlay of options in an alert. The interface can be changed to use the
|
* to open up the overlay of options in an alert. The interface can be changed to use the
|
||||||
* {@link ../../action-sheet/ActionSheetController ActionSheetController API} by passing
|
* {@link ../../action-sheet/ActionSheetController ActionSheetController API} or
|
||||||
* `action-sheet` to the `interface` property. Read the other sections for the limitations of the
|
* {@link ../../popover/PopoverController PopoverController API} by passing `action-sheet` or `popover`,
|
||||||
* action sheet interface.
|
* respectively, to the `interface` property. Read on to the other sections for the limitations
|
||||||
|
* of the different interfaces.
|
||||||
*
|
*
|
||||||
* ### Single Value: Radio Buttons
|
* ### Single Value: Radio Buttons
|
||||||
*
|
*
|
||||||
@ -70,7 +74,7 @@ export const SELECT_VALUE_ACCESSOR: any = {
|
|||||||
* selected option values. In the example below, because each option is not given
|
* selected option values. In the example below, because each option is not given
|
||||||
* a `value`, then it'll use its text as the value instead.
|
* a `value`, then it'll use its text as the value instead.
|
||||||
*
|
*
|
||||||
* Note: the action sheet interface will not work with a multi-value select.
|
* Note: the `action-sheet` and `popover` interfaces will not work with a multi-value select.
|
||||||
*
|
*
|
||||||
* ```html
|
* ```html
|
||||||
* <ion-item>
|
* <ion-item>
|
||||||
@ -96,19 +100,22 @@ export const SELECT_VALUE_ACCESSOR: any = {
|
|||||||
* </ion-select>
|
* </ion-select>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* The action sheet interface does not have an `OK` button, clicking
|
* The `action-sheet` and `popover` interfaces do not have an `OK` button, clicking
|
||||||
* on any of the options will automatically close the overlay and select
|
* on any of the options will automatically close the overlay and select
|
||||||
* that value.
|
* that value.
|
||||||
*
|
*
|
||||||
* ### Select Options
|
* ### Select Options
|
||||||
*
|
*
|
||||||
* Since `ion-select` uses the `Alert` and `Action Sheet` interfaces, options can be
|
* Since `ion-select` uses the `Alert`, `Action Sheet` and `Popover` interfaces, options can be
|
||||||
* passed to these components through the `selectOptions` property. This can be used
|
* passed to these components through the `selectOptions` property. This can be used
|
||||||
* to pass a custom title, subtitle, css class, and more. See the
|
* to pass a custom title, subtitle, css class, and more. See the
|
||||||
* {@link ../../alert/AlertController/#create AlertController API docs} and
|
* {@link ../../alert/AlertController/#create AlertController API docs},
|
||||||
* {@link ../../action-sheet/ActionSheetController/#create ActionSheetController API docs}
|
* {@link ../../action-sheet/ActionSheetController/#create ActionSheetController API docs}, and
|
||||||
|
* {@link ../../popover/PopoverController/#create PopoverController API docs}
|
||||||
* for the properties that each interface accepts.
|
* for the properties that each interface accepts.
|
||||||
*
|
*
|
||||||
|
* For example, to change the `mode` of the overlay, pass it into `selectOptions`.
|
||||||
|
*
|
||||||
* ```html
|
* ```html
|
||||||
* <ion-select [selectOptions]="selectOptions">
|
* <ion-select [selectOptions]="selectOptions">
|
||||||
* ...
|
* ...
|
||||||
@ -118,7 +125,8 @@ export const SELECT_VALUE_ACCESSOR: any = {
|
|||||||
* ```ts
|
* ```ts
|
||||||
* this.selectOptions = {
|
* this.selectOptions = {
|
||||||
* title: 'Pizza Toppings',
|
* title: 'Pizza Toppings',
|
||||||
* subTitle: 'Select your toppings'
|
* subTitle: 'Select your toppings',
|
||||||
|
* mode: 'md'
|
||||||
* };
|
* };
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
@ -176,7 +184,7 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
|||||||
@Input() selectOptions: any = {};
|
@Input() selectOptions: any = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {string} The interface the select should use: `action-sheet` or `alert`. Default: `alert`.
|
* @input {string} The interface the select should use: `action-sheet`, `popover` or `alert`. Default: `alert`.
|
||||||
*/
|
*/
|
||||||
@Input() interface: string = '';
|
@Input() interface: string = '';
|
||||||
|
|
||||||
@ -197,7 +205,8 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
|||||||
elementRef: ElementRef,
|
elementRef: ElementRef,
|
||||||
renderer: Renderer,
|
renderer: Renderer,
|
||||||
@Optional() item: Item,
|
@Optional() item: Item,
|
||||||
@Optional() private _nav: NavController
|
@Optional() private _nav: NavController,
|
||||||
|
public deepLinker: DeepLinker
|
||||||
) {
|
) {
|
||||||
super(config, elementRef, renderer, 'select', [], form, item, null);
|
super(config, elementRef, renderer, 'select', [], form, item, null);
|
||||||
}
|
}
|
||||||
@ -215,7 +224,7 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
|||||||
}
|
}
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.open();
|
this.open(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('keyup.space')
|
@HostListener('keyup.space')
|
||||||
@ -226,7 +235,7 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
|||||||
/**
|
/**
|
||||||
* Open the select interface.
|
* Open the select interface.
|
||||||
*/
|
*/
|
||||||
open() {
|
open(ev?: UIEvent) {
|
||||||
if (this.isFocus() || this._disabled) {
|
if (this.isFocus() || this._disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -257,12 +266,18 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
|||||||
this.interface = 'alert';
|
this.interface = 'alert';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.interface === 'action-sheet' && this._multi) {
|
if ((this.interface === 'action-sheet' || this.interface === 'popover') && this._multi) {
|
||||||
console.warn('Interface cannot be "action-sheet" with a multi-value select. Using the "alert" interface.');
|
console.warn('Interface cannot be "' + this.interface + '" with a multi-value select. Using the "alert" interface.');
|
||||||
this.interface = 'alert';
|
this.interface = 'alert';
|
||||||
}
|
}
|
||||||
|
|
||||||
let overlay: ActionSheet | Alert;
|
if (this.interface === 'popover' && !ev) {
|
||||||
|
console.warn('Interface cannot be "popover" without UIEvent.');
|
||||||
|
this.interface = 'alert';
|
||||||
|
}
|
||||||
|
|
||||||
|
let overlay: ActionSheet | Alert | Popover;
|
||||||
|
|
||||||
if (this.interface === 'action-sheet') {
|
if (this.interface === 'action-sheet') {
|
||||||
selectOptions.buttons = selectOptions.buttons.concat(options.map(input => {
|
selectOptions.buttons = selectOptions.buttons.concat(options.map(input => {
|
||||||
return {
|
return {
|
||||||
@ -282,6 +297,25 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
|||||||
selectOptions.cssClass = selectCssClass;
|
selectOptions.cssClass = selectCssClass;
|
||||||
overlay = new ActionSheet(this._app, selectOptions, this.config);
|
overlay = new ActionSheet(this._app, selectOptions, this.config);
|
||||||
|
|
||||||
|
} else if (this.interface === 'popover') {
|
||||||
|
let popoverOptions: SelectPopoverOption[] = options.map(input => ({
|
||||||
|
text: input.text,
|
||||||
|
checked: input.selected,
|
||||||
|
disabled: input.disabled,
|
||||||
|
value: input.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
overlay = new Popover(this._app, SelectPopover, {
|
||||||
|
options: popoverOptions
|
||||||
|
}, {
|
||||||
|
cssClass: 'select-popover'
|
||||||
|
}, this.config, this.deepLinker);
|
||||||
|
|
||||||
|
// ev.target is readonly.
|
||||||
|
// place popover regarding to ion-select instead of .button-inner
|
||||||
|
Object.defineProperty(ev, 'target', { value: ev.currentTarget });
|
||||||
|
selectOptions.ev = ev;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// default to use the alert interface
|
// default to use the alert interface
|
||||||
this.interface = 'alert';
|
this.interface = 'alert';
|
||||||
@ -330,9 +364,15 @@ export class Select extends BaseInput<string[]> implements AfterViewInit, OnDest
|
|||||||
}
|
}
|
||||||
|
|
||||||
overlay.present(selectOptions);
|
overlay.present(selectOptions);
|
||||||
|
|
||||||
this._fireFocus();
|
this._fireFocus();
|
||||||
overlay.onDidDismiss(() => {
|
overlay.onDidDismiss((value: any) => {
|
||||||
this._fireBlur();
|
this._fireBlur();
|
||||||
|
|
||||||
|
if (this.interface === 'popover' && value) {
|
||||||
|
this.value = value;
|
||||||
|
this.ionChange.emit(value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import { Select } from '../select';
|
import { Select } from '../select';
|
||||||
import { mockApp, mockConfig, mockElementRef, mockRenderer, mockItem, mockForm } from '../../../util/mock-providers';
|
import { mockApp, mockConfig, mockDeepLinker, mockElementRef, mockRenderer, mockItem, mockForm } from '../../../util/mock-providers';
|
||||||
import { commonInputTest } from '../../../util/input-tester';
|
import { commonInputTest } from '../../../util/input-tester';
|
||||||
|
|
||||||
describe('Select', () => {
|
describe('Select', () => {
|
||||||
@ -9,11 +9,12 @@ describe('Select', () => {
|
|||||||
|
|
||||||
const app = mockApp();
|
const app = mockApp();
|
||||||
const config = mockConfig();
|
const config = mockConfig();
|
||||||
|
const deepLinker = mockDeepLinker();
|
||||||
const elementRef = mockElementRef();
|
const elementRef = mockElementRef();
|
||||||
const renderer = mockRenderer();
|
const renderer = mockRenderer();
|
||||||
const item: any = mockItem();
|
const item: any = mockItem();
|
||||||
const form = mockForm();
|
const form = mockForm();
|
||||||
const select = new Select(app, form, config, elementRef, renderer, item, null);
|
const select = new Select(app, form, config, elementRef, renderer, item, null, deepLinker);
|
||||||
|
|
||||||
commonInputTest(select, {
|
commonInputTest(select, {
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
|
@ -17,6 +17,15 @@
|
|||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Popover</ion-label>
|
||||||
|
<ion-select [(ngModel)]="interfaces" interface="popover">
|
||||||
|
<ion-option value="select">Select</ion-option>
|
||||||
|
<ion-option value="action">Action Sheet</ion-option>
|
||||||
|
<ion-option value="popover">Popover</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Gaming</ion-label>
|
<ion-label>Gaming</ion-label>
|
||||||
<ion-select [(ngModel)]="gaming" (ionCancel)="gamingCancel()" (ionChange)="gamingChange($event)">
|
<ion-select [(ngModel)]="gaming" (ionCancel)="gamingCancel()" (ionChange)="gamingChange($event)">
|
||||||
|
@ -240,6 +240,7 @@ export { Searchbar } from './components/searchbar/searchbar';
|
|||||||
export { Segment } from './components/segment/segment';
|
export { Segment } from './components/segment/segment';
|
||||||
export { SegmentButton } from './components/segment/segment-button';
|
export { SegmentButton } from './components/segment/segment-button';
|
||||||
export { Select } from './components/select/select';
|
export { Select } from './components/select/select';
|
||||||
|
export { SelectPopover } from './components/select/select-popover-component';
|
||||||
export { ShowWhen } from './components/show-hide-when/show-when';
|
export { ShowWhen } from './components/show-hide-when/show-when';
|
||||||
export { DisplayWhen } from './components/show-hide-when/display-when';
|
export { DisplayWhen } from './components/show-hide-when/display-when';
|
||||||
export { HideWhen } from './components/show-hide-when/hide-when';
|
export { HideWhen } from './components/show-hide-when/hide-when';
|
||||||
|
Reference in New Issue
Block a user