@justeattakeaway/pie-textarea 0.4.0 → 0.6.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.
package/README.md CHANGED
@@ -73,10 +73,16 @@ import { PieTextarea } from '@justeattakeaway/pie-textarea/dist/react';
73
73
  ## Props
74
74
 
75
75
  | Property | Type | Default | Description |
76
- | --- | --- | --- | --- |
77
- | `disabled` | `boolean` | `false` | Indicates whether or not the textarea is disabled. |
78
- | `size` | `"small"`, `"medium"`, `"large"` | `"medium"` | The size of the textarea field. |
79
- | `resize` | `"auto"`, `"manual"` | `"auto"` | Controls the resizing behaviour of the textarea. |
76
+ |---|---|---|---|
77
+ | `disabled` | `boolean` | `false`| Indicates whether or not the textarea is disabled. |
78
+ | `size` | `'small'`, `'medium'`, `'large'` | `'medium'` | The size of the textarea field. |
79
+ | `resize` | `'auto'`, `'manual'` | `'auto'` | Controls the resizing behaviour of the textarea. |
80
+ | `value` | `string` | `''` | The value of the textarea (used as a key/value pair in HTML forms with `name`). |
81
+ | `name` | `string` | `''` | The name of the textarea (used as a key/value pair with `value`). This is required in order to work properly with forms. |
82
+ | `autocomplete` | `string` | `''` | Allows the user to enable or disable autocomplete functionality on the textarea field. |
83
+ | `autoFocus` | `boolean` | `false` | If true, the textarea will be focused on the first render. |
84
+ | `readonly` | `boolean` | `false` | When true, the user cannot edit the control. Not the same as disabled. |
85
+ | `required` | `boolean` | `false` | If true, the textarea is required to have a value before submitting the form. If there is no value, then the component validity state will be invalid. |
80
86
 
81
87
  In your markup or JSX, you can then use these to set the properties for the `pie-textarea` component:
82
88
 
@@ -34,7 +34,7 @@
34
34
  "type": {
35
35
  "text": "DefaultProps"
36
36
  },
37
- "default": "{\n disabled: false,\n size: 'medium',\n resize: 'auto',\n}",
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}",
38
38
  "description": "Default values for optional properties that have default fallback values in the component."
39
39
  }
40
40
  ],
@@ -83,6 +83,12 @@
83
83
  "static": true,
84
84
  "default": "{ ...LitElement.shadowRootOptions, delegatesFocus: true }"
85
85
  },
86
+ {
87
+ "kind": "field",
88
+ "name": "value",
89
+ "privacy": "public",
90
+ "attribute": "value"
91
+ },
86
92
  {
87
93
  "kind": "field",
88
94
  "name": "disabled",
@@ -102,6 +108,57 @@
102
108
  "privacy": "public",
103
109
  "attribute": "resize"
104
110
  },
111
+ {
112
+ "kind": "field",
113
+ "name": "label",
114
+ "privacy": "public",
115
+ "attribute": "label"
116
+ },
117
+ {
118
+ "kind": "field",
119
+ "name": "maxLength",
120
+ "type": {
121
+ "text": "TextareaProps['maxLength']"
122
+ },
123
+ "privacy": "public",
124
+ "attribute": "maxLength"
125
+ },
126
+ {
127
+ "kind": "field",
128
+ "name": "readonly",
129
+ "privacy": "public",
130
+ "attribute": "readonly"
131
+ },
132
+ {
133
+ "kind": "field",
134
+ "name": "autoFocus",
135
+ "privacy": "public",
136
+ "attribute": "autoFocus"
137
+ },
138
+ {
139
+ "kind": "field",
140
+ "name": "required",
141
+ "privacy": "public",
142
+ "attribute": "required"
143
+ },
144
+ {
145
+ "kind": "field",
146
+ "name": "name",
147
+ "type": {
148
+ "text": "TextareaProps['name'] | undefined"
149
+ },
150
+ "privacy": "public",
151
+ "attribute": "name"
152
+ },
153
+ {
154
+ "kind": "field",
155
+ "name": "autocomplete",
156
+ "type": {
157
+ "text": "TextareaProps['autocomplete'] | undefined"
158
+ },
159
+ "privacy": "public",
160
+ "attribute": "autocomplete"
161
+ },
105
162
  {
106
163
  "kind": "field",
107
164
  "name": "_textarea",
@@ -115,13 +172,120 @@
115
172
  "name": "_throttledResize",
116
173
  "privacy": "private"
117
174
  },
175
+ {
176
+ "kind": "field",
177
+ "name": "validity",
178
+ "type": {
179
+ "text": "ValidityState"
180
+ },
181
+ "privacy": "public",
182
+ "description": "(Read-only) returns a ValidityState with the validity states that this element is in.\nhttps://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity",
183
+ "readonly": true
184
+ },
185
+ {
186
+ "kind": "method",
187
+ "name": "formDisabledCallback",
188
+ "privacy": "public",
189
+ "return": {
190
+ "type": {
191
+ "text": "void"
192
+ }
193
+ },
194
+ "parameters": [
195
+ {
196
+ "name": "disabled",
197
+ "type": {
198
+ "text": "boolean"
199
+ },
200
+ "description": "The latest disabled state of the input."
201
+ }
202
+ ],
203
+ "description": "Called after the disabled state of the element changes,\neither because the disabled attribute of this element was added or removed;\nor because the disabled state changed on a <fieldset> that's an ancestor of this element."
204
+ },
205
+ {
206
+ "kind": "method",
207
+ "name": "formResetCallback",
208
+ "privacy": "public",
209
+ "return": {
210
+ "type": {
211
+ "text": "void"
212
+ }
213
+ },
214
+ "description": "Called when the form that owns this component is reset.\nResets the value to the default value."
215
+ },
118
216
  {
119
217
  "kind": "method",
120
218
  "name": "handleResize",
121
219
  "privacy": "private"
220
+ },
221
+ {
222
+ "kind": "method",
223
+ "name": "restrictInputLength",
224
+ "privacy": "private"
225
+ },
226
+ {
227
+ "kind": "field",
228
+ "name": "handleInput",
229
+ "privacy": "private",
230
+ "description": "Handles data processing in response to the input event. The native input event is left to bubble up.",
231
+ "parameters": [
232
+ {
233
+ "description": "The input event.",
234
+ "name": "event"
235
+ }
236
+ ]
237
+ },
238
+ {
239
+ "kind": "field",
240
+ "name": "handleChange",
241
+ "privacy": "private"
242
+ },
243
+ {
244
+ "kind": "field",
245
+ "name": "handleKeyDown",
246
+ "privacy": "private"
247
+ },
248
+ {
249
+ "kind": "method",
250
+ "name": "renderLabel",
251
+ "parameters": [
252
+ {
253
+ "name": "label",
254
+ "type": {
255
+ "text": "string"
256
+ }
257
+ },
258
+ {
259
+ "name": "maxLength",
260
+ "optional": true,
261
+ "type": {
262
+ "text": "number"
263
+ }
264
+ }
265
+ ]
266
+ }
267
+ ],
268
+ "events": [
269
+ {
270
+ "type": {
271
+ "text": "InputEvent"
272
+ },
273
+ "description": "when the textarea value is changed.",
274
+ "name": "input"
275
+ },
276
+ {
277
+ "type": {
278
+ "text": "CustomEvent"
279
+ },
280
+ "description": "when the textarea value is changed.",
281
+ "name": "change"
122
282
  }
123
283
  ],
