@justeattakeaway/pie-textarea 0.7.0 → 0.9.0

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.
@@ -28,13 +28,21 @@
28
28
  },
29
29
  "default": "['auto', 'manual']"
30
30
  },
31
+ {
32
+ "kind": "variable",
33
+ "name": "statusTypes",
34
+ "type": {
35
+ "text": "['default', 'success', 'error']"
36
+ },
37
+ "default": "['default', 'success', 'error']"
38
+ },
31
39
  {
32
40
  "kind": "variable",
33
41
  "name": "defaultProps",
34
42
  "type": {
35
43
  "text": "DefaultProps"
36
44
  },
37
- "default": "{\n disabled: false,\n size: 'medium',\n resize: 'auto',\n label: '',\n value: '',\n autoFocus: false,\n readonly: false,\n required: false,\n}",
45
+ "default": "{\n disabled: false,\n size: 'medium',\n resize: 'auto',\n label: '',\n value: '',\n placeholder: '',\n status: 'default',\n autoFocus: false,\n readonly: false,\n required: false,\n}",
38
46
  "description": "Default values for optional properties that have default fallback values in the component."
39
47
  }
40
48
  ],
@@ -55,6 +63,14 @@
55
63
  "module": "src/defs.js"
56
64
  }
57
65
  },
66
+ {
67
+ "kind": "js",
68
+ "name": "statusTypes",
69
+ "declaration": {
70
+ "name": "statusTypes",
71
+ "module": "src/defs.js"
72
+ }
73
+ },
58
74
  {
59
75
  "kind": "js",
60
76
  "name": "defaultProps",
@@ -150,11 +166,26 @@
150
166
  "privacy": "public",
151
167
  "attribute": "required"
152
168
  },
169
+ {
170
+ "kind": "field",
171
+ "name": "status",
172
+ "privacy": "public",
173
+ "attribute": "status"
174
+ },
175
+ {
176
+ "kind": "field",
177
+ "name": "assistiveText",
178
+ "type": {
179
+ "text": "TextareaProps['assistiveText']"
180
+ },
181
+ "privacy": "public",
182
+ "attribute": "assistiveText"
183
+ },
153
184
  {
154
185
  "kind": "field",
155
186
  "name": "name",
156
187
  "type": {
157
- "text": "TextareaProps['name'] | undefined"
188
+ "text": "TextareaProps['name']"
158
189
  },
159
190
  "privacy": "public",
160
191
  "attribute": "name"
@@ -163,11 +194,20 @@
163
194
  "kind": "field",
164
195
  "name": "autocomplete",
165
196
  "type": {
166
- "text": "TextareaProps['autocomplete'] | undefined"
197
+ "text": "TextareaProps['autocomplete']"
167
198
  },
168
199
  "privacy": "public",
169
200
  "attribute": "autocomplete"
170
201
  },
202
+ {
203
+ "kind": "field",
204
+ "name": "placeholder",
205
+ "type": {
206
+ "text": "TextareaProps['placeholder']"
207
+ },
208
+ "privacy": "public",
209
+ "attribute": "placeholder"
210
+ },
171
211
  {
172
212
  "kind": "field",
173
213
  "name": "_textarea",
@@ -176,6 +216,14 @@
176
216
  },
177
217
  "privacy": "private"
178
218
  },
219
+ {
220
+ "kind": "field",
221
+ "name": "focusTarget",
222
+ "type": {
223
+ "text": "HTMLElement"
224
+ },
225
+ "privacy": "public"
226
+ },
179
227
  {
180
228
  "kind": "field",
181
229
  "name": "_throttledResize",
@@ -272,6 +320,10 @@
272
320
  }
273
321
  }
274
322
  ]
323
+ },
324
+ {
325
+ "kind": "method",
326
+ "name": "renderAssistiveText"
275
327
  }
276
328
  ],
277
329
  "events": [
@@ -337,19 +389,37 @@
337
389
  "name": "required",
338
390
  "fieldName": "required"
339
391
  },
392
+ {
393
+ "name": "status",
394
+ "fieldName": "status"
395
+ },
396
+ {
397
+ "name": "assistiveText",
398
+ "type": {
399
+ "text": "TextareaProps['assistiveText']"
400
+ },
401
+ "fieldName": "assistiveText"
402
+ },
340
403
  {
341
404
  "name": "name",
342
405
  "type": {
343
- "text": "TextareaProps['name'] | undefined"
406
+ "text": "TextareaProps['name']"
344
407
  },
345
408
  "fieldName": "name"
346
409
  },
347
410
  {
348
411
  "name": "autocomplete",
349
412
  "type": {
350
- "text": "TextareaProps['autocomplete'] | undefined"
413
+ "text": "TextareaProps['autocomplete']"
351
414
  },
352
415
  "fieldName": "autocomplete"
416
+ },
417
+ {
418
+ "name": "placeholder",
419
+ "type": {
420
+ "text": "TextareaProps['placeholder']"
421
+ },
422
+ "fieldName": "placeholder"
353
423
  }
354
424
  ],
