feat(picker-column-option): add the new component (#28591)

This commit is contained in:
Maria Hutt
2023-11-30 09:54:01 -08:00
committed by GitHub
parent 5e47412e1f
commit 0b469646b2
11 changed files with 253 additions and 0 deletions

View File

@@ -918,6 +918,10 @@ ion-picker-column,prop,mode,"ios" | "md",undefined,false,false
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,disabled,boolean,false,false,false
ion-picker-column-option,prop,value,any,undefined,false,false
ion-picker-legacy,scoped
ion-picker-legacy,prop,animated,boolean,true,false,false
ion-picker-legacy,prop,backdropDismiss,boolean,true,false,false

View File

@@ -1986,6 +1986,16 @@ export namespace Components {
*/
"value"?: string | number;
}
interface IonPickerColumnOption {
/**
* If `true`, the user cannot interact with the picker column option.
*/
"disabled": boolean;
/**
* The text value of the option.
*/
"value"?: any | null;
}
interface IonPickerLegacy {
/**
* If `true`, the picker will animate.
@@ -4053,6 +4063,12 @@ declare global {
prototype: HTMLIonPickerColumnElement;
new (): HTMLIonPickerColumnElement;
};
interface HTMLIonPickerColumnOptionElement extends Components.IonPickerColumnOption, HTMLStencilElement {
}
var HTMLIonPickerColumnOptionElement: {
prototype: HTMLIonPickerColumnOptionElement;
new (): HTMLIonPickerColumnOptionElement;
};
interface HTMLIonPickerLegacyElementEventMap {
"ionPickerDidPresent": void;
"ionPickerWillPresent": void;
@@ -4647,6 +4663,7 @@ declare global {
"ion-note": HTMLIonNoteElement;
"ion-picker": HTMLIonPickerElement;
"ion-picker-column": HTMLIonPickerColumnElement;
"ion-picker-column-option": HTMLIonPickerColumnOptionElement;
"ion-picker-legacy": HTMLIonPickerLegacyElement;
"ion-picker-legacy-column": HTMLIonPickerLegacyColumnElement;
"ion-popover": HTMLIonPopoverElement;
@@ -6616,6 +6633,16 @@ declare namespace LocalJSX {
*/
"value"?: string | number;
}
interface IonPickerColumnOption {
/**
* If `true`, the user cannot interact with the picker column option.
*/
"disabled"?: boolean;
/**
* The text value of the option.
*/
"value"?: any | null;
}
interface IonPickerLegacy {
/**
* If `true`, the picker will animate.
@@ -8086,6 +8113,7 @@ declare namespace LocalJSX {
"ion-note": IonNote;
"ion-picker": IonPicker;
"ion-picker-column": IonPickerColumn;
"ion-picker-column-option": IonPickerColumnOption;
"ion-picker-legacy": IonPickerLegacy;
"ion-picker-legacy-column": IonPickerLegacyColumn;
"ion-popover": IonPopover;
@@ -8183,6 +8211,7 @@ declare module "@stencil/core" {
"ion-note": LocalJSX.IonNote & JSXBase.HTMLAttributes<HTMLIonNoteElement>;
"ion-picker": LocalJSX.IonPicker & JSXBase.HTMLAttributes<HTMLIonPickerElement>;
"ion-picker-column": LocalJSX.IonPickerColumn & JSXBase.HTMLAttributes<HTMLIonPickerColumnElement>;
"ion-picker-column-option": LocalJSX.IonPickerColumnOption & JSXBase.HTMLAttributes<HTMLIonPickerColumnOptionElement>;
"ion-picker-legacy": LocalJSX.IonPickerLegacy & JSXBase.HTMLAttributes<HTMLIonPickerLegacyElement>;
"ion-picker-legacy-column": LocalJSX.IonPickerLegacyColumn & JSXBase.HTMLAttributes<HTMLIonPickerLegacyColumnElement>;
"ion-popover": LocalJSX.IonPopover & JSXBase.HTMLAttributes<HTMLIonPopoverElement>;

View File

@@ -0,0 +1,71 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Prop, State, Watch, h } from '@stencil/core';
import { inheritAttributes } from '@utils/helpers';
@Component({
tag: 'ion-picker-column-option',
shadow: true,
})
export class PickerColumnOption implements ComponentInterface {
@Element() el!: HTMLElement;
/**
* The aria-label of the option.
*
* If the value changes, then it will trigger a
* re-render of the picker since it's a @State variable.
* Otherwise, the `aria-label` attribute cannot be updated
* after the component is loaded.
*/
@State() ariaLabel?: string | null = null;
/**
* If `true`, the user cannot interact with the picker column option.
*/
@Prop() disabled = false;
/**
* The text value of the option.
*/
@Prop() value?: any | null;
/**
* The aria-label of the option has changed after the
* first render and needs to be updated within the component.
*
* @param ariaLbl The new aria-label value.
*/
@Watch('aria-label')
onAriaLabelChange(ariaLbl: string) {
this.ariaLabel = ariaLbl;
}
componentWillLoad() {
const inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
/**
* The initial value of `aria-label` needs to be set for
* the first render.
*/
this.ariaLabel = inheritedAttributes['aria-label'] || null;
}
render() {
const { value, disabled, ariaLabel } = this;
return (
<Host>
<button
tabindex="-1"
aria-label={ariaLabel}
class={{
'picker-opt': true,
'picker-opt-disabled': !!disabled,
}}
disabled={disabled}
>
<slot>{value}</slot>
</button>
</Host>
);
}
}

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Picker Column Option - 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>
</head>
<body>
<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>
</main>
</body>
</html>

View File

@@ -0,0 +1,18 @@
import AxeBuilder from '@axe-core/playwright';
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across directions
*/
configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
test.describe(title('picker column option: a11y'), () => {
test('should not have accessibility violations', async ({ page }) => {
await page.goto(`/src/components/picker-column-option/test/a11y`, config);
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
});
});

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Picker Column Option - 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 Option - Basic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>Default</h2>
<ion-picker-column-option> my option </ion-picker-column-option>
</div>
</div>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -53,6 +53,7 @@ export const DIRECTIVES = [
d.IonNote,
d.IonPicker,
d.IonPickerColumn,
d.IonPickerColumnOption,
d.IonPickerLegacy,
d.IonProgressBar,
d.IonRadio,

View File

@@ -1475,6 +1475,28 @@ export declare interface IonPickerColumn extends Components.IonPickerColumn {
}
@ProxyCmp({
inputs: ['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'],
})
export class IonPickerColumnOption {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
export declare interface IonPickerColumnOption extends Components.IonPickerColumnOption {}
@ProxyCmp({
inputs: ['animated', 'backdropDismiss', 'buttons', 'columns', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop', 'trigger'],
methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss', 'getColumn']

View File

@@ -53,6 +53,7 @@ import { defineCustomElement as defineIonNavLink } from '@ionic/core/components/
import { defineCustomElement as defineIonNote } from '@ionic/core/components/ion-note.js';
import { defineCustomElement as defineIonPicker } from '@ionic/core/components/ion-picker.js';
import { defineCustomElement as defineIonPickerColumn } from '@ionic/core/components/ion-picker-column.js';
import { defineCustomElement as defineIonPickerColumnOption } from '@ionic/core/components/ion-picker-column-option.js';
import { defineCustomElement as defineIonPickerLegacy } from '@ionic/core/components/ion-picker-legacy.js';
import { defineCustomElement as defineIonProgressBar } from '@ionic/core/components/ion-progress-bar.js';
import { defineCustomElement as defineIonRadio } from '@ionic/core/components/ion-radio.js';
@@ -1470,6 +1471,30 @@ export declare interface IonPickerColumn extends Components.IonPickerColumn {
}
@ProxyCmp({
defineCustomElementFn: defineIonPickerColumnOption,
inputs: ['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'],
standalone: true
})
export class IonPickerColumnOption {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
export declare interface IonPickerColumnOption extends Components.IonPickerColumnOption {}
@ProxyCmp({
defineCustomElementFn: defineIonPickerLegacy,
inputs: ['animated', 'backdropDismiss', 'buttons', 'columns', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop', 'trigger'],

View File

@@ -46,6 +46,7 @@ import { defineCustomElement as defineIonNavLink } from '@ionic/core/components/
import { defineCustomElement as defineIonNote } from '@ionic/core/components/ion-note.js';
import { defineCustomElement as defineIonPicker } from '@ionic/core/components/ion-picker.js';
import { defineCustomElement as defineIonPickerColumn } from '@ionic/core/components/ion-picker-column.js';
import { defineCustomElement as defineIonPickerColumnOption } from '@ionic/core/components/ion-picker-column-option.js';
import { defineCustomElement as defineIonProgressBar } from '@ionic/core/components/ion-progress-bar.js';
import { defineCustomElement as defineIonRadio } from '@ionic/core/components/ion-radio.js';
import { defineCustomElement as defineIonRadioGroup } from '@ionic/core/components/ion-radio-group.js';
@@ -113,6 +114,7 @@ export const IonNavLink = /*@__PURE__*/createReactComponent<JSX.IonNavLink, HTML
export const IonNote = /*@__PURE__*/createReactComponent<JSX.IonNote, HTMLIonNoteElement>('ion-note', undefined, undefined, defineIonNote);
export const IonPicker = /*@__PURE__*/createReactComponent<JSX.IonPicker, HTMLIonPickerElement>('ion-picker', undefined, undefined, defineIonPicker);
export const IonPickerColumn = /*@__PURE__*/createReactComponent<JSX.IonPickerColumn, HTMLIonPickerColumnElement>('ion-picker-column', undefined, undefined, defineIonPickerColumn);
export const IonPickerColumnOption = /*@__PURE__*/createReactComponent<JSX.IonPickerColumnOption, HTMLIonPickerColumnOptionElement>('ion-picker-column-option', undefined, undefined, defineIonPickerColumnOption);
export const IonProgressBar = /*@__PURE__*/createReactComponent<JSX.IonProgressBar, HTMLIonProgressBarElement>('ion-progress-bar', undefined, undefined, defineIonProgressBar);
export const IonRadio = /*@__PURE__*/createReactComponent<JSX.IonRadio, HTMLIonRadioElement>('ion-radio', undefined, undefined, defineIonRadio);
export const IonRadioGroup = /*@__PURE__*/createReactComponent<JSX.IonRadioGroup, HTMLIonRadioGroupElement>('ion-radio-group', undefined, undefined, defineIonRadioGroup);

View File

@@ -52,6 +52,7 @@ import { defineCustomElement as defineIonNavLink } from '@ionic/core/components/
import { defineCustomElement as defineIonNote } from '@ionic/core/components/ion-note.js';
import { defineCustomElement as defineIonPicker } from '@ionic/core/components/ion-picker.js';
import { defineCustomElement as defineIonPickerColumn } from '@ionic/core/components/ion-picker-column.js';
import { defineCustomElement as defineIonPickerColumnOption } from '@ionic/core/components/ion-picker-column-option.js';
import { defineCustomElement as defineIonProgressBar } from '@ionic/core/components/ion-progress-bar.js';
import { defineCustomElement as defineIonRadio } from '@ionic/core/components/ion-radio.js';
import { defineCustomElement as defineIonRadioGroup } from '@ionic/core/components/ion-radio-group.js';
@@ -586,6 +587,12 @@ export const IonPickerColumn = /*@__PURE__*/ defineContainer<JSX.IonPickerColumn
]);
export const IonPickerColumnOption = /*@__PURE__*/ defineContainer<JSX.IonPickerColumnOption>('ion-picker-column-option', defineIonPickerColumnOption, [
'disabled',
'value'
]);
export const IonProgressBar = /*@__PURE__*/ defineContainer<JSX.IonProgressBar>('ion-progress-bar', defineIonProgressBar, [
'type',
'reversed',