feat(select): add helperText and errorText properties (#30143)
Issue number: resolves #29205 --------- ## What is the current behavior? Select does not support helper and error text. ## What is the new behavior? - Adds support for `helperText` and `errorText` - Adds parts for `helper-text`, `error-text` and `supporting-text` - Adds an e2e test for helper and error text with functional tests and screenshot tests ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Preview](https://ionic-framework-git-rou-11551-ionic1.vercel.app/src/components/select/test/bottom-content) --------- Co-authored-by: swimer11 <65334157+swimer11@users.noreply.github.com> --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
@@ -1626,8 +1626,10 @@ ion-select,prop,cancelText,string,'Cancel',false,false
|
||||
ion-select,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
|
||||
ion-select,prop,compareWith,((currentValue: any, compareValue: any) => boolean) | null | string | undefined,undefined,false,false
|
||||
ion-select,prop,disabled,boolean,false,false,false
|
||||
ion-select,prop,errorText,string | undefined,undefined,false,false
|
||||
ion-select,prop,expandedIcon,string | undefined,undefined,false,false
|
||||
ion-select,prop,fill,"outline" | "solid" | undefined,undefined,false,false
|
||||
ion-select,prop,helperText,string | undefined,undefined,false,false
|
||||
ion-select,prop,interface,"action-sheet" | "alert" | "modal" | "popover",'alert',false,false
|
||||
ion-select,prop,interfaceOptions,any,{},false,false
|
||||
ion-select,prop,justify,"end" | "space-between" | "start" | undefined,undefined,false,false
|
||||
@@ -1682,9 +1684,12 @@ ion-select,css-prop,--placeholder-opacity,md
|
||||
ion-select,css-prop,--ripple-color,ios
|
||||
ion-select,css-prop,--ripple-color,md
|
||||
ion-select,part,container
|
||||
ion-select,part,error-text
|
||||
ion-select,part,helper-text
|
||||
ion-select,part,icon
|
||||
ion-select,part,label
|
||||
ion-select,part,placeholder
|
||||
ion-select,part,supporting-text
|
||||
ion-select,part,text
|
||||
|
||||
ion-select-modal,scoped
|
||||
|
||||
16
core/src/components.d.ts
vendored
@@ -2771,6 +2771,10 @@ export namespace Components {
|
||||
* If `true`, the user cannot interact with the select.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
/**
|
||||
* Text that is placed under the select and displayed when an error is detected.
|
||||
*/
|
||||
"errorText"?: string;
|
||||
/**
|
||||
* The toggle icon to show when the select is open. If defined, the icon rotation behavior in `md` mode will be disabled. If undefined, `toggleIcon` will be used for when the select is both open and closed.
|
||||
*/
|
||||
@@ -2779,6 +2783,10 @@ export namespace Components {
|
||||
* The fill for the item. If `"solid"` the item will have a background. If `"outline"` the item will be transparent with a border. Only available in `md` mode.
|
||||
*/
|
||||
"fill"?: 'outline' | 'solid';
|
||||
/**
|
||||
* Text that is placed under the select and displayed when no error is detected.
|
||||
*/
|
||||
"helperText"?: string;
|
||||
/**
|
||||
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
|
||||
*/
|
||||
@@ -7616,6 +7624,10 @@ declare namespace LocalJSX {
|
||||
* If `true`, the user cannot interact with the select.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
/**
|
||||
* Text that is placed under the select and displayed when an error is detected.
|
||||
*/
|
||||
"errorText"?: string;
|
||||
/**
|
||||
* The toggle icon to show when the select is open. If defined, the icon rotation behavior in `md` mode will be disabled. If undefined, `toggleIcon` will be used for when the select is both open and closed.
|
||||
*/
|
||||
@@ -7624,6 +7636,10 @@ declare namespace LocalJSX {
|
||||
* The fill for the item. If `"solid"` the item will have a background. If `"outline"` the item will be transparent with a border. Only available in `md` mode.
|
||||
*/
|
||||
"fill"?: 'outline' | 'solid';
|
||||
/**
|
||||
* Text that is placed under the select and displayed when no error is detected.
|
||||
*/
|
||||
"helperText"?: string;
|
||||
/**
|
||||
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
--border-width: #{$hairlines-width};
|
||||
--border-color: #{$item-ios-border-color};
|
||||
--highlight-height: 0px;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
--border-color: var(--highlight-color);
|
||||
}
|
||||
|
||||
/**
|
||||
* The bottom content should never have
|
||||
* a border with the solid style.
|
||||
*/
|
||||
:host(.select-fill-solid) .select-bottom {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
@@ -275,6 +275,71 @@ button {
|
||||
--highlight-color: var(--highlight-color-valid);
|
||||
}
|
||||
|
||||
// Select Bottom Content
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
.select-bottom {
|
||||
/**
|
||||
* The bottom content should take on the start and end
|
||||
* padding so it is always aligned with either the label
|
||||
* or the start of the text select.
|
||||
*/
|
||||
@include padding(5px, var(--padding-end), 0, var(--padding-start));
|
||||
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
border-top: var(--border-width) var(--border-style) var(--border-color);
|
||||
|
||||
font-size: dynamic-font(12px);
|
||||
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the select has a validity state, the
|
||||
* border and label should reflect that as a color.
|
||||
* The invalid state should show if the select is
|
||||
* invalid and has already been touched.
|
||||
* The valid state should show if the select
|
||||
* is valid, has already been touched, and
|
||||
* is currently focused. Do not show the valid
|
||||
* highlight when the select is blurred.
|
||||
*/
|
||||
:host(.has-focus.ion-valid),
|
||||
:host(.ion-touched.ion-invalid) {
|
||||
--border-color: var(--highlight-color);
|
||||
}
|
||||
|
||||
// Select Hint Text
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error text should only be shown when .ion-invalid is
|
||||
* present on the select. Otherwise the helper text should
|
||||
* be shown.
|
||||
*/
|
||||
.select-bottom .error-text {
|
||||
display: none;
|
||||
|
||||
color: var(--highlight-color-invalid);
|
||||
}
|
||||
|
||||
.select-bottom .helper-text {
|
||||
display: block;
|
||||
|
||||
color: $text-color-step-300;
|
||||
}
|
||||
|
||||
:host(.ion-touched.ion-invalid) .select-bottom .error-text {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host(.ion-touched.ion-invalid) .select-bottom .helper-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Select Label
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from '
|
||||
* @part icon - The select icon container.
|
||||
* @part container - The container for the selected text or placeholder.
|
||||
* @part label - The label text describing the select.
|
||||
* @part supporting-text - Supporting text displayed beneath the select.
|
||||
* @part helper-text - Supporting text displayed beneath the select when the select is valid.
|
||||
* @part error-text - Supporting text displayed beneath the select when the select is invalid and touched.
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-select',
|
||||
@@ -52,6 +55,8 @@ import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from '
|
||||
})
|
||||
export class Select implements ComponentInterface {
|
||||
private inputId = `ion-sel-${selectIds++}`;
|
||||
private helperTextId = `${this.inputId}-helper-text`;
|
||||
private errorTextId = `${this.inputId}-error-text`;
|
||||
private overlay?: OverlaySelect;
|
||||
private focusEl?: HTMLButtonElement;
|
||||
private mutationO?: MutationObserver;
|
||||
@@ -98,6 +103,16 @@ export class Select implements ComponentInterface {
|
||||
*/
|
||||
@Prop() fill?: 'outline' | 'solid';
|
||||
|
||||
/**
|
||||
* Text that is placed under the select and displayed when an error is detected.
|
||||
*/
|
||||
@Prop() errorText?: string;
|
||||
|
||||
/**
|
||||
* Text that is placed under the select and displayed when no error is detected.
|
||||
*/
|
||||
@Prop() helperText?: string;
|
||||
|
||||
/**
|
||||
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
|
||||
*/
|
||||
@@ -1014,6 +1029,8 @@ export class Select implements ComponentInterface {
|
||||
aria-label={this.ariaLabel}
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={`${isExpanded}`}
|
||||
aria-describedby={this.getHintTextID()}
|
||||
aria-invalid={this.getHintTextID() === this.errorTextId}
|
||||
aria-required={`${required}`}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
@@ -1022,6 +1039,55 @@ export class Select implements ComponentInterface {
|
||||
);
|
||||
}
|
||||
|
||||
private getHintTextID(): string | undefined {
|
||||
const { el, helperText, errorText, helperTextId, errorTextId } = this;
|
||||
|
||||
if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
|
||||
return errorTextId;
|
||||
}
|
||||
|
||||
if (helperText) {
|
||||
return helperTextId;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the helper text or error text values
|
||||
*/
|
||||
private renderHintText() {
|
||||
const { helperText, errorText, helperTextId, errorTextId } = this;
|
||||
|
||||
return [
|
||||
<div id={helperTextId} class="helper-text" part="supporting-text helper-text">
|
||||
{helperText}
|
||||
</div>,
|
||||
<div id={errorTextId} class="error-text" part="supporting-text error-text">
|
||||
{errorText}
|
||||
</div>,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for rendering helper text, and error text. This element
|
||||
* should only be rendered if hint text is set.
|
||||
*/
|
||||
private renderBottomContent() {
|
||||
const { helperText, errorText } = this;
|
||||
|
||||
/**
|
||||
* undefined and empty string values should
|
||||
* be treated as not having helper/error text.
|
||||
*/
|
||||
const hasHintText = !!helperText || !!errorText;
|
||||
if (!hasHintText) {
|
||||
return;
|
||||
}
|
||||
|
||||
return <div class="select-bottom">{this.renderHintText()}</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { disabled, el, isExpanded, expandedIcon, labelPlacement, justify, placeholder, fill, shape, name, value } =
|
||||
this;
|
||||
@@ -1101,6 +1167,7 @@ export class Select implements ComponentInterface {
|
||||
{hasFloatingOrStackedLabel && this.renderSelectIcon()}
|
||||
{shouldRenderHighlight && <div class="select-highlight"></div>}
|
||||
</label>
|
||||
{this.renderBottomContent()}
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
||||
141
core/src/components/select/test/bottom-content/index.html
Normal file
@@ -0,0 +1,141 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Select - Bottom Content</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
grid-row-gap: 20px;
|
||||
grid-column-gap: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
|
||||
color: #6f7378;
|
||||
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
ion-select.custom-error-color {
|
||||
--highlight-color-invalid: purple;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body onLoad="onLoad()">
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Select - Bottom Content</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="content" class="ion-padding">
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>No Hint</h2>
|
||||
<ion-select label="Label">
|
||||
<ion-select-option>Option</ion-select-option>
|
||||
</ion-select>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>No Hint: Stacked</h2>
|
||||
<ion-select label="Label" label-placement="stacked">
|
||||
<ion-select-option>Option</ion-select-option>
|
||||
</ion-select>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Text</h2>
|
||||
<ion-select label="Label" helper-text="Helper text">
|
||||
<ion-select-option>Option</ion-select-option>
|
||||
</ion-select>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Text: Stacked</h2>
|
||||
<ion-select label="Label" label-placement="stacked" helper-text="Helper text"
|
||||
>Label
|
||||
<ion-select-option>Option</ion-select-option>
|
||||
</ion-select>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Text</h2>
|
||||
<ion-select class="ion-touched ion-invalid" label="Label" error-text="Error text">
|
||||
<ion-select-option>Option</ion-select-option></ion-select
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Text: Stacked</h2>
|
||||
<ion-select class="ion-touched ion-invalid" label="Label" label-placement="stacked" error-text="Error text">
|
||||
<ion-select-option>Option</ion-select-option></ion-select
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Error Text: Custom Color</h2>
|
||||
<ion-select class="ion-touched ion-invalid custom-error-color" label="Label" error-text="Error text">
|
||||
<ion-select-option>Option</ion-select-option></ion-select
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Helper Text: Wrapping</h2>
|
||||
<ion-select
|
||||
label="Label"
|
||||
helper-text="Helper text helper text helper text helper text helper text helper text helper text helper text helper text"
|
||||
>
|
||||
<ion-select-option>Option</ion-select-option>
|
||||
</ion-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="expand" onclick="toggleFill()">Toggle Fill</button>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
// Hide the toggle fill button on ios mode since it's not supported
|
||||
function onLoad() {
|
||||
const toggleFillButton = document.querySelector('button');
|
||||
|
||||
if (Ionic.mode === 'ios' && toggleFillButton) {
|
||||
toggleFillButton.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
const selects = document.querySelectorAll('ion-select');
|
||||
|
||||
function toggleFill() {
|
||||
selects.forEach((select) => {
|
||||
switch (select.fill) {
|
||||
case 'outline':
|
||||
select.fill = 'solid';
|
||||
break;
|
||||
case 'solid':
|
||||
select.fill = undefined;
|
||||
break;
|
||||
default:
|
||||
select.fill = 'outline';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
215
core/src/components/select/test/bottom-content/select.e2e.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* Functionality is the same across modes & directions
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('select: bottom content functionality'), () => {
|
||||
test('should not render bottom content if no hint is enabled', async ({ page }) => {
|
||||
await page.setContent(`<ion-select label="Label"></ion-select>`, config);
|
||||
|
||||
const bottomEl = page.locator('ion-select .select-bottom');
|
||||
await expect(bottomEl).toHaveCount(0);
|
||||
});
|
||||
test('helper text should be visible initially', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" helper-text="Helper text" error-text="Error text"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-select .helper-text');
|
||||
const errorText = page.locator('ion-select .error-text');
|
||||
await expect(helperText).toBeVisible();
|
||||
await expect(helperText).toHaveText('Helper text');
|
||||
await expect(errorText).toBeHidden();
|
||||
});
|
||||
test('select should have an aria-describedby attribute when helper text is present', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" helper-text="Helper text" error-text="Error text"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const select = page.locator('ion-select button');
|
||||
const helperText = page.locator('ion-select .helper-text');
|
||||
const helperTextId = await helperText.getAttribute('id');
|
||||
const ariaDescribedBy = await select.getAttribute('aria-describedby');
|
||||
|
||||
expect(ariaDescribedBy).toBe(helperTextId);
|
||||
});
|
||||
test('error text should be visible when select is invalid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" class="ion-invalid ion-touched" helper-text="Helper text" error-text="Error text"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-select .helper-text');
|
||||
const errorText = page.locator('ion-select .error-text');
|
||||
await expect(helperText).toBeHidden();
|
||||
await expect(errorText).toBeVisible();
|
||||
await expect(errorText).toHaveText('Error text');
|
||||
});
|
||||
|
||||
test('select should have an aria-describedby attribute when error text is present', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" class="ion-invalid ion-touched" helper-text="Helper text" error-text="Error text"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const select = page.locator('ion-select button');
|
||||
const errorText = page.locator('ion-select .error-text');
|
||||
const errorTextId = await errorText.getAttribute('id');
|
||||
const ariaDescribedBy = await select.getAttribute('aria-describedby');
|
||||
|
||||
expect(ariaDescribedBy).toBe(errorTextId);
|
||||
});
|
||||
test('select should have aria-invalid attribute when select is invalid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" class="ion-invalid ion-touched" helper-text="Helper text" error-text="Error text"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const select = page.locator('ion-select button');
|
||||
|
||||
await expect(select).toHaveAttribute('aria-invalid');
|
||||
});
|
||||
test('select should not have aria-invalid attribute when select is valid', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" helper-text="Helper text" error-text="Error text"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const select = page.locator('ion-select button');
|
||||
|
||||
await expect(select).not.toHaveAttribute('aria-invalid');
|
||||
});
|
||||
test('select should not have aria-describedby attribute when no hint or error text is present', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setContent(`<ion-select label="Label"></ion-select>`, config);
|
||||
|
||||
const select = page.locator('ion-select button');
|
||||
|
||||
await expect(select).not.toHaveAttribute('aria-describedby');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Rendering is different across modes
|
||||
*/
|
||||
configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('select: helper text rendering'), () => {
|
||||
test('should not have visual regressions when rendering helper text', async ({ page }) => {
|
||||
await page.setContent(`<ion-select label="Label" helper-text="Helper text"></ion-select>`, config);
|
||||
|
||||
const bottomEl = page.locator('ion-select');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`select-helper-text`));
|
||||
});
|
||||
test('should not have visual regressions when rendering helper text with wrapping text', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" helper-text="Helper text helper text helper text helper text helper text helper text helper text helper text helper text"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-select');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`select-helper-text-wrapping`));
|
||||
});
|
||||
test('should not have visual regressions when rendering helper text with a stacked label', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" label-placement="stacked" helper-text="Helper text"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-select');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`select-helper-text-stacked-label`));
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('select: error text rendering'), () => {
|
||||
test('should not have visual regressions when rendering error text', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" class="ion-invalid ion-touched" error-text="Error text"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-select');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`select-error-text`));
|
||||
});
|
||||
test('should not have visual regressions when rendering error text with a stacked label', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-select label="Label" class="ion-invalid ion-touched" error-text="Error text" label-placement="stacked"></ion-select>`,
|
||||
config
|
||||
);
|
||||
|
||||
const bottomEl = page.locator('ion-select');
|
||||
await expect(bottomEl).toHaveScreenshot(screenshot(`select-error-text-stacked-label`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Customizing supporting text is the same across modes and directions
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('select: supporting text customization'), () => {
|
||||
test('should not have visual regressions when rendering helper text with custom css', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
ion-select::part(supporting-text) {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
ion-select::part(helper-text) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<ion-select label="Label" helper-text="Helper text"></ion-select>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const helperText = page.locator('ion-select');
|
||||
await expect(helperText).toHaveScreenshot(screenshot(`select-helper-text-custom-css`));
|
||||
});
|
||||
test('should not have visual regressions when rendering error text with custom css', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
ion-select::part(supporting-text) {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
ion-select::part(error-text) {
|
||||
color: purple;
|
||||
}
|
||||
</style>
|
||||
<ion-select class="ion-invalid ion-touched" label="Label" error-text="Error text"></ion-select>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const errorText = page.locator('ion-select');
|
||||
await expect(errorText).toHaveScreenshot(screenshot(`select-error-text-custom-css`));
|
||||
});
|
||||
test('should not have visual regressions when rendering error text with a custom css variable', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
ion-select {
|
||||
--highlight-color-invalid: purple;
|
||||
}
|
||||
</style>
|
||||
<ion-select class="ion-invalid ion-touched" label="Label" error-text="Error text"></ion-select>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const errorText = page.locator('ion-select');
|
||||
await expect(errorText).toHaveScreenshot(screenshot(`select-error-text-custom-css-var`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -2060,7 +2060,7 @@ export declare interface IonSegmentView extends Components.IonSegmentView {
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'required', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'errorText', 'expandedIcon', 'fill', 'helperText', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'required', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
methods: ['open']
|
||||
})
|
||||
@Component({
|
||||
@@ -2068,7 +2068,7 @@ export declare interface IonSegmentView extends Components.IonSegmentView {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'required', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'errorText', 'expandedIcon', 'fill', 'helperText', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'required', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
})
|
||||
export class IonSelect {
|
||||
protected el: HTMLIonSelectElement;
|
||||
|
||||
@@ -21,8 +21,10 @@ const SELECT_INPUTS = [
|
||||
'color',
|
||||
'compareWith',
|
||||
'disabled',
|
||||
'errorText',
|
||||
'expandedIcon',
|
||||
'fill',
|
||||
'helperText',
|
||||
'interface',
|
||||
'interfaceOptions',
|
||||
'justify',
|
||||
|
||||
@@ -872,6 +872,8 @@ export const IonSelect = /*@__PURE__*/ defineContainer<JSX.IonSelect, JSX.IonSel
|
||||
'compareWith',
|
||||
'disabled',
|
||||
'fill',
|
||||
'errorText',
|
||||
'helperText',
|
||||
'interface',
|
||||
'interfaceOptions',
|
||||
'justify',
|
||||
|
||||