Merge textarea & input styles

Also simplify textarea component DOM
This commit is contained in:
Lea Verou
2024-12-19 01:38:39 -05:00
parent 474d8d7c7b
commit 491533e09c
6 changed files with 95 additions and 173 deletions

View File

@@ -10,11 +10,14 @@
align-items: stretch;
justify-content: start;
position: relative;
height: var(--wa-form-control-height);
border-color: inherit;
border-style: inherit;
border-radius: inherit;
transition: inherit;
height: var(--wa-form-control-height);
padding-block: 0;
}
input {
@@ -26,6 +29,7 @@ input {
/* prettier-ignore */
background-color: rgb(118 118 118 / 0); /* ensures proper placeholder styles in webkit's date input */
height: calc(var(--wa-form-control-height) - var(--border-width) * 2);
padding-block: 0;
}
input::-webkit-search-decoration,

View File

@@ -1,13 +1,26 @@
:host {
display: block;
display: flex;
flex-flow: column;
border-width: 0;
}
:host([appearance='filled']) {
--background-color: var(--wa-color-neutral-fill-quiet);
--border-color: var(--background-color);
.wa-text-field {
display: grid;
align-items: center;
padding: 0;
border-color: inherit;
border-style: inherit;
border-radius: inherit;
transition: inherit;
}
.control,
textarea[part] {
padding: var(--wa-space);
border-radius: inherit;
border: var(--border-width) solid transparent; /* make resizer look a little nicer */
}
textarea,
.size-adjuster {
grid-area: 1 / 1 / 2 / 2;
}
@@ -18,38 +31,26 @@
opacity: 0;
}
.control {
border: none;
background: none;
box-shadow: none;
cursor: inherit;
textarea::-webkit-search-decoration,
textarea::-webkit-search-cancel-button,
textarea::-webkit-search-results-button,
textarea::-webkit-search-results-decoration {
-webkit-appearance: none;
}
.control::-webkit-search-decoration,
.control::-webkit-search-cancel-button,
.control::-webkit-search-results-button,
.control::-webkit-search-results-decoration {
-webkit-appearance: none;
}
.control:focus {
outline: none;
}
/*
* Resize types
*/
.textarea--resize-none .control {
:host([resize='none']) textarea {
resize: none;
}
.textarea--resize-vertical .control {
:host([resize='vertical']) textarea {
resize: vertical;
}
.textarea--resize-auto .control {
:host([resize='auto']) textarea {
height: auto;
resize: none;
overflow-y: hidden;

View File

@@ -11,7 +11,7 @@ import { HasSlotController } from '../../internal/slot.js';
import { MirrorValidator } from '../../internal/validators/mirror-validator.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/textarea.css';
import nativeStyles from '../../styles/native/input.css';
import formControlStyles from '../../styles/shadow/form-control.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';
@@ -32,12 +32,10 @@ import styles from './textarea.css';
* @event wa-input - Emitted when the control receives input.
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @csspart form-control - The form control that wraps the label, input, and hint.
* @csspart form-control-label - The label's wrapper.
* @csspart label - The label
* @csspart form-control-input - The input's wrapper.
* @csspart hint - The hint's wrapper.
* @csspart base - The component's base wrapper.
* @csspart textarea - The internal `<textarea>` control.
* @csspart base - The internal `<textarea>` control.
*
* @cssproperty --background-color - The textarea's background color.
* @cssproperty --border-color - The color of the textarea's borders.
@@ -61,7 +59,6 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
@query('.control') input: HTMLTextAreaElement;
@query('.size-adjuster') sizeAdjuster: HTMLTextAreaElement;
@state() private hasFocus = false;
@property() title = ''; // make reactive to pass through
/** The name of the textarea, submitted as a name/value pair with form data. */
@@ -204,7 +201,6 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
}
private handleBlur() {
this.hasFocus = false;
this.dispatchEvent(new WaBlurEvent());
this.checkValidity();
}
@@ -218,7 +214,6 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
}
private handleFocus() {
this.hasFocus = true;
this.dispatchEvent(new WaFocusEvent());
}
@@ -230,7 +225,9 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
private setTextareaHeight() {
if (this.resize === 'auto') {
// This prevents layout shifts. We use `clientHeight` instead of `scrollHeight` to account for if the `<textarea>` has a max-height set on it. In my tests, this has worked fine. Im not aware of any edge cases. [Konnor]
// This prevents layout shifts. We use `clientHeight` instead of `scrollHeight` to account for if the `<textarea>` has a max-height set on it.
// In my tests, this has worked fine. Im not aware of any edge cases. [Konnor]
// Lets switch to `field-sizing: content` once it has better support: https://caniuse.com/mdn-css_properties_field-sizing [Lea]
this.sizeAdjuster.style.height = `${this.input.clientHeight}px`;
this.input.style.height = 'auto';
this.input.style.height = `${this.input.scrollHeight}px`;
@@ -320,77 +317,51 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
const hasHint = this.hint ? true : !!hasHintSlot;
return html`
<div
part="form-control"
class=${classMap({
'form-control': true,
'form-control--has-label': hasLabel,
})}
>
<label part="form-control-label" class="label" for="input" aria-hidden=${hasLabel ? 'false' : 'true'}>
<slot name="label">${this.label}</slot>
</label>
<label part="label" class="label" for="input" aria-hidden=${hasLabel ? 'false' : 'true'}>
<slot name="label">${this.label}</slot>
</label>
<div part="form-control-input" class="form-control-input">
<div
part="base"
class=${classMap({
textarea: true,
'textarea--small': this.size === 'small',
'textarea--medium': this.size === 'medium',
'textarea--large': this.size === 'large',
'textarea--standard': this.appearance !== 'filled',
'textarea--filled': this.appearance === 'filled',
'textarea--disabled': this.disabled,
'textarea--focused': this.hasFocus,
'textarea--empty': !this.value,
'textarea--resize-none': this.resize === 'none',
'textarea--resize-vertical': this.resize === 'vertical',
'textarea--resize-auto': this.resize === 'auto',
})}
>
<textarea
part="textarea"
id="input"
class="control"
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${ifDefined(this.name)}
.value=${live(this.value)}
?disabled=${this.disabled}
?readonly=${this.readonly}
?required=${this.required}
placeholder=${ifDefined(this.placeholder)}
rows=${ifDefined(this.rows)}
minlength=${ifDefined(this.minlength)}
maxlength=${ifDefined(this.maxlength)}
autocapitalize=${ifDefined(this.autocapitalize)}
autocorrect=${ifDefined(this.autocorrect)}
?autofocus=${this.autofocus}
spellcheck=${ifDefined(this.spellcheck)}
enterkeyhint=${ifDefined(this.enterkeyhint)}
inputmode=${ifDefined(this.inputmode)}
aria-describedby="hint"
@change=${this.handleChange}
@input=${this.handleInput}
@focus=${this.handleFocus}
@blur=${this.handleBlur}
></textarea>
<div part="textarea" class="textarea wa-text-field">
<textarea
part="base"
id="input"
class="control"
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${ifDefined(this.name)}
.value=${live(this.value)}
?disabled=${this.disabled}
?readonly=${this.readonly}
?required=${this.required}
placeholder=${ifDefined(this.placeholder)}
rows=${ifDefined(this.rows)}
minlength=${ifDefined(this.minlength)}
maxlength=${ifDefined(this.maxlength)}
autocapitalize=${ifDefined(this.autocapitalize)}
autocorrect=${ifDefined(this.autocorrect)}
?autofocus=${this.autofocus}
spellcheck=${ifDefined(this.spellcheck)}
enterkeyhint=${ifDefined(this.enterkeyhint)}
inputmode=${ifDefined(this.inputmode)}
aria-describedby="hint"
@change=${this.handleChange}
@input=${this.handleInput}
@focus=${this.handleFocus}
@blur=${this.handleBlur}
></textarea>
<!-- This "adjuster" exists to prevent layout shifting. https://github.com/shoelace-style/shoelace/issues/2180 -->
<div part="textarea-adjuster" class="size-adjuster" ?hidden=${this.resize !== 'auto'}></div>
</div>
</div>
<slot
name="hint"
part="hint"
aria-hidden=${hasHint ? 'false' : 'true'}
class=${classMap({
'has-slotted': hasHint,
})}
>${this.hint}</slot
>
<!-- This "adjuster" exists to prevent layout shifting. https://github.com/shoelace-style/shoelace/issues/2180 -->
<div part="textarea-adjuster" class="size-adjuster" ?hidden=${this.resize !== 'auto'}></div>
</div>
<slot
name="hint"
part="hint"
aria-hidden=${hasHint ? 'false' : 'true'}
class=${classMap({
'has-slotted': hasHint,
})}
>${this.hint}</slot
>
`;
}
}

View File

@@ -8,7 +8,6 @@
@import url('native/input.css');
@import url('native/details.css');
@import url('native/tables.css');
@import url('native/textarea.css');
@import url('native/blockquote.css');
@import url('native/dialog.css');
@import url('native/slider.css');

View File

@@ -1,6 +1,7 @@
.wa-text-field,
:host,
input:not(
textarea,
input:where(:not(
/* Exclude inputs that don't accept text */
[type='button'],
[type='checkbox'],
@@ -12,9 +13,9 @@ input:not(
[type='range'],
[type='reset'],
[type='submit']
) {
)) {
/* Style native inputs and <wa-input>'s visible container */
&:where(:not(.wa-text-field *, :host input)) {
&:where(:not(.wa-text-field *, :host input, :host textarea)) {
/* Do NOT reset --background-color and --border-color here so they trickle in from the appearance utils
* Instead we provide the fallback when setting
*/
@@ -39,12 +40,11 @@ input:not(
}
/* Style text controls of inputs, including within <wa-input> */
&:not(:host, input:where(.wa-text-field *)) {
&:where(:not(:host, .wa-text-field :is(input, textarea))) {
background-color: var(--background-color, var(--wa-form-control-background-color));
border-width: var(--border-width);
box-shadow: var(--box-shadow);
padding: 0 var(--wa-space);
height: var(--wa-form-control-height);
padding: var(--wa-space-smaller) var(--wa-space);
/* Style focused inputs */
&:focus-within {
@@ -60,14 +60,16 @@ input:not(
}
}
&:where(input) {
&:where(input, textarea) {
/* Actual inputs */
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus,
&:-webkit-autofill:active {
box-shadow: none;
caret-color: var(--wa-form-control-value-color);
&:autofill {
&,
&:hover,
&:focus,
&:active {
box-shadow: none;
caret-color: var(--wa-form-control-value-color);
}
}
&::placeholder {
@@ -78,7 +80,8 @@ input:not(
}
}
.wa-text-field input {
.wa-text-field input,
.wa-text-field textarea {
padding: 0;
border: none;
outline: none;

View File

@@ -1,56 +0,0 @@
textarea:not(:host *, .wa-off, .wa-off-deep *),
:host {
--background-color: var(--wa-form-control-background-color);
--border-color: var(--wa-form-control-resting-color);
--border-radius: var(--wa-form-control-border-radius);
--border-style: var(--wa-form-control-border-style);
--border-width: var(--wa-form-control-border-width);
--box-shadow: initial;
}
textarea:not(:host *, .wa-off, .wa-off-deep *),
:host .textarea {
background-color: var(--background-color);
border-color: var(--border-color);
border-radius: var(--border-radius);
border-style: var(--border-style);
border-width: var(--border-width);
box-shadow: var(--box-shadow);
display: grid;
align-items: center;
position: relative;
width: 100%;
font: inherit;
line-height: var(--wa-form-control-value-line-height);
vertical-align: middle;
transition:
background var(--wa-transition-normal) var(--wa-transition-easing),
border var(--wa-transition-normal) var(--wa-transition-easing),
outline var(--wa-transition-fast) var(--wa-transition-easing);
cursor: text;
}
textarea:not(:host *, .wa-off, .wa-off-deep *):focus:not(:disabled),
:host .textarea:has(> textarea:focus:not(:disabled)) {
outline: var(--wa-focus-ring);
outline-offset: var(--wa-focus-ring-offset);
}
textarea:not(:host *, .wa-off, .wa-off-deep *):disabled,
:host .textarea:has(> textarea:disabled) {
opacity: 0.5;
cursor: not-allowed;
}
textarea:not(.wa-off, .wa-off-deep *) {
font: inherit;
line-height: var(--wa-line-height-normal);
color: var(--wa-form-control-value-color);
padding: calc(var(--wa-space-smaller) - var(--border-width)) var(--wa-space);
&::placeholder {
color: var(--wa-form-control-placeholder-color);
user-select: none;
-webkit-user-select: none;
}
}