diff --git a/demos/src/select/pages/page-one/page-one.html b/demos/src/select/pages/page-one/page-one.html index e48f644bae..e8eced2919 100644 --- a/demos/src/select/pages/page-one/page-one.html +++ b/demos/src/select/pages/page-one/page-one.html @@ -65,6 +65,62 @@ + + Popover Interface Select + + + Gender + + Female + Male + + + + + Gaming + + NES + Nintendo64 + PlayStation + Sega Genesis + Sega Saturn + SNES + + + + + Date + + January + February + March + April + May + June + July + August + September + October + November + December + + + 1989 + 1990 + 1991 + 1992 + 1993 + 1994 + 1995 + 1996 + 1997 + 1998 + 1999 + + + + + Multiple Value Select diff --git a/src/components/select/select-popover-component.ts b/src/components/select/select-popover-component.ts new file mode 100644 index 0000000000..27b27b2efd --- /dev/null +++ b/src/components/select/select-popover-component.ts @@ -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: ` + + + {{option.text}} + + + + ` +}) +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; + } +} diff --git a/src/components/select/select.module.ts b/src/components/select/select.module.ts index d45f10c4c4..b4e8e449b9 100644 --- a/src/components/select/select.module.ts +++ b/src/components/select/select.module.ts @@ -1,18 +1,37 @@ import { CommonModule } from '@angular/common'; import { NgModule, ModuleWithProviders } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 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 */ @NgModule({ imports: [ - CommonModule + CommonModule, + FormsModule, + ReactiveFormsModule, + ItemModule, + LabelModule, + ListModule, + RadioModule ], declarations: [ - Select + Select, + SelectPopover ], exports: [ - Select + Select, + SelectPopover + ], + entryComponents: [ + SelectPopover ] }) export class SelectModule { diff --git a/src/components/select/select.scss b/src/components/select/select.scss index 9831751787..74f13c087f 100644 --- a/src/components/select/select.scss +++ b/src/components/select/select.scss @@ -3,6 +3,19 @@ // 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 { display: flex; overflow: hidden; @@ -32,3 +45,7 @@ ion-select { 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; +} diff --git a/src/components/select/select.ts b/src/components/select/select.ts index 96670089db..7343540be7 100644 --- a/src/components/select/select.ts +++ b/src/components/select/select.ts @@ -3,14 +3,17 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { ActionSheet } from '../action-sheet/action-sheet'; import { Alert } from '../alert/alert'; +import { Popover } from '../popover/popover'; import { App } from '../app/app'; import { Config } from '../../config/config'; +import { DeepLinker } from '../../navigation/deep-linker'; import { Form } from '../../util/form'; import { BaseInput } from '../../util/base-input'; import { isCheckedProperty, isTrueProperty, deepCopy, deepEqual } from '../../util/util'; import { Item } from '../item/item'; import { NavController } from '../../navigation/nav-controller'; import { Option } from '../option/option'; +import { SelectPopover, SelectPopoverOption } from './select-popover-component'; export const SELECT_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, @@ -30,7 +33,7 @@ export const SELECT_VALUE_ACCESSOR: any = { * The select component takes child `ion-option` components. If `ion-option` is not * given a `value` attribute then it will use its text as the value. * - * If `ngModel` is bound to `ion-select`, the selected value will be based on the + * If `ngModel` is bound to `ion-select`, the selected value will be based on the * bound value of the model. Otherwise, the `selected` attribute can be used on * `ion-option` components. * @@ -38,9 +41,10 @@ export const SELECT_VALUE_ACCESSOR: any = { * * 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 - * {@link ../../action-sheet/ActionSheetController ActionSheetController API} by passing - * `action-sheet` to the `interface` property. Read the other sections for the limitations of the - * action sheet interface. + * {@link ../../action-sheet/ActionSheetController ActionSheetController API} or + * {@link ../../popover/PopoverController PopoverController API} by passing `action-sheet` or `popover`, + * respectively, to the `interface` property. Read on to the other sections for the limitations + * of the different interfaces. * * ### 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 * 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 * @@ -96,19 +100,22 @@ export const SELECT_VALUE_ACCESSOR: any = { * * ``` * - * 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 * that value. * * ### 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 * to pass a custom title, subtitle, css class, and more. See the - * {@link ../../alert/AlertController/#create AlertController API docs} and - * {@link ../../action-sheet/ActionSheetController/#create ActionSheetController API docs} + * {@link ../../alert/AlertController/#create AlertController 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 example, to change the `mode` of the overlay, pass it into `selectOptions`. + * * ```html * * ... @@ -118,7 +125,8 @@ export const SELECT_VALUE_ACCESSOR: any = { * ```ts * this.selectOptions = { * title: 'Pizza Toppings', - * subTitle: 'Select your toppings' + * subTitle: 'Select your toppings', + * mode: 'md' * }; * ``` * @@ -176,7 +184,7 @@ export class Select extends BaseInput implements AfterViewInit, OnDest @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 = ''; @@ -197,7 +205,8 @@ export class Select extends BaseInput implements AfterViewInit, OnDest elementRef: ElementRef, renderer: Renderer, @Optional() item: Item, - @Optional() private _nav: NavController + @Optional() private _nav: NavController, + public deepLinker: DeepLinker ) { super(config, elementRef, renderer, 'select', [], form, item, null); } @@ -215,7 +224,7 @@ export class Select extends BaseInput implements AfterViewInit, OnDest } ev.preventDefault(); ev.stopPropagation(); - this.open(); + this.open(ev); } @HostListener('keyup.space') @@ -226,7 +235,7 @@ export class Select extends BaseInput implements AfterViewInit, OnDest /** * Open the select interface. */ - open() { + open(ev?: UIEvent) { if (this.isFocus() || this._disabled) { return; } @@ -257,12 +266,18 @@ export class Select extends BaseInput implements AfterViewInit, OnDest this.interface = 'alert'; } - if (this.interface === 'action-sheet' && this._multi) { - console.warn('Interface cannot be "action-sheet" with a multi-value select. Using the "alert" interface.'); + if ((this.interface === 'action-sheet' || this.interface === 'popover') && this._multi) { + console.warn('Interface cannot be "' + this.interface + '" with a multi-value select. Using the "alert" interface.'); 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') { selectOptions.buttons = selectOptions.buttons.concat(options.map(input => { return { @@ -282,6 +297,25 @@ export class Select extends BaseInput implements AfterViewInit, OnDest selectOptions.cssClass = selectCssClass; 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 { // default to use the alert interface this.interface = 'alert'; @@ -330,9 +364,15 @@ export class Select extends BaseInput implements AfterViewInit, OnDest } overlay.present(selectOptions); + this._fireFocus(); - overlay.onDidDismiss(() => { + overlay.onDidDismiss((value: any) => { this._fireBlur(); + + if (this.interface === 'popover' && value) { + this.value = value; + this.ionChange.emit(value); + } }); } diff --git a/src/components/select/test/select.spec.ts b/src/components/select/test/select.spec.ts index c289784c2b..1ff033d05e 100644 --- a/src/components/select/test/select.spec.ts +++ b/src/components/select/test/select.spec.ts @@ -1,6 +1,6 @@ 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'; describe('Select', () => { @@ -9,11 +9,12 @@ describe('Select', () => { const app = mockApp(); const config = mockConfig(); + const deepLinker = mockDeepLinker(); const elementRef = mockElementRef(); const renderer = mockRenderer(); const item: any = mockItem(); 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, { defaultValue: [], diff --git a/src/components/select/test/single-value/pages/page-one/page-one.html b/src/components/select/test/single-value/pages/page-one/page-one.html index 0cdbd1a477..4852b27174 100644 --- a/src/components/select/test/single-value/pages/page-one/page-one.html +++ b/src/components/select/test/single-value/pages/page-one/page-one.html @@ -17,6 +17,15 @@ + + Popover + + Select + Action Sheet + Popover + + + Gaming diff --git a/src/index.ts b/src/index.ts index fd63d97467..1ba51ef8ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -240,6 +240,7 @@ export { Searchbar } from './components/searchbar/searchbar'; export { Segment } from './components/segment/segment'; export { SegmentButton } from './components/segment/segment-button'; export { Select } from './components/select/select'; +export { SelectPopover } from './components/select/select-popover-component'; export { ShowWhen } from './components/show-hide-when/show-when'; export { DisplayWhen } from './components/show-hide-when/display-when'; export { HideWhen } from './components/show-hide-when/hide-when';