@vaadin/text-area 22.0.0-alpha4 → 22.0.0-alpha8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,45 +1,48 @@
1
1
  {
2
2
  "name": "@vaadin/text-area",
3
- "version": "22.0.0-alpha4",
3
+ "version": "22.0.0-alpha8",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
4
7
  "description": "vaadin-text-area",
8
+ "license": "Apache-2.0",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/vaadin/web-components.git",
12
+ "directory": "packages/text-area"
13
+ },
14
+ "author": "Vaadin Ltd",
15
+ "homepage": "https://vaadin.com/components",
16
+ "bugs": {
17
+ "url": "https://github.com/vaadin/web-components/issues"
18
+ },
5
19
  "main": "vaadin-text-area.js",
6
20
  "module": "vaadin-text-area.js",
7
- "repository": "vaadin/web-components",
21
+ "files": [
22
+ "src",
23
+ "theme",
24
+ "vaadin-*.d.ts",
25
+ "vaadin-*.js"
26
+ ],
8
27
  "keywords": [
9
28
  "Vaadin",
10
29
  "input",
11
30
  "web-components",
12
31
  "web-component"
13
32
  ],
14
- "author": "Vaadin Ltd",
15
- "license": "Apache-2.0",
16
- "bugs": {
17
- "url": "https://github.com/vaadin/web-components/issues"
18
- },
19
- "homepage": "https://vaadin.com/components",
20
- "files": [
21
- "vaadin-*.d.ts",
22
- "vaadin-*.js",
23
- "src",
24
- "theme"
25
- ],
26
33
  "dependencies": {
27
34
  "@polymer/polymer": "^3.0.0",
28
- "@vaadin/field-base": "^22.0.0-alpha4",
29
- "@vaadin/input-container": "^22.0.0-alpha4",
30
- "@vaadin/text-field": "^22.0.0-alpha4",
31
- "@vaadin/vaadin-element-mixin": "^22.0.0-alpha4",
32
- "@vaadin/vaadin-lumo-styles": "^22.0.0-alpha4",
33
- "@vaadin/vaadin-material-styles": "^22.0.0-alpha4",
34
- "@vaadin/vaadin-themable-mixin": "^22.0.0-alpha4"
35
+ "@vaadin/component-base": "22.0.0-alpha8",
36
+ "@vaadin/field-base": "22.0.0-alpha8",
37
+ "@vaadin/input-container": "22.0.0-alpha8",
38
+ "@vaadin/vaadin-lumo-styles": "22.0.0-alpha8",
39
+ "@vaadin/vaadin-material-styles": "22.0.0-alpha8",
40
+ "@vaadin/vaadin-themable-mixin": "22.0.0-alpha8"
35
41
  },
36
42
  "devDependencies": {
37
43
  "@esm-bundle/chai": "^4.3.4",
38
- "@vaadin/testing-helpers": "^0.2.1",
44
+ "@vaadin/testing-helpers": "^0.3.0",
39
45
  "sinon": "^9.2.1"
40
46
  },
41
- "publishConfig": {
42
- "access": "public"
43
- },
44
- "gitHead": "86c025abd605d5a4a3c0ae36eb07c34704cee1f2"
47
+ "gitHead": "c24468526298ee26ad7f7280b59f6c8789e1f75f"
45
48
  }
@@ -3,10 +3,8 @@
3
3
  * Copyright (c) 2021 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
7
- import { CharLengthMixin } from '@vaadin/field-base/src/char-length-mixin.js';
6
+ import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
8
7
  import { InputFieldMixin } from '@vaadin/field-base/src/input-field-mixin.js';
9
- import { TextAreaSlotMixin } from '@vaadin/field-base/src/text-area-slot-mixin.js';
10
8
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
11
9
 
12
10
  /**
@@ -50,6 +48,12 @@ export interface TextAreaEventMap extends HTMLElementEventMap, TextAreaCustomEve
50
48
  *
51
49
  * ### Styling
52
50
  *
51
+ * The following custom properties are available for styling:
52
+ *
53
+ * Custom property | Description | Default
54
+ * -------------------------------|----------------------------|---------
55
+ * `--vaadin-field-default-width` | Default width of the field | `12em`
56
+ *
53
57
  * The following shadow DOM parts are available for styling:
54
58
  *
55
59
  * Part name | Description
@@ -80,9 +84,17 @@ export interface TextAreaEventMap extends HTMLElementEventMap, TextAreaCustomEve
80
84
  * @fires {CustomEvent} invalid-changed - Fired when the `invalid` property changes.
81
85
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
82
86
  */
