feat(picker-column): add prefix and suffix slots (#28603)

Issue number: Internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

The new picker component does not have a counterpart for the `prefix`
and `suffix` properties on the old picker column.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- `prefix` and `suffix` slots added.
- Picker items wrapped in a new `.picker-opts` element for positioning
purposes, similar to the legacy picker column. This necessitated
additional changes to functionality and styling, since previously the
items were direct descendants of the host.
- New `setFocus` method added. This gets around a browser bug in Firefox
where calling `focus()` on the column doesn't correctly move focus, as
the nearest focusable element (`.picker-opts`) is in the shadow DOM.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->

While you can no longer call `focus()` on the column in Firefox and must
use `setFocus()` instead, this isn't considered a breaking change
because this component isn't public API yet.

## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

---------

Co-authored-by: ionitron <hi@ionicframework.com>
This commit is contained in:
Amanda Johnston
2023-12-05 13:14:53 -06:00
committed by GitHub
parent fe3070bac2
commit cf9fe315a3
59 changed files with 284 additions and 83 deletions

View File

@@ -916,6 +916,7 @@ ion-picker-column,prop,disabled,boolean,false,false,false
ion-picker-column,prop,items,PickerColumnItem[],[],false,false
ion-picker-column,prop,mode,"ios" | "md",undefined,false,false
ion-picker-column,prop,value,number | string | undefined,undefined,false,false
ion-picker-column,method,setFocus,setFocus() => Promise<void>
ion-picker-column,event,ionChange,PickerColumnItem,true
ion-picker-column-option,shadow

View File

@@ -1977,6 +1977,10 @@ export namespace Components {
*/
"numericInput": boolean;
"scrollActiveItemIntoView": () => Promise<void>;
/**
* Sets focus on the scrollable container within the picker column. Use this method instead of the global `pickerColumn.focus()`.
*/
"setFocus": () => Promise<void>;
/**
* Sets the value prop and fires the ionChange event. This is used when we need to fire ionChange from user-generated events that cannot be caught with normal input/change event listeners.
*/

View File

@@ -25,19 +25,6 @@
overflow: hidden;
}
/**
* When using the wheel picker to switch
* between months, sometimes the allowed
* dates may be filtered. As a result, it
* is possible to get a layout shift as
* the picker column will shrink to fit the
* widest item in the column. Setting a minimum
* width avoids this layout shifting.
*/
ion-picker-column {
min-width: 26px;
}
:host(.datetime-size-fixed) {
width: auto;
height: auto;

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -25,7 +25,7 @@
<ion-content class="ion-padding">
<ion-button id="open-datetime" onclick="presentPopover(defaultPopover, event)">Present Popover</ion-button>
<ion-popover class="datetime-popover" id="default-popover">
<ion-datetime value="2022-05-03"></ion-datetime>
<ion-datetime value="2022-05-03T14:23:00.000Z"></ion-datetime>
</ion-popover>
</ion-content>

View File

@@ -447,6 +447,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dateColumn = page.locator('.date-column');
const dateColumnScrollEl = dateColumn.locator('.picker-opts');
const dateValues = dateColumn.locator('.picker-item:not(.picker-item-empty)');
expect(await dateValues.count()).toBe(90);
@@ -455,7 +456,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
* Select 1st item to change the dates rendered
*/
await expect(dateValues.nth(0)).toHaveAttribute('data-value', '2022-1-1');
await dateColumn.evaluate((el: HTMLElement) => (el.scrollTop = 0));
await dateColumnScrollEl.evaluate((el: HTMLElement) => (el.scrollTop = 0));
await page.waitForChanges();
await expect(dateValues.nth(0)).toHaveAttribute('data-value', '2021-12-1');
@@ -632,6 +633,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dateColumn = page.locator('.date-column');
const dateColumnScrollEl = dateColumn.locator('.picker-opts');
const dateValues = dateColumn.locator('.picker-item:not(.picker-item-empty)');
expect(await dateValues.count()).toBe(90);
@@ -640,7 +642,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
* Select 1st item to change the dates rendered
*/
await expect(dateValues.nth(0)).toHaveAttribute('data-value', '2022-1-1');
await dateColumn.evaluate((el: HTMLElement) => (el.scrollTop = 0));
await dateColumnScrollEl.evaluate((el: HTMLElement) => (el.scrollTop = 0));
await page.waitForChanges();
await expect(dateValues.nth(0)).toHaveAttribute('data-value', '2021-12-1');

View File

@@ -4,13 +4,53 @@
// --------------------------------------------------
:host {
@include padding(0px, 16px, 0px, 16px);
display: flex;
align-items: center;
justify-content: center;
max-width: 100%;
height: 200px;
font-size: 22px;
text-align: center;
}
.picker-opts {
/**
* This padding must be set here and not on the
* host to ensure that the focus highlight on the
* column is not overly narrow.
*/
@include padding(0px, 16px, 0px, 16px);
/**
* When using reactive data, such as in datetime's
* wheel picker, there may be layout shifts between
* individual columns in a picker as the columns
* shrink to fit the widest item in the column.
* Setting a minimum width avoids this layout shifting.
*
* Note that we need this min-width set on the same
* element as the vertical padding (i.e. not on the
* host).
*/
min-width: 26px;
max-height: 200px;
outline: none;
font-size: 22px;
/**
* Added so that text-align can be set on the host
* from external components. For example, datetime
* changes the alignment of specific columns in its
* wheel pickers.
*/
text-align: inherit;
scroll-snap-type: y mandatory;
/**
@@ -22,17 +62,45 @@
// Hide scrollbars on Firefox
scrollbar-width: none;
text-align: center;
}
/**
* Hide scrollbars on Chrome and Safari
*/
:host::-webkit-scrollbar {
.picker-opts::-webkit-scrollbar {
display: none;
}
::slotted([slot="prefix"]), ::slotted([slot="suffix"]) {
max-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
::slotted([slot="prefix"]) {
/**
* This is added in addition to the column's own padding so that there is white
* space between the slot and the column's focus highlight.
*/
@include padding(0, 16px);
justify-content: end;
}
::slotted([slot="suffix"]) {
/**
* This is added in addition to the column's own padding so that there is white
* space between the slot and the column's focus highlight.
*/
@include padding(0, 16px);
justify-content: start;
}
:host .picker-item {
@include padding(0);
@include margin(0);
@@ -93,7 +161,7 @@
}
@media (any-hover: hover) {
:host(:focus) {
:host(:focus) .picker-opts {
outline: none;
background: current-color(base, 0.2);

View File

@@ -13,6 +13,9 @@ import type { PickerColumnItem } from './picker-column-interfaces';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*
* @slot prefix - Content to show on the left side of the picker options.
* @slot suffix - Content to show on the right side of the picker options.
*/
@Component({
tag: 'ion-picker-column',
@@ -23,6 +26,7 @@ import type { PickerColumnItem } from './picker-column-interfaces';
shadow: true,
})
export class PickerColumn implements ComponentInterface {
private scrollEl?: HTMLDivElement | null;
private destroyScrollListener?: () => void;
private isScrolling = false;
private scrollEndCallback?: () => void;
@@ -175,13 +179,25 @@ export class PickerColumn implements ComponentInterface {
}
}
/**
* Sets focus on the scrollable container within the picker column.
* Use this method instead of the global `pickerColumn.focus()`.
*/
@Method()
async setFocus() {
if (this.scrollEl) {
this.scrollEl.focus();
}
}
private centerPickerItemInView = (target: HTMLElement, smooth = true, canExitInputMode = true) => {
const { el, isColumnVisible } = this;
if (isColumnVisible) {
const { isColumnVisible, scrollEl } = this;
if (isColumnVisible && scrollEl) {
// (Vertical offset from parent) - (three empty picker rows) + (half the height of the target to ensure the scroll triggers)
const top = target.offsetTop - 3 * target.clientHeight + target.clientHeight / 2;
if (el.scrollTop !== top) {
if (scrollEl.scrollTop !== top) {
/**
* Setting this flag prevents input
* mode from exiting in the picker column's
@@ -190,7 +206,7 @@ export class PickerColumn implements ComponentInterface {
* of these can cause a scroll to occur.
*/
this.canExitInputMode = canExitInputMode;
el.scroll({
scrollEl.scroll({
top,
left: 0,
behavior: smooth ? 'smooth' : undefined,
@@ -267,13 +283,15 @@ export class PickerColumn implements ComponentInterface {
* be disabled on Android.
*/
const enableHaptics = isPlatform('ios');
const { el } = this;
const { el, scrollEl } = this;
let timeout: ReturnType<typeof setTimeout> | undefined;
let activeEl: HTMLElement | null = this.activeItem;
const scrollCallback = () => {
raf(() => {
if (!scrollEl) return;
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
@@ -288,7 +306,7 @@ export class PickerColumn implements ComponentInterface {
* Select item in the center of the column
* which is the month/year that we want to select
*/
const bbox = el.getBoundingClientRect();
const bbox = scrollEl.getBoundingClientRect();
const centerX = bbox.x + bbox.width / 2;
const centerY = bbox.y + bbox.height / 2;
@@ -378,10 +396,12 @@ export class PickerColumn implements ComponentInterface {
* does not fire when component is initially shown.
*/
raf(() => {
el.addEventListener('scroll', scrollCallback);
if (!scrollEl) return;
scrollEl.addEventListener('scroll', scrollCallback);
this.destroyScrollListener = () => {
el.removeEventListener('scroll', scrollCallback);
scrollEl.removeEventListener('scroll', scrollCallback);
};
});
};
@@ -434,62 +454,71 @@ export class PickerColumn implements ComponentInterface {
<Host
exportparts={`${PICKER_ITEM_PART}, ${PICKER_ITEM_ACTIVE_PART}`}
disabled={pickerDisabled}
tabindex={pickerDisabled ? null : 0}
class={createColorClasses(color, {
[mode]: true,
['picker-column-active']: isActive,
['picker-column-numeric-input']: numericInput,
})}
>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
{items.map((item, index) => {
const isItemDisabled = pickerDisabled || item.disabled || false;
<slot name="prefix"></slot>
<div
class="picker-opts"
tabindex={pickerDisabled ? undefined : 0}
ref={(el) => {
this.scrollEl = el;
}}
>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
{items.map((item, index) => {
const isItemDisabled = pickerDisabled || item.disabled || false;
{
/*
Users should be able to tab
between multiple columns. As a result,
we set tabindex here so that tabbing switches
between columns instead of buttons. Users
can still use arrow keys on the keyboard to
navigate the column up and down.
*/
}
return (
<button
tabindex="-1"
class={{
'picker-item': true,
}}
data-value={item.value}
data-index={index}
onClick={(ev: Event) => {
this.centerPickerItemInView(ev.target as HTMLElement, true);
}}
disabled={isItemDisabled}
part={PICKER_ITEM_PART}
>
{item.text}
</button>
);
})}
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
{
/*
Users should be able to tab
between multiple columns. As a result,
we set tabindex here so that tabbing switches
between columns instead of buttons. Users
can still use arrow keys on the keyboard to
navigate the column up and down.
*/
}
return (
<button
tabindex="-1"
class={{
'picker-item': true,
}}
data-value={item.value}
data-index={index}
onClick={(ev: Event) => {
this.centerPickerItemInView(ev.target as HTMLElement, true);
}}
disabled={isItemDisabled}
part={PICKER_ITEM_PART}
>
{item.text}
</button>
);
})}
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
</div>
<slot name="suffix"></slot>
</Host>
);
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Picker Column - Slots</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header translucent="true">
<ion-toolbar>
<ion-title>Picker Column - Basic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-picker>
<ion-picker-column>
<div slot="prefix">Prefix</div>
<div slot="suffix">Suffix</div>
</ion-picker-column>
</ion-picker>
<ion-picker>
<ion-picker-column>
<div slot="prefix">Prefix</div>
<div slot="suffix">Long Suffix Long Suffix Long Suffix</div>
</ion-picker-column>
</ion-picker>
</ion-content>
<script>
const pickerColumns = document.querySelectorAll('ion-picker-column');
const items = Array(24)
.fill()
.map((_, i) => ({
text: `${i}`,
value: i,
}));
pickerColumns.forEach((col) => {
col.items = [...items];
});
</script>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,58 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('picker-column: slots'), () => {
test('should not have visual regressions with prefix and suffix', async ({ page }) => {
await page.setContent(
`
<ion-picker>
<ion-picker-column value="b">
<div slot="prefix">Prefix</div>
<div slot="suffix">Suffix</div>
</ion-picker-column>
</ion-picker>
<script>
const column = document.querySelector('ion-picker-column');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b' },
{ text: 'C', value: 'c' }
]
</script>
`,
config
);
const picker = page.locator('ion-picker');
await expect(picker).toHaveScreenshot(screenshot(`picker-prefix-suffix`));
});
test('should not have visual regressions with a long prefix and suffix', async ({ page }) => {
await page.setContent(
`
<ion-picker>
<ion-picker-column value="b">
<div slot="prefix">Long prefix long prefix long prefix</div>
<div slot="suffix">Long suffix long suffix long suffix</div>
</ion-picker-column>
</ion-picker>
<script>
const column = document.querySelector('ion-picker-column');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b' },
{ text: 'C', value: 'c' }
]
</script>
`,
config
);
const picker = page.locator('ion-picker');
await expect(picker).toHaveScreenshot(screenshot(`picker-long-prefix-suffix`));
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -131,7 +131,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const firstColumn = page.locator('ion-picker-column#first');
const secondColumn = page.locator('ion-picker-column#second');
await secondColumn.focus();
await secondColumn.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());
await expect(secondColumn).toBeFocused();
await page.waitForChanges();

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -32,7 +32,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const column = page.locator('ion-picker-column');
const ionChange = await page.spyOnEvent('ionChange');
await column.focus();
await column.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());
await page.keyboard.press('Digit2');
@@ -126,7 +126,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const column = page.locator('ion-picker-column');
const ionChange = await page.spyOnEvent('ionChange');
await column.focus();
await column.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());
await page.keyboard.press('Digit0');

View File

@@ -1446,7 +1446,8 @@ export declare interface IonPicker extends Components.IonPicker {
@ProxyCmp({
inputs: ['color', 'disabled', 'items', 'mode', 'value']
inputs: ['color', 'disabled', 'items', 'mode', 'value'],
methods: ['setFocus']
})
@Component({
selector: 'ion-picker-column',

View File

@@ -1441,7 +1441,8 @@ export declare interface IonPicker extends Components.IonPicker {
@ProxyCmp({
defineCustomElementFn: defineIonPickerColumn,
inputs: ['color', 'disabled', 'items', 'mode', 'value']
inputs: ['color', 'disabled', 'items', 'mode', 'value'],
methods: ['setFocus']
})
@Component({
selector: 'ion-picker-column',