Compare commits
10 Commits
5580-backu
...
5580-test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
629d862c54 | ||
|
|
2c773ed0e6 | ||
|
|
b1fc67227c | ||
|
|
75ee951ce8 | ||
|
|
2f3f9dc9ca | ||
|
|
b68c93d55d | ||
|
|
eace6425a2 | ||
|
|
1aeb19403b | ||
|
|
9d0834b201 | ||
|
|
7b21bd40a6 |
@@ -919,6 +919,7 @@ ion-picker-column,prop,value,number | string | undefined,undefined,false,false
|
||||
ion-picker-column,event,ionChange,PickerColumnItem,true
|
||||
|
||||
ion-picker-column-option,shadow
|
||||
ion-picker-column-option,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,'primary',false,true
|
||||
ion-picker-column-option,prop,disabled,boolean,false,false,false
|
||||
ion-picker-column-option,prop,value,any,undefined,false,false
|
||||
|
||||
|
||||
12
core/src/components.d.ts
vendored
@@ -1987,6 +1987,10 @@ export namespace Components {
|
||||
"value"?: string | number;
|
||||
}
|
||||
interface IonPickerColumnOption {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the picker column option.
|
||||
*/
|
||||
@@ -4047,7 +4051,7 @@ declare global {
|
||||
new (): HTMLIonPickerElement;
|
||||
};
|
||||
interface HTMLIonPickerColumnElementEventMap {
|
||||
"ionChange": PickerColumnItem;
|
||||
"ionChange": string | number | undefined;
|
||||
}
|
||||
interface HTMLIonPickerColumnElement extends Components.IonPickerColumn, HTMLStencilElement {
|
||||
addEventListener<K extends keyof HTMLIonPickerColumnElementEventMap>(type: K, listener: (this: HTMLIonPickerColumnElement, ev: IonPickerColumnCustomEvent<HTMLIonPickerColumnElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
@@ -6627,13 +6631,17 @@ declare namespace LocalJSX {
|
||||
/**
|
||||
* Emitted when the value has changed.
|
||||
*/
|
||||
"onIonChange"?: (event: IonPickerColumnCustomEvent<PickerColumnItem>) => void;
|
||||
"onIonChange"?: (event: IonPickerColumnCustomEvent<string | number | undefined>) => void;
|
||||
/**
|
||||
* The selected option in the picker.
|
||||
*/
|
||||
"value"?: string | number;
|
||||
}
|
||||
interface IonPickerColumnOption {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the picker column option.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
@import "./picker-column-option.scss";
|
||||
@@ -0,0 +1,5 @@
|
||||
@import "./picker-column-option.scss";
|
||||
|
||||
:host(.option-active) button {
|
||||
color: current-color(base);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Picker Column
|
||||
// --------------------------------------------------
|
||||
|
||||
button {
|
||||
@include padding(0);
|
||||
@include margin(0);
|
||||
|
||||
width: 100%;
|
||||
|
||||
height: 34px;
|
||||
|
||||
border: 0px;
|
||||
|
||||
outline: none;
|
||||
|
||||
background: transparent;
|
||||
|
||||
color: inherit;
|
||||
|
||||
font-family: $font-family-base;
|
||||
|
||||
font-size: inherit;
|
||||
|
||||
line-height: 34px;
|
||||
|
||||
text-align: inherit;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host(.option-disabled) {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
:host(.option-disabled) button {
|
||||
cursor: default;
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Element, Host, Prop, State, Watch, h } from '@stencil/core';
|
||||
import { inheritAttributes } from '@utils/helpers';
|
||||
import { createColorClasses } from '@utils/theme';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import type { Color } from '../../interface';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-picker-column-option',
|
||||
styleUrls: {
|
||||
ios: 'picker-column-option.ios.scss',
|
||||
md: 'picker-column-option.md.scss',
|
||||
},
|
||||
shadow: true,
|
||||
})
|
||||
export class PickerColumnOption implements ComponentInterface {
|
||||
@@ -29,6 +37,13 @@ export class PickerColumnOption implements ComponentInterface {
|
||||
*/
|
||||
@Prop() value?: any | null;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
* For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
@Prop({ reflect: true }) color?: Color = 'primary';
|
||||
|
||||
/**
|
||||
* The aria-label of the option has changed after the
|
||||
* first render and needs to be updated within the component.
|
||||
@@ -49,20 +64,45 @@ export class PickerColumnOption implements ComponentInterface {
|
||||
this.ariaLabel = inheritedAttributes['aria-label'] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The column options can load at any time
|
||||
* so the selected option needs to tell the
|
||||
* parent picker column when it is loaded
|
||||
* so the picker column can ensure it is
|
||||
* centered in the view.
|
||||
*/
|
||||
componentDidLoad() {
|
||||
const parentPickerColumn = this.el.closest('ion-picker-column');
|
||||
if (parentPickerColumn !== null && this.value === parentPickerColumn.value) {
|
||||
parentPickerColumn.scrollActiveItemIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When an option is clicked update the
|
||||
* parent picker column value. This
|
||||
* component will handle centering the option
|
||||
* in the column view.
|
||||
*/
|
||||
onClick() {
|
||||
const parentPickerColumn = this.el.closest('ion-picker-column');
|
||||
if (parentPickerColumn !== null) {
|
||||
parentPickerColumn.setValue(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value, disabled, ariaLabel } = this;
|
||||
const { color, value, disabled, ariaLabel } = this;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
return (
|
||||
<Host>
|
||||
<button
|
||||
tabindex="-1"
|
||||
aria-label={ariaLabel}
|
||||
class={{
|
||||
'picker-opt': true,
|
||||
'picker-opt-disabled': !!disabled,
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Host
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
['option-disabled']: disabled,
|
||||
})}
|
||||
>
|
||||
<button tabindex="-1" aria-label={ariaLabel} disabled={disabled} onClick={() => this.onClick()}>
|
||||
<slot>{value}</slot>
|
||||
</button>
|
||||
</Host>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Picker Column Option - Basic</title>
|
||||
<title>Picker Column Option - a11y</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" />
|
||||
@@ -14,6 +14,9 @@
|
||||
<main>
|
||||
<ion-picker-column-option> my option </ion-picker-column-option>
|
||||
<ion-picker-column-option aria-label="the best one"> other option </ion-picker-column-option>
|
||||
<ion-picker-column-option color="tertiary" class="option-active">option</ion-picker-column-option>
|
||||
<ion-picker-column-option disabled="true">option</ion-picker-column-option>
|
||||
<ion-picker-column-option color="tertiary" class="option-active" disabled="true">option</ion-picker-column-option>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -46,7 +46,19 @@
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Default</h2>
|
||||
<ion-picker-column-option> my option </ion-picker-column-option>
|
||||
<ion-picker-column-option>My Option</ion-picker-column-option>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Disabled</h2>
|
||||
<ion-picker-column-option disabled="true">My Option</ion-picker-column-option>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Active</h2>
|
||||
<ion-picker-column-option class="option-active">My Option</ion-picker-column-option>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Active / Disabled</h2>
|
||||
<ion-picker-column-option class="option-active" disabled="true">My Option</ion-picker-column-option>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('picker-column-option: rendering'), () => {
|
||||
test('picker option should not have visual regressions', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker-column-option value="option">My Option</ion-picker-column-option>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const option = page.locator('ion-picker-column-option');
|
||||
|
||||
await expect(option).toHaveScreenshot(screenshot('picker-column-option'));
|
||||
});
|
||||
test('disabled picker option should not have visual regressions', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker-column-option disabled="true" value="option">My Option</ion-picker-column-option>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const option = page.locator('ion-picker-column-option');
|
||||
|
||||
await expect(option).toHaveScreenshot(screenshot('disabled-picker-column-option'));
|
||||
});
|
||||
test('active picker option should not have visual regressions', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker-column-option class="option-active" value="option">My Option</ion-picker-column-option>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const option = page.locator('ion-picker-column-option');
|
||||
|
||||
await expect(option).toHaveScreenshot(screenshot('active-picker-column-option'));
|
||||
});
|
||||
test('disabled active picker option should not have visual regressions', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker-column-option class="option-active" disabled="true" value="option">My Option</ion-picker-column-option>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const option = page.locator('ion-picker-column-option');
|
||||
|
||||
await expect(option).toHaveScreenshot(screenshot('disabled-active-picker-column-option'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,31 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { PickerColumnOption } from '../picker-column-option';
|
||||
|
||||
describe('PickerColumnOption', () => {
|
||||
it('option should be enabled by default', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [PickerColumnOption],
|
||||
html: `
|
||||
<ion-picker-column-option value="a">A</ion-picker-column-option>
|
||||
`,
|
||||
});
|
||||
|
||||
const pickerColumnOption = page.body.querySelector('ion-picker-column-option')!;
|
||||
const button = pickerColumnOption.shadowRoot!.querySelector('button')!;
|
||||
expect(button.hasAttribute('disabled')).toEqual(false);
|
||||
});
|
||||
it('disabled option should have disabled button', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [PickerColumnOption],
|
||||
html: `
|
||||
<ion-picker-column-option value="a" disabled="true">A</ion-picker-column-option>
|
||||
`,
|
||||
});
|
||||
|
||||
const pickerColumnOption = page.body.querySelector('ion-picker-column-option')!;
|
||||
const button = pickerColumnOption.shadowRoot!.querySelector('button')!;
|
||||
|
||||
expect(button.hasAttribute('disabled')).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -33,6 +33,17 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
::slotted(ion-picker-column-option) {
|
||||
display: block;
|
||||
|
||||
scroll-snap-align: center;
|
||||
}
|
||||
|
||||
.picker-item-empty,
|
||||
:host(:not([disabled])) ::slotted(ion-picker-column-option.option-disabled) {
|
||||
scroll-snap-align: none;
|
||||
}
|
||||
|
||||
:host .picker-item {
|
||||
@include padding(0);
|
||||
@include margin(0);
|
||||
|
||||
@@ -70,7 +70,7 @@ export class PickerColumn implements ComponentInterface {
|
||||
/**
|
||||
* Emitted when the value has changed.
|
||||
*/
|
||||
@Event() ionChange!: EventEmitter<PickerColumnItem>;
|
||||
@Event() ionChange!: EventEmitter<string | number | undefined>;
|
||||
|
||||
@Watch('value')
|
||||
valueChange() {
|
||||
@@ -101,7 +101,9 @@ export class PickerColumn implements ComponentInterface {
|
||||
* Because this initial call to scrollActiveItemIntoView has to fire before
|
||||
* the scroll listener is set up, we need to manage the active class manually.
|
||||
*/
|
||||
const oldActive = getElementRoot(el).querySelector(`.${PICKER_ITEM_ACTIVE_CLASS}`);
|
||||
const oldActive = getElementRoot(el).querySelector(
|
||||
`.${PICKER_ITEM_ACTIVE_CLASS}`
|
||||
) as HTMLIonPickerColumnOptionElement | null;
|
||||
if (oldActive) {
|
||||
this.setPickerItemActiveState(oldActive, false);
|
||||
}
|
||||
@@ -130,20 +132,20 @@ export class PickerColumn implements ComponentInterface {
|
||||
}
|
||||
|
||||
componentDidRender() {
|
||||
const { activeItem, items, isColumnVisible, value } = this;
|
||||
const { el, activeItem, isColumnVisible, value } = this;
|
||||
|
||||
if (isColumnVisible) {
|
||||
if (activeItem) {
|
||||
this.scrollActiveItemIntoView();
|
||||
} else if (items[0]?.value !== value) {
|
||||
/**
|
||||
* If the picker column does not have an active item and the current value
|
||||
* does not match the first item in the picker column, that means
|
||||
* the value is out of bounds. In this case, we assign the value to the
|
||||
* first item to match the scroll position of the column.
|
||||
*
|
||||
*/
|
||||
this.setValue(items[0].value);
|
||||
if (isColumnVisible && !activeItem) {
|
||||
const firstOption = el.querySelector('ion-picker-column-option');
|
||||
|
||||
/**
|
||||
* If the picker column does not have an active item and the current value
|
||||
* does not match the first item in the picker column, that means
|
||||
* the value is out of bounds. In this case, we assign the value to the
|
||||
* first item to match the scroll position of the column.
|
||||
*
|
||||
*/
|
||||
if (firstOption !== null && firstOption.value !== value) {
|
||||
this.setValue(firstOption.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,12 +169,10 @@ export class PickerColumn implements ComponentInterface {
|
||||
*/
|
||||
@Method()
|
||||
async setValue(value?: string | number) {
|
||||
const { items } = this;
|
||||
if (this.value === value) { return; }
|
||||
|
||||
this.value = value;
|
||||
const findItem = items.find((item) => item.value === value && item.disabled !== true);
|
||||
if (findItem) {
|
||||
this.ionChange.emit(findItem);
|
||||
}
|
||||
this.ionChange.emit(value);
|
||||
}
|
||||
|
||||
private centerPickerItemInView = (target: HTMLElement, smooth = true, canExitInputMode = true) => {
|
||||
@@ -199,7 +199,7 @@ export class PickerColumn implements ComponentInterface {
|
||||
}
|
||||
};
|
||||
|
||||
private setPickerItemActiveState = (item: Element, isActive: boolean) => {
|
||||
private setPickerItemActiveState = (item: HTMLIonPickerColumnOptionElement, isActive: boolean) => {
|
||||
if (isActive) {
|
||||
item.classList.add(PICKER_ITEM_ACTIVE_CLASS);
|
||||
item.part.add(PICKER_ITEM_ACTIVE_PART);
|
||||
@@ -270,7 +270,7 @@ export class PickerColumn implements ComponentInterface {
|
||||
const { el } = this;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
let activeEl: HTMLElement | null = this.activeItem;
|
||||
let activeEl: HTMLIonPickerColumnOptionElement | undefined = this.activeItem;
|
||||
|
||||
const scrollCallback = () => {
|
||||
raf(() => {
|
||||
@@ -292,13 +292,24 @@ export class PickerColumn implements ComponentInterface {
|
||||
const centerX = bbox.x + bbox.width / 2;
|
||||
const centerY = bbox.y + bbox.height / 2;
|
||||
|
||||
const activeElement = el.shadowRoot!.elementFromPoint(centerX, centerY) as HTMLButtonElement | null;
|
||||
const newActiveElement = el.shadowRoot!.elementFromPoint(
|
||||
centerX,
|
||||
centerY
|
||||
) as HTMLIonPickerColumnOptionElement | null;
|
||||
|
||||
if (activeEl !== null) {
|
||||
if (activeEl !== undefined) {
|
||||
this.setPickerItemActiveState(activeEl, false);
|
||||
}
|
||||
|
||||
if (activeElement === null || activeElement.disabled) {
|
||||
/**
|
||||
* This null check is important because activeEl
|
||||
* can be undefined but newActiveElement can be
|
||||
* null since we are using a querySelector.
|
||||
* newActiveElement !== activeEl would return true
|
||||
* below if newActiveElement was null but activeEl
|
||||
* was undefined.
|
||||
*/
|
||||
if (newActiveElement === null || newActiveElement.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -306,7 +317,7 @@ export class PickerColumn implements ComponentInterface {
|
||||
* If we are selecting a new value,
|
||||
* we need to run haptics again.
|
||||
*/
|
||||
if (activeElement !== activeEl) {
|
||||
if (newActiveElement !== activeEl) {
|
||||
enableHaptics && hapticSelectionChanged();
|
||||
|
||||
if (this.canExitInputMode) {
|
||||
@@ -325,8 +336,8 @@ export class PickerColumn implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
activeEl = activeElement;
|
||||
this.setPickerItemActiveState(activeElement, true);
|
||||
activeEl = newActiveElement;
|
||||
this.setPickerItemActiveState(newActiveElement, true);
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
this.isScrolling = false;
|
||||
@@ -352,23 +363,7 @@ export class PickerColumn implements ComponentInterface {
|
||||
*/
|
||||
this.canExitInputMode = true;
|
||||
|
||||
const dataIndex = activeElement.getAttribute('data-index');
|
||||
|
||||
/**
|
||||
* If no value it is
|
||||
* possible we hit one of the
|
||||
* empty padding columns.
|
||||
*/
|
||||
if (dataIndex === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = parseInt(dataIndex, 10);
|
||||
const selectedItem = this.items[index];
|
||||
|
||||
if (selectedItem.value !== this.value) {
|
||||
this.setValue(selectedItem.value);
|
||||
}
|
||||
this.setValue(newActiveElement.value);
|
||||
}, 250);
|
||||
});
|
||||
};
|
||||
@@ -412,15 +407,25 @@ export class PickerColumn implements ComponentInterface {
|
||||
};
|
||||
|
||||
get activeItem() {
|
||||
// If the whole picker column is disabled, the current value should appear active
|
||||
// If the current value item is specifically disabled, it should not appear active
|
||||
const selector = `.picker-item[data-value="${this.value}"]${this.disabled ? '' : ':not([disabled])'}`;
|
||||
const { value } = this;
|
||||
const options = Array.from(
|
||||
this.el.querySelectorAll('ion-picker-column-option') as NodeListOf<HTMLIonPickerColumnOptionElement>
|
||||
);
|
||||
return options.find((option: HTMLIonPickerColumnOptionElement) => {
|
||||
/**
|
||||
* If the whole picker column is disabled, the current value should appear active
|
||||
* If the current value item is specifically disabled, it should not appear active
|
||||
*/
|
||||
if (!this.disabled && option.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getElementRoot(this.el).querySelector(selector) as HTMLElement | null;
|
||||
return option.value === value;
|
||||
}) as HTMLIonPickerColumnOptionElement | undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { items, color, disabled: pickerDisabled, isActive, numericInput } = this;
|
||||
const { color, disabled: pickerDisabled, isActive, numericInput } = this;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
/**
|
||||
@@ -450,37 +455,7 @@ export class PickerColumn implements ComponentInterface {
|
||||
<div class="picker-item picker-item-empty" aria-hidden="true">
|
||||
|
||||
</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>
|
||||
);
|
||||
})}
|
||||
<slot></slot>
|
||||
<div class="picker-item picker-item-empty" aria-hidden="true">
|
||||
|
||||
</div>
|
||||
@@ -495,6 +470,6 @@ export class PickerColumn implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
const PICKER_ITEM_ACTIVE_CLASS = 'picker-item-active';
|
||||
const PICKER_ITEM_ACTIVE_CLASS = 'option-active';
|
||||
const PICKER_ITEM_PART = 'wheel-item';
|
||||
const PICKER_ITEM_ACTIVE_PART = 'active';
|
||||
|
||||
@@ -57,12 +57,18 @@
|
||||
|
||||
const items = Array(24)
|
||||
.fill()
|
||||
.map((_, i) => ({
|
||||
text: `${i}`,
|
||||
value: i,
|
||||
}));
|
||||
.forEach((_, i) => {
|
||||
const option = document.createElement('ion-picker-column-option');
|
||||
option.value = i;
|
||||
option.textContent = i;
|
||||
|
||||
defaultPickerColumn.items = items;
|
||||
defaultPickerColumn.appendChild(option);
|
||||
});
|
||||
//defaultPickerColumn.value = 11;
|
||||
|
||||
defaultPickerColumn.addEventListener('ionChange', (ev) => {
|
||||
console.log(ev);
|
||||
})
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
@@ -10,18 +10,13 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await page.goto('/src/components/picker-column/test/basic', config);
|
||||
});
|
||||
|
||||
test('should render a picker item for each item', async ({ page }) => {
|
||||
const columns = page.locator('ion-picker-column .picker-item:not(.picker-item-empty)');
|
||||
await expect(columns).toHaveCount(24);
|
||||
});
|
||||
|
||||
test('should render 6 empty picker items', async ({ page }) => {
|
||||
const columns = page.locator('ion-picker-column .picker-item-empty');
|
||||
await expect(columns).toHaveCount(6);
|
||||
});
|
||||
|
||||
test('should not have an active item when value is not set', async ({ page }) => {
|
||||
const activeColumn = page.locator('ion-picker-column .picker-item-active');
|
||||
const activeColumn = page.locator('ion-picker-column .option-active');
|
||||
await expect(activeColumn).toHaveCount(0);
|
||||
});
|
||||
|
||||
@@ -31,7 +26,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
});
|
||||
await page.waitForChanges();
|
||||
|
||||
const activeColumn = page.locator('ion-picker-column .picker-item-active');
|
||||
const activeColumn = page.locator('ion-picker-column .option-active');
|
||||
|
||||
expect(activeColumn).not.toBeNull();
|
||||
});
|
||||
@@ -45,9 +40,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
});
|
||||
await page.waitForChanges();
|
||||
|
||||
const activeColumn = page.locator('ion-picker-column .picker-item-active');
|
||||
const activeColumn = page.locator('ion-picker-column .option-active');
|
||||
|
||||
expect(await activeColumn?.innerText()).toEqual('23');
|
||||
await expect(activeColumn).toHaveJSProperty('value', 23);
|
||||
});
|
||||
|
||||
test('should not emit ionChange when the value is modified externally', async ({ page, skip }) => {
|
||||
|
||||
@@ -53,31 +53,36 @@
|
||||
<div class="grid-item">
|
||||
<h2>Column disabled</h2>
|
||||
<ion-picker>
|
||||
<ion-picker-column id="column-disabled" value="11" disabled></ion-picker-column>
|
||||
<ion-picker-column id="column-disabled" disabled="true"></ion-picker-column>
|
||||
</ion-picker>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
<script>
|
||||
const halfDisabledPicker = document.getElementById('half-disabled');
|
||||
const halfDisabledItems = Array(24)
|
||||
Array(24)
|
||||
.fill()
|
||||
.map((_, i) => ({
|
||||
text: `${i}`,
|
||||
value: i,
|
||||
disabled: i % 2 === 0,
|
||||
}));
|
||||
halfDisabledPicker.items = halfDisabledItems;
|
||||
halfDisabledPicker.value = 12;
|
||||
.forEach((_, i) => {
|
||||
const option = document.createElement('ion-picker-column-option');
|
||||
option.value = i;
|
||||
option.textContent = i;
|
||||
option.disabled = i % 2 === 0;
|
||||
|
||||
halfDisabledPicker.appendChild(option);
|
||||
});
|
||||
halfDisabledPicker.value = 4;
|
||||
|
||||
const fullDisabledPicker = document.getElementById('column-disabled');
|
||||
const items = Array(24)
|
||||
Array(24)
|
||||
.fill()
|
||||
.map((_, i) => ({
|
||||
text: `${i}`,
|
||||
value: i,
|
||||
}));
|
||||
fullDisabledPicker.items = items;
|
||||
.forEach((_, i) => {
|
||||
const option = document.createElement('ion-picker-column-option');
|
||||
option.value = i;
|
||||
option.textContent = i;
|
||||
|
||||
fullDisabledPicker.appendChild(option);
|
||||
});
|
||||
fullDisabledPicker.value = 11;
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
@@ -1,36 +1,6 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* This behavior does not vary across directions.
|
||||
*/
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('picker-column: disabled rendering'), () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker>
|
||||
<ion-picker-column value="b"></ion-picker-column>
|
||||
</ion-picker>
|
||||
|
||||
<script>
|
||||
const column = document.querySelector('ion-picker-column');
|
||||
column.items = [
|
||||
{ text: 'A', value: 'a', disabled: true },
|
||||
{ text: 'B', value: 'b' },
|
||||
{ text: 'C', value: 'c', disabled: true }
|
||||
]
|
||||
</script>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const picker = page.locator('ion-picker');
|
||||
await expect(picker).toHaveScreenshot(screenshot(`picker-disabled`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This behavior does not vary across modes/directions.
|
||||
*/
|
||||
|
||||
@@ -1476,14 +1476,14 @@ export declare interface IonPickerColumn extends Components.IonPickerColumn {
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['disabled', 'value']
|
||||
inputs: ['color', 'disabled', 'value']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-picker-column-option',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['disabled', 'value'],
|
||||
inputs: ['color', 'disabled', 'value'],
|
||||
})
|
||||
export class IonPickerColumnOption {
|
||||
protected el: HTMLElement;
|
||||
|
||||
@@ -1473,14 +1473,14 @@ export declare interface IonPickerColumn extends Components.IonPickerColumn {
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonPickerColumnOption,
|
||||
inputs: ['disabled', 'value']
|
||||
inputs: ['color', 'disabled', 'value']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-picker-column-option',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['disabled', 'value'],
|
||||
inputs: ['color', 'disabled', 'value'],
|
||||
standalone: true
|
||||
})
|
||||
export class IonPickerColumnOption {
|
||||
|
||||
@@ -589,7 +589,8 @@ export const IonPickerColumn = /*@__PURE__*/ defineContainer<JSX.IonPickerColumn
|
||||
|
||||
export const IonPickerColumnOption = /*@__PURE__*/ defineContainer<JSX.IonPickerColumnOption>('ion-picker-column-option', defineIonPickerColumnOption, [
|
||||
'disabled',
|
||||
'value'
|
||||
'value',
|
||||
'color'
|
||||
]);
|
||||
|
||||
|
||||
|
||||