355
425
  "mixins": [
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { FormControlInterface } from '@justeattakeaway/pie-webc-core';
4
4
  import type { GenericConstructor } from '@justeattakeaway/pie-webc-core';
5
5
  import type { LitElement } from 'lit';
6
6
  import type { nothing } from 'lit';
7
+ import type { PIEInputElement } from '@justeattakeaway/pie-webc-core';
7
8
  import type { PropertyValues } from 'lit';
8
9
  import type { RTLInterface } from '@justeattakeaway/pie-webc-core';
9
10
  import type { TemplateResult } from 'lit-html';
@@ -11,7 +12,7 @@ import type { TemplateResult } from 'lit-html';
11
12
  /**
12
13
  * The default values for the `TextareaProps` that are required (i.e. they have a fallback value in the component).
13
14
  */
14
- declare type DefaultProps = ComponentDefaultProps<TextareaProps, keyof Omit<TextareaProps, 'name' | 'autocomplete' | 'maxLength' | 'defaultValue'>>;
15
+ declare type DefaultProps = ComponentDefaultProps<TextareaProps, keyof Omit<TextareaProps, 'name' | 'autocomplete' | 'maxLength' | 'assistiveText' | 'defaultValue'>>;
15
16
 
16
17
  /**
17
18
  * Default values for optional properties that have default fallback values in the component.
@@ -23,7 +24,7 @@ export declare const defaultProps: DefaultProps;
23
24
  * @event {InputEvent} input - when the textarea value is changed.
24
25
  * @event {CustomEvent} change - when the textarea value is changed.
25
26
  */
26
- export declare class PieTextarea extends PieTextarea_base implements TextareaProps {
27
+ export declare class PieTextarea extends PieTextarea_base implements TextareaProps, PIEInputElement {
27
28
  static shadowRootOptions: {
28
29
  delegatesFocus: boolean;
29
30
  mode: ShadowRootMode;
@@ -39,9 +40,13 @@ export declare class PieTextarea extends PieTextarea_base implements TextareaPro
39
40
  readonly: boolean;
40
41
  autoFocus: boolean;
41
42
  required: boolean;
42
- name?: TextareaProps['name'];
43
- autocomplete?: TextareaProps['autocomplete'];
43
+ status: "default" | "error" | "success";
44
+ assistiveText: TextareaProps['assistiveText'];
45
+ name: TextareaProps['name'];
46
+ autocomplete: TextareaProps['autocomplete'];
47
+ placeholder: TextareaProps['placeholder'];
44
48
  private _textarea;
49
+ focusTarget: HTMLElement;
45
50
  private _throttledResize;
46
51
  /**
47
52
  * (Read-only) returns a ValidityState with the validity states that this element is in.
@@ -73,6 +78,7 @@ export declare class PieTextarea extends PieTextarea_base implements TextareaPro
73
78
  private handleKeyDown;
74
79
  disconnectedCallback(): void;
75
80
  renderLabel(label: string, maxLength?: number): TemplateResult<1> | typeof nothing;
81
+ renderAssistiveText(): TemplateResult<1> | typeof nothing;
76
82
  render(): TemplateResult<1>;
77
83
  static styles: CSSResult;
78
84
  }
@@ -83,6 +89,8 @@ export declare const resizeModes: readonly ["auto", "manual"];
83
89
 
84
90
  export declare const sizes: readonly ["small", "medium", "large"];
85
91
 
92
+ export declare const statusTypes: readonly ["default", "success", "error"];
93
+
86
94
  export declare interface TextareaProps {
87
95
  /**
88
96
  * Same as the HTML disabled attribute - indicates whether the textarea is disabled.
@@ -106,6 +114,14 @@ export declare interface TextareaProps {
106
114
  * An optional default value to use when the textarea is reset.
107
115
  */
108
116
  defaultValue?: string;
117
+ /**
118
+ * An optional assistive text to display below the textarea element. Must be provided when the status is success or error.
119
+ */
120
+ assistiveText?: string;
121
+ /**
122
+ * The status of the textarea component / assistive text. Can be default, success or error.
123
+ */
124
+ status?: typeof statusTypes[number];
109
125
  /**
110
126
  * The name of the textarea (used as a key/value pair with `value`). This is required in order to work properly with forms.
111
127
  */
@@ -139,6 +155,10 @@ export declare interface TextareaProps {
139
155
  * If the `label` property is not set, this property will have no effect.
140
156
  */
141
157
  maxLength?: number;
158
+ /**
159
+ * The placeholder text to display when the textarea is empty.
160
+ */
161
+ placeholder?: string;
142
162
  }
143
163
 
144
164
  export { }
package/dist/index.js CHANGED
@@ -1,124 +1,128 @@
1
- import { LitElement as w, html as I, nothing as q, unsafeCSS as B } from "lit";
2
- import { property as c, query as N } from "lit/decorators.js";
3
- import { live as A } from "lit/directives/live.js";
4
- import { FormControlMixin as K, RtlMixin as G, wrapNativeEvent as H, validPropertyValues as F, defineCustomElement as U } from "@justeattakeaway/pie-webc-core";
5
- import { ifDefined as O } from "lit/directives/if-defined.js";
1
+ import { LitElement as P, html as W, nothing as R, unsafeCSS as A } from "lit";
2
+ import { property as l, query as V } from "lit/decorators.js";
3
+ import { classMap as K } from "lit/directives/class-map.js";
4
+ import { ifDefined as b } from "lit/directives/if-defined.js";
5
+ import { live as G } from "lit/directives/live.js";
6
+ import "@justeattakeaway/pie-assistive-text";
6
7
  import "@justeattakeaway/pie-form-label";
7
- var z = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {};
8
- function X(t) {
8
+ import { FormControlMixin as H, RtlMixin as U, wrapNativeEvent as X, validPropertyValues as O, defineCustomElement as J } from "@justeattakeaway/pie-webc-core";
9
+ var T = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {};
10
+ function Q(t) {
9
11
  return t && t.__esModule && Object.prototype.hasOwnProperty.call(t, "default") ? t.default : t;
10
12
  }
11
- var R = "Expected a function", C = 0 / 0, J = "[object Symbol]", Q = /^\s+|\s+$/g, Y = /^[-+]0x[0-9a-f]+$/i, Z = /^0b[01]+$/i, ee = /^0o[0-7]+$/i, te = parseInt, ae = typeof z == "object" && z && z.Object === Object && z, re = typeof self == "object" && self && self.Object === Object && self, ie = ae || re || Function("return this")(), ne = Object.prototype, oe = ne.toString, se = Math.max, le = Math.min, S = function() {
12
- return ie.Date.now();
13
+ var q = "Expected a function", D = 0 / 0, Y = "[object Symbol]", Z = /^\s+|\s+$/g, ee = /^[-+]0x[0-9a-f]+$/i, te = /^0b[01]+$/i, ae = /^0o[0-7]+$/i, re = parseInt, ie = typeof T == "object" && T && T.Object === Object && T, oe = typeof self == "object" && self && self.Object === Object && self, ne = ie || oe || Function("return this")(), se = Object.prototype, le = se.toString, de = Math.max, ce = Math.min, j = function() {
14
+ return ne.Date.now();
13
15
  };
14
- function de(t, e, a) {
15
- var r, i, u, d, s, p, g = 0, m = !1, v = !1, _ = !0;
16
+ function ue(t, e, a) {
17
+ var i, o, p, u, d, h, f = 0, k = !1, v = !1, y = !0;
16
18
  if (typeof t != "function")
17
- throw new TypeError(R);
18
- e = W(e) || 0, k(a) && (m = !!a.leading, v = "maxWait" in a, u = v ? se(W(a.maxWait) || 0, e) : u, _ = "trailing" in a ? !!a.trailing : _);
19
- function $(n) {
20
- var f = r, b = i;
21
- return r = i = void 0, g = n, d = t.apply(b, f), d;
19
+ throw new TypeError(q);
20
+ e = M(e) || 0, _(a) && (k = !!a.leading, v = "maxWait" in a, p = v ? de(M(a.maxWait) || 0, e) : p, y = "trailing" in a ? !!a.trailing : y);
21
+ function g(n) {
22
+ var x = i, z = o;
23
+ return i = o = void 0, f = n, u = t.apply(z, x), u;
22
24
  }
23
- function D(n) {
24
- return g = n, s = setTimeout(y, e), m ? $(n) : d;
25
+ function S(n) {
26
+ return f = n, d = setTimeout($, e), k ? g(n) : u;
25
27
  }
26
- function P(n) {
27
- var f = n - p, b = n - g, E = e - f;
28
- return v ? le(E, u - b) : E;
28
+ function w(n) {
29
+ var x = n - h, z = n - f, F = e - x;
30
+ return v ? ce(F, p - z) : F;
29
31
  }
30
- function L(n) {
31
- var f = n - p, b = n - g;
32
- return p === void 0 || f >= e || f < 0 || v && b >= u;
32
+ function I(n) {
33
+ var x = n - h, z = n - f;
34
+ return h === void 0 || x >= e || x < 0 || v && z >= p;
33
35
  }
34
- function y() {
35
- var n = S();
36
- if (L(n))
37
- return T(n);
38
- s = setTimeout(y, P(n));
36
+ function $() {
37
+ var n = j();
38
+ if (I(n))
39
+ return C(n);
40
+ d = setTimeout($, w(n));
39
41
  }
40
- function T(n) {
41
- return s = void 0, _ && r ? $(n) : (r = i = void 0, d);
42
+ function C(n) {
43
+ return d = void 0, y && i ? g(n) : (i = o = void 0, u);
42
44
  }
43
- function M() {
44
- s !== void 0 && clearTimeout(s), g = 0, r = p = i = s = void 0;
45
+ function B() {
46
+ d !== void 0 && clearTimeout(d), f = 0, i = h = o = d = void 0;
45
47
  }
46
- function V() {
47
- return s === void 0 ? d : T(S());
48
+ function N() {
49
+ return d === void 0 ? u : C(j());
48
50
  }
49
- function j() {
50
- var n = S(), f = L(n);
51
- if (r = arguments, i = this, p = n, f) {
52
- if (s === void 0)
53
- return D(p);
51
+ function L() {
52
+ var n = j(), x = I(n);
53
+ if (i = arguments, o = this, h = n, x) {
54
+ if (d === void 0)
55
+ return S(h);
54
56
  if (v)
55
- return s = setTimeout(y, e), $(p);
57
+ return d = setTimeout($, e), g(h);
56
58
  }
57
- return s === void 0 && (s = setTimeout(y, e)), d;
59
+ return d === void 0 && (d = setTimeout($, e)), u;
58
60
  }
59
- return j.cancel = M, j.flush = V, j;
61
+ return L.cancel = B, L.flush = N, L;
60
62
  }
61
- function ce(t, e, a) {
62
- var r = !0, i = !0;
63
+ function pe(t, e, a) {
64
+ var i = !0, o = !0;
63
65
  if (typeof t != "function")
64
- throw new TypeError(R);
65
- return k(a) && (r = "leading" in a ? !!a.leading : r, i = "trailing" in a ? !!a.trailing : i), de(t, e, {
66
- leading: r,
66
+ throw new TypeError(q);
67
+ return _(a) && (i = "leading" in a ? !!a.leading : i, o = "trailing" in a ? !!a.trailing : o), ue(t, e, {
68
+ leading: i,
67
69
  maxWait: e,
68
- trailing: i
70
+ trailing: o
69
71
  });
70
72
  }
71
- function k(t) {
73
+ function _(t) {
72
74
  var e = typeof t;
73
75
  return !!t && (e == "object" || e == "function");
74
76
  }
75
77
  function he(t) {
76
78
  return !!t && typeof t == "object";
77
79
  }
78
- function ue(t) {
79
- return typeof t == "symbol" || he(t) && oe.call(t) == J;
80
+ function xe(t) {
81
+ return typeof t == "symbol" || he(t) && le.call(t) == Y;
80
82
  }
81
- function W(t) {
83
+ function M(t) {
82
84
  if (typeof t == "number")
83
85
  return t;
84
- if (ue(t))
85
- return C;
86
- if (k(t)) {
86
+ if (xe(t))
87
+ return D;
88
+ if (_(t)) {
87
89
  var e = typeof t.valueOf == "function" ? t.valueOf() : t;
88
- t = k(e) ? e + "" : e;
90
+ t = _(e) ? e + "" : e;
89
91
  }
90
92
  if (typeof t != "string")
91
93
  return t === 0 ? t : +t;
92
- t = t.replace(Q, "");
93
- var a = Z.test(t);
94
- return a || ee.test(t) ? te(t.slice(2), a ? 2 : 8) : Y.test(t) ? C : +t;
94
+ t = t.replace(Z, "");
95
+ var a = te.test(t);
96
+ return a || ae.test(t) ? re(t.slice(2), a ? 2 : 8) : ee.test(t) ? D : +t;
95
97
  }
96
- var pe = ce;
97
- const fe = /* @__PURE__ */ X(pe), ge = `*,*:after,*:before{box-sizing:inherit}.c-textareaWrapper{--textarea-line-height: calc(var(--dt-font-body-l-line-height) * 1px);--textarea-border-thickness: 1px;--textarea-resize: none;--textarea-padding-inline: var(--dt-spacing-d);--textarea-padding-block: var(--dt-spacing-c);--textarea-background-color: var(--dt-color-container-default);--textarea-border-color: var(--dt-color-interactive-form);--textarea-content-color: var(--dt-color-content-default);--textarea-height: calc((var(--textarea-line-height) * 2) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));line-height:0}.c-textareaWrapper textarea{font-size:calc(var(--dt-font-body-l-size) * 1px);line-height:var(--textarea-line-height);font-family:var(--dt-font-body-l-family);resize:var(--textarea-resize);border:var(--textarea-border-thickness) solid var(--textarea-border-color);background-color:var(--textarea-background-color);color:var(--textarea-content-color);border-radius:var(--dt-radius-rounded-c);block-size:var(--textarea-height);max-block-size:var(--textarea-max-height);min-block-size:var(--textarea-min-height);padding-block-start:var(--textarea-padding-block);padding-block-end:var(--textarea-padding-block);padding-inline-start:var(--textarea-padding-inline);padding-inline-end:var(--textarea-padding-inline)}.c-textareaWrapper textarea[disabled]{--textarea-background-color: var(--dt-color-disabled-01);--textarea-border-color: var(--dt-color-disabled-01);--textarea-content-color: var(--dt-color-content-disabled)}@media (hover: hover){.c-textareaWrapper textarea:hover:not([disabled]){--textarea-background-color: hsl(var(--dt-color-container-default-h), var(--dt-color-container-default-s), calc(var(--dt-color-container-default-l) + calc(-1 * var(--dt-color-hover-01))))}}.c-textareaWrapper textarea:focus-visible{box-shadow:0 0 0 2px var(--dt-color-focus-inner),0 0 0 4px var(--dt-color-focus-outer);outline:none}.c-textareaWrapper[data-pie-size=large]{--textarea-padding-block: var(--dt-spacing-d)}.c-textareaWrapper[data-pie-size=small]{--textarea-padding-block: var(--dt-spacing-b)}.c-textareaWrapper[data-pie-resize=manual]{--textarea-resize: vertical;--textarea-min-height: calc((var(--textarea-line-height) * 1) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2))}@media (pointer: coarse){.c-textareaWrapper[data-pie-resize=manual]{--textarea-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));--textarea-min-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));--textarea-max-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));--textarea-resize: none}}.c-textareaWrapper[data-pie-resize=auto]{--textarea-max-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));--textarea-min-height: var(--textarea-height)}
98
- `, ve = ["small", "medium", "large"], xe = ["auto", "manual"], h = {
98
+ var fe = pe;
99
+ const ve = /* @__PURE__ */ Q(fe), ge = `*,*:after,*:before{box-sizing:inherit}.c-textareaWrapper{--textarea-line-height: calc(var(--dt-font-body-l-line-height) * 1px);--textarea-border-thickness: 1px;--textarea-resize: none;--textarea-padding-inline: var(--dt-spacing-c);--textarea-padding-block: var(--dt-spacing-b);--textarea-background-color: var(--dt-color-container-default);--textarea-border-color: var(--dt-color-interactive-form);--textarea-content-color: var(--dt-color-content-default);--textarea-placeholder-color: var(--dt-color-content-subdued);--textarea-height: calc((var(--textarea-line-height) * 2) + (var(--textarea-padding-block) * 2));line-height:0;padding:var(--dt-spacing-a);border:var(--textarea-border-thickness) solid var(--textarea-border-color);background-color:var(--textarea-background-color);border-radius:var(--dt-radius-rounded-c);inline-size:100%}.c-textareaWrapper textarea{font-size:calc(var(--dt-font-body-l-size) * 1px);line-height:var(--textarea-line-height);font-family:var(--dt-font-body-l-family);resize:var(--textarea-resize);border:none;background-color:var(--textarea-background-color);color:var(--textarea-content-color);block-size:var(--textarea-height);max-block-size:var(--textarea-max-height);min-block-size:var(--textarea-min-height);inline-size:100%;padding-block-start:var(--textarea-padding-block);padding-block-end:var(--textarea-padding-block);padding-inline-start:var(--textarea-padding-inline);padding-inline-end:var(--textarea-padding-inline)}.c-textareaWrapper textarea:focus{box-shadow:none;outline:none}.c-textareaWrapper textarea::placeholder{color:var(--textarea-placeholder-color);opacity:1}.c-textareaWrapper.c-textarea--readonly{--textarea-background-color: var(--dt-color-container-subtle);--textarea-border-color: var(--dt-color-interactive-form)}.c-textareaWrapper.c-textarea--disabled{--textarea-background-color: var(--dt-color-disabled-01);--textarea-border-color: var(--dt-color-disabled-01);--textarea-content-color: var(--dt-color-content-disabled);--textarea-placeholder-color: var(--dt-color-content-disabled)}@media (hover: hover){.c-textareaWrapper:hover:not(.c-textarea--disabled,.c-textarea--readonly){--textarea-background-color: hsl(var(--dt-color-container-default-h), var(--dt-color-container-default-s), calc(var(--dt-color-container-default-l) + calc(-1 * var(--dt-color-hover-01))))}}.c-textareaWrapper:focus-within{box-shadow:0 0 0 2px var(--dt-color-focus-inner),0 0 0 4px var(--dt-color-focus-outer);outline:none}.c-textareaWrapper.c-textarea--large{--textarea-padding-block: var(--dt-spacing-c)}.c-textareaWrapper.c-textarea--small{--textarea-padding-block: var(--dt-spacing-a)}.c-textareaWrapper.c-textarea--resize-manual{--textarea-resize: vertical;--textarea-min-height: calc((var(--textarea-line-height) * 1) + (var(--textarea-padding-block) * 2))}@media (pointer: coarse){.c-textareaWrapper.c-textarea--resize-manual{--textarea-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2));--textarea-min-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2));--textarea-max-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2));--textarea-resize: none}}.c-textareaWrapper.c-textarea--resize-auto{--textarea-max-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2));--textarea-min-height: var(--textarea-height)}.c-textareaWrapper.c-textarea--error{--textarea-border-color: var(--dt-color-support-error)}
100
+ `, be = ["small", "medium", "large"], me = ["auto", "manual"], ye = ["default", "success", "error"], c = {
99
101
  disabled: !1,
100
102
  size: "medium",
101
103
  resize: "auto",
102
104
  label: "",
103
105
  value: "",
106
+ placeholder: "",
107
+ status: "default",
104
108
  autoFocus: !1,
105
109
  readonly: !1,
106
110
  required: !1
107
111
  };
108
- var be = Object.defineProperty, me = Object.getOwnPropertyDescriptor, l = (t, e, a, r) => {
109
- for (var i = r > 1 ? void 0 : r ? me(e, a) : e, u = t.length - 1, d; u >= 0; u--)
110
- (d = t[u]) && (i = (r ? d(e, a, i) : d(i)) || i);
111
- return r && i && be(e, a, i), i;
112
+ var ze = Object.defineProperty, ke = Object.getOwnPropertyDescriptor, s = (t, e, a, i) => {
113
+ for (var o = i > 1 ? void 0 : i ? ke(e, a) : e, p = t.length - 1, u; p >= 0; p--)
114
+ (u = t[p]) && (o = (i ? u(e, a, o) : u(o)) || o);
115
+ return i && o && ze(e, a, o), o;
112
116
  };
113
- const x = "pie-textarea";
114
- class o extends K(G(w)) {
117
+ const m = "pie-textarea", E = "assistive-text";
118
+ class r extends H(U(P)) {
115
119
  constructor() {
116
- super(...arguments), this.value = h.value, this.disabled = h.disabled, this.size = h.size, this.resize = h.resize, this.label = h.label, this.readonly = h.readonly, this.autoFocus = h.autoFocus, this.required = h.required, this._throttledResize = fe(() => {
117
- this.resize === "auto" && (this._textarea.style.height = "auto", this._textarea.style.height = `${this._textarea.scrollHeight + 2}px`);
120
+ super(...arguments), this.value = c.value, this.disabled = c.disabled, this.size = c.size, this.resize = c.resize, this.label = c.label, this.readonly = c.readonly, this.autoFocus = c.autoFocus, this.required = c.required, this.status = c.status, this._throttledResize = ve(() => {
121
+ this.resize === "auto" && (this._textarea.style.height = "auto", this._textarea.style.height = `${this._textarea.scrollHeight}px`);
118
122
  }, 100), this.handleInput = (e) => {
119
123
  this.value = e.target.value, this.restrictInputLength(), this._internals.setFormValue(this.value), this.handleResize();
120
124
  }, this.handleChange = (e) => {
121
- const a = H(e);
125
+ const a = X(e);
122
126
  this.dispatchEvent(a);
123
127
  }, this.handleKeyDown = (e) => {
124
128
  e.key === "Enter" && e.stopPropagation();
@@ -145,10 +149,10 @@ class o extends K(G(w)) {
145
149
  * Resets the value to the default value.
146
150
  */
147
151
  formResetCallback() {
148
- this.value = this.defaultValue ?? h.value, this._internals.setFormValue(this.value);
152
+ this.value = this.defaultValue ?? c.value, this._internals.setFormValue(this.value);
149
153
  }
150
154
  firstUpdated() {
151
- this.restrictInputLength(), this._internals.setFormValue(this.value), this._textarea.addEventListener("keydown", this.handleKeyDown);
155
+ this.restrictInputLength(), this._internals.setFormValue(this.value), window.addEventListener("resize", () => this.handleResize()), this._textarea.addEventListener("keydown", this.handleKeyDown);
152
156
  }
153
157
  handleResize() {
154
158
  this._throttledResize();
@@ -163,96 +167,134 @@ class o extends K(G(w)) {
163
167
  e.has("value") && (this.restrictInputLength(), this._internals.setFormValue(this.value)), this.resize === "auto" && (e.has("resize") || e.has("size")) && this.handleResize();
164
168
  }
165
169
  disconnectedCallback() {
166
- this._textarea.removeEventListener("keydown", this.handleKeyDown);
170
+ this._textarea.removeEventListener("keydown", this.handleKeyDown), window.removeEventListener("resize", () => this.handleResize());
167
171
  }
168
172
  renderLabel(e, a) {
169
- const r = a ? `${this.value.length}/${a}` : void 0;
170
- return e != null && e.length ? I`<pie-form-label for="${x}" trailing=${O(r)}>${e}</pie-form-label>` : q;
173
+ const i = a ? `${this.value.length}/${a}` : void 0;
174
+ return e != null && e.length ? W`<pie-form-label for="${m}" trailing=${b(i)}>${e}</pie-form-label>` : R;
175
+ }
176
+ renderAssistiveText() {
177
+ return this.assistiveText ? W`
178
+ <pie-assistive-text
179
+ id="${E}"
180
+ variant=${b(this.status)}
181
+ data-test-id="pie-textarea-assistive-text">
182
+ ${this.assistiveText}
183
+ </pie-assistive-text>
184
+ ` : R;
171
185
  }
172
186
  render() {
173
187
  const {
174
188
  disabled: e,
175
189
  resize: a,
176
- size: r,
177
- autocomplete: i,
178
- autoFocus: u,
179
- name: d,
180
- readonly: s,
181
- value: p,
182
- required: g,
183
- label: m,
184
- maxLength: v
185
- } = this;
186
- return I`
190
+ size: i,
191
+ autocomplete: o,
192
+ autoFocus: p,
193
+ name: u,
194
+ readonly: d,
195
+ placeholder: h,
196
+ value: f,
197
+ required: k,
198
+ label: v,
199
+ maxLength: y,
200
+ status: g,
201
+ assistiveText: S
202
+ } = this, w = {
203
+ "c-textareaWrapper": !0,
204
+ "c-textarea--disabled": e,
205
+ "c-textarea--readonly": d,
206
+ "c-textarea--error": g === "error",
207
+ [`c-textarea--resize-${a}`]: !0,
208
+ [`c-textarea--${i}`]: !0
209
+ };
210
+ return W`<div>
211
+ ${this.renderLabel(v, y)}
187
212
  <div
188
- class="c-textareaWrapper"
189
- data-test-id="pie-textarea-wrapper"
190
- data-pie-size="${r}"
191
- data-pie-resize="${a}">
192
- ${this.renderLabel(m, v)}
213
+ class="${K(w)}"
214
+ data-test-id="pie-textarea-wrapper">
193
215
  <textarea
194
- id="${x}"
195
- data-test-id="${x}"
196
- name=${O(d)}
197
- autocomplete=${O(i)}
198
- .value=${A(p)}
199
- ?autofocus=${u}
200
- ?readonly=${s}
201
- ?required=${g}
216
+ id="${m}"
217
+ data-test-id="${m}"
218
+ name=${b(u)}
219
+ autocomplete=${b(o)}
220
+ placeholder=${b(h)}
221
+ .value=${G(f)}
222
+ ?autofocus=${p}
223
+ ?readonly=${d}
224
+ ?required=${k}
202
225
  ?disabled=${e}
226
+ aria-describedby=${b(S ? E : void 0)}
227
+ aria-invalid=${g === "error" ? "true" : "false"}
228
+ aria-errormessage="${b(g === "error" ? E : void 0)}"
203
229
  @input=${this.handleInput}
204
230
  @change=${this.handleChange}
205
231
  ></textarea>
206
- </div>`;
232
+ </div>
233
+ ${this.renderAssistiveText()}
234
+ </div>`;
207
235
  }
208
236
  }
209
- o.shadowRootOptions = { ...w.shadowRootOptions, delegatesFocus: !0 };
210
- o.styles = B(ge);
211
- l([
212
- c({ type: String })
213
- ], o.prototype, "value", 2);
214
- l([
215
- c({ type: String })
216
- ], o.prototype, "defaultValue", 2);
217
- l([
218
- c({ type: Boolean, reflect: !0 })
219
- ], o.prototype, "disabled", 2);
220
- l([
221
- c({ type: String }),
222
- F(x, ve, h.size)
223
- ], o.prototype, "size", 2);
224
- l([
225
- c({ type: String }),
226
- F(x, xe, h.resize)
227
- ], o.prototype, "resize", 2);
228
- l([
229
- c({ type: String })
230
- ], o.prototype, "label", 2);
231
- l([
232
- c({ type: Number })
233
- ], o.prototype, "maxLength", 2);
234
- l([
235
- c({ type: Boolean })
236
- ], o.prototype, "readonly", 2);
237
- l([
238
- c({ type: Boolean })
239
- ], o.prototype, "autoFocus", 2);
240
- l([
241
- c({ type: Boolean })
242
- ], o.prototype, "required", 2);
243
- l([
244
- c({ type: String })
245
- ], o.prototype, "name", 2);
246
- l([
247
- c({ type: String })
248
- ], o.prototype, "autocomplete", 2);
249
- l([
250
- N("textarea")
251
- ], o.prototype, "_textarea", 2);
252
- U(x, o);
237
+ r.shadowRootOptions = { ...P.shadowRootOptions, delegatesFocus: !0 };
238
+ r.styles = A(ge);
239
+ s([
240
+ l({ type: String })
241
+ ], r.prototype, "value", 2);
242
+ s([
243
+ l({ type: String })
244
+ ], r.prototype, "defaultValue", 2);
245
+ s([
246
+ l({ type: Boolean, reflect: !0 })
247
+ ], r.prototype, "disabled", 2);
248
+ s([
249
+ l({ type: String }),
250
+ O(m, be, c.size)
251
+ ], r.prototype, "size", 2);
252
+ s([
253
+ l({ type: String }),
254
+ O(m, me, c.resize)
255
+ ], r.prototype, "resize", 2);
256
+ s([
257
+ l({ type: String })
258
+ ], r.prototype, "label", 2);
259
+ s([
260
+ l({ type: Number })
261
+ ], r.prototype, "maxLength", 2);
262
+ s([
263
+ l({ type: Boolean })
264
+ ], r.prototype, "readonly", 2);
265
+ s([
266
+ l({ type: Boolean })
267
+ ], r.prototype, "autoFocus", 2);
268
+ s([
269
+ l({ type: Boolean })
270
+ ], r.prototype, "required", 2);
271
+ s([
272
+ l({ type: String }),
273
+ O(m, ye, c.status)
274
+ ], r.prototype, "status", 2);
275
+ s([
276
+ l({ type: String })
277
+ ], r.prototype, "assistiveText", 2);
278
+ s([
279
+ l({ type: String })
280
+ ], r.prototype, "name", 2);
281
+ s([
282
+ l({ type: String })
283
+ ], r.prototype, "autocomplete", 2);
284
+ s([
285
+ l({ type: String })
286
+ ], r.prototype, "placeholder", 2);
287
+ s([
288
+ V("textarea")
289
+ ], r.prototype, "_textarea", 2);
290
+ s([
291
+ V("textarea")
292
+ ], r.prototype, "focusTarget", 2);
293
+ J(m, r);
253
294
  export {
254
- o as PieTextarea,
255
- h as defaultProps,
256
- xe as resizeModes,
257
- ve as sizes
295
+ r as PieTextarea,
296
+ c as defaultProps,
297
+ me as resizeModes,
298
+ be as sizes,
299
+ ye as statusTypes
258
300
  };
package/dist/react.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { FormControlInterface } from '@justeattakeaway/pie-webc-core';
4
4
  import type { GenericConstructor } from '@justeattakeaway/pie-webc-core';
5
5
  import type { LitElement } from 'lit';
6
6
  import type { nothing } from 'lit';
7
+ import type { PIEInputElement } from '@justeattakeaway/pie-webc-core';
7
8
  import type { PropertyValues } from 'lit';
8
9
  import * as React_2 from 'react';
9
10
  import type { RTLInterface } from '@justeattakeaway/pie-webc-core';
@@ -12,7 +13,7 @@ import type { TemplateResult } from 'lit-html';
12
13
  /**
13
14
  * The default values for the `TextareaProps` that are required (i.e. they have a fallback value in the component).
14
15
  */
15
- declare type DefaultProps = ComponentDefaultProps<TextareaProps, keyof Omit<TextareaProps, 'name' | 'autocomplete' | 'maxLength' | 'defaultValue'>>;
16
+ declare type DefaultProps = ComponentDefaultProps<TextareaProps, keyof Omit<TextareaProps, 'name' | 'autocomplete' | 'maxLength' | 'assistiveText' | 'defaultValue'>>;
16
17
 
17
18
  /**
18
19
  * Default values for optional properties that have default fallback values in the component.
@@ -26,7 +27,7 @@ export declare const PieTextarea: React_2.ForwardRefExoticComponent<TextareaProp
26
27
  * @event {InputEvent} input - when the textarea value is changed.
27
28
  * @event {CustomEvent} change - when the textarea value is changed.
28
29
  */
29
- declare class PieTextarea_2 extends PieTextarea_base implements TextareaProps {
30
+ declare class PieTextarea_2 extends PieTextarea_base implements TextareaProps, PIEInputElement {
30
31
  static shadowRootOptions: {
31
32
  delegatesFocus: boolean;
32
33
  mode: ShadowRootMode;
@@ -42,9 +43,13 @@ declare class PieTextarea_2 extends PieTextarea_base implements TextareaProps {
42
43
  readonly: boolean;
43
44
  autoFocus: boolean;
44
45
  required: boolean;
45
- name?: TextareaProps['name'];
46
- autocomplete?: TextareaProps['autocomplete'];
46
+ status: "default" | "error" | "success";
47
+ assistiveText: TextareaProps['assistiveText'];
48
+ name: TextareaProps['name'];
49
+ autocomplete: TextareaProps['autocomplete'];
50
+ placeholder: TextareaProps['placeholder'];
47
51
  private _textarea;
52
+ focusTarget: HTMLElement;
48
53
  private _throttledResize;
49
54
  /**
50
55
  * (Read-only) returns a ValidityState with the validity states that this element is in.
@@ -76,6 +81,7 @@ declare class PieTextarea_2 extends PieTextarea_base implements TextareaProps {
76
81
  private handleKeyDown;
77
82
  disconnectedCallback(): void;
78
83
  renderLabel(label: string, maxLength?: number): TemplateResult<1> | typeof nothing;
84
+ renderAssistiveText(): TemplateResult<1> | typeof nothing;
79
85
  render(): TemplateResult<1>;
80
86
  static styles: CSSResult;
81
87
  }
@@ -93,6 +99,8 @@ export declare const resizeModes: readonly ["auto", "manual"];
93
99
 
94
100
  export declare const sizes: readonly ["small", "medium", "large"];
95
101
 
102
+ export declare const statusTypes: readonly ["default", "success", "error"];
103
+
96
104
  export declare interface TextareaProps {
97
105
  /**
98
106
  * Same as the HTML disabled attribute - indicates whether the textarea is disabled.
@@ -116,6 +124,14 @@ export declare interface TextareaProps {
116
124
  * An optional default value to use when the textarea is reset.
117
125
  */
118
126
  defaultValue?: string;
127
+ /**
128
+ * An optional assistive text to display below the textarea element. Must be provided when the status is success or error.
129
+ */
130
+ assistiveText?: string;
131
+ /**
132
+ * The status of the textarea component / assistive text. Can be default, success or error.
133
+ */
134
+ status?: typeof statusTypes[number];
119
135
  /**
120
136
  * The name of the textarea (used as a key/value pair with `value`). This is required in order to work properly with forms.
121
137
  */
@@ -149,6 +165,10 @@ export declare interface TextareaProps {
149
165
  * If the `label` property is not set, this property will have no effect.
150
166
  */
151
167
  maxLength?: number;
168
+ /**
169
+ * The placeholder text to display when the textarea is empty.
170
+ */
171
+ placeholder?: string;
152
172
  }
153
173
 
154
174
  export { }
package/dist/react.js CHANGED
@@ -1,13 +1,15 @@
1
1
  import * as e from "react";
2
2
  import { createComponent as t } from "@lit/react";
3
3
  import { PieTextarea as a } from "./index.js";
4
- import { defaultProps as l, resizeModes as d, sizes as g } from "./index.js";
4
+ import { defaultProps as d, resizeModes as g, sizes as C, statusTypes as h } from "./index.js";
5
5
  import "lit";
6
6
  import "lit/decorators.js";
7
- import "lit/directives/live.js";
8
- import "@justeattakeaway/pie-webc-core";
7
+ import "lit/directives/class-map.js";
9
8
  import "lit/directives/if-defined.js";
9
+ import "lit/directives/live.js";
10
+ import "@justeattakeaway/pie-assistive-text";
10
11
  import "@justeattakeaway/pie-form-label";
12
+ import "@justeattakeaway/pie-webc-core";
11
13
  const r = t({
12
14
  displayName: "PieTextarea",
13
15
  elementClass: a,
@@ -19,10 +21,11 @@ const r = t({
19
21
  onChange: "change"
20
22
  // when the textarea value is changed.
21
23
  }
22
- }), P = r;
24
+ }), f = r;
23
25
  export {
24
- P as PieTextarea,
25
- l as defaultProps,
26
- d as resizeModes,
27
- g as sizes
26
+ f as PieTextarea,
27
+ d as defaultProps,
28
+ g as resizeModes,
29
+ C as sizes,
30
+ h as statusTypes
28
31
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-textarea",
3
3
  "description": "PIE Design System Textarea built using Web Components",
4
- "version": "0.7.0",
4
+ "version": "0.9.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "license": "Apache-2.0",
37
37
  "devDependencies": {
38
38
  "@custom-elements-manifest/analyzer": "0.9.0",
39
- "@justeattakeaway/pie-components-config": "0.17.0",
39
+ "@justeattakeaway/pie-components-config": "0.18.0",
40
40
  "@justeattakeaway/pie-css": "0.12.1",
41
41
  "@types/lodash.throttle": "4.1.9",
42
42
  "cem-plugin-module-file-extensions": "0.0.5"
package/src/defs.ts CHANGED
@@ -2,6 +2,7 @@ import { type ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
2
2
 
3
3
  export const sizes = ['small', 'medium', 'large'] as const;
4
4
  export const resizeModes = ['auto', 'manual'] as const;
5
+ export const statusTypes = ['default', 'success', 'error'] as const;
5
6
 
6
7
  export interface TextareaProps {
7
8
  /**
@@ -31,6 +32,16 @@ export interface TextareaProps {
31
32
  */
32
33
  defaultValue?: string;
33
34
 
35
+ /**
36
+ * An optional assistive text to display below the textarea element. Must be provided when the status is success or error.
37
+ */
38
+ assistiveText?: string;
39
+
40
+ /**
41
+ * The status of the textarea component / assistive text. Can be default, success or error.
42
+ */
43
+ status?: typeof statusTypes[number];
44
+
34
45
  /**
35
46
  * The name of the textarea (used as a key/value pair with `value`). This is required in order to work properly with forms.
36
47
  */
@@ -70,12 +81,17 @@ export interface TextareaProps {
70
81
  * If the `label` property is not set, this property will have no effect.
71
82
  */
72
83
  maxLength?: number;
84
+
85
+ /**
86
+ * The placeholder text to display when the textarea is empty.
87
+ */
88
+ placeholder?: string;
73
89
  }
74
90
 
75
91
  /**
76
92
  * The default values for the `TextareaProps` that are required (i.e. they have a fallback value in the component).
77
93
  */
78
- type DefaultProps = ComponentDefaultProps<TextareaProps, keyof Omit<TextareaProps, 'name' | 'autocomplete' | 'maxLength' | 'defaultValue'>>;
94
+ type DefaultProps = ComponentDefaultProps<TextareaProps, keyof Omit<TextareaProps, 'name' | 'autocomplete' | 'maxLength' | 'assistiveText' | 'defaultValue'>>;
79
95
 
80
96
  /**
81
97
  * Default values for optional properties that have default fallback values in the component.
@@ -86,6 +102,8 @@ export const defaultProps: DefaultProps = {
86
102
  resize: 'auto',
87
103
  label: '',
88
104
  value: '',
105
+ placeholder: '',
106
+ status: 'default',
89
107
  autoFocus: false,
90
108
  readonly: false,
91
109
  required: false,
package/src/index.ts CHANGED
@@ -1,34 +1,35 @@
1
1
  import {
2
- LitElement, html, unsafeCSS, PropertyValues, nothing,
2
+ LitElement, html, unsafeCSS, type PropertyValues, nothing,
3
3
  } from 'lit';
4
-
5
4
  import { property, query } from 'lit/decorators.js';
5
+ import { classMap } from 'lit/directives/class-map.js';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
6
7
  import { live } from 'lit/directives/live.js';
7
8
  import throttle from 'lodash.throttle';
8
9
 
10
+ import '@justeattakeaway/pie-assistive-text';
11
+ import '@justeattakeaway/pie-form-label';
9
12
  import {
10
- validPropertyValues, RtlMixin, defineCustomElement, FormControlMixin, wrapNativeEvent,
13
+ validPropertyValues, RtlMixin, defineCustomElement, FormControlMixin, wrapNativeEvent, type PIEInputElement,
11
14
  } from '@justeattakeaway/pie-webc-core';
12
15
 
13
- import { ifDefined } from 'lit/directives/if-defined.js';
14
16
  import styles from './textarea.scss?inline';
15
17
  import {
16
- TextareaProps, defaultProps, sizes, resizeModes,
18
+ type TextareaProps, defaultProps, sizes, resizeModes, statusTypes,
17
19
  } from './defs';
18
20
 
19
- import '@justeattakeaway/pie-form-label';
20
-
21
21
  // Valid values available to consumers
22
22
  export * from './defs';
23
23
 
24
24
  const componentSelector = 'pie-textarea';
25
+ const assistiveTextIdValue = 'assistive-text';
25
26
 
26
27
  /**
27
28
  * @tagname pie-textarea
28
29
  * @event {InputEvent} input - when the textarea value is changed.
29
30
  * @event {CustomEvent} change - when the textarea value is changed.
30
31
  */
31
- export class PieTextarea extends FormControlMixin(RtlMixin(LitElement)) implements TextareaProps {
32
+ export class PieTextarea extends FormControlMixin(RtlMixin(LitElement)) implements TextareaProps, PIEInputElement {
32
33
  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
33
34
 
34
35
  @property({ type: String })
@@ -64,18 +65,31 @@ export class PieTextarea extends FormControlMixin(RtlMixin(LitElement)) implemen
64
65
  public required = defaultProps.required;
65
66
 
66
67
  @property({ type: String })
67
- public name?: TextareaProps['name'];
68
+ @validPropertyValues(componentSelector, statusTypes, defaultProps.status)
69
+ public status = defaultProps.status;
70
+
71
+ @property({ type: String })
72
+ public assistiveText: TextareaProps['assistiveText'];
73
+
74
+ @property({ type: String })
75
+ public name: TextareaProps['name'];
68
76
 
69
77
  @property({ type: String })
70
- public autocomplete?: TextareaProps['autocomplete'];
78
+ public autocomplete: TextareaProps['autocomplete'];
79
+
80
+ @property({ type: String })
81
+ public placeholder: TextareaProps['placeholder'];
71
82
 
72
83
  @query('textarea')
73
84
  private _textarea!: HTMLTextAreaElement;
74
85
 
86
+ @query('textarea')
87
+ public focusTarget!: HTMLElement;
88
+
75
89
  private _throttledResize = throttle(() => {
76
90
  if (this.resize === 'auto') {
77
91
  this._textarea.style.height = 'auto';
78
- this._textarea.style.height = `${this._textarea.scrollHeight + 2}px`; // +2 for border thicknesses
92
+ this._textarea.style.height = `${this._textarea.scrollHeight}px`;
79
93
  }
80
94
  }, 100);
81
95
 
@@ -111,6 +125,7 @@ export class PieTextarea extends FormControlMixin(RtlMixin(LitElement)) implemen
111
125
  this.restrictInputLength();
112
126
  this._internals.setFormValue(this.value);
113
127
 
128
+ window.addEventListener('resize', () => this.handleResize());
114
129
  this._textarea.addEventListener('keydown', this.handleKeyDown);
115
130
  }
116
131
 
@@ -169,6 +184,7 @@ export class PieTextarea extends FormControlMixin(RtlMixin(LitElement)) implemen
169
184
 
170
185
  public disconnectedCallback (): void {
171
186
  this._textarea.removeEventListener('keydown', this.handleKeyDown);
187
+ window.removeEventListener('resize', () => this.handleResize());
172
188
  }
173
189
 
174
190
  renderLabel (label: string, maxLength?: number) {
@@ -179,6 +195,21 @@ export class PieTextarea extends FormControlMixin(RtlMixin(LitElement)) implemen
179
195
  : nothing;
180
196
  }
181
197
 
198
+ renderAssistiveText () {
199
+ if (!this.assistiveText) {
200
+ return nothing;
201
+ }
202
+
203
+ return html`
204
+ <pie-assistive-text
205
+ id="${assistiveTextIdValue}"
206
+ variant=${ifDefined(this.status)}
207
+ data-test-id="pie-textarea-assistive-text">
208
+ ${this.assistiveText}
209
+ </pie-assistive-text>
210
+ `;
211
+ }
212
+
182
213
  render () {
183
214
  const {
184
215
  disabled,
@@ -188,33 +219,49 @@ export class PieTextarea extends FormControlMixin(RtlMixin(LitElement)) implemen
188
219
  autoFocus,
189
220
  name,
190
221
  readonly,
222
+ placeholder,
191
223
  value,
192
224
  required,
193
225
  label,
194
226
  maxLength,
227
+ status,
228
+ assistiveText,
195
229
  } = this;
196
230
 
197
- return html`
231
+ const classes = {
232
+ 'c-textareaWrapper': true,
233
+ 'c-textarea--disabled': disabled,
234
+ 'c-textarea--readonly': readonly,
235
+ 'c-textarea--error': status === 'error',
236
+ [`c-textarea--resize-${resize}`]: true,
237
+ [`c-textarea--${size}`]: true,
238
+ };
239
+
240
+ return html`<div>
241
+ ${this.renderLabel(label, maxLength)}
198
242
  <div
199
- class="c-textareaWrapper"
200
- data-test-id="pie-textarea-wrapper"
201
- data-pie-size="${size}"
202
- data-pie-resize="${resize}">
203
- ${this.renderLabel(label, maxLength)}
243
+ class="${classMap(classes)}"
244
+ data-test-id="pie-textarea-wrapper">
204
245
  <textarea
205
246
  id="${componentSelector}"
206
247
  data-test-id="${componentSelector}"
207
248
  name=${ifDefined(name)}
208
249
  autocomplete=${ifDefined(autocomplete)}
250
+ placeholder=${ifDefined(placeholder)}
209
251
  .value=${live(value)}
210
252
  ?autofocus=${autoFocus}
211
253
  ?readonly=${readonly}
212
254
  ?required=${required}
213
255
  ?disabled=${disabled}
256
+ aria-describedby=${ifDefined(assistiveText ? assistiveTextIdValue : undefined)}
257
+ aria-invalid=${status === 'error' ? 'true' : 'false'}
258
+ aria-errormessage="${ifDefined(status === 'error' ? assistiveTextIdValue : undefined)}"
214
259
  @input=${this.handleInput}
215
260
  @change=${this.handleChange}
216
261
  ></textarea>
217
- </div>`;
262
+ </div>
263
+ ${this.renderAssistiveText()}
264
+ </div>`;
218
265
  }
219
266
 
220
267
  // Renders a `CSSResult` generated from SCSS by Vite
package/src/textarea.scss CHANGED
@@ -1,6 +1,7 @@
1
1
  @use '@justeattakeaway/pie-css/scss' as p;
2
2
 
3
3
  // Heights are being defined based on the line height of the text and the padding.
4
+ // Some of the padding is in the wrapper element to properly position the textarea resize handle.
4
5
  // Changing the `size` property affects the padding and therefore the height of the textarea.
5
6
  // Default height is two lines of text.
6
7
  // Minimum height in manual resize mode is one line of text.
@@ -9,81 +10,108 @@
9
10
  --textarea-line-height: #{p.line-height(--dt-font-body-l-line-height)};
10
11
  --textarea-border-thickness: 1px;
11
12
  --textarea-resize: none;
12
- --textarea-padding-inline: var(--dt-spacing-d);
13
- --textarea-padding-block: var(--dt-spacing-c);
13
+ --textarea-padding-inline: var(--dt-spacing-c);
14
+ --textarea-padding-block: var(--dt-spacing-b);
14
15
  --textarea-background-color: var(--dt-color-container-default);
15
16
  --textarea-border-color: var(--dt-color-interactive-form);
16
17
  --textarea-content-color: var(--dt-color-content-default);
18
+ --textarea-placeholder-color: var(--dt-color-content-subdued);
17
19
 
18
20
  // Default height is two lines of text
19
- --textarea-height: calc((var(--textarea-line-height) * 2) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));
21
+ --textarea-height: calc((var(--textarea-line-height) * 2) + (var(--textarea-padding-block) * 2));
20
22
 
21
- line-height: 0; // Remove once there is text outside the textarea
23
+ line-height: 0;
24
+ padding: var(--dt-spacing-a);
25
+ border: var(--textarea-border-thickness) solid var(--textarea-border-color);
26
+ background-color: var(--textarea-background-color);
27
+ border-radius: var(--dt-radius-rounded-c);
28
+ inline-size: 100%;
22
29
 
23
30
  textarea {
24
31
  @include p.font-size(--dt-font-body-l-size);
25
32
  line-height: var(--textarea-line-height);
26
33
  font-family: var(--dt-font-body-l-family);
27
34
  resize: var(--textarea-resize);
28
- border: var(--textarea-border-thickness) solid var(--textarea-border-color);
35
+ border: none;
29
36
  background-color: var(--textarea-background-color);
30
37
  color: var(--textarea-content-color);
31
38
 
32
- border-radius: var(--dt-radius-rounded-c);
33
39
  block-size: var(--textarea-height);
34
40
  max-block-size: var(--textarea-max-height);
35
41
  min-block-size: var(--textarea-min-height);
42
+ inline-size: 100%;
36
43
 
37
44
  padding-block-start: var(--textarea-padding-block);
38
45
  padding-block-end: var(--textarea-padding-block);
39
46
  padding-inline-start: var(--textarea-padding-inline);
40
47
  padding-inline-end: var(--textarea-padding-inline);
41
48
 
42
- &[disabled] {
43
- --textarea-background-color: var(--dt-color-disabled-01);
44
- --textarea-border-color: var(--dt-color-disabled-01);
45
- --textarea-content-color: var(--dt-color-content-disabled);
49
+ &:focus {
50
+ box-shadow: none;
51
+ outline: none;
46
52
  }
47
53
 
48
- @media (hover: hover) {
49
- &:hover:not([disabled]) {
50
- --textarea-background-color: hsl(var(--dt-color-container-default-h), var(--dt-color-container-default-s), calc(var(--dt-color-container-default-l) + calc(-1 * var(--dt-color-hover-01))));
51
- }
54
+ &::placeholder {
55
+ color: var(--textarea-placeholder-color);
56
+
57
+ opacity: 1; // normalize opacity for all browsers
52
58
  }
59
+ }
60
+
61
+ &.c-textarea--readonly {
62
+ --textarea-background-color: var(--dt-color-container-subtle);
63
+ --textarea-border-color: var(--dt-color-interactive-form);
64
+ }
65
+
66
+ &.c-textarea--disabled {
67
+ --textarea-background-color: var(--dt-color-disabled-01);
68
+ --textarea-border-color: var(--dt-color-disabled-01);
69
+ --textarea-content-color: var(--dt-color-content-disabled);
70
+ --textarea-placeholder-color: var(--dt-color-content-disabled);
71
+ }
53
72
 
54
- &:focus-visible {
55
- @include p.focus;
73
+ @media (hover: hover) {
74
+ &:hover:not(.c-textarea--disabled, .c-textarea--readonly) {
75
+ --textarea-background-color: hsl(var(--dt-color-container-default-h), var(--dt-color-container-default-s), calc(var(--dt-color-container-default-l) + calc(-1 * var(--dt-color-hover-01))));
56
76
  }
57
77
  }
58
78
 
59
- &[data-pie-size="large"] {
60
- --textarea-padding-block: var(--dt-spacing-d);
79
+ &:focus-within {
80
+ @include p.focus;
81
+ }
82
+
83
+ &.c-textarea--large {
84
+ --textarea-padding-block: var(--dt-spacing-c);
61
85
  }
62
86
 
63
- &[data-pie-size="small"] {
64
- --textarea-padding-block: var(--dt-spacing-b);
87
+ &.c-textarea--small {
88
+ --textarea-padding-block: var(--dt-spacing-a);
65
89
  }
66
90
 
67
- &[data-pie-resize="manual"] {
91
+ &.c-textarea--resize-manual {
68
92
  --textarea-resize: vertical;
69
93
 
70
94
  // Minimum is one line of text
71
- --textarea-min-height: calc((var(--textarea-line-height) * 1) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2)); // One line of text
95
+ --textarea-min-height: calc((var(--textarea-line-height) * 1) + (var(--textarea-padding-block) * 2));
72
96
 
73
97
  @media (pointer: coarse) {
74
98
  // Fixed size for touch devices
75
- --textarea-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));
76
- --textarea-min-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));
77
- --textarea-max-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));
99
+ --textarea-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2));
100
+ --textarea-min-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2));
101
+ --textarea-max-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2));
78
102
  --textarea-resize: none;
79
103
  }
80
104
  }
81
105
 
82
- &[data-pie-resize="auto"] {
106
+ &.c-textarea--resize-auto {
83
107
  // Maximum is six lines of text
84
- --textarea-max-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2) + (var(--textarea-border-thickness) * 2));
108
+ --textarea-max-height: calc((var(--textarea-line-height) * 6) + (var(--textarea-padding-block) * 2));
85
109
 
86
110
  // Minimum is two lines of text
87
111
  --textarea-min-height: var(--textarea-height);
88
112
  }
113
+
114
+ &.c-textarea--error {
115
+ --textarea-border-color: var(--dt-color-support-error);
116
+ }
89
117
  }