@justeattakeaway/pie-select 0.1.0 → 0.3.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.
@@ -34,7 +34,7 @@
34
34
  "type": {
35
35
  "text": "DefaultProps"
36
36
  },
37
- "default": "{\n size: 'medium',\n status: 'default',\n disabled: false,\n}"
37
+ "default": "{\n size: 'medium',\n status: 'default',\n disabled: false,\n options: [],\n}"
38
38
  }
39
39
  ],
40
40
  "exports": [
@@ -118,14 +118,31 @@
118
118
  "privacy": "public",
119
119
  "attribute": "name"
120
120
  },
121
+ {
122
+ "kind": "field",
123
+ "name": "options",
124
+ "type": {
125
+ "text": "SelectProps['options']"
126
+ },
127
+ "privacy": "public",
128
+ "attribute": "options"
129
+ },
121
130
  {
122
131
  "kind": "field",
123
132
  "name": "focusTarget",
124
133
  "type": {
125
- "text": "HTMLElement"
134
+ "text": "HTMLSelectElement"
126
135
  },
127
136
  "privacy": "public"
128
137
  },
138
+ {
139
+ "kind": "field",
140
+ "name": "_select",
141
+ "type": {
142
+ "text": "HTMLSelectElement"
143
+ },
144
+ "privacy": "private"
145
+ },
129
146
  {
130
147
  "kind": "field",
131
148
  "name": "_leadingIconSlot",
@@ -143,15 +160,103 @@
143
160
  "privacy": "private",
144
161
  "default": "false"
145
162
  },
163
+ {
164
+ "kind": "field",
165
+ "name": "validity",
166
+ "type": {
167
+ "text": "ValidityState"
168
+ },
169
+ "privacy": "public",
170
+ "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",
171
+ "readonly": true
172
+ },
173
+ {
174
+ "kind": "method",
175
+ "name": "formDisabledCallback",
176
+ "privacy": "public",
177
+ "return": {
178
+ "type": {
179
+ "text": "void"
180
+ }
181
+ },
182
+ "parameters": [
183
+ {
184
+ "name": "disabled",
185
+ "type": {
186
+ "text": "boolean"
187
+ },
188
+ "description": "The latest disabled state of the select."
189
+ }
190
+ ],
191
+ "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."
192
+ },
193
+ {
194
+ "kind": "method",
195
+ "name": "formResetCallback",
196
+ "privacy": "public",
197
+ "return": {
198
+ "type": {
199
+ "text": "void"
200
+ }
201
+ },
202
+ "description": "Called when the form that owns this component is reset.\nResets the value to the default select value."
203
+ },
204
+ {
205
+ "kind": "field",
206
+ "name": "_handleChange",
207
+ "privacy": "private",
208
+ "description": "Captures the native change event and wraps it in a custom event.",
209
+ "parameters": [
210
+ {
211
+ "description": "The change event.",
212
+ "name": "event"
213
+ }
214
+ ]
215
+ },
146
216
  {
147
217
  "kind": "method",
148
218
  "name": "_handleLeadingIconSlotchange",
149
219
  "privacy": "private"
150
220
  },
221
+ {
222
+ "kind": "method",
223
+ "name": "renderChildren",
224
+ "privacy": "private",
225
+ "return": {
226
+ "type": {
227
+ "text": ""
228
+ }
229
+ },
230
+ "parameters": [
231
+ {
232
+ "name": "options",
233
+ "type": {
234
+ "text": "SelectProps['options']"
235
+ },
236
+ "description": "The options to render"
237
+ }
238
+ ],
239
+ "description": "Renders the options from the options property"
240
+ },
151
241
  {
152
242
  "kind": "method",
153
243
  "name": "renderAssistiveText",
154
- "privacy": "private"
244
+ "privacy": "private",
245
+ "return": {
246
+ "type": {
247
+ "text": ""
248
+ }
249
+ },
250
+ "description": "Renders the assistive text if available."
251
+ }
252
+ ],
253
+ "events": [
254
+ {
255
+ "type": {
256
+ "text": "CustomEvent"
257
+ },
258
+ "description": "when the selected option is changed.",
259
+ "name": "change"
155
260
  }
156
261
  ],
157
262
  "attributes": [
@@ -180,9 +285,20 @@
180
285
  "text": "SelectProps['name']"
181
286
  },
182
287
  "fieldName": "name"
288
+ },
289
+ {
290
+ "name": "options",
291
+ "type": {
292
+ "text": "SelectProps['options']"
293
+ },
294
+ "fieldName": "options"
183
295
  }
184
296
  ],
