Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2921,6 +2921,9 @@ export namespace Components {
*/
"mode"?: "ios" | "md";
}
/**
* @experimental The label text to associate with the textarea. Use the `labelPlacement` property to control where the label is placed relative to the textarea. Use this if you need to render a label with custom HTML.
*/
interface IonTextarea {
/**
* If `true`, the textarea container will grow and shrink based on the contents of the textarea.
Expand Down Expand Up @@ -2987,7 +2990,7 @@ export namespace Components {
*/
"inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
/**
* The visible label associated with the textarea.
* The visible label associated with the textarea. Use this if you need to render a plaintext label. The `label` property will take priority over the `label` slot if both are used.
*/
"label"?: string;
/**
Expand Down Expand Up @@ -3909,6 +3912,9 @@ declare global {
prototype: HTMLIonTextElement;
new (): HTMLIonTextElement;
};
/**
* @experimental The label text to associate with the textarea. Use the `labelPlacement` property to control where the label is placed relative to the textarea. Use this if you need to render a label with custom HTML.
*/
interface HTMLIonTextareaElement extends Components.IonTextarea, HTMLStencilElement {
}
var HTMLIonTextareaElement: {
Expand Down Expand Up @@ -7021,6 +7027,9 @@ declare namespace LocalJSX {
*/
"mode"?: "ios" | "md";
}
/**
* @experimental The label text to associate with the textarea. Use the `labelPlacement` property to control where the label is placed relative to the textarea. Use this if you need to render a label with custom HTML.
*/
interface IonTextarea {
/**
* If `true`, the textarea container will grow and shrink based on the contents of the textarea.
Expand Down Expand Up @@ -7083,7 +7092,7 @@ declare namespace LocalJSX {
*/
"inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
/**
* The visible label associated with the textarea.
* The visible label associated with the textarea. Use this if you need to render a plaintext label. The `label` property will take priority over the `label` slot if both are used.
*/
"label"?: string;
/**
Expand Down Expand Up @@ -7542,6 +7551,9 @@ declare module "@stencil/core" {
"ion-tab-button": LocalJSX.IonTabButton & JSXBase.HTMLAttributes<HTMLIonTabButtonElement>;
"ion-tabs": LocalJSX.IonTabs & JSXBase.HTMLAttributes<HTMLIonTabsElement>;
"ion-text": LocalJSX.IonText & JSXBase.HTMLAttributes<HTMLIonTextElement>;
/**
* @experimental The label text to associate with the textarea. Use the `labelPlacement` property to control where the label is placed relative to the textarea. Use this if you need to render a label with custom HTML.
*/
"ion-textarea": LocalJSX.IonTextarea & JSXBase.HTMLAttributes<HTMLIonTextareaElement>;
"ion-thumbnail": LocalJSX.IonThumbnail & JSXBase.HTMLAttributes<HTMLIonThumbnailElement>;
"ion-title": LocalJSX.IonTitle & JSXBase.HTMLAttributes<HTMLIonTitleElement>;
Expand Down
1 change: 1 addition & 0 deletions core/src/components/textarea/test/a11y/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<main>
<h1>Textarea - a11y</h1>

<ion-textarea><div slot="label">Slotted Label</div></ion-textarea><br />
<ion-textarea label="my label"></ion-textarea><br />
<ion-textarea aria-label="my aria label"></ion-textarea><br />
<ion-textarea label="Email" label-placement="stacked" value="[email protected]"></ion-textarea>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions core/src/components/textarea/test/fill/textarea.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,19 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
});
});
});

configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('textarea: notch cutout'), () => {
test('notch cutout should be hidden when no label is passed', async ({ page }) => {
await page.setContent(
`
<ion-textarea fill="outline" label-placement="stacked" aria-label="my textarea"></ion-textarea>
`,
config
);

const notchCutout = page.locator('ion-textarea .textarea-outline-notch');
await expect(notchCutout).toBeHidden();
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 29 additions & 23 deletions core/src/components/textarea/test/label-placement/textarea.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,6 @@ configs().forEach(({ title, screenshot, config }) => {
const textarea = page.locator('ion-textarea');
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-placement-start-multi-line-value`));
});

test('label should be truncated', async ({ page }) => {
await page.setContent(
`
<ion-textarea label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." label-placement="start"></ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-placement-start-label-truncated`));
});
});
test.describe(title('textarea: label placement end'), () => {
test('label should appear on the ending side of the textarea', async ({ page }) => {
Expand All @@ -61,17 +49,6 @@ configs().forEach(({ title, screenshot, config }) => {
const textarea = page.locator('ion-textarea');
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-placement-end-multi-line-value`));
});
test('label should be truncated', async ({ page }) => {
await page.setContent(
`
<ion-textarea label="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." label-placement="end"></ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-placement-end-label-truncated`));
});
});
test.describe(title('textarea: label placement fixed'), () => {
test('label should appear on the starting side of the textarea and have a fixed width', async ({ page }) => {
Expand Down Expand Up @@ -234,3 +211,32 @@ configs().forEach(({ title, screenshot, config }) => {
});
});
});

configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('textarea: label overflow'), () => {
test('label property should be truncated with ellipses', async ({ page }) => {
await page.setContent(
`
<ion-textarea label="Label Label Label Label Label" placeholder="Text Input"></ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-label-truncate`));
});
test('label slot should be truncated with ellipses', async ({ page }) => {
await page.setContent(
`
<ion-textarea placeholder="Text Input">
<div slot="label">Label Label Label Label Label</div>
</ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-label-slot-truncate`));
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
98 changes: 98 additions & 0 deletions core/src/components/textarea/test/slot/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Textarea - Slot</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(3, 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;
}
}

.required {
color: red;
}
</style>
</head>

<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Textarea - Slot</ion-title>
</ion-toolbar>
</ion-header>

<ion-content id="content" class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>No Fill / Start</h2>
<ion-textarea label-placement="start" value="[email protected]">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Solid / Start</h2>
<ion-textarea label-placement="start" fill="solid" value="[email protected]">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Start</h2>
<ion-textarea label-placement="start" fill="outline" value="[email protected]">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>No Fill / Floating</h2>
<ion-textarea label-placement="floating" value="[email protected]">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Solid / Floating</h2>
<ion-textarea label-placement="floating" fill="solid" value="[email protected]">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>

<div class="grid-item">
<h2>Outline / Floating</h2>
<ion-textarea label-placement="floating" fill="outline" value="[email protected]">
<div slot="label">Email <span class="required">*</span></div>
</ion-textarea>
</div>
</div>
</ion-content>
</ion-app>
</body>
</html>
54 changes: 54 additions & 0 deletions core/src/components/textarea/test/textarea.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,57 @@ it('should inherit attributes', async () => {
expect(nativeEl.getAttribute('tabindex')).toBe('-1');
expect(nativeEl.getAttribute('data-form-type')).toBe('password');
});

/**
* Textarea uses emulated slots, so the internal
* behavior will not exactly match IonSelect's slots.
* For example, Textarea does not render an actual `<slot>` element
* internally, so we do not check for that here. Instead,
* we check to see which label text is being used.
* If Textarea is updated to use Shadow DOM (and therefore native slots),
* then we can update these tests to more closely match the Select tests.
**/
describe('textarea: label rendering', () => {
it('should render label prop if only prop provided', async () => {
const page = await newSpecPage({
components: [Textarea],
html: `
<ion-textarea label="Label Prop Text"></ion-textarea>
`,
});

const textarea = page.body.querySelector('ion-textarea');

const labelText = textarea.querySelector('.label-text-wrapper');

expect(labelText.textContent).toBe('Label Prop Text');
});
it('should render label slot if only slot provided', async () => {
const page = await newSpecPage({
components: [Textarea],
html: `
<ion-textarea><div slot="label">Label Prop Slot</div></ion-textarea>
`,
});

const textarea = page.body.querySelector('ion-textarea');

const labelText = textarea.querySelector('.label-text-wrapper');

expect(labelText.textContent).toBe('Label Prop Slot');
});
it('should render label prop if both prop and slot provided', async () => {
const page = await newSpecPage({
components: [Textarea],
html: `
<ion-textarea label="Label Prop Text"><div slot="label">Label Prop Slot</div></ion-textarea>
`,
});

const textarea = page.body.querySelector('ion-textarea');

const labelText = textarea.querySelector('.label-text-wrapper');

expect(labelText.textContent).toBe('Label Prop Text');
});
});
30 changes: 24 additions & 6 deletions core/src/components/textarea/textarea.scss
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@

font-family: $font-family-base;

white-space: pre-wrap;

z-index: $z-index-item-input;

box-sizing: border-box;
Expand All @@ -74,6 +72,8 @@
flex: 1;

background: var(--background);

white-space: pre-wrap;
}

// TODO: FW-2876 - Remove this selector
Expand Down Expand Up @@ -131,9 +131,8 @@
outline: none;

background: transparent;
box-sizing: border-box;
resize: none;
appearance: none;

white-space: pre-wrap;

/**
* This ensures the textarea
Expand All @@ -145,6 +144,9 @@
* contrast of the textarea.
*/
z-index: 1;
box-sizing: border-box;
resize: none;
appearance: none;

&::placeholder {
@include padding(0);
Expand All @@ -159,6 +161,11 @@
}
}

// TODO: FW-2876 - Remove this selector
:host(.legacy-textarea) .native-textarea {
white-space: inherit;
}

// TODO: FW-2876 - Remove this selector
:host(.legacy-textarea) .native-textarea,
:host(.legacy-textarea) .textarea-legacy-wrapper::after {
Expand Down Expand Up @@ -455,14 +462,25 @@
* works on block-level elements. A flex item is
* considered blockified (https://www.w3.org/TR/css-display-3/#blockify).
*/
.label-text {
.label-text,
::slotted([slot="label"]) {
text-overflow: ellipsis;

white-space: nowrap;

overflow: hidden;
}

/**
* If no label text is placed into the slot
* then the element should be hidden otherwise
* there will be additional margins added.
*/
.label-text-wrapper-hidden,
.textarea-outline-notch-hidden {
display: none;
}

.textarea-wrapper textarea {
/**
* When the floating label appears on top of the
Expand Down
Loading