83
- declare class TextArea extends CharLengthMixin(
84
- InputFieldMixin(TextAreaSlotMixin(ThemableMixin(ElementMixin(HTMLElement))))
85
- ) {
87
+ declare class TextArea extends InputFieldMixin(ThemableMixin(ElementMixin(HTMLElement))) {
88
+ /**
89
+ * Maximum number of characters (in Unicode code points) that the user can enter.
90
+ */
91
+ maxlength: number | null | undefined;
92
+
93
+ /**
94
+ * Minimum number of characters (in Unicode code points) that the user can enter.
95
+ */
96
+ minlength: number | null | undefined;
97
+
86
98
  addEventListener<K extends keyof TextAreaEventMap>(
87
99
  type: K,
88
100
  listener: (this: TextArea, ev: TextAreaEventMap[K]) => void,
@@ -4,13 +4,16 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { PolymerElement, html } from '@polymer/polymer';
7
- import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
8
- import { CharLengthMixin } from '@vaadin/field-base/src/char-length-mixin.js';
7
+ import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
8
+ import { AriaLabelController } from '@vaadin/field-base/src/aria-label-controller.js';
9
9
  import { InputFieldMixin } from '@vaadin/field-base/src/input-field-mixin.js';
10
- import { TextAreaSlotMixin } from '@vaadin/field-base/src/text-area-slot-mixin.js';
10
+ import { TextAreaController } from '@vaadin/field-base/src/text-area-controller.js';
11
+ import { inputFieldShared } from '@vaadin/field-base/src/styles/input-field-shared-styles.js';
11
12
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
13
+ import { registerStyles } from '@vaadin/vaadin-themable-mixin/register-styles.js';
12
14
  import '@vaadin/input-container/src/vaadin-input-container.js';
13
- import '@vaadin/text-field/src/vaadin-input-field-shared-styles.js';
15
+
16
+ registerStyles('vaadin-text-area', inputFieldShared, { moduleId: 'vaadin-text-area-styles' });
14
17
 
15
18
  /**
16
19
  * `<vaadin-text-area>` is a web component for multi-line text input.
@@ -35,6 +38,12 @@ import '@vaadin/text-field/src/vaadin-input-field-shared-styles.js';
35
38
  *
36
39
  * ### Styling
37
40
  *
41
+ * The following custom properties are available for styling:
42
+ *
43
+ * Custom property | Description | Default
44
+ * -------------------------------|----------------------------|---------
45
+ * `--vaadin-field-default-width` | Default width of the field | `12em`
46
+ *
38
47
  * The following shadow DOM parts are available for styling:
39
48
  *
40
49
  * Part name | Description
@@ -66,27 +75,23 @@ import '@vaadin/text-field/src/vaadin-input-field-shared-styles.js';
66
75
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
67
76
  *
68
77
  * @extends HTMLElement
69
- * @mixes CharLengthMixin
70
78
  * @mixes InputFieldMixin
71
- * @mixes TextAreaSlotMixin
72
79
  * @mixes ElementMixin
73
80
  * @mixes ThemableMixin
74
81
  */
75
- export class TextArea extends CharLengthMixin(
76
- InputFieldMixin(TextAreaSlotMixin(ThemableMixin(ElementMixin(PolymerElement))))
77
- ) {
82
+ export class TextArea extends InputFieldMixin(ThemableMixin(ElementMixin(PolymerElement))) {
78
83
  static get is() {
79
84
  return 'vaadin-text-area';
80
85
  }
81
86
 
82
87
  static get template() {
83
88
  return html`
84
- <style include="vaadin-input-field-shared-styles">
89
+ <style>
85
90
  :host {
86
91
  animation: 1ms vaadin-text-area-appear;
87
92
  }
88
93
 
89
- [part='container'] {
94
+ .vaadin-text-area-container {
90
95
  flex: auto;
91
96
  }
92
97
 
@@ -98,35 +103,22 @@ export class TextArea extends CharLengthMixin(
98
103
  }
99
104
 
100
105
  [part='input-field'] {
106
+ flex: auto;
101
107
  overflow: auto;
102
108
  -webkit-overflow-scrolling: touch;
103
109
  }
104
110
 
105
- .textarea-wrapper {
106
- display: grid;
107
- flex: 1 1 auto;
108
- align-self: stretch;
109
- padding: 0;
110
- }
111
-
112
- .textarea-wrapper::after {
113
- content: attr(data-replicated-value) ' ';
114
- white-space: pre-wrap;
115
- visibility: hidden;
116
- }
117
-
118
111
  ::slotted(textarea) {
119
112
  -webkit-appearance: none;
120
113
  -moz-appearance: none;
121
114
  flex: auto;
122
- white-space: nowrap;
123
115
  overflow: hidden;
124
116
  width: 100%;
125
117
  height: 100%;
126
118
  outline: none;
127
119
  resize: none;
128
120
  margin: 0;
129
- padding: 0;
121
+ padding: 0 0.25em;
130
122
  border: 0;
131
123
  border-radius: 0;
132
124
  min-width: 0;
@@ -139,16 +131,6 @@ export class TextArea extends CharLengthMixin(
139
131
  box-shadow: none;
140
132
  }
141
133
 
142
- ::slotted(textarea),
143
- .textarea-wrapper::after {
144
- grid-area: 1 / 1 / 2 / 2;
145
- box-sizing: border-box;
146
- padding: 0 0.25em;
147
- overflow-wrap: break-word;
148
- word-wrap: break-word;
149
- word-break: break-word;
150
- }
151
-
152
134
  [part='input-field'] ::slotted(*) {
153
135
  align-self: flex-start;
154
136
  }
@@ -160,10 +142,10 @@ export class TextArea extends CharLengthMixin(
160
142
  }
161
143
  </style>
162
144
 
163
- <div part="container">
145
+ <div class="vaadin-text-area-container">
164
146
  <div part="label">
165
147
  <slot name="label"></slot>
166
- <span part="indicator" aria-hidden="true"></span>
148
+ <span part="required-indicator" aria-hidden="true"></span>
167
149
  </div>
168
150
 
169
151
  <vaadin-input-container
@@ -174,9 +156,7 @@ export class TextArea extends CharLengthMixin(
174
156
  theme$="[[theme]]"
175
157
  >
176
158
  <slot name="prefix" slot="prefix"></slot>
177
- <div class="textarea-wrapper">
178
- <slot name="textarea"></slot>
179
- </div>
159
+ <slot name="textarea"></slot>
180
160
  <slot name="suffix" slot="suffix"></slot>
181
161
  <div id="clearButton" part="clear-button" slot="suffix"></div>
182
162
  </vaadin-input-container>
@@ -192,6 +172,32 @@ export class TextArea extends CharLengthMixin(
192
172
  `;
193
173
  }
194
174
 
175
+ static get properties() {
176
+ return {
177
+ /**
178
+ * Maximum number of characters (in Unicode code points) that the user can enter.
179
+ */
180
+ maxlength: {
181
+ type: Number
182
+ },
183
+
184
+ /**
185
+ * Minimum number of characters (in Unicode code points) that the user can enter.
186
+ */
187
+ minlength: {
188
+ type: Number
189
+ }
190
+ };
191
+ }
192
+
193
+ static get delegateAttrs() {
194
+ return [...super.delegateAttrs, 'maxlength', 'minlength'];
195
+ }
196
+
197
+ static get constraints() {
198
+ return [...super.constraints, 'maxlength', 'minlength'];
199
+ }
200
+
195
201
  /**
196
202
  * Used by `ClearButtonMixin` as a reference to the clear button element.
197
203
  * @protected
@@ -204,12 +210,23 @@ export class TextArea extends CharLengthMixin(
204
210
  connectedCallback() {
205
211
  super.connectedCallback();
206
212
 
213
+ this._inputField = this.shadowRoot.querySelector('[part=input-field]');
207
214
  this._updateHeight();
208
215
  }
209
216
 
210
217
  /** @protected */
211
218
  ready() {
212
219
  super.ready();
220
+
221
+ this.addController(
222
+ new TextAreaController(this, (input) => {
223
+ this._setInputElement(input);
224
+ this._setFocusElement(input);
225
+ this.stateTarget = input;
226
+ this.ariaTarget = input;
227
+ })
228
+ );
229
+ this.addController(new AriaLabelController(this.inputElement, this._labelNode));
213
230
  this.addEventListener('animationend', this._onAnimationEnd);
214
231
  }
215
232
 
@@ -234,13 +251,49 @@ export class TextArea extends CharLengthMixin(
234
251
 
235
252
  /** @private */
236
253
  _updateHeight() {
237
- if (this.inputElement) {
238
- /* https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/ */
239
- this.__textAreaWrapper = this.__textAreaWrapper || this.shadowRoot.querySelector('.textarea-wrapper');
240
- this.__textAreaWrapper.dataset.replicatedValue = this.inputElement.value;
241
- // getComputedStyle is expensive, maybe we can use ResizeObserver in the future
242
- this._dispatchIronResizeEventIfNeeded('InputHeight', getComputedStyle(this.__textAreaWrapper).height);
254
+ const input = this.inputElement;
255
+ const inputField = this._inputField;
256
+
257
+ if (!input || !inputField) {
258
+ return;
259
+ }
260
+
261
+ const scrollTop = inputField.scrollTop;
262
+
263
+ // Only clear the height when the content shortens to minimize scrollbar flickering.
264
+ const valueLength = this.value ? this.value.length : 0;
265
+
266
+ if (this._oldValueLength >= valueLength) {
267
+ const inputFieldHeight = getComputedStyle(inputField).height;
268
+ const inputWidth = getComputedStyle(input).width;
269
+
270
+ // Temporarily fix the height of the wrapping input field container to prevent changing the browsers scroll
271
+ // position while resetting the textareas height. If the textarea had a large height, then removing its height
272
+ // will reset its height to the default of two rows. That might reduce the height of the page, and the
273
+ // browser might adjust the scroll position before we can restore the measured height of the textarea.
274
+ inputField.style.display = 'block';
275
+ inputField.style.height = inputFieldHeight;
276
+
277
+ // Fix the input element width so its scroll height isn't affected by host's disappearing scrollbars
278
+ input.style.maxWidth = inputWidth;
279
+
280
+ // Clear the height of the textarea to allow measuring a reduced scroll height
281
+ input.style.height = 'auto';
243
282
  }
283
+ this._oldValueLength = valueLength;
284
+
285
+ const inputHeight = input.scrollHeight;
286
+ if (inputHeight > input.clientHeight) {
287
+ input.style.height = inputHeight + 'px';
288
+ }
289
+
290
+ // Restore
291
+ input.style.removeProperty('max-width');
292
+ inputField.style.removeProperty('display');
293
+ inputField.style.removeProperty('height');
294
+ inputField.scrollTop = scrollTop;
295
+
296
+ this._dispatchIronResizeEventIfNeeded('InputHeight', inputHeight);
244
297
  }
245
298
  }
246
299
 
@@ -7,63 +7,63 @@ import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styl
7
7
  import '@vaadin/vaadin-lumo-styles/color.js';
8
8
  import '@vaadin/vaadin-lumo-styles/sizing.js';
9
9
  import '@vaadin/vaadin-lumo-styles/typography.js';
10
- import '@vaadin/text-field/theme/lumo/vaadin-input-field-shared-styles.js';
10
+ import { inputFieldShared } from '@vaadin/vaadin-lumo-styles/mixins/input-field-shared.js';
11
11
 
12
- registerStyles(
13
- 'vaadin-text-area',
14
- css`
15
- [part='input-field'],
16
- [part='input-field'] ::slotted(textarea) {
17
- height: auto;
18
- box-sizing: border-box;
19
- }
12
+ const textArea = css`
13
+ [part='input-field'],
14
+ [part='input-field'] ::slotted(textarea) {
15
+ height: auto;
16
+ box-sizing: border-box;
17
+ }
20
18
 
21
- [part='input-field'] {
22
- /* Equal to the implicit padding in vaadin-text-field */
23
- padding-top: calc((var(--lumo-text-field-size) - 1em * var(--lumo-line-height-s)) / 2);
24
- padding-bottom: calc((var(--lumo-text-field-size) - 1em * var(--lumo-line-height-s)) / 2);
25
- transition: background-color 0.1s;
26
- line-height: var(--lumo-line-height-s);
27
- }
19
+ [part='input-field'] {
20
+ /* Equal to the implicit padding in vaadin-text-field */
21
+ padding-top: calc((var(--lumo-text-field-size) - 1em * var(--lumo-line-height-s)) / 2);
22
+ padding-bottom: calc((var(--lumo-text-field-size) - 1em * var(--lumo-line-height-s)) / 2);
23
+ transition: background-color 0.1s;
24
+ line-height: var(--lumo-line-height-s);
25
+ }
28
26
 
29
- :host(:not([readonly])) [part='input-field']::after {
30
- display: none;
31
- }
27
+ :host(:not([readonly])) [part='input-field']::after {
28
+ display: none;
29
+ }
32
30
 
33
- :host([readonly]) [part='input-field'] {
34
- border: 1px dashed var(--lumo-contrast-30pct);
35
- }
31
+ :host([readonly]) [part='input-field'] {
32
+ border: 1px dashed var(--lumo-contrast-30pct);
33
+ }
36
34
 
37
- :host([readonly]) [part='input-field']::after {
38
- border: none;
39
- }
35
+ :host([readonly]) [part='input-field']::after {
36
+ border: none;
37
+ }
38
+
39
+ :host(:hover:not([readonly]):not([focused]):not([invalid])) [part='input-field'] {
40
+ background-color: var(--lumo-contrast-20pct);
41
+ }
40
42
 
43
+ @media (pointer: coarse) {
41
44
  :host(:hover:not([readonly]):not([focused]):not([invalid])) [part='input-field'] {
42
- background-color: var(--lumo-contrast-20pct);
45
+ background-color: var(--lumo-contrast-10pct);
43
46
  }
44
47
 
45
- @media (pointer: coarse) {
46
- :host(:hover:not([readonly]):not([focused]):not([invalid])) [part='input-field'] {
47
- background-color: var(--lumo-contrast-10pct);
48
- }
49
-
50
- :host(:active:not([readonly]):not([focused])) [part='input-field'] {
51
- background-color: var(--lumo-contrast-20pct);
52
- }
48
+ :host(:active:not([readonly]):not([focused])) [part='input-field'] {
49
+ background-color: var(--lumo-contrast-20pct);
53
50
  }
51
+ }
54
52
 
55
- [part='input-field'] ::slotted(textarea) {
56
- white-space: pre-wrap; /* override "nowrap" from <vaadin-text-field> */
57
- align-self: stretch; /* override "baseline" from <vaadin-text-field> */
58
- line-height: inherit;
59
- --_lumo-text-field-overflow-mask-image: none;
60
- }
53
+ [part='input-field'] ::slotted(textarea) {
54
+ white-space: pre-wrap; /* override "nowrap" from <vaadin-text-field> */
55
+ align-self: stretch; /* override "baseline" from <vaadin-text-field> */
56
+ line-height: inherit;
57
+ --_lumo-text-field-overflow-mask-image: none;
58
+ }
61
59
 
62
- /* Vertically align icon prefix/suffix with the first line of text */
63
- [part='input-field'] ::slotted(icon-icon),
64
- [part='input-field'] ::slotted(vaadin-icon) {
65
- margin-top: calc((var(--lumo-icon-size-m) - 1em * var(--lumo-line-height-s)) / -2);
66
- }
67
- `,
68
- { moduleId: 'lumo-text-area', include: ['lumo-input-field-shared-styles'] }
69
- );
60
+ /* Vertically align icon prefix/suffix with the first line of text */
61
+ [part='input-field'] ::slotted(iron-icon),
62
+ [part='input-field'] ::slotted(vaadin-icon) {
63
+ margin-top: calc((var(--lumo-icon-size-m) - 1em * var(--lumo-line-height-s)) / -2);
64
+ }
65
+ `;
66
+
67
+ registerStyles('vaadin-text-area', [inputFieldShared, textArea], {
68
+ moduleId: 'lumo-text-area'
69
+ });
@@ -4,30 +4,23 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js';
7
- import '@vaadin/text-field/theme/material/vaadin-input-field-shared-styles.js';
7
+ import { inputFieldShared } from '@vaadin/vaadin-material-styles/mixins/input-field-shared.js';
8
8
 
9
- registerStyles(
10
- 'vaadin-text-area',
11
- css`
12
- [part='input-field'] {
13
- height: auto;
14
- box-sizing: border-box;
15
- }
9
+ const textArea = css`
10
+ [part='input-field'] {
11
+ height: auto;
12
+ box-sizing: border-box;
13
+ }
16
14
 
17
- .textarea-wrapper {
18
- margin-top: 4px;
19
- padding: 0;
20
- }
15
+ [part='input-field'] ::slotted(textarea) {
16
+ padding-top: 0;
17
+ margin-top: 4px;
18
+ }
21
19
 
22
- [part='input-field'] ::slotted(textarea),
23
- .textarea-wrapper::after {
24
- padding: 0 0 8px;
25
- }
20
+ [part='input-field'] ::slotted(textarea) {
21
+ white-space: pre-wrap; /* override "nowrap" from <vaadin-text-field> */
22
+ align-self: stretch; /* override "baseline" from <vaadin-text-field> */
23
+ }
24
+ `;
26
25
 
27
- [part='input-field'] ::slotted(textarea) {
28
- white-space: pre-wrap; /* override "nowrap" from <vaadin-text-field> */
29
- align-self: stretch; /* override "baseline" from <vaadin-text-field> */
30
- }
31
- `,
32
- { moduleId: 'material-text-area', include: ['material-input-field-shared-styles'] }
33
- );
26
+ registerStyles('vaadin-text-area', [inputFieldShared, textArea], { moduleId: 'material-text-area' });