Skip to content

Commit

Permalink
feat: configure minimum number of rows in text area (#8138)
Browse files Browse the repository at this point in the history
* feat: configure minimum number of rows in text area

* add test

* document that min rows is not applied to custom slotted text area

* remove unused observer argument

* log warning when setting minRows to less than two
  • Loading branch information
sissbruecker authored Nov 13, 2024
1 parent d5df9f5 commit 0dee248
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 8 deletions.
9 changes: 9 additions & 0 deletions packages/text-area/src/vaadin-text-area-mixin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ export declare class TextAreaMixinClass {
*/
pattern: string;

/**
* Minimum number of rows to show. Default is two rows, which is also the minimum value.
*
* When using a custom slotted textarea, the minimum number of rows are not applied for backwards compatibility.
*
* @attr {number} min-rows
*/
minRows: number;

/**
* Scrolls the textarea to the start if it has a vertical scrollbar.
*/
Expand Down
56 changes: 48 additions & 8 deletions packages/text-area/src/vaadin-text-area-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ export const TextAreaMixin = (superClass) =>
pattern: {
type: String,
},

/**
* Minimum number of rows to show. Default is two rows, which is also the minimum value.
*
* When using a custom slotted textarea, the minimum number of rows are not applied for backwards compatibility.
*
* @attr {number} min-rows
*/
minRows: {
type: Number,
value: 2,
observer: '__minRowsChanged',
},
};
}

Expand All @@ -51,6 +64,10 @@ export const TextAreaMixin = (superClass) =>
return [...super.constraints, 'maxlength', 'minlength', 'pattern'];
}

static get observers() {
return ['__updateMinHeight(minRows, inputElement)'];
}

/**
* Used by `InputControlMixin` as a reference to the clear button element.
* @protected
Expand All @@ -77,14 +94,13 @@ export const TextAreaMixin = (superClass) =>
ready() {
super.ready();

this.addController(
new TextAreaController(this, (input) => {
this._setInputElement(input);
this._setFocusElement(input);
this.stateTarget = input;
this.ariaTarget = input;
}),
);
this.__textAreaController = new TextAreaController(this, (input) => {
this._setInputElement(input);
this._setFocusElement(input);
this.stateTarget = input;
this.ariaTarget = input;
});
this.addController(this.__textAreaController);
this.addController(new LabelledInputController(this.inputElement, this._labelController));

this.addEventListener('animationend', this._onAnimationEnd);
Expand Down Expand Up @@ -178,6 +194,30 @@ export const TextAreaMixin = (superClass) =>
inputField.scrollTop = scrollTop;
}

/** @private */
__updateMinHeight(minRows) {
if (!this.inputElement) {
return;
}

// For minimum height, just set the number of rows on the native textarea,
// which causes the input container to grow as well.
// Do not override this on custom slotted textarea as number of rows may
// have been configured there.
if (this.inputElement === this.__textAreaController.defaultNode) {
this.inputElement.rows = Math.max(minRows, 2);
}
}

/**
* @private
*/
__minRowsChanged(minRows) {
if (minRows < 2) {
console.warn('<vaadin-text-area> minRows must be at least 2.');
}
}

/**
* Scrolls the textarea to the start if it has a vertical scrollbar.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ snapshots["vaadin-text-area host default"] =
</div>
<textarea
id="textarea-vaadin-text-area-3"
rows="2"
slot="textarea"
>
</textarea>
Expand All @@ -41,6 +42,7 @@ snapshots["vaadin-text-area host helper"] =
<textarea
aria-describedby="helper-vaadin-text-area-1"
id="textarea-vaadin-text-area-3"
rows="2"
slot="textarea"
>
</textarea>
Expand Down Expand Up @@ -77,6 +79,7 @@ snapshots["vaadin-text-area host error"] =
aria-invalid="true"
id="textarea-vaadin-text-area-3"
invalid=""
rows="2"
slot="textarea"
>
</textarea>
Expand Down
87 changes: 87 additions & 0 deletions packages/text-area/test/text-area.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,93 @@ describe('text-area', () => {
);
});

describe('min rows', () => {
const lineHeight = 20;
let consoleWarn;

beforeEach(async () => {
const fixture = fixtureSync(`
<div>
<style>
vaadin-text-area textarea {
line-height: ${lineHeight}px;
}
</style>
<vaadin-text-area></vaadin-text-area>
</div>
`);
textArea = fixture.querySelector('vaadin-text-area');
await nextUpdate(textArea);

consoleWarn = sinon.stub(console, 'warn');
});

afterEach(() => {
consoleWarn.restore();
});

it('should use min-height of two rows by default', () => {
expect(textArea.clientHeight).to.equal(lineHeight * 2);
});

it('should use min-height based on minimum rows', async () => {
textArea.minRows = 4;
await nextUpdate(textArea);

expect(textArea.clientHeight).to.equal(lineHeight * 4);
});

it('should not be possible to set min-height to less than two rows', async () => {
textArea.minRows = 1;
await nextUpdate(textArea);

expect(textArea.clientHeight).to.closeTo(lineHeight * 2, 1);
});

it('should log warning when setting minRows to less than two rows', async () => {
textArea.minRows = 1;
await nextUpdate(textArea);

expect(console.warn).to.be.calledWith('<vaadin-text-area> minRows must be at least 2.');
});

it('should not log warning when setting minRows to two rows or more', async () => {
textArea.minRows = 2;
await nextUpdate(textArea);

expect(console.warn).not.to.be.called;

textArea.minRows = 3;
await nextUpdate(textArea);

expect(console.warn).not.to.be.called;
});

it('should not overwrite rows on custom slotted textarea', async () => {
const custom = document.createElement('textarea');
custom.setAttribute('slot', 'textarea');
custom.rows = 1;
textArea.appendChild(custom);
await nextUpdate(textArea);

textArea.minRows = 4;
await nextUpdate(textArea);

expect(custom.rows).to.equal(1);
expect(textArea.clientHeight).to.closeTo(lineHeight, 1);
});

it('should grow beyond the min-height defined by minimum rows', async () => {
textArea.minRows = 4;
await nextUpdate(textArea);

textArea.value = Array(400).join('400');
await nextUpdate(textArea);

expect(textArea.clientHeight).to.be.above(80);
});
});

describe('--_text-area-vertical-scroll-position CSS variable', () => {
function wheel({ element = inputField, deltaY = 0 }) {
const e = new CustomEvent('wheel', { bubbles: true, cancelable: true });
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/text-area/test/visual/lumo/text-area.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,10 @@ describe('text-area', () => {
element.appendChild(span);
await visualDiff(div, 'suffix');
});

it('min-rows', async () => {
element.value = 'value';
element.minRows = 4;
await visualDiff(div, 'min-rows');
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/text-area/test/visual/material/text-area.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,10 @@ describe('text-area', () => {
element.appendChild(span);
await visualDiff(div, 'suffix');
});

it('min-rows', async () => {
element.value = 'value';
element.minRows = 4;
await visualDiff(div, 'min-rows');
});
});

0 comments on commit 0dee248

Please sign in to comment.