feat(input): add the large size for the ionic theme (#29249)
Issue number: internal --------- ## What is the current behavior? Input does not have a size property. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Adds the `size` property with support for the `"large"` size on the `"ionic"` theme only - Adds tests for the size property - Note: the screenshots will not look right until the fill styles are added ## Does this introduce a breaking change? - [ ] Yes - [x] No
@ -623,6 +623,7 @@ ion-input,prop,placeholder,string | undefined,undefined,false,false
|
|||||||
ion-input,prop,readonly,boolean,false,false,true
|
ion-input,prop,readonly,boolean,false,false,true
|
||||||
ion-input,prop,required,boolean,false,false,false
|
ion-input,prop,required,boolean,false,false,false
|
||||||
ion-input,prop,shape,"round" | undefined,undefined,false,false
|
ion-input,prop,shape,"round" | undefined,undefined,false,false
|
||||||
|
ion-input,prop,size,"large" | undefined,undefined,false,false
|
||||||
ion-input,prop,spellcheck,boolean,false,false,false
|
ion-input,prop,spellcheck,boolean,false,false,false
|
||||||
ion-input,prop,step,string | undefined,undefined,false,false
|
ion-input,prop,step,string | undefined,undefined,false,false
|
||||||
ion-input,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
ion-input,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
||||||
|
8
core/src/components.d.ts
vendored
@ -1450,6 +1450,10 @@ export namespace Components {
|
|||||||
* The shape of the input. If "round" it will have an increased border radius.
|
* The shape of the input. If "round" it will have an increased border radius.
|
||||||
*/
|
*/
|
||||||
"shape"?: 'round';
|
"shape"?: 'round';
|
||||||
|
/**
|
||||||
|
* The size of the input. If "large", it will have an increased height. By default the size is unset. This property only applies to the `"ionic"` theme.
|
||||||
|
*/
|
||||||
|
"size"?: 'large';
|
||||||
/**
|
/**
|
||||||
* If `true`, the element will have its spelling and grammar checked.
|
* If `true`, the element will have its spelling and grammar checked.
|
||||||
*/
|
*/
|
||||||
@ -6694,6 +6698,10 @@ declare namespace LocalJSX {
|
|||||||
* The shape of the input. If "round" it will have an increased border radius.
|
* The shape of the input. If "round" it will have an increased border radius.
|
||||||
*/
|
*/
|
||||||
"shape"?: 'round';
|
"shape"?: 'round';
|
||||||
|
/**
|
||||||
|
* The size of the input. If "large", it will have an increased height. By default the size is unset. This property only applies to the `"ionic"` theme.
|
||||||
|
*/
|
||||||
|
"size"?: 'large';
|
||||||
/**
|
/**
|
||||||
* If `true`, the element will have its spelling and grammar checked.
|
* If `true`, the element will have its spelling and grammar checked.
|
||||||
*/
|
*/
|
||||||
|
@ -3,3 +3,12 @@
|
|||||||
|
|
||||||
// Ionic Input
|
// Ionic Input
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Ionic Input Sizes
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
:host(.input-size-large) {
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import type { NotchController } from '@utils/forms';
|
|||||||
import { createNotchController } from '@utils/forms';
|
import { createNotchController } from '@utils/forms';
|
||||||
import type { Attributes } from '@utils/helpers';
|
import type { Attributes } from '@utils/helpers';
|
||||||
import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from '@utils/helpers';
|
import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from '@utils/helpers';
|
||||||
|
import { printIonWarning } from '@utils/logging';
|
||||||
import { createSlotMutationController } from '@utils/slot-mutation-controller';
|
import { createSlotMutationController } from '@utils/slot-mutation-controller';
|
||||||
import type { SlotMutationController } from '@utils/slot-mutation-controller';
|
import type { SlotMutationController } from '@utils/slot-mutation-controller';
|
||||||
import { createColorClasses, hostContext } from '@utils/theme';
|
import { createColorClasses, hostContext } from '@utils/theme';
|
||||||
@ -251,6 +252,12 @@ export class Input implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop() step?: string;
|
@Prop() step?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of the input. If "large", it will have an increased height. By default the
|
||||||
|
* size is unset. This property only applies to the `"ionic"` theme.
|
||||||
|
*/
|
||||||
|
@Prop() size?: 'large';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of control to display. The default type is text.
|
* The type of control to display. The default type is text.
|
||||||
*/
|
*/
|
||||||
@ -464,6 +471,16 @@ export class Input implements ComponentInterface {
|
|||||||
return typeof this.value === 'number' ? this.value.toString() : (this.value || '').toString();
|
return typeof this.value === 'number' ? this.value.toString() : (this.value || '').toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSize() {
|
||||||
|
const theme = getIonTheme(this);
|
||||||
|
const { size } = this;
|
||||||
|
if (theme !== 'ionic' && size === 'large') {
|
||||||
|
printIonWarning(`The "${size}" size is not supported in the ${theme} theme.`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
private onInput = (ev: InputEvent | Event) => {
|
private onInput = (ev: InputEvent | Event) => {
|
||||||
const input = ev.target as HTMLInputElement | null;
|
const input = ev.target as HTMLInputElement | null;
|
||||||
if (input) {
|
if (input) {
|
||||||
@ -686,6 +703,7 @@ export class Input implements ComponentInterface {
|
|||||||
const { disabled, fill, readonly, shape, inputId, labelPlacement, el, hasFocus } = this;
|
const { disabled, fill, readonly, shape, inputId, labelPlacement, el, hasFocus } = this;
|
||||||
const theme = getIonTheme(this);
|
const theme = getIonTheme(this);
|
||||||
const value = this.getValue();
|
const value = this.getValue();
|
||||||
|
const size = this.getSize();
|
||||||
const inItem = hostContext('ion-item', this.el);
|
const inItem = hostContext('ion-item', this.el);
|
||||||
const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem;
|
const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem;
|
||||||
|
|
||||||
@ -721,6 +739,7 @@ export class Input implements ComponentInterface {
|
|||||||
'label-floating': labelShouldFloat,
|
'label-floating': labelShouldFloat,
|
||||||
[`input-fill-${fill}`]: fill !== undefined,
|
[`input-fill-${fill}`]: fill !== undefined,
|
||||||
[`input-shape-${shape}`]: shape !== undefined,
|
[`input-shape-${shape}`]: shape !== undefined,
|
||||||
|
[`input-size-${size}`]: size !== undefined,
|
||||||
[`input-label-placement-${labelPlacement}`]: true,
|
[`input-label-placement-${labelPlacement}`]: true,
|
||||||
'in-item': inItem,
|
'in-item': inItem,
|
||||||
'in-item-color': hostContext('ion-item.ion-color', this.el),
|
'in-item-color': hostContext('ion-item.ion-color', this.el),
|
||||||
|
74
core/src/components/input/test/size/index.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Input - Size</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(2, minmax(250px, 1fr));
|
||||||
|
grid-row-gap: 20px;
|
||||||
|
grid-column-gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
color: #6f7378;
|
||||||
|
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Input - Size</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content id="content" class="ion-padding">
|
||||||
|
<div class="grid">
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>No Fill: No Size</h2>
|
||||||
|
<ion-input label="Email"></ion-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Outline: No Size</h2>
|
||||||
|
<ion-input fill="outline" label="Email"></ion-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>No Fill: Large Size</h2>
|
||||||
|
<ion-input size="large" label="Email"></ion-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Outline: Large Size</h2>
|
||||||
|
<ion-input size="large" fill="outline" label="Email"></ion-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
</ion-app>
|
||||||
|
</body>
|
||||||
|
</html>
|
78
core/src/components/input/test/size/input.e2e.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size is only available in the Ionic theme
|
||||||
|
*/
|
||||||
|
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||||
|
test.describe(title('input: size'), () => {
|
||||||
|
test.describe('input: size large', () => {
|
||||||
|
test('should not have visual regressions', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-input
|
||||||
|
size="large"
|
||||||
|
label="Email"
|
||||||
|
value="hi@ionic.io"
|
||||||
|
></ion-input>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = page.locator('ion-input');
|
||||||
|
await expect(input).toHaveScreenshot(screenshot(`input-size-large`));
|
||||||
|
});
|
||||||
|
test('should render correctly with floating label', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-input
|
||||||
|
size="large"
|
||||||
|
label="Email"
|
||||||
|
label-placement="floating"
|
||||||
|
value="hi@ionic.io"
|
||||||
|
></ion-input>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = page.locator('ion-input');
|
||||||
|
await expect(input).toHaveScreenshot(screenshot(`input-size-large-label-floating`));
|
||||||
|
});
|
||||||
|
test('should not have visual regressions with fill outline', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-input
|
||||||
|
fill="outline"
|
||||||
|
size="large"
|
||||||
|
label="Email"
|
||||||
|
label-placement="floating"
|
||||||
|
value="hi@ionic.io"
|
||||||
|
></ion-input>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = page.locator('ion-input');
|
||||||
|
await expect(input).toHaveScreenshot(screenshot(`input-size-large-outline`));
|
||||||
|
});
|
||||||
|
test('should not have visual regressions with fill outline and round shape', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-input
|
||||||
|
fill="outline"
|
||||||
|
shape="round"
|
||||||
|
size="large"
|
||||||
|
label="Email"
|
||||||
|
label-placement="floating"
|
||||||
|
value="hi@ionic.io"
|
||||||
|
></ion-input>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = page.locator('ion-input');
|
||||||
|
await expect(input).toHaveScreenshot(screenshot(`input-size-large-outline-round`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
@ -957,7 +957,7 @@ export declare interface IonInfiniteScrollContent extends Components.IonInfinite
|
|||||||
|
|
||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
inputs: ['autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'clearInput', 'clearOnEdit', 'color', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'max', 'maxlength', 'min', 'minlength', 'mode', 'multiple', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'shape', 'spellcheck', 'step', 'theme', 'type', 'value'],
|
inputs: ['autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'clearInput', 'clearOnEdit', 'color', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'max', 'maxlength', 'min', 'minlength', 'mode', 'multiple', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'shape', 'size', 'spellcheck', 'step', 'theme', 'type', 'value'],
|
||||||
methods: ['setFocus', 'getInputElement']
|
methods: ['setFocus', 'getInputElement']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
@ -965,7 +965,7 @@ export declare interface IonInfiniteScrollContent extends Components.IonInfinite
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
template: '<ng-content></ng-content>',
|
template: '<ng-content></ng-content>',
|
||||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||||
inputs: ['autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'clearInput', 'clearOnEdit', 'color', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'max', 'maxlength', 'min', 'minlength', 'mode', 'multiple', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'shape', 'spellcheck', 'step', 'theme', 'type', 'value'],
|
inputs: ['autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'clearInput', 'clearOnEdit', 'color', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'max', 'maxlength', 'min', 'minlength', 'mode', 'multiple', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'shape', 'size', 'spellcheck', 'step', 'theme', 'type', 'value'],
|
||||||
})
|
})
|
||||||
export class IonInput {
|
export class IonInput {
|
||||||
protected el: HTMLElement;
|
protected el: HTMLElement;
|
||||||
|
@ -427,6 +427,7 @@ export const IonInput = /*@__PURE__*/ defineContainer<JSX.IonInput, JSX.IonInput
|
|||||||
'shape',
|
'shape',
|
||||||
'spellcheck',
|
'spellcheck',
|
||||||
'step',
|
'step',
|
||||||
|
'size',
|
||||||
'type',
|
'type',
|
||||||
'value',
|
'value',
|
||||||
'ionInput',
|
'ionInput',
|
||||||
|