Skip to content

feat(textarea): add experimental label slot #27677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5f83994
feat(input): add base label slot implementation
liamdebeasi Jun 12, 2023
514b677
test(input): update tests
liamdebeasi Jun 12, 2023
de1f128
chore(): add updated snapshots
Ionitron Jun 12, 2023
1f867f2
test(input): add spec and a11y tests
liamdebeasi Jun 12, 2023
5618a55
note feature is experimental
liamdebeasi Jun 12, 2023
a343c37
feat: add bae slot mutation observer
liamdebeasi Jun 12, 2023
a905a91
add base slot mutation observer impl
liamdebeasi Jun 12, 2023
03d4032
clean up code
liamdebeasi Jun 12, 2023
f0da048
add test
liamdebeasi Jun 12, 2023
1188234
feat(input): add label slot (#27633)
liamdebeasi Jun 12, 2023
b66a627
chore(): add updated snapshots
Ionitron Jun 12, 2023
71b0214
feat(input): support notch with label slot (#27635)
liamdebeasi Jun 12, 2023
d36c413
sync
liamdebeasi Jun 12, 2023
126ae3e
update test for outline notch
liamdebeasi Jun 12, 2023
d48a950
listen for changes to slotted content itself
liamdebeasi Jun 12, 2023
b02d1cc
add correct usage check
liamdebeasi Jun 13, 2023
b66bd8d
update file location
liamdebeasi Jun 14, 2023
cea8a22
lint
liamdebeasi Jun 14, 2023
5a35918
feat(textarea): add label slot (#27647)
liamdebeasi Jun 15, 2023
2698d43
chore(): sync
liamdebeasi Jun 20, 2023
c2dffd0
Merge remote-tracking branch 'origin/feature-7.1' into FW-4409
liamdebeasi Jun 20, 2023
884fecd
feat(textarea): support notch with label slot (#27660)
liamdebeasi Jun 20, 2023
cb31b86
feat(textarea): add workaround for dynamic slot content (#27661)
liamdebeasi Jun 20, 2023
aa65dee
Merge branch 'feature-7.1' into FW-4409
liamdebeasi Jun 20, 2023
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
4 changes: 2 additions & 2 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2992,7 +2992,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 @@ -7090,7 +7090,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
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.
73 changes: 72 additions & 1 deletion core/src/components/textarea/test/fill/textarea.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
helper-text="Enter your email"
maxlength="20"
counter="true"
></ion-input>
></ion-textarea>
`,
config
);
Expand Down Expand Up @@ -180,3 +180,74 @@ 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();
});
});
});

configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('textarea: label slot'), () => {
test('should render the notch correctly with a slotted label', async ({ page }) => {
await page.setContent(
`
<style>
.custom-label {
font-size: 30px;
}
</style>
<ion-textarea
fill="outline"
label-placement="stacked"
value="apple"
>
<div slot="label" class="custom-label">My Label Content</div>
</ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-outline-slotted-label`));
});
test('should render the notch correctly with a slotted label after the textarea was originally hidden', async ({
page,
}) => {
await page.setContent(
`
<style>
.custom-label {
font-size: 30px;
}
</style>
<ion-textarea
fill="outline"
label-placement="stacked"
value="apple"
style="display: none"
>
<div slot="label" class="custom-label">My Label Content</div>
</ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');

await textarea.evaluate((el: HTMLIonSelectElement) => el.style.removeProperty('display'));

expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-fill-outline-hidden-slotted-label`));
});
});
});
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.
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.
79 changes: 56 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,59 @@ 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 an ellipsis', 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 an ellipsis', 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`));
});
});
});

configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('textarea: async label'), () => {
test('textarea should re-render when label slot is added async', async ({ page }) => {
await page.setContent(
`
<ion-textarea fill="solid" label-placement="stacked" placeholder="Text Input"></ion-textarea>
`,
config
);

const textarea = page.locator('ion-textarea');

await textarea.evaluate((el: HTMLIonInputElement) => {
const labelEl = document.createElement('div');
labelEl.slot = 'label';
labelEl.innerHTML = 'Comments <span class="required" style="color: red">*</span';

el.appendChild(labelEl);
});

await page.waitForChanges();

expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-async-label`));
});
});
});
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
139 changes: 139 additions & 0 deletions core/src/components/textarea/test/slot/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<!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 class="grid-item">
<h2>Outline / Floating / Async</h2>
<ion-textarea id="solid-async" label-placement="floating" fill="outline" value="[email protected]"></ion-textarea>
</div>
</div>

<ion-button onclick="addSlot()">Add Slotted Content</ion-button>
<ion-button onclick="updateSlot()">Update Slotted Content</ion-button>
<ion-button onclick="removeSlot()">Remove Slotted Content</ion-button>
</ion-content>
</ion-app>

<script>
const solidAsync = document.querySelector('#solid-async');

const getSlottedContent = () => {
return solidAsync.querySelector('[slot="label"]');
};

const addSlot = () => {
if (getSlottedContent() === null) {
const labelEl = document.createElement('div');
labelEl.slot = 'label';
labelEl.innerHTML = 'Comments <span class="required">*</span>';

solidAsync.appendChild(labelEl);
}
};

const removeSlot = () => {
if (getSlottedContent() !== null) {
solidAsync.querySelector('[slot="label"]').remove();
}
};

const updateSlot = () => {
const slottedContent = getSlottedContent();

if (slottedContent !== null) {
slottedContent.textContent = 'This is my really really really long text';
}
};
</script>
</body>
</html>
Loading