feat(picker-column-internal): add ability to disable items (#25412)

This commit is contained in:
Liam DeBeasi
2022-06-07 11:27:25 -04:00
committed by GitHub
parent 01c40eae55
commit 8b7c07968e
6 changed files with 219 additions and 9 deletions

View File

@ -1,4 +1,5 @@
export interface PickerColumnItem { export interface PickerColumnItem {
text: string; text: string;
value: string | number; value: string | number;
disabled?: boolean;
} }

View File

@ -34,21 +34,38 @@
} }
:host .picker-item { :host .picker-item {
display: block;
width: 100%;
height: 34px; height: 34px;
border: 0px;
outline: none;
background: transparent;
font-size: inherit;
line-height: 34px; line-height: 34px;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
cursor: pointer;
overflow: hidden; overflow: hidden;
scroll-snap-align: center; scroll-snap-align: center;
} }
:host .picker-item-empty { :host .picker-item-empty,
:host .picker-item.picker-item-disabled {
scroll-snap-align: none; scroll-snap-align: none;
cursor: default;
} }
:host(.picker-column-active) .picker-item.picker-item-active { :host(.picker-column-active) .picker-item.picker-item-active {

View File

@ -146,7 +146,7 @@ export class PickerColumnInternal implements ComponentInterface {
private setValue(value?: string | number) { private setValue(value?: string | number) {
const { items } = this; const { items } = this;
this.value = value; this.value = value;
const findItem = items.find((item) => item.value === value); const findItem = items.find((item) => item.value === value && item.disabled !== true);
if (findItem) { if (findItem) {
this.ionChange.emit(findItem); this.ionChange.emit(findItem);
} }
@ -226,11 +226,15 @@ export class PickerColumnInternal implements ComponentInterface {
const centerX = bbox.x + bbox.width / 2; const centerX = bbox.x + bbox.width / 2;
const centerY = bbox.y + bbox.height / 2; const centerY = bbox.y + bbox.height / 2;
const activeElement = el.shadowRoot!.elementFromPoint(centerX, centerY) as HTMLElement; const activeElement = el.shadowRoot!.elementFromPoint(centerX, centerY) as HTMLButtonElement;
if (activeEl !== null) { if (activeEl !== null) {
activeEl.classList.remove(PICKER_COL_ACTIVE); activeEl.classList.remove(PICKER_COL_ACTIVE);
} }
if (activeElement.disabled) {
return;
}
/** /**
* If we are selecting a new value, * If we are selecting a new value,
* we need to run haptics again. * we need to run haptics again.
@ -280,7 +284,9 @@ export class PickerColumnInternal implements ComponentInterface {
}; };
get activeItem() { get activeItem() {
return getElementRoot(this.el).querySelector(`.picker-item[data-value="${this.value}"]`) as HTMLElement | null; return getElementRoot(this.el).querySelector(
`.picker-item[data-value="${this.value}"]:not([disabled])`
) as HTMLElement | null;
} }
render() { render() {
@ -301,16 +307,20 @@ export class PickerColumnInternal implements ComponentInterface {
<div class="picker-item picker-item-empty">&nbsp;</div> <div class="picker-item picker-item-empty">&nbsp;</div>
{items.map((item, index) => { {items.map((item, index) => {
return ( return (
<div <button
class="picker-item" class={{
'picker-item': true,
'picker-item-disabled': item.disabled || false,
}}
data-value={item.value} data-value={item.value}
data-index={index} data-index={index}
onClick={(ev: Event) => { onClick={(ev: Event) => {
this.centerPickerItemInView(ev.target as HTMLElement); this.centerPickerItemInView(ev.target as HTMLElement);
}} }}
disabled={item.disabled}
> >
{item.text} {item.text}
</div> </button>
); );
})} })}
<div class="picker-item picker-item-empty">&nbsp;</div> <div class="picker-item picker-item-empty">&nbsp;</div>

View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Picker Column Internal - Basic</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>
<style>
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(250px, 1fr));
grid-row-gap: 20px;
grid-column-gap: 20px;
}
h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin-top: 10px;
margin-left: 5px;
}
@media screen and (max-width: 800px) {
.grid {
grid-template-columns: 1fr;
padding: 0;
}
}
</style>
</head>
<body>
<ion-app>
<ion-header translucent="true">
<ion-toolbar>
<ion-title>Picker Column Internal - Disabled</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>Default</h2>
<ion-picker-internal>
<ion-picker-column-internal id="default"></ion-picker-column-internal>
</ion-picker-internal>
</div>
</div>
</ion-content>
<script>
const defaultPickerColumn = document.getElementById('default');
const items = Array(24)
.fill()
.map((_, i) => ({
text: `${i}`,
value: i,
disabled: i % 2 === 0,
}));
defaultPickerColumn.items = items;
defaultPickerColumn.value = 12;
</script>
</ion-app>
</body>
</html>

View File

@ -0,0 +1,111 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('picker-column-internal: disabled', () => {
test('all picker items should be enabled by default', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b' },
{ text: 'C', value: 'c' }
]
</script>
`);
const pickerItems = page.locator(
'ion-picker-column-internal .picker-item:not(.picker-item-empty, .picker-item-disabled)'
);
expect(await pickerItems.count()).toBe(3);
});
test('disabled picker item should not be interactive', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b', disabled: true },
{ text: 'C', value: 'c' }
]
</script>
`);
const disabledItem = page.locator('ion-picker-column-internal .picker-item.picker-item-disabled');
expect(disabledItem).not.toBeEnabled();
});
test('disabled picker item should not be considered active', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal value="b"></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b', disabled: true },
{ text: 'C', value: 'c' }
]
</script>
`);
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
expect(disabledItem).not.toHaveClass(/picker-item-active/);
});
test('setting the value to a disabled item should not cause that item to be active', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b', disabled: true },
{ text: 'C', value: 'c' }
]
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => (el.value = 'b'));
await page.waitForChanges();
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
expect(disabledItem).toHaveClass(/picker-item-disabled/);
expect(disabledItem).not.toHaveClass(/picker-item-active/);
});
test('defaulting the value to a disabled item should not cause that item to be active', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b', disabled: true },
{ text: 'C', value: 'c' }
]
column.value = 'b'
</script>
`);
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
expect(disabledItem).toHaveClass(/picker-item-disabled/);
expect(disabledItem).not.toHaveClass(/picker-item-active/);
});
});

View File

@ -303,7 +303,7 @@ export class PickerInternal implements ComponentInterface {
return; return;
} }
const values = inputModeColumn.items; const values = inputModeColumn.items.filter((item) => item.disabled !== true);
/** /**
* If users pause for a bit, the search * If users pause for a bit, the search
@ -374,7 +374,7 @@ export class PickerInternal implements ComponentInterface {
zeroBehavior: 'start' | 'end' = 'start' zeroBehavior: 'start' | 'end' = 'start'
) => { ) => {
const behavior = zeroBehavior === 'start' ? /^0+/ : /0$/; const behavior = zeroBehavior === 'start' ? /^0+/ : /0$/;
const item = colEl.items.find(({ text }) => text.replace(behavior, '') === value); const item = colEl.items.find(({ text, disabled }) => disabled !== true && text.replace(behavior, '') === value);
if (item) { if (item) {
colEl.value = item.value; colEl.value = item.value;