124
284
  "attributes": [
285
+ {
286
+ "name": "value",
287
+ "fieldName": "value"
288
+ },
125
289
  {
126
290
  "name": "disabled",
127
291
  "fieldName": "disabled"
@@ -133,9 +297,50 @@
133
297
  {
134
298
  "name": "resize",
135
299
  "fieldName": "resize"
300
+ },
301
+ {
302
+ "name": "label",
303
+ "fieldName": "label"
304
+ },
305
+ {
306
+ "name": "maxLength",
307
+ "type": {
308
+ "text": "TextareaProps['maxLength']"
309
+ },
310
+ "fieldName": "maxLength"
311
+ },
312
+ {
313
+ "name": "readonly",
314
+ "fieldName": "readonly"
315
+ },
316
+ {
317
+ "name": "autoFocus",
318
+ "fieldName": "autoFocus"
319
+ },
320
+ {
321
+ "name": "required",
322
+ "fieldName": "required"
323
+ },
324
+ {
325
+ "name": "name",
326
+ "type": {
327
+ "text": "TextareaProps['name'] | undefined"
328
+ },
329
+ "fieldName": "name"
330
+ },
331
+ {
332
+ "name": "autocomplete",
333
+ "type": {
334
+ "text": "TextareaProps['autocomplete'] | undefined"
335
+ },
336
+ "fieldName": "autocomplete"
136
337
  }
137
338
  ],
138
339
  "mixins": [
340
+ {
341
+ "name": "FormControlMixin",
342
+ "package": "@justeattakeaway/pie-webc-core"
343
+ },
139
344
  {
140
345
  "name": "RtlMixin",
141
346
  "package": "@justeattakeaway/pie-webc-core"
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
2
2
  import type { CSSResult } from 'lit';
3
+ import type { FormControlInterface } from '@justeattakeaway/pie-webc-core';
3
4
  import type { GenericConstructor } from '@justeattakeaway/pie-webc-core';
4
5
  import type { LitElement } from 'lit';
6
+ import type { nothing } from 'lit';
5
7
  import type { PropertyValues } from 'lit';
6
8
  import type { RTLInterface } from '@justeattakeaway/pie-webc-core';
7
9
  import type { TemplateResult } from 'lit-html';
@@ -9,7 +11,7 @@ import type { TemplateResult } from 'lit-html';
9
11
  /**
10
12
  * The default values for the `TextareaProps` that are required (i.e. they have a fallback value in the component).
11
13
  */
12
- declare type DefaultProps = ComponentDefaultProps<TextareaProps>;
14
+ declare type DefaultProps = ComponentDefaultProps<TextareaProps, keyof Omit<TextareaProps, 'name' | 'autocomplete' | 'maxLength'>>;
13
15
 
14
16
  /**
15
17
  * Default values for optional properties that have default fallback values in the component.
@@ -18,6 +20,8 @@ export declare const defaultProps: DefaultProps;
18
20
 
19
21
  /**
20
22
  * @tagname pie-textarea
23
+ * @event {InputEvent} input - when the textarea value is changed.
24
+ * @event {CustomEvent} change - when the textarea value is changed.
21
25
  */
22
26
  export declare class PieTextarea extends PieTextarea_base implements TextareaProps {
23
27
  static shadowRootOptions: {
@@ -25,18 +29,54 @@ export declare class PieTextarea extends PieTextarea_base implements TextareaPro
25
29
  mode: ShadowRootMode;
26
30
  slotAssignment?: SlotAssignmentMode | undefined;
27
31
  };
32
+ value: string;
28
33
  disabled: boolean;
29
34
  size: "small" | "medium" | "large";
30
35
  resize: "auto" | "manual";
36
+ label: string;
37
+ maxLength: TextareaProps['maxLength'];
38
+ readonly: boolean;
39
+ autoFocus: boolean;
40
+ required: boolean;
41
+ name?: TextareaProps['name'];
42
+ autocomplete?: TextareaProps['autocomplete'];
31
43
  private _textarea;
32
44
  private _throttledResize;
45
+ /**
46
+ * (Read-only) returns a ValidityState with the validity states that this element is in.
47
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
48
+ */
49
+ get validity(): ValidityState;
50
+ /**
51
+ * Called after the disabled state of the element changes,
52
+ * either because the disabled attribute of this element was added or removed;
53
+ * or because the disabled state changed on a <fieldset> that's an ancestor of this element.
54
+ * @param disabled - The latest disabled state of the input.
55
+ */
56
+ formDisabledCallback(disabled: boolean): void;
57
+ /**
58
+ * Called when the form that owns this component is reset.
59
+ * Resets the value to the default value.
60
+ */
61
+ formResetCallback(): void;
62
+ protected firstUpdated(): void;
33
63
  private handleResize;
34
- updated(changedProperties: PropertyValues<this>): void;
64
+ private restrictInputLength;
65
+ protected updated(changedProperties: PropertyValues<this>): void;
66
+ /**
67
+ * Handles data processing in response to the input event. The native input event is left to bubble up.
68
+ * @param event - The input event.
69
+ */
70
+ private handleInput;
71
+ private handleChange;
72
+ private handleKeyDown;
73
+ disconnectedCallback(): void;
74
+ renderLabel(label: string, maxLength?: number): TemplateResult<1> | typeof nothing;
35
75
  render(): TemplateResult<1>;
36
76
  static styles: CSSResult;
37
77
  }
38
78
 
39
- declare const PieTextarea_base: GenericConstructor<RTLInterface> & typeof LitElement;
79
+ declare const PieTextarea_base: GenericConstructor<FormControlInterface> & GenericConstructor<RTLInterface> & typeof LitElement;
40
80
 
41
81
  export declare const resizeModes: readonly ["auto", "manual"];
42
82
 
@@ -57,6 +97,43 @@ export declare interface TextareaProps {
57
97
  * When set to `manual`, the textarea will not resize automatically but can be resized by the user.
58
98
  */
59
99
  resize?: typeof resizeModes[number];
100
+ /**
101
+ * The value of the textarea (used as a key/value pair in HTML forms with `name`).
102
+ */
103
+ value: string;
104
+ /**
105
+ * The name of the textarea (used as a key/value pair with `value`). This is required in order to work properly with forms.
106
+ */
107
+ name?: string;
108
+ /**
109
+ * Allows the user to enable or disable autocomplete functionality on the textarea field.
110
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) for more information and values.
111
+ */
112
+ autocomplete?: string;
113
+ /**
114
+ * If true, the textarea will be focused on the first render.
115
+ * No more than one element in the document or dialog may have the autofocus attribute. If applied to multiple elements the first one will receive focus.
116
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) for more information.
117
+ */
118
+ autoFocus?: boolean;
119
+ /**
120
+ * When true, the user cannot edit the control. Not the same as disabled.
121
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly) for more information.
122
+ */
123
+ readonly?: boolean;
124
+ /**
125
+ * If true, the textarea is required to have a value before submitting the form. If there is no value, then the component validity state will be invalid.
126
+ */
127
+ required?: boolean;
128
+ /**
129
+ * The label text for the textarea field.
130
+ */
131
+ label?: string;
132
+ /**
133
+ * The maximum number of characters allowed in the textarea field.
134
+ * If the `label` property is not set, this property will have no effect.
135
+ */
136
+ maxLength?: number;
60
137
  }
61
138
 
62
139
  export { }
package/dist/index.js CHANGED
@@ -1,160 +1,255 @@
1
- import { LitElement as $, html as B, unsafeCSS as D } from "lit";
2
- import { property as O, query as N } from "lit/decorators.js";
3
- import { RtlMixin as A, validPropertyValues as w, defineCustomElement as G } from "@justeattakeaway/pie-webc-core";
4
- var b = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {};
5
- function H(e) {
6
- return e && e.__esModule && Object.prototype.hasOwnProperty.call(e, "default") ? e.default : e;
1
+ import { LitElement as w, html as I, nothing as B, unsafeCSS as N } from "lit";
2
+ import { property as h, query as V } 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";
6
+ 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) {
9
+ return t && t.__esModule && Object.prototype.hasOwnProperty.call(t, "default") ? t.default : t;
7
10
  }
8
- var P = "Expected a function", I = 0 / 0, q = "[object Symbol]", U = /^\s+|\s+$/g, V = /^[-+]0x[0-9a-f]+$/i, X = /^0b[01]+$/i, J = /^0o[0-7]+$/i, K = parseInt, Q = typeof b == "object" && b && b.Object === Object && b, Y = typeof self == "object" && self && self.Object === Object && self, Z = Q || Y || Function("return this")(), ee = Object.prototype, te = ee.toString, re = Math.max, ae = Math.min, j = function() {
9
- return Z.Date.now();
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, L = function() {
12
+ return ie.Date.now();
10
13
  };
11
- function ie(e, t, r) {
12
- var i, n, s, d, o, c, p = 0, _ = !1, u = !1, y = !0;
13
- if (typeof e != "function")
14
- throw new TypeError(P);
15
- t = R(t) || 0, v(r) && (_ = !!r.leading, u = "maxWait" in r, s = u ? re(R(r.maxWait) || 0, t) : s, y = "trailing" in r ? !!r.trailing : y);
16
- function z(a) {
17
- var l = i, h = n;
18
- return i = n = void 0, p = a, d = e.apply(h, l), d;
19
- }
20
- function C(a) {
21
- return p = a, o = setTimeout(g, t), _ ? z(a) : d;
22
- }
23
- function M(a) {
24
- var l = a - c, h = a - p, E = t - l;
25
- return u ? ae(E, s - h) : E;
26
- }
27
- function W(a) {
28
- var l = a - c, h = a - p;
29
- return c === void 0 || l >= t || l < 0 || u && h >= s;
30
- }
31
- function g() {
32
- var a = j();
33
- if (W(a))
34
- return S(a);
35
- o = setTimeout(g, M(a));
36
- }
37
- function S(a) {
38
- return o = void 0, y && i ? z(a) : (i = n = void 0, d);
39
- }
40
- function L() {
41
- o !== void 0 && clearTimeout(o), p = 0, i = c = n = o = void 0;
42
- }
43
- function F() {
44
- return o === void 0 ? d : S(j());
45
- }
46
- function k() {
47
- var a = j(), l = W(a);
48
- if (i = arguments, n = this, c = a, l) {
49
- if (o === void 0)
50
- return C(c);
51
- if (u)
52
- return o = setTimeout(g, t), z(c);
14
+ function de(t, e, a) {
15
+ var r, i, u, l, s, p, g = 0, m = !1, v = !1, _ = !0;
16
+ 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, l = t.apply(b, f), l;
22
+ }
23
+ function D(n) {
24
+ return g = n, s = setTimeout(y, e), m ? $(n) : l;
25
+ }
26
+ function P(n) {
27
+ var f = n - p, b = n - g, E = e - f;
28
+ return v ? le(E, u - b) : E;
29
+ }
30
+ function S(n) {
31
+ var f = n - p, b = n - g;
32
+ return p === void 0 || f >= e || f < 0 || v && b >= u;
33
+ }
34
+ function y() {
35
+ var n = L();
36
+ if (S(n))
37
+ return T(n);
38
+ s = setTimeout(y, P(n));
39
+ }
40
+ function T(n) {
41
+ return s = void 0, _ && r ? $(n) : (r = i = void 0, l);
42
+ }
43
+ function M() {
44
+ s !== void 0 && clearTimeout(s), g = 0, r = p = i = s = void 0;
45
+ }
46
+ function q() {
47
+ return s === void 0 ? l : T(L());
48
+ }
49
+ function j() {
50
+ var n = L(), f = S(n);
51
+ if (r = arguments, i = this, p = n, f) {
52
+ if (s === void 0)
53
+ return D(p);
54
+ if (v)
55
+ return s = setTimeout(y, e), $(p);
53
56
  }
54
- return o === void 0 && (o = setTimeout(g, t)), d;
57
+ return s === void 0 && (s = setTimeout(y, e)), l;
55
58
  }
56
- return k.cancel = L, k.flush = F, k;
59
+ return j.cancel = M, j.flush = q, j;
57
60
  }
58
- function ne(e, t, r) {
59
- var i = !0, n = !0;
60
- if (typeof e != "function")
61
- throw new TypeError(P);
62
- return v(r) && (i = "leading" in r ? !!r.leading : i, n = "trailing" in r ? !!r.trailing : n), ie(e, t, {
63
- leading: i,
64
- maxWait: t,
65
- trailing: n
61
+ function ce(t, e, a) {
62
+ var r = !0, i = !0;
63
+ 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,
67
+ maxWait: e,
68
+ trailing: i
66
69
  });
67
70
  }
68
- function v(e) {
69
- var t = typeof e;
70
- return !!e && (t == "object" || t == "function");
71
+ function k(t) {
72
+ var e = typeof t;
73
+ return !!t && (e == "object" || e == "function");
71
74
  }
72
- function oe(e) {
73
- return !!e && typeof e == "object";
75
+ function he(t) {
76
+ return !!t && typeof t == "object";
74
77
  }
75
- function de(e) {
76
- return typeof e == "symbol" || oe(e) && te.call(e) == q;
78
+ function ue(t) {
79
+ return typeof t == "symbol" || he(t) && oe.call(t) == J;
77
80
  }
78
- function R(e) {
79
- if (typeof e == "number")
80
- return e;
81
- if (de(e))
82
- return I;
83
- if (v(e)) {
84
- var t = typeof e.valueOf == "function" ? e.valueOf() : e;
85
- e = v(t) ? t + "" : t;
86
- }
87
- if (typeof e != "string")
88
- return e === 0 ? e : +e;
89
- e = e.replace(U, "");
90
- var r = X.test(e);
91
- return r || J.test(e) ? K(e.slice(2), r ? 2 : 8) : V.test(e) ? I : +e;
81
+ function W(t) {
82
+ if (typeof t == "number")
83
+ return t;
84
+ if (ue(t))
85
+ return C;
86
+ if (k(t)) {
87
+ var e = typeof t.valueOf == "function" ? t.valueOf() : t;
88
+ t = k(e) ? e + "" : e;
89
+ }
90
+ if (typeof t != "string")
91
+ 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;
92
95
  }
93
- var se = ne;
94
- const le = /* @__PURE__ */ H(se), ce = `*,*: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)}
95
- `, fe = ["small", "medium", "large"], pe = ["auto", "manual"], x = {
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"], c = {
96
99
  disabled: !1,
97
100
  size: "medium",
98
- resize: "auto"
101
+ resize: "auto",
102
+ label: "",
103
+ value: "",
104
+ autoFocus: !1,
105
+ readonly: !1,
106
+ required: !1
99
107
  };
100
- var ue = Object.defineProperty, he = Object.getOwnPropertyDescriptor, m = (e, t, r, i) => {
101
- for (var n = i > 1 ? void 0 : i ? he(t, r) : t, s = e.length - 1, d; s >= 0; s--)
102
- (d = e[s]) && (n = (i ? d(t, r, n) : d(n)) || n);
103
- return i && n && ue(t, r, n), n;
108
+ var be = Object.defineProperty, me = Object.getOwnPropertyDescriptor, d = (t, e, a, r) => {
109
+ for (var i = r > 1 ? void 0 : r ? me(e, a) : e, u = t.length - 1, l; u >= 0; u--)
110
+ (l = t[u]) && (i = (r ? l(e, a, i) : l(i)) || i);
111
+ return r && i && be(e, a, i), i;
104
112
  };
105
- const T = "pie-textarea";
106
- class f extends A($) {
113
+ const x = "pie-textarea";
114
+ class o extends K(G(w)) {
107
115
  constructor() {
108
- super(...arguments), this.disabled = x.disabled, this.size = x.size, this.resize = x.resize, this._throttledResize = le(() => {
116
+ 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._throttledResize = fe(() => {
109
117
  this.resize === "auto" && (this._textarea.style.height = "auto", this._textarea.style.height = `${this._textarea.scrollHeight + 2}px`);
110
- }, 100);
118
+ }, 100), this.handleInput = (e) => {
119
+ this.value = e.target.value, this.restrictInputLength(), this._internals.setFormValue(this.value), this.handleResize();
120
+ }, this.handleChange = (e) => {
121
+ const a = H(e);
122
+ this.dispatchEvent(a);
123
+ }, this.handleKeyDown = (e) => {
124
+ e.key === "Enter" && e.stopPropagation();
125
+ };
126
+ }
127
+ /**
128
+ * (Read-only) returns a ValidityState with the validity states that this element is in.
129
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
130
+ */
131
+ get validity() {
132
+ return this._textarea.validity;
133
+ }
134
+ /**
135
+ * Called after the disabled state of the element changes,
136
+ * either because the disabled attribute of this element was added or removed;
137
+ * or because the disabled state changed on a <fieldset> that's an ancestor of this element.
138
+ * @param disabled - The latest disabled state of the input.
139
+ */
140
+ formDisabledCallback(e) {
141
+ this.disabled = e;
142
+ }
143
+ /**
144
+ * Called when the form that owns this component is reset.
145
+ * Resets the value to the default value.
146
+ */
147
+ formResetCallback() {
148
+ this.value = c.value, this._internals.setFormValue(this.value);
149
+ }
150
+ firstUpdated() {
151
+ this.restrictInputLength(), this._internals.setFormValue(this.value), this._textarea.addEventListener("keydown", this.handleKeyDown);
111
152
  }
112
153
  handleResize() {
113
154
  this._throttledResize();
114
155
  }
115
- updated(t) {
116
- this.resize === "auto" && (t.has("resize") || t.has("size")) && this.handleResize();
156
+ restrictInputLength() {
157
+ if (this.label.length && this.maxLength && this.value.length > this.maxLength) {
158
+ const e = this.value.slice(0, this.maxLength);
159
+ this._textarea.value = e, this.value = e;
160
+ }
161
+ }
162
+ updated(e) {
163
+ e.has("value") && (this.restrictInputLength(), this._internals.setFormValue(this.value)), this.resize === "auto" && (e.has("resize") || e.has("size")) && this.handleResize();
164
+ }
165
+ disconnectedCallback() {
166
+ this._textarea.removeEventListener("keydown", this.handleKeyDown);
167
+ }
168
+ 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>` : B;
117
171
  }
118
172
  render() {
119
173
  const {
120
- disabled: t,
121
- resize: r,
122
- size: i
174
+ disabled: e,
175
+ resize: a,
176
+ size: r,
177
+ autocomplete: i,
178
+ autoFocus: u,
179
+ name: l,
180
+ readonly: s,
181
+ value: p,
182
+ required: g,
183
+ label: m,
184
+ maxLength: v
123
185
  } = this;
124
- return B`
186
+ return I`
125
187
  <div
126
188
  class="c-textareaWrapper"
127
189
  data-test-id="pie-textarea-wrapper"
128
- data-pie-size="${i}"
129
- data-pie-resize="${r}">
190
+ data-pie-size="${r}"
191
+ data-pie-resize="${a}">
192
+ ${this.renderLabel(m, v)}
130
193
  <textarea
131
- data-test-id="pie-textarea"
132
- @input=${this.handleResize}
133
- ?disabled=${t}
194
+ id="${x}"
195
+ data-test-id="${x}"
196
+ name=${O(l)}
197
+ autocomplete=${O(i)}
198
+ .value=${A(p)}
199
+ ?autofocus=${u}
200
+ ?readonly=${s}
201
+ ?required=${g}
202
+ ?disabled=${e}
203
+ @input=${this.handleInput}
204
+ @change=${this.handleChange}
134
205
  ></textarea>
135
206
  </div>`;
136
207
  }
137
208
  }
138
- f.shadowRootOptions = { ...$.shadowRootOptions, delegatesFocus: !0 };
139
- f.styles = D(ce);
140
- m([
141
- O({ type: Boolean, reflect: !0 })
142
- ], f.prototype, "disabled", 2);
143
- m([
144
- O({ type: String }),
145
- w(T, fe, x.size)
146
- ], f.prototype, "size", 2);
147
- m([
148
- O({ type: String }),
149
- w(T, pe, x.resize)
150
- ], f.prototype, "resize", 2);
151
- m([
152
- N("textarea")
153
- ], f.prototype, "_textarea", 2);
154
- G(T, f);
209
+ o.shadowRootOptions = { ...w.shadowRootOptions, delegatesFocus: !0 };
210
+ o.styles = N(ge);
211
+ d([
212
+ h({ type: String })
213
+ ], o.prototype, "value", 2);
214
+ d([
215
+ h({ type: Boolean, reflect: !0 })
216
+ ], o.prototype, "disabled", 2);
217
+ d([
218
+ h({ type: String }),
219
+ F(x, ve, c.size)
220
+ ], o.prototype, "size", 2);
221
+ d([
222
+ h({ type: String }),
223
+ F(x, xe, c.resize)
224
+ ], o.prototype, "resize", 2);
225
+ d([
226
+ h({ type: String })
227
+ ], o.prototype, "label", 2);
228
+ d([
229
+ h({ type: Number })
230
+ ], o.prototype, "maxLength", 2);
231
+ d([
232
+ h({ type: Boolean })
233
+ ], o.prototype, "readonly", 2);
234
+ d([
235
+ h({ type: Boolean })
236
+ ], o.prototype, "autoFocus", 2);
237
+ d([
238
+ h({ type: Boolean })
239
+ ], o.prototype, "required", 2);
240
+ d([
241
+ h({ type: String })
242
+ ], o.prototype, "name", 2);
243
+ d([
244
+ h({ type: String })
245
+ ], o.prototype, "autocomplete", 2);
246
+ d([
247
+ V("textarea")
248
+ ], o.prototype, "_textarea", 2);
249
+ U(x, o);
155
250
  export {
156
- f as PieTextarea,
157
- x as defaultProps,
158
- pe as resizeModes,
159
- fe as sizes
251
+ o as PieTextarea,
252
+ c as defaultProps,
253
+ xe as resizeModes,
254
+ ve as sizes
160
255
  };
package/dist/react.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
2
2
  import type { CSSResult } from 'lit';
3
+ import type { FormControlInterface } from '@justeattakeaway/pie-webc-core';
3
4
  import type { GenericConstructor } from '@justeattakeaway/pie-webc-core';
4
5
  import type { LitElement } from 'lit';
6
+ import type { nothing } from 'lit';
5
7
  import type { PropertyValues } from 'lit';
6
8
  import * as React_2 from 'react';
7
9
  import type { RTLInterface } from '@justeattakeaway/pie-webc-core';
@@ -10,17 +12,19 @@ import type { TemplateResult } from 'lit-html';
10
12
  /**
11
13
  * The default values for the `TextareaProps` that are required (i.e. they have a fallback value in the component).
12
14
  */
13
- declare type DefaultProps = ComponentDefaultProps<TextareaProps>;
15
+ declare type DefaultProps = ComponentDefaultProps<TextareaProps, keyof Omit<TextareaProps, 'name' | 'autocomplete' | 'maxLength'>>;
14
16
 
15
17
  /**
16
18
  * Default values for optional properties that have default fallback values in the component.
17
19
  */
18
20
  export declare const defaultProps: DefaultProps;
19
21
 
20
- export declare const PieTextarea: React_2.ForwardRefExoticComponent<TextareaProps & React_2.RefAttributes<PieTextarea_2> & ReactBaseType>;
22
+ export declare const PieTextarea: React_2.ForwardRefExoticComponent<TextareaProps & React_2.RefAttributes<PieTextarea_2> & PieTextareaEvents & ReactBaseType>;
21
23
 
22
24
  /**
23
25
  * @tagname pie-textarea
26
+ * @event {InputEvent} input - when the textarea value is changed.
27
+ * @event {CustomEvent} change - when the textarea value is changed.
24
28
  */
25
29
  declare class PieTextarea_2 extends PieTextarea_base implements TextareaProps {
26
30
  static shadowRootOptions: {
@@ -28,18 +32,59 @@ declare class PieTextarea_2 extends PieTextarea_base implements TextareaProps {
28
32
  mode: ShadowRootMode;
29
33
  slotAssignment?: SlotAssignmentMode | undefined;
30
34
  };
35
+ value: string;
31
36
  disabled: boolean;
32
37
  size: "small" | "medium" | "large";
33
38
  resize: "auto" | "manual";
39
+ label: string;
40
+ maxLength: TextareaProps['maxLength'];
41
+ readonly: boolean;
42
+ autoFocus: boolean;
43
+ required: boolean;
44
+ name?: TextareaProps['name'];
45
+ autocomplete?: TextareaProps['autocomplete'];
34
46
  private _textarea;
35
47
  private _throttledResize;
48
+ /**
49
+ * (Read-only) returns a ValidityState with the validity states that this element is in.
50
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
51
+ */
52
+ get validity(): ValidityState;
53
+ /**
54
+ * Called after the disabled state of the element changes,
55
+ * either because the disabled attribute of this element was added or removed;
56
+ * or because the disabled state changed on a <fieldset> that's an ancestor of this element.
57
+ * @param disabled - The latest disabled state of the input.
58
+ */
59
+ formDisabledCallback(disabled: boolean): void;
60
+ /**
61
+ * Called when the form that owns this component is reset.
62
+ * Resets the value to the default value.
63
+ */
64
+ formResetCallback(): void;
65
+ protected firstUpdated(): void;
36
66
  private handleResize;
37
- updated(changedProperties: PropertyValues<this>): void;
67
+ private restrictInputLength;
68
+ protected updated(changedProperties: PropertyValues<this>): void;
69
+ /**
70
+ * Handles data processing in response to the input event. The native input event is left to bubble up.
71
+ * @param event - The input event.
72
+ */
73
+ private handleInput;
74
+ private handleChange;
75
+ private handleKeyDown;
76
+ disconnectedCallback(): void;
77
+ renderLabel(label: string, maxLength?: number): TemplateResult<1> | typeof nothing;
38
78
  render(): TemplateResult<1>;
39
79
  static styles: CSSResult;
40
80
  }
41
81
 
42
- declare const PieTextarea_base: GenericConstructor<RTLInterface> & typeof LitElement;
82
+ declare const PieTextarea_base: GenericConstructor<FormControlInterface> & GenericConstructor<RTLInterface> & typeof LitElement;
83
+
84
+ declare type PieTextareaEvents = {
85
+ onInput?: (event: InputEvent) => void;
86
+ onChange?: (event: CustomEvent) => void;
87
+ };
43
88
 
44
89
  declare type ReactBaseType = React_2.HTMLAttributes<HTMLTextAreaElement>;
45
90
 
@@ -62,6 +107,43 @@ export declare interface TextareaProps {
62
107
  * When set to `manual`, the textarea will not resize automatically but can be resized by the user.
63
108
  */
64
109
  resize?: typeof resizeModes[number];
110
+ /**
111
+ * The value of the textarea (used as a key/value pair in HTML forms with `name`).
112
+ */
113
+ value: string;
114
+ /**
115
+ * The name of the textarea (used as a key/value pair with `value`). This is required in order to work properly with forms.
116
+ */
117
+ name?: string;
118
+ /**
119
+ * Allows the user to enable or disable autocomplete functionality on the textarea field.
120
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) for more information and values.
121
+ */
122
+ autocomplete?: string;
123
+ /**
124
+ * If true, the textarea will be focused on the first render.
125
+ * No more than one element in the document or dialog may have the autofocus attribute. If applied to multiple elements the first one will receive focus.
126
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) for more information.
127
+ */
128
+ autoFocus?: boolean;
129
+ /**
130
+ * When true, the user cannot edit the control. Not the same as disabled.
131
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly) for more information.
132
+ */
133
+ readonly?: boolean;
134
+ /**
135
+ * If true, the textarea is required to have a value before submitting the form. If there is no value, then the component validity state will be invalid.
136
+ */
137
+ required?: boolean;
138
+ /**
139
+ * The label text for the textarea field.
140
+ */
141
+ label?: string;
142
+ /**
143
+ * The maximum number of characters allowed in the textarea field.
144
+ * If the `label` property is not set, this property will have no effect.
145
+ */
146
+ maxLength?: number;
65
147
  }
66
148
 
67
149
  export { }
package/dist/react.js CHANGED
@@ -1,20 +1,28 @@
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 P, resizeModes as f, sizes as T } from "./index.js";
4
+ import { defaultProps as l, resizeModes as d, sizes as g } from "./index.js";
5
5
  import "lit";
6
6
  import "lit/decorators.js";
7
+ import "lit/directives/live.js";
7
8
  import "@justeattakeaway/pie-webc-core";
9
+ import "lit/directives/if-defined.js";
10
+ import "@justeattakeaway/pie-form-label";
8
11
  const r = t({
9
12
  displayName: "PieTextarea",
10
13
  elementClass: a,
11
14
  react: e,
12
15
  tagName: "pie-textarea",
13
- events: {}
14
- }), x = r;
16
+ events: {
17
+ onInput: "input",
18
+ // when the textarea value is changed.
19
+ onChange: "change"
20
+ // when the textarea value is changed.
21
+ }
22
+ }), P = r;
15
23
  export {
16
- x as PieTextarea,
17
- P as defaultProps,
18
- f as resizeModes,
19
- T as sizes
24
+ P as PieTextarea,
25
+ l as defaultProps,
26
+ d as resizeModes,
27
+ g as sizes
20
28
  };
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.4.0",
4
+ "version": "0.6.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -41,6 +41,7 @@
41
41
  "cem-plugin-module-file-extensions": "0.0.5"
42
42
  },
43
43
  "dependencies": {
44
+ "@justeattakeaway/pie-form-label": "0.14.0",
44
45
  "@justeattakeaway/pie-webc-core": "0.24.0",
45
46
  "lodash.throttle": "4.1.1"
46
47
  },
package/src/defs.ts CHANGED
@@ -20,12 +20,57 @@ export interface TextareaProps {
20
20
  * When set to `manual`, the textarea will not resize automatically but can be resized by the user.
21
21
  */
22
22
  resize?: typeof resizeModes[number];
23
+
24
+ /**
25
+ * The value of the textarea (used as a key/value pair in HTML forms with `name`).
26
+ */
27
+ value: string;
28
+
29
+ /**
30
+ * The name of the textarea (used as a key/value pair with `value`). This is required in order to work properly with forms.
31
+ */
32
+ name?: string;
33
+
34
+ /**
35
+ * Allows the user to enable or disable autocomplete functionality on the textarea field.
36
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) for more information and values.
37
+ */
38
+ autocomplete?: string;
39
+
40
+ /**
41
+ * If true, the textarea will be focused on the first render.
42
+ * No more than one element in the document or dialog may have the autofocus attribute. If applied to multiple elements the first one will receive focus.
43
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) for more information.
44
+ */
45
+ autoFocus?: boolean;
46
+
47
+ /**
48
+ * When true, the user cannot edit the control. Not the same as disabled.
49
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly) for more information.
50
+ */
51
+ readonly?: boolean;
52
+
53
+ /**
54
+ * If true, the textarea is required to have a value before submitting the form. If there is no value, then the component validity state will be invalid.
55
+ */
56
+ required?: boolean;
57
+
58
+ /**
59
+ * The label text for the textarea field.
60
+ */
61
+ label?: string;
62
+
63
+ /**
64
+ * The maximum number of characters allowed in the textarea field.
65
+ * If the `label` property is not set, this property will have no effect.
66
+ */
67
+ maxLength?: number;
23
68
  }
24
69
 
25
70
  /**
26
71
  * The default values for the `TextareaProps` that are required (i.e. they have a fallback value in the component).
27
72
  */
28
- type DefaultProps = ComponentDefaultProps<TextareaProps>;
73
+ type DefaultProps = ComponentDefaultProps<TextareaProps, keyof Omit<TextareaProps, 'name' | 'autocomplete' | 'maxLength'>>;
29
74
 
30
75
  /**
31
76
  * Default values for optional properties that have default fallback values in the component.
@@ -34,4 +79,9 @@ export const defaultProps: DefaultProps = {
34
79
  disabled: false,
35
80
  size: 'medium',
36
81
  resize: 'auto',
82
+ label: '',
83
+ value: '',
84
+ autoFocus: false,
85
+ readonly: false,
86
+ required: false,
37
87
  };
package/src/index.ts CHANGED
@@ -1,16 +1,23 @@
1
1
  import {
2
- LitElement, html, unsafeCSS, PropertyValues,
2
+ LitElement, html, unsafeCSS, PropertyValues, nothing,
3
3
  } from 'lit';
4
+
4
5
  import { property, query } from 'lit/decorators.js';
6
+ import { live } from 'lit/directives/live.js';
5
7
  import throttle from 'lodash.throttle';
6
8
 
7
- import { validPropertyValues, RtlMixin, defineCustomElement } from '@justeattakeaway/pie-webc-core';
9
+ import {
10
+ validPropertyValues, RtlMixin, defineCustomElement, FormControlMixin, wrapNativeEvent,
11
+ } from '@justeattakeaway/pie-webc-core';
8
12
 
13
+ import { ifDefined } from 'lit/directives/if-defined.js';
9
14
  import styles from './textarea.scss?inline';
10
15
  import {
11
16
  TextareaProps, defaultProps, sizes, resizeModes,
12
17
  } from './defs';
13
18
 
19
+ import '@justeattakeaway/pie-form-label';
20
+
14
21
  // Valid values available to consumers
15
22
  export * from './defs';
16
23
 
@@ -18,10 +25,15 @@ const componentSelector = 'pie-textarea';
18
25
 
19
26
  /**
20
27
  * @tagname pie-textarea
28
+ * @event {InputEvent} input - when the textarea value is changed.
29
+ * @event {CustomEvent} change - when the textarea value is changed.
21
30
  */
22
- export class PieTextarea extends RtlMixin(LitElement) implements TextareaProps {
31
+ export class PieTextarea extends FormControlMixin(RtlMixin(LitElement)) implements TextareaProps {
23
32
  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
24
33
 
34
+ @property({ type: String })
35
+ public value = defaultProps.value;
36
+
25
37
  @property({ type: Boolean, reflect: true })
26
38
  public disabled = defaultProps.disabled;
27
39
 
@@ -33,6 +45,27 @@ export class PieTextarea extends RtlMixin(LitElement) implements TextareaProps {
33
45
  @validPropertyValues(componentSelector, resizeModes, defaultProps.resize)
34
46
  public resize = defaultProps.resize;
35
47
 
48
+ @property({ type: String })
49
+ public label = defaultProps.label;
50
+
51
+ @property({ type: Number })
52
+ public maxLength: TextareaProps['maxLength'];
53
+
54
+ @property({ type: Boolean })
55
+ public readonly = defaultProps.readonly;
56
+
57
+ @property({ type: Boolean })
58
+ public autoFocus = defaultProps.autoFocus;
59
+
60
+ @property({ type: Boolean })
61
+ public required = defaultProps.required;
62
+
63
+ @property({ type: String })
64
+ public name?: TextareaProps['name'];
65
+
66
+ @property({ type: String })
67
+ public autocomplete?: TextareaProps['autocomplete'];
68
+
36
69
  @query('textarea')
37
70
  private _textarea!: HTMLTextAreaElement;
38
71
 
@@ -43,21 +76,119 @@ export class PieTextarea extends RtlMixin(LitElement) implements TextareaProps {
43
76
  }
44
77
  }, 100);
45
78
 
79
+ /**
80
+ * (Read-only) returns a ValidityState with the validity states that this element is in.
81
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
82
+ */
83
+ public get validity (): ValidityState {
84
+ return (this._textarea as HTMLTextAreaElement).validity;
85
+ }
86
+
87
+ /**
88
+ * Called after the disabled state of the element changes,
89
+ * either because the disabled attribute of this element was added or removed;
90
+ * or because the disabled state changed on a <fieldset> that's an ancestor of this element.
91
+ * @param disabled - The latest disabled state of the input.
92
+ */
93
+ public formDisabledCallback (disabled: boolean): void {
94
+ this.disabled = disabled;
95
+ }
96
+
97
+ /**
98
+ * Called when the form that owns this component is reset.
99
+ * Resets the value to the default value.
100
+ */
101
+ public formResetCallback (): void {
102
+ this.value = defaultProps.value;
103
+
104
+ this._internals.setFormValue(this.value);
105
+ }
106
+
107
+ protected firstUpdated (): void {
108
+ this.restrictInputLength();
109
+ this._internals.setFormValue(this.value);
110
+
111
+ this._textarea.addEventListener('keydown', this.handleKeyDown);
112
+ }
113
+
46
114
  private handleResize () {
47
115
  this._throttledResize();
48
116
  }
49
117
 
50
- updated (changedProperties: PropertyValues<this>) {
118
+ private restrictInputLength () {
119
+ if (this.label.length && this.maxLength && this.value.length > this.maxLength) {
120
+ const trimmedValue = this.value.slice(0, this.maxLength);
121
+ // Ensures that the internal text area is correctly trimmed and synced with our value.
122
+ // The live() directive does not solve this for us.
123
+ this._textarea.value = trimmedValue;
124
+ this.value = trimmedValue;
125
+ }
126
+ }
127
+
128
+ protected updated (changedProperties: PropertyValues<this>) {
129
+ if (changedProperties.has('value')) {
130
+ this.restrictInputLength();
131
+ this._internals.setFormValue(this.value);
132
+ }
133
+
51
134
  if (this.resize === 'auto' && (changedProperties.has('resize') || changedProperties.has('size'))) {
52
135
  this.handleResize();
53
136
  }
54
137
  }
55
138
 
139
+ /**
140
+ * Handles data processing in response to the input event. The native input event is left to bubble up.
141
+ * @param event - The input event.
142
+ */
143
+ private handleInput = (event: InputEvent) => {
144
+ this.value = (event.target as HTMLTextAreaElement).value;
145
+ this.restrictInputLength();
146
+ this._internals.setFormValue(this.value);
147
+
148
+ this.handleResize();
149
+ };
150
+
151
+ private handleChange = (event: Event) => {
152
+ // We have to create our own change event because the native one
153
+ // does not penetrate the shadow boundary.
154
+
155
+ // This is because some events set `composed` to `false`.
156
+ // Reference: https://javascript.info/shadow-dom-events#event-composed
157
+ const customChangeEvent = wrapNativeEvent(event);
158
+ this.dispatchEvent(customChangeEvent);
159
+ };
160
+
161
+ private handleKeyDown = (event: KeyboardEvent) => {
162
+ if (event.key === 'Enter') {
163
+ event.stopPropagation();
164
+ }
165
+ };
166
+
167
+ public disconnectedCallback (): void {
168
+ this._textarea.removeEventListener('keydown', this.handleKeyDown);
169
+ }
170
+
171
+ renderLabel (label: string, maxLength?: number) {
172
+ const characterCount = maxLength ? `${this.value.length}/${maxLength}` : undefined;
173
+
174
+ return label?.length
175
+ ? html`<pie-form-label for="${componentSelector}" trailing=${ifDefined(characterCount)}>${label}</pie-form-label>`
176
+ : nothing;
177
+ }
178
+
56
179
  render () {
57
180
  const {
58
181
  disabled,
59
182
  resize,
60
183
  size,
184
+ autocomplete,
185
+ autoFocus,
186
+ name,
187
+ readonly,
188
+ value,
189
+ required,
190
+ label,
191
+ maxLength,
61
192
  } = this;
62
193
 
63
194
  return html`
@@ -66,10 +197,19 @@ export class PieTextarea extends RtlMixin(LitElement) implements TextareaProps {
66
197
  data-test-id="pie-textarea-wrapper"
67
198
  data-pie-size="${size}"
68
199
  data-pie-resize="${resize}">
200
+ ${this.renderLabel(label, maxLength)}
69
201
  <textarea
70
- data-test-id="pie-textarea"
71
- @input=${this.handleResize}
202
+ id="${componentSelector}"
203
+ data-test-id="${componentSelector}"
204
+ name=${ifDefined(name)}
205
+ autocomplete=${ifDefined(autocomplete)}
206
+ .value=${live(value)}
207
+ ?autofocus=${autoFocus}
208
+ ?readonly=${readonly}
209
+ ?required=${required}
72
210
  ?disabled=${disabled}
211
+ @input=${this.handleInput}
212
+ @change=${this.handleChange}
73
213
  ></textarea>
74
214
  </div>`;
75
215
  }
package/src/react.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { createComponent } from '@lit/react';
2
+ import { createComponent, EventName } from '@lit/react';
3
3
  import { PieTextarea as PieTextareaLit } from './index';
4
4
  import { TextareaProps } from './defs';
5
5
 
@@ -10,10 +10,18 @@ const PieTextareaReact = createComponent({
10
10
  elementClass: PieTextareaLit,
11
11
  react: React,
12
12
  tagName: 'pie-textarea',
13
- events: {},
13
+ events: {
14
+ onInput: 'input' as EventName<InputEvent>, // when the textarea value is changed.
15
+ onChange: 'change' as EventName<CustomEvent>, // when the textarea value is changed.
16
+ },
14
17
  });
15
18
 
16
19
  type ReactBaseType = React.HTMLAttributes<HTMLTextAreaElement>
17
20
 
21
+ type PieTextareaEvents = {
22
+ onInput?: (event: InputEvent) => void;
23
+ onChange?: (event: CustomEvent) => void;
24
+ };
25
+
18
26
  export const PieTextarea = PieTextareaReact as React.ForwardRefExoticComponent<React.PropsWithoutRef<TextareaProps>
19
- & React.RefAttributes<PieTextareaLit> & ReactBaseType>;
27
+ & React.RefAttributes<PieTextareaLit> & PieTextareaEvents & ReactBaseType>;