185
297
  "mixins": [
298
+ {
299
+ "name": "FormControlMixin",
300
+ "package": "@justeattakeaway/pie-webc-core"
301
+ },
186
302
  {
187
303
  "name": "RtlMixin",
188
304
  "package": "@justeattakeaway/pie-webc-core"
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
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';
5
6
  import type { RTLInterface } from '@justeattakeaway/pie-webc-core';
6
- import type { TemplateResult } from 'lit-html';
7
+ import type { TemplateResult } from 'lit';
7
8
 
8
9
  declare type DefaultProps = ComponentDefaultProps<SelectProps, keyof Omit<SelectProps, 'name' | 'assistiveText'>>;
9
10
 
@@ -11,6 +12,7 @@ export declare const defaultProps: DefaultProps;
11
12
 
12
13
  /**
13
14
  * @tagname pie-select
15
+ * @event {CustomEvent} change - when the selected option is changed.
14
16
  */
15
17
  export declare class PieSelect extends PieSelect_base implements SelectProps {
16
18
  static shadowRootOptions: {
@@ -23,16 +25,93 @@ export declare class PieSelect extends PieSelect_base implements SelectProps {
23
25
  status: "default" | "error";
24
26
  assistiveText: SelectProps['assistiveText'];
25
27
  name: SelectProps['name'];
26
- focusTarget: HTMLElement;
28
+ options: SelectProps['options'];
29
+ focusTarget: HTMLSelectElement;
30
+ private _select;
27
31
  private _leadingIconSlot;
28
32
  private _hasLeadingIcon;
33
+ protected firstUpdated(): void;
34
+ /**
35
+ * (Read-only) returns a ValidityState with the validity states that this element is in.
36
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
37
+ */
38
+ get validity(): ValidityState;
39
+ /**
40
+ * Called after the disabled state of the element changes,
41
+ * either because the disabled attribute of this element was added or removed;
42
+ * or because the disabled state changed on a <fieldset> that's an ancestor of this element.
43
+ * @param disabled - The latest disabled state of the select.
44
+ */
45
+ formDisabledCallback(disabled: boolean): void;
46
+ /**
47
+ * Called when the form that owns this component is reset.
48
+ * Resets the value to the default select value.
49
+ */
50
+ formResetCallback(): void;
51
+ /**
52
+ * Captures the native change event and wraps it in a custom event.
53
+ * @param event - The change event.
54
+ */
55
+ private _handleChange;
29
56
  private _handleLeadingIconSlotchange;
57
+ /**
58
+ * Renders the options from the options property
59
+ * @param options - The options to render
60
+ * @returns A template result with the rendered options
61
+ */
62
+ private renderChildren;
63
+ /**
64
+ * Renders the assistive text if available.
65
+ * @returns A template result with the assistive text
66
+ */
30
67
  private renderAssistiveText;
31
68
  render(): TemplateResult<1>;
32
69
  static styles: CSSResult;
33
70
  }
34
71
 
35
- declare const PieSelect_base: GenericConstructor<RTLInterface> & typeof LitElement;
72
+ declare const PieSelect_base: GenericConstructor<FormControlInterface> & GenericConstructor<RTLInterface> & typeof LitElement;
73
+
74
+ declare interface SelectOptionGroupProps {
75
+ /**
76
+ * What HTML element the option should be such option or optgroup.
77
+ */
78
+ tag: 'optgroup';
79
+ /**
80
+ * The label for the select option group.
81
+ */
82
+ label?: string;
83
+ /**
84
+ * The options within the select option group.
85
+ */
86
+ options: SelectOptionProps[];
87
+ /**
88
+ * Same as the HTML disabled attribute - indicates whether the select option group is disabled.
89
+ */
90
+ disabled?: boolean;
91
+ }
92
+
93
+ declare interface SelectOptionProps {
94
+ /**
95
+ * What HTML element the option should be such option or optgroup.
96
+ */
97
+ tag: 'option';
98
+ /**
99
+ * The text content to display for the select option.
100
+ */
101
+ text: string;
102
+ /**
103
+ * The value of the select option (used as a key/value pair in HTML forms with `name`).
104
+ */
105
+ value?: string;
106
+ /**
107
+ * Same as the HTML disabled attribute - indicates whether the select option is disabled.
108
+ */
109
+ disabled?: boolean;
110
+ /**
111
+ * Same as the HTML selected attribute - indicates whether the select option is selected by default when the page first loads.
112
+ */
113
+ selected?: boolean;
114
+ }
36
115
 
37
116
  export declare interface SelectProps {
38
117
  /**
@@ -44,7 +123,7 @@ export declare interface SelectProps {
44
123
  */
45
124
  disabled?: boolean;
46
125
  /**
47
- * An optional assistive text to display below the select element. Must be provided when the status is success or error.
126
+ * An optional assistive text to display below the select element. Must be provided when the status is error.
48
127
  */
49
128
  assistiveText?: string;
50
129
  /**
@@ -55,6 +134,10 @@ export declare interface SelectProps {
55
134
  * The name of the select (used as a key/value pair with `value`). This is required in order to work properly with forms.
56
135
  */
57
136
  name?: string;
137
+ /**
138
+ * The options to display in the select. Can be an array of option objects or option group objects.
139
+ */
140
+ options: (SelectOptionProps | SelectOptionGroupProps)[];
58
141
  }
59
142
 
60
143
  export declare const sizes: readonly ["small", "medium", "large"];
package/dist/index.js CHANGED
@@ -1,104 +1,171 @@
1
- import { LitElement as u, nothing as x, html as b, unsafeCSS as y } from "lit";
2
- import { RtlMixin as I, validPropertyValues as f, defineCustomElement as k } from "@justeattakeaway/pie-webc-core";
3
- import { property as c, query as z, queryAssignedElements as $, state as w } from "lit/decorators.js";
4
- import { ifDefined as d } from "lit/directives/if-defined.js";
5
- import { classMap as _ } from "lit/directives/class-map.js";
1
+ import { LitElement as b, html as c, nothing as _, unsafeCSS as $ } from "lit";
2
+ import { FormControlMixin as I, RtlMixin as k, wrapNativeEvent as z, validPropertyValues as f, defineCustomElement as w } from "@justeattakeaway/pie-webc-core";
3
+ import { property as l, query as m, queryAssignedElements as C, state as L } from "lit/decorators.js";
4
+ import { ifDefined as n } from "lit/directives/if-defined.js";
5
+ import { classMap as S } from "lit/directives/class-map.js";
6
+ import { live as T } from "lit/directives/live.js";
6
7
  import "@justeattakeaway/pie-icons-webc/dist/IconChevronDown.js";
7
- const L = "*,*:after,*:before{box-sizing:inherit}.c-select{--select-padding-block: var(--dt-spacing-c);--select-padding-inline-start: var(--dt-spacing-d);--select-padding-inline-end: var(--dt-spacing-h);--select-background-color: var(--dt-color-container-default);--select-text-color: var(--dt-color-content-default);--select-border-color: var(--dt-color-border-form);--select-font-size: calc(var(--dt-font-body-l-size) * 1px);--select-line-height: calc(var(--dt-font-body-l-line-height) * 1px);--select-height: 48px;--select-cursor: pointer;position:relative;color:var(--select-text-color);font-size:var(--select-font-size);line-height:var(--select-line-height)}.c-select select{height:var(--select-height);width:100%;border:1px solid var(--select-border-color);border-radius:var(--dt-radius-rounded-c);padding-inline-start:var(--select-padding-inline-start);padding-inline-end:var(--select-padding-inline-end);padding-block-start:var(--select-padding-block);padding-block-end:var(--select-padding-block);background-color:var(--select-background-color);font-size:inherit;line-height:inherit;color:inherit;outline:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:var(--select-cursor)}.c-select select: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-select.c-select--small{--select-padding-block: var(--dt-spacing-b);--select-height: 40px}.c-select.c-select--large{--select-padding-block: var(--dt-spacing-d);--select-height: 56px}.c-select.c-select--error{--select-border-color: var(--dt-color-support-error)}.c-select.c-select--withLeadingIcon{--select-padding-inline-start: calc(var(--dt-spacing-h) - var(--dt-spacing-a))}.c-select ::slotted([slot=leadingIcon]),.c-select .c-select-trailingIcon{position:absolute;top:50%;transform:translateY(-50%);pointer-events:none}.c-select:not(.is-disabled) ::slotted([slot=leadingIcon]),.c-select:not(.is-disabled) .c-select-trailingIcon{color:var(--dt-color-content-subdued)}.c-select ::slotted([slot=leadingIcon]){--icon-display-override: block;--icon-size-override: 24px;inset-inline-start:var(--dt-spacing-d)}.c-select .c-select-trailingIcon{inset-inline-end:var(--dt-spacing-d)}@media (hover: hover){.c-select:hover{--select-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))))}@supports (background-color: color-mix(in srgb,black,white)){.c-select:hover{--select-background-color: color-mix(in srgb, var(--dt-color-hover-01-bg) var(--dt-color-hover-01), var(--dt-color-container-default))}}}.c-select.is-disabled{--select-background-color: var(--dt-color-disabled-01);--select-border-color: var(--dt-color-disabled-01);--select-text-color: var(--dt-color-content-disabled);--select-cursor: auto}", T = ["small", "medium", "large"], S = ["default", "error"], i = {
8
+ import "@justeattakeaway/pie-assistive-text";
9
+ const E = "*,*:after,*:before{box-sizing:inherit}.c-select{--select-padding-block: var(--dt-spacing-c);--select-padding-inline-start: var(--dt-spacing-d);--select-padding-inline-end: var(--dt-spacing-h);--select-background-color: var(--dt-color-container-default);--select-text-color: var(--dt-color-content-default);--select-border-color: var(--dt-color-border-form);--select-font-size: calc(var(--dt-font-body-l-size) * 1px);--select-line-height: calc(var(--dt-font-body-l-line-height) * 1px);--select-height: 48px;--select-cursor: pointer;position:relative;color:var(--select-text-color);font-size:var(--select-font-size);line-height:var(--select-line-height)}.c-select select{height:var(--select-height);width:100%;border:1px solid var(--select-border-color);border-radius:var(--dt-radius-rounded-c);padding-inline-start:var(--select-padding-inline-start);padding-inline-end:var(--select-padding-inline-end);padding-block-start:var(--select-padding-block);padding-block-end:var(--select-padding-block);background-color:var(--select-background-color);font-size:inherit;line-height:inherit;color:inherit;outline:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:var(--select-cursor)}.c-select select: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-select.c-select--small{--select-padding-block: var(--dt-spacing-b);--select-height: 40px}.c-select.c-select--large{--select-padding-block: var(--dt-spacing-d);--select-height: 56px}.c-select.c-select--error{--select-border-color: var(--dt-color-support-error)}.c-select.c-select--withLeadingIcon{--select-padding-inline-start: calc(var(--dt-spacing-h) - var(--dt-spacing-a))}.c-select ::slotted([slot=leadingIcon]),.c-select .c-select-trailingIcon{position:absolute;top:50%;transform:translateY(-50%);pointer-events:none}.c-select:not(.is-disabled) ::slotted([slot=leadingIcon]),.c-select:not(.is-disabled) .c-select-trailingIcon{color:var(--dt-color-content-subdued)}.c-select ::slotted([slot=leadingIcon]){--icon-display-override: block;--icon-size-override: 24px;inset-inline-start:var(--dt-spacing-d)}.c-select .c-select-trailingIcon{inset-inline-end:var(--dt-spacing-d)}@media (hover: hover){.c-select:hover{--select-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))))}@supports (background-color: color-mix(in srgb,black,white)){.c-select:hover{--select-background-color: color-mix(in srgb, var(--dt-color-hover-01-bg) var(--dt-color-hover-01), var(--dt-color-container-default))}}}.c-select.is-disabled{--select-background-color: var(--dt-color-disabled-01);--select-border-color: var(--dt-color-disabled-01);--select-text-color: var(--dt-color-content-disabled);--select-cursor: auto}", A = ["small", "medium", "large"], F = ["default", "error"], r = {
8
10
  size: "medium",
9
11
  status: "default",
10
- disabled: !1
12
+ disabled: !1,
13
+ options: []
11
14
  };
12
- var C = Object.defineProperty, s = (h, a, o, n) => {
13
- for (var t = void 0, r = h.length - 1, l; r >= 0; r--)
14
- (l = h[r]) && (t = l(a, o, t) || t);
15
- return t && C(a, o, t), t;
15
+ var V = Object.defineProperty, o = (v, t, e, d) => {
16
+ for (var i = void 0, a = v.length - 1, p; a >= 0; a--)
17
+ (p = v[a]) && (i = p(t, e, i) || i);
18
+ return i && V(t, e, i), i;
16
19
  };
17
- const v = "pie-select", g = "assistive-text", p = class p extends I(u) {
20
+ const g = "pie-select", u = "assistive-text", h = class h extends I(k(b)) {
18
21
  constructor() {
19
- super(...arguments), this.size = i.size, this.disabled = i.disabled, this.status = i.status, this._hasLeadingIcon = !1;
22
+ super(...arguments), this.size = r.size, this.disabled = r.disabled, this.status = r.status, this.options = r.options, this._hasLeadingIcon = !1, this._handleChange = (t) => {
23
+ const e = z(t);
24
+ this.dispatchEvent(e), this._internals.setFormValue(this._select.value);
25
+ };
26
+ }
27
+ firstUpdated() {
28
+ this._internals.setFormValue(this._select.value);
29
+ }
30
+ /**
31
+ * (Read-only) returns a ValidityState with the validity states that this element is in.
32
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
33
+ */
34
+ get validity() {
35
+ return this._select.validity;
36
+ }
37
+ /**
38
+ * Called after the disabled state of the element changes,
39
+ * either because the disabled attribute of this element was added or removed;
40
+ * or because the disabled state changed on a <fieldset> that's an ancestor of this element.
41
+ * @param disabled - The latest disabled state of the select.
42
+ */
43
+ formDisabledCallback(t) {
44
+ this.disabled = t;
45
+ }
46
+ /**
47
+ * Called when the form that owns this component is reset.
48
+ * Resets the value to the default select value.
49
+ */
50
+ formResetCallback() {
51
+ const t = this._select.querySelector("option[selected]");
52
+ this._select.value = (t == null ? void 0 : t.getAttribute("value")) ?? "", this._select.selectedIndex = t ? this._select.selectedIndex : 0, this._internals.setFormValue(this._select.value);
20
53
  }
21
54
  _handleLeadingIconSlotchange() {
22
55
  this._hasLeadingIcon = !!this._leadingIconSlot.length;
23
56
  }
57
+ /**
58
+ * Renders the options from the options property
59
+ * @param options - The options to render
60
+ * @returns A template result with the rendered options
61
+ */
62
+ renderChildren(t) {
63
+ return c`
64
+ ${t.map((e) => e.tag === "optgroup" ? c`
65
+ <optgroup
66
+ ?disabled="${e.disabled}"
67
+ label="${n(e.label)}">
68
+ ${this.renderChildren(e.options)}
69
+ </optgroup>
70
+ ` : c`
71
+ <option
72
+ .value="${T(e.value)}"
73
+ ?disabled="${e.disabled}"
74
+ ?selected="${e.selected}">
75
+ ${e.text}
76
+ </option>
77
+ `)}
78
+ `;
79
+ }
80
+ /**
81
+ * Renders the assistive text if available.
82
+ * @returns A template result with the assistive text
83
+ */
24
84
  renderAssistiveText() {
25
- return this.assistiveText ? b`
85
+ return this.assistiveText ? c`
26
86
  <pie-assistive-text
27
- id="${g}"
28
- variant=${d(this.status)}
29
- data-test-id="pie-textarea-assistive-text">
87
+ id="${u}"
88
+ variant="${n(this.status)}"
89
+ data-test-id="pie-select-assistive-text">
30
90
  ${this.assistiveText}
31
91
  </pie-assistive-text>
32
- ` : x;
92
+ ` : _;
33
93
  }
34
94
  render() {
35
95
  const {
36
- assistiveText: a,
37
- disabled: o,
38
- status: n,
39
- size: t,
40
- name: r,
41
- _hasLeadingIcon: l
42
- } = this, m = {
96
+ assistiveText: t,
97
+ disabled: e,
98
+ status: d,
99
+ size: i,
100
+ name: a,
101
+ options: p,
102
+ _hasLeadingIcon: x
103
+ } = this, y = {
43
104
  "c-select": !0,
44
- [`c-select--${t}`]: !0,
45
- [`c-select--${n}`]: !0,
46
- "c-select--withLeadingIcon": l,
47
- "is-disabled": o
105
+ [`c-select--${i}`]: !0,
106
+ [`c-select--${d}`]: !0,
107
+ "c-select--withLeadingIcon": x,
108
+ "is-disabled": e
48
109
  };
49
- return b`
110
+ return c`
50
111
  <div
51
- class="${_(m)}"
112
+ class="${S(y)}"
52
113
  data-test-id="pie-select-shell">
53
- <slot name="leadingIcon" @slotchange=${this._handleLeadingIconSlotchange}></slot>
54
- <select
55
- name=${d(r)}
56
- ?disabled=${o}
57
- aria-describedby=${d(a ? g : void 0)}
58
- aria-invalid=${n === "error" ? "true" : "false"}
59
- aria-errormessage="${d(n === "error" ? g : void 0)}">
60
- <option value="dog">Dog</option>
61
- <option value="cat">Cat</option>
62
- <option value="hamster">Hamster</option>
63
- </select>
64
- <icon-chevron-down size='s' class='c-select-trailingIcon'></icon-chevron-down>
114
+ <slot name="leadingIcon" @slotchange=${this._handleLeadingIconSlotchange}></slot>
115
+ <select
116
+ data-test-id="pie-select-element"
117
+ name="${n(a)}"
118
+ ?disabled="${e}"
119
+ aria-describedby="${n(t ? u : void 0)}"
120
+ aria-invalid="${d === "error" ? "true" : "false"}"
121
+ aria-errormessage="${n(d === "error" ? u : void 0)}"
122
+ @change=${this._handleChange}>
123
+ ${this.renderChildren(p)}
124
+ </select>
125
+ <icon-chevron-down size='s' class='c-select-trailingIcon'></icon-chevron-down>
65
126
  </div>
66
127
  ${this.renderAssistiveText()}
67
128
  `;
68
129
  }
69
130
  };
70
- p.shadowRootOptions = { ...u.shadowRootOptions, delegatesFocus: !0 }, p.styles = y(L);
71
- let e = p;
72
- s([
73
- c({ type: String }),
74
- f(v, T, i.size)
75
- ], e.prototype, "size");
76
- s([
77
- c({ type: Boolean })
78
- ], e.prototype, "disabled");
79
- s([
80
- c({ type: String }),
81
- f(v, S, i.status)
82
- ], e.prototype, "status");
83
- s([
84
- c({ type: String })
85
- ], e.prototype, "assistiveText");
86
- s([
87
- c({ type: String })
88
- ], e.prototype, "name");
89
- s([
90
- z("select")
91
- ], e.prototype, "focusTarget");
92
- s([
93
- $({ slot: "leadingIcon", flatten: !0 })
94
- ], e.prototype, "_leadingIconSlot");
95
- s([
96
- w()
97
- ], e.prototype, "_hasLeadingIcon");
98
- k(v, e);
131
+ h.shadowRootOptions = { ...b.shadowRootOptions, delegatesFocus: !0 }, h.styles = $(E);
132
+ let s = h;
133
+ o([
134
+ l({ type: String }),
135
+ f(g, A, r.size)
136
+ ], s.prototype, "size");
137
+ o([
138
+ l({ type: Boolean })
139
+ ], s.prototype, "disabled");
140
+ o([
141
+ l({ type: String }),
142
+ f(g, F, r.status)
143
+ ], s.prototype, "status");
144
+ o([
145
+ l({ type: String })
146
+ ], s.prototype, "assistiveText");
147
+ o([
148
+ l({ type: String })
149
+ ], s.prototype, "name");
150
+ o([
151
+ l({ type: Array })
152
+ ], s.prototype, "options");
153
+ o([
154
+ m("select")
155
+ ], s.prototype, "focusTarget");
156
+ o([
157
+ m("select")
158
+ ], s.prototype, "_select");
159
+ o([
160
+ C({ slot: "leadingIcon", flatten: !0 })
161
+ ], s.prototype, "_leadingIconSlot");
162
+ o([
163
+ L()
164
+ ], s.prototype, "_hasLeadingIcon");
165
+ w(g, s);
99
166
  export {
100
- e as PieSelect,
101
- i as defaultProps,
102
- T as sizes,
103
- S as statusTypes
167
+ s as PieSelect,
168
+ r as defaultProps,
169
+ A as sizes,
170
+ F as statusTypes
104
171
  };
package/dist/react.d.ts CHANGED
@@ -1,19 +1,21 @@
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';
5
6
  import * as React_2 from 'react';
6
7
  import type { RTLInterface } from '@justeattakeaway/pie-webc-core';
7
- import type { TemplateResult } from 'lit-html';
8
+ import type { TemplateResult } from 'lit';
8
9
 
9
10
  declare type DefaultProps = ComponentDefaultProps<SelectProps, keyof Omit<SelectProps, 'name' | 'assistiveText'>>;
10
11
 
11
12
  export declare const defaultProps: DefaultProps;
12
13
 
13
- export declare const PieSelect: React_2.ForwardRefExoticComponent<SelectProps & React_2.RefAttributes<PieSelect_2> & ReactBaseType>;
14
+ export declare const PieSelect: React_2.ForwardRefExoticComponent<SelectProps & React_2.RefAttributes<PieSelect_2> & PieSelectEvents & ReactBaseType>;
14
15
 
15
16
  /**
16
17
  * @tagname pie-select
18
+ * @event {CustomEvent} change - when the selected option is changed.
17
19
  */
18
20
  declare class PieSelect_2 extends PieSelect_base implements SelectProps {
19
21
  static shadowRootOptions: {
@@ -26,19 +28,100 @@ declare class PieSelect_2 extends PieSelect_base implements SelectProps {
26
28
  status: "default" | "error";
27
29
  assistiveText: SelectProps['assistiveText'];
28
30
  name: SelectProps['name'];
29
- focusTarget: HTMLElement;
31
+ options: SelectProps['options'];
32
+ focusTarget: HTMLSelectElement;
33
+ private _select;
30
34
  private _leadingIconSlot;
31
35
  private _hasLeadingIcon;
36
+ protected firstUpdated(): void;
37
+ /**
38
+ * (Read-only) returns a ValidityState with the validity states that this element is in.
39
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
40
+ */
41
+ get validity(): ValidityState;
42
+ /**
43
+ * Called after the disabled state of the element changes,
44
+ * either because the disabled attribute of this element was added or removed;
45
+ * or because the disabled state changed on a <fieldset> that's an ancestor of this element.
46
+ * @param disabled - The latest disabled state of the select.
47
+ */
48
+ formDisabledCallback(disabled: boolean): void;
49
+ /**
50
+ * Called when the form that owns this component is reset.
51
+ * Resets the value to the default select value.
52
+ */
53
+ formResetCallback(): void;
54
+ /**
55
+ * Captures the native change event and wraps it in a custom event.
56
+ * @param event - The change event.
57
+ */
58
+ private _handleChange;
32
59
  private _handleLeadingIconSlotchange;
60
+ /**
61
+ * Renders the options from the options property
62
+ * @param options - The options to render
63
+ * @returns A template result with the rendered options
64
+ */
65
+ private renderChildren;
66
+ /**
67
+ * Renders the assistive text if available.
68
+ * @returns A template result with the assistive text
69
+ */
33
70
  private renderAssistiveText;
34
71
  render(): TemplateResult<1>;
35
72
  static styles: CSSResult;
36
73
  }
37
74
 
38
- declare const PieSelect_base: GenericConstructor<RTLInterface> & typeof LitElement;
75
+ declare const PieSelect_base: GenericConstructor<FormControlInterface> & GenericConstructor<RTLInterface> & typeof LitElement;
76
+
77
+ declare type PieSelectEvents = {
78
+ onChange?: (event: CustomEvent) => void;
79
+ };
39
80
 
40
81
  declare type ReactBaseType = React_2.HTMLAttributes<HTMLSelectElement>;
41
82
 
83
+ declare interface SelectOptionGroupProps {
84
+ /**
85
+ * What HTML element the option should be such option or optgroup.
86
+ */
87
+ tag: 'optgroup';
88
+ /**
89
+ * The label for the select option group.
90
+ */
91
+ label?: string;
92
+ /**
93
+ * The options within the select option group.
94
+ */
95
+ options: SelectOptionProps[];
96
+ /**
97
+ * Same as the HTML disabled attribute - indicates whether the select option group is disabled.
98
+ */
99
+ disabled?: boolean;
100
+ }
101
+
102
+ declare interface SelectOptionProps {
103
+ /**
104
+ * What HTML element the option should be such option or optgroup.
105
+ */
106
+ tag: 'option';
107
+ /**
108
+ * The text content to display for the select option.
109
+ */
110
+ text: string;
111
+ /**
112
+ * The value of the select option (used as a key/value pair in HTML forms with `name`).
113
+ */
114
+ value?: string;
115
+ /**
116
+ * Same as the HTML disabled attribute - indicates whether the select option is disabled.
117
+ */
118
+ disabled?: boolean;
119
+ /**
120
+ * Same as the HTML selected attribute - indicates whether the select option is selected by default when the page first loads.
121
+ */
122
+ selected?: boolean;
123
+ }
124
+
42
125
  export declare interface SelectProps {
43
126
  /**
44
127
  * The size of the select component. Can be `small`, `medium` or `large`. Defaults to `medium`.
@@ -49,7 +132,7 @@ export declare interface SelectProps {
49
132
  */
50
133
  disabled?: boolean;
51
134
  /**
52
- * An optional assistive text to display below the select element. Must be provided when the status is success or error.
135
+ * An optional assistive text to display below the select element. Must be provided when the status is error.
53
136
  */
54
137
  assistiveText?: string;
55
138
  /**
@@ -60,6 +143,10 @@ export declare interface SelectProps {
60
143
  * The name of the select (used as a key/value pair with `value`). This is required in order to work properly with forms.
61
144
  */
62
145
  name?: string;
146
+ /**
147
+ * The options to display in the select. Can be an array of option objects or option group objects.
148
+ */
149
+ options: (SelectOptionProps | SelectOptionGroupProps)[];
63
150
  }
64
151
 
65
152
  export declare const sizes: readonly ["small", "medium", "large"];
package/dist/react.js CHANGED
@@ -1,13 +1,16 @@
1
1
  import * as e from "react";
2
2
  import { createComponent as t } from "@lit/react";
3
- import { PieSelect as s } from "./index.js";
3
+ import { PieSelect as a } from "./index.js";
4
4
  import { defaultProps as l, sizes as p, statusTypes as n } from "./index.js";
5
5
  const o = t({
6
6
  displayName: "PieSelect",
7
- elementClass: s,
7
+ elementClass: a,
8
8
  react: e,
9
9
  tagName: "pie-select",
10
- events: {}
10
+ events: {
11
+ onChange: "change"
12
+ // when the selected option is changed.
13
+ }
11
14
  }), r = o;
12
15
  export {
13
16
  r as PieSelect,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-select",
3
3
  "description": "PIE Design System Select built using Web Components",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -33,11 +33,12 @@
33
33
  "license": "Apache-2.0",
34
34
  "devDependencies": {
35
35
  "@custom-elements-manifest/analyzer": "0.9.0",
36
- "@justeattakeaway/pie-components-config": "0.18.0",
36
+ "@justeattakeaway/pie-components-config": "0.18.1",
37
37
  "@justeattakeaway/pie-css": "0.16.0",
38
38
  "cem-plugin-module-file-extensions": "0.0.5"
39
39
  },
40
40
  "dependencies": {
41
+ "@justeattakeaway/pie-assistive-text": "0.8.5",
41
42
  "@justeattakeaway/pie-webc-core": "0.24.2"
42
43
  },
43
44
  "volta": {
package/src/defs.ts CHANGED
@@ -4,6 +4,55 @@ export const sizes = ['small', 'medium', 'large'] as const;
4
4
 
5
5
  export const statusTypes = ['default', 'error'] as const;
6
6
 
7
+ interface SelectOptionProps {
8
+ /**
9
+ * What HTML element the option should be such option or optgroup.
10
+ */
11
+ tag: 'option';
12
+
13
+ /**
14
+ * The text content to display for the select option.
15
+ */
16
+ text: string;
17
+
18
+ /**
19
+ * The value of the select option (used as a key/value pair in HTML forms with `name`).
20
+ */
21
+ value?: string;
22
+
23
+ /**
24
+ * Same as the HTML disabled attribute - indicates whether the select option is disabled.
25
+ */
26
+ disabled?: boolean;
27
+
28
+ /**
29
+ * Same as the HTML selected attribute - indicates whether the select option is selected by default when the page first loads.
30
+ */
31
+ selected?: boolean;
32
+ }
33
+
34
+ interface SelectOptionGroupProps {
35
+ /**
36
+ * What HTML element the option should be such option or optgroup.
37
+ */
38
+ tag: 'optgroup';
39
+
40
+ /**
41
+ * The label for the select option group.
42
+ */
43
+ label?: string;
44
+
45
+ /**
46
+ * The options within the select option group.
47
+ */
48
+ options: SelectOptionProps[];
49
+
50
+ /**
51
+ * Same as the HTML disabled attribute - indicates whether the select option group is disabled.
52
+ */
53
+ disabled?: boolean;
54
+ }
55
+
7
56
  export interface SelectProps {
8
57
  /**
9
58
  * The size of the select component. Can be `small`, `medium` or `large`. Defaults to `medium`.
@@ -16,7 +65,7 @@ export interface SelectProps {
16
65
  disabled?: boolean;
17
66
 
18
67
  /**
19
- * An optional assistive text to display below the select element. Must be provided when the status is success or error.
68
+ * An optional assistive text to display below the select element. Must be provided when the status is error.
20
69
  */
21
70
  assistiveText?: string;
22
71
 
@@ -29,12 +78,18 @@ export interface SelectProps {
29
78
  * The name of the select (used as a key/value pair with `value`). This is required in order to work properly with forms.
30
79
  */
31
80
  name?: string;
81
+
82
+ /**
83
+ * The options to display in the select. Can be an array of option objects or option group objects.
84
+ */
85
+ options: (SelectOptionProps | SelectOptionGroupProps)[];
32
86
  }
33
87
 
34
- type DefaultProps = ComponentDefaultProps<SelectProps, keyof Omit<SelectProps, 'name' | 'assistiveText' >>;
88
+ type DefaultProps = ComponentDefaultProps<SelectProps, keyof Omit<SelectProps, 'name' | 'assistiveText'>>;
35
89
 
36
90
  export const defaultProps: DefaultProps = {
37
91
  size: 'medium',
38
92
  status: 'default',
39
93
  disabled: false,
94
+ options: [],
40
95
  };
package/src/index.ts CHANGED
@@ -3,11 +3,14 @@ import {
3
3
  html,
4
4
  nothing,
5
5
  unsafeCSS,
6
+ type TemplateResult,
6
7
  } from 'lit';
7
8
  import {
9
+ FormControlMixin,
8
10
  RtlMixin,
9
11
  defineCustomElement,
10
12
  validPropertyValues,
13
+ wrapNativeEvent,
11
14
  } from '@justeattakeaway/pie-webc-core';
12
15
  import {
13
16
  property,
@@ -17,7 +20,9 @@ import {
17
20
  } from 'lit/decorators.js';
18
21
  import { ifDefined } from 'lit/directives/if-defined.js';
19
22
  import { classMap, type ClassInfo } from 'lit/directives/class-map.js';
23
+ import { live } from 'lit/directives/live.js';
20
24
  import '@justeattakeaway/pie-icons-webc/dist/IconChevronDown.js';
25
+ import '@justeattakeaway/pie-assistive-text';
21
26
 
22
27
  import styles from './select.scss?inline';
23
28
  import {
@@ -35,8 +40,9 @@ const assistiveTextIdValue = 'assistive-text';
35
40
 
36
41
  /**
37
42
  * @tagname pie-select
43
+ * @event {CustomEvent} change - when the selected option is changed.
38
44
  */
39
- export class PieSelect extends RtlMixin(LitElement) implements SelectProps {
45
+ export class PieSelect extends FormControlMixin(RtlMixin(LitElement)) implements SelectProps {
40
46
  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
41
47
 
42
48
  @property({ type: String })
@@ -56,8 +62,14 @@ export class PieSelect extends RtlMixin(LitElement) implements SelectProps {
56
62
  @property({ type: String })
57
63
  public name: SelectProps['name'];
58
64
 
65
+ @property({ type: Array })
66
+ public options: SelectProps['options'] = defaultProps.options;
67
+
68
+ @query('select')
69
+ public focusTarget!: HTMLSelectElement;
70
+
59
71
  @query('select')
60
- public focusTarget!: HTMLElement;
72
+ private _select!: HTMLSelectElement;
61
73
 
62
74
  @queryAssignedElements({ slot: 'leadingIcon', flatten: true })
63
75
  private _leadingIconSlot!: Array<HTMLElement>;
@@ -65,11 +77,94 @@ export class PieSelect extends RtlMixin(LitElement) implements SelectProps {
65
77
  @state()
66
78
  private _hasLeadingIcon = false;
67
79
 
80
+ protected firstUpdated (): void {
81
+ this._internals.setFormValue(this._select.value);
82
+ }
83
+
84
+ /**
85
+ * (Read-only) returns a ValidityState with the validity states that this element is in.
86
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
87
+ */
88
+ public get validity (): ValidityState {
89
+ return this._select.validity;
90
+ }
91
+
92
+ /**
93
+ * Called after the disabled state of the element changes,
94
+ * either because the disabled attribute of this element was added or removed;
95
+ * or because the disabled state changed on a <fieldset> that's an ancestor of this element.
96
+ * @param disabled - The latest disabled state of the select.
97
+ */
98
+ public formDisabledCallback (disabled: boolean): void {
99
+ this.disabled = disabled;
100
+ }
101
+
102
+ /**
103
+ * Called when the form that owns this component is reset.
104
+ * Resets the value to the default select value.
105
+ */
106
+ public formResetCallback (): void {
107
+ const selected = this._select.querySelector('option[selected]');
108
+ this._select.value = selected?.getAttribute('value') ?? '';
109
+ this._select.selectedIndex = selected ? this._select.selectedIndex : 0;
110
+ this._internals.setFormValue(this._select.value);
111
+ }
112
+
113
+ /**
114
+ * Captures the native change event and wraps it in a custom event.
115
+ * @param event - The change event.
116
+ */
117
+ private _handleChange = (event: Event) => {
118
+ // We have to create our own change event because the native one
119
+ // does not penetrate the shadow boundary.
120
+
121
+ // This is because some events set `composed` to `false`.
122
+ // Reference: https://javascript.info/shadow-dom-events#event-composed
123
+ const customChangeEvent = wrapNativeEvent(event);
124
+ this.dispatchEvent(customChangeEvent);
125
+
126
+ this._internals.setFormValue(this._select.value);
127
+ };
128
+
68
129
  private _handleLeadingIconSlotchange () {
69
130
  this._hasLeadingIcon = Boolean(this._leadingIconSlot.length);
70
131
  }
71
132
 
72
- private renderAssistiveText () {
133
+ /**
134
+ * Renders the options from the options property
135
+ * @param options - The options to render
136
+ * @returns A template result with the rendered options
137
+ */
138
+ private renderChildren (options: SelectProps['options']): TemplateResult {
139
+ return html`
140
+ ${options.map((option) => {
141
+ if (option.tag === 'optgroup') {
142
+ return html`
143
+ <optgroup
144
+ ?disabled="${option.disabled}"
145
+ label="${ifDefined(option.label)}">
146
+ ${this.renderChildren(option.options)}
147
+ </optgroup>
148
+ `;
149
+ }
150
+
151
+ return html`
152
+ <option
153
+ .value="${live(option.value)}"
154
+ ?disabled="${option.disabled}"
155
+ ?selected="${option.selected}">
156
+ ${option.text}
157
+ </option>
158
+ `;
159
+ })}
160
+ `;
161
+ }
162
+
163
+ /**
164
+ * Renders the assistive text if available.
165
+ * @returns A template result with the assistive text
166
+ */
167
+ private renderAssistiveText (): TemplateResult | typeof nothing {
73
168
  if (!this.assistiveText) {
74
169
  return nothing;
75
170
  }
@@ -77,8 +172,8 @@ export class PieSelect extends RtlMixin(LitElement) implements SelectProps {
77
172
  return html`
78
173
  <pie-assistive-text
79
174
  id="${assistiveTextIdValue}"
80
- variant=${ifDefined(this.status)}
81
- data-test-id="pie-textarea-assistive-text">
175
+ variant="${ifDefined(this.status)}"
176
+ data-test-id="pie-select-assistive-text">
82
177
  ${this.assistiveText}
83
178
  </pie-assistive-text>
84
179
  `;
@@ -91,6 +186,7 @@ export class PieSelect extends RtlMixin(LitElement) implements SelectProps {
91
186
  status,
92
187
  size,
93
188
  name,
189
+ options,
94
190
  _hasLeadingIcon,
95
191
  } = this;
96
192
 
@@ -106,18 +202,18 @@ export class PieSelect extends RtlMixin(LitElement) implements SelectProps {
106
202
  <div
107
203
  class="${classMap(classes)}"
108
204
  data-test-id="pie-select-shell">
109
- <slot name="leadingIcon" @slotchange=${this._handleLeadingIconSlotchange}></slot>
110
- <select
111
- name=${ifDefined(name)}
112
- ?disabled=${disabled}
113
- aria-describedby=${ifDefined(assistiveText ? assistiveTextIdValue : undefined)}
114
- aria-invalid=${status === 'error' ? 'true' : 'false'}
115
- aria-errormessage="${ifDefined(status === 'error' ? assistiveTextIdValue : undefined)}">
116
- <option value="dog">Dog</option>
117
- <option value="cat">Cat</option>
118
- <option value="hamster">Hamster</option>
119
- </select>
120
- <icon-chevron-down size='s' class='c-select-trailingIcon'></icon-chevron-down>
205
+ <slot name="leadingIcon" @slotchange=${this._handleLeadingIconSlotchange}></slot>
206
+ <select
207
+ data-test-id="pie-select-element"
208
+ name="${ifDefined(name)}"
209
+ ?disabled="${disabled}"
210
+ aria-describedby="${ifDefined(assistiveText ? assistiveTextIdValue : undefined)}"
211
+ aria-invalid="${status === 'error' ? 'true' : 'false'}"
212
+ aria-errormessage="${ifDefined(status === 'error' ? assistiveTextIdValue : undefined)}"
213
+ @change=${this._handleChange}>
214
+ ${this.renderChildren(options)}
215
+ </select>
216
+ <icon-chevron-down size='s' class='c-select-trailingIcon'></icon-chevron-down>
121
217
  </div>
122
218
  ${this.renderAssistiveText()}
123
219
  `;
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, type EventName } from '@lit/react';
3
3
  import { PieSelect as PieSelectLit } from './index';
4
4
  import { type SelectProps } from './defs';
5
5
 
@@ -10,10 +10,16 @@ const PieSelectReact = createComponent({
10
10
  elementClass: PieSelectLit,
11
11
  react: React,
12
12
  tagName: 'pie-select',
13
- events: {},
13
+ events: {
14
+ onChange: 'change' as EventName<CustomEvent>, // when the selected option is changed.
15
+ },
14
16
  });
15
17
 
16
18
  type ReactBaseType = React.HTMLAttributes<HTMLSelectElement>
17
19
 
20
+ type PieSelectEvents = {
21
+ onChange?: (event: CustomEvent) => void;
22
+ };
23
+
18
24
  export const PieSelect = PieSelectReact as React.ForwardRefExoticComponent<React.PropsWithoutRef<SelectProps>
19
- & React.RefAttributes<PieSelectLit> & ReactBaseType>;
25
+ & React.RefAttributes<PieSelectLit> & PieSelectEvents & ReactBaseType>;