@justeattakeaway/pie-chip 0.12.11 → 0.14.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/src/index.ts CHANGED
@@ -7,12 +7,16 @@ import { ifDefined } from 'lit/directives/if-defined.js';
7
7
  import { classMap } from 'lit/directives/class-map.js';
8
8
 
9
9
  import {
10
- validPropertyValues, dispatchCustomEvent,
10
+ validPropertyValues,
11
11
  safeCustomElement,
12
+ DelegatesFocusMixin,
12
13
  } from '@justeattakeaway/pie-webc-core';
13
14
  import styles from './chip.scss?inline';
14
15
  import {
15
- type ChipProps, variants, ON_CHIP_CLOSE_EVENT, defaultProps,
16
+ type ChipProps,
17
+ variants,
18
+ types,
19
+ defaultProps,
16
20
  } from './defs';
17
21
  import '@justeattakeaway/pie-icons-webc/dist/IconCloseCircleFilled.js';
18
22
  import '@justeattakeaway/pie-spinner';
@@ -26,21 +30,26 @@ const componentSelector = 'pie-chip';
26
30
  * @tagname pie-chip
27
31
  * @slot icon - The icon slot
28
32
  * @slot - Default slot
29
- * @event {CustomEvent} pie-chip-close - when a user clicks close button.
33
+ * @event {Event} close - when a user clicks the close button.
34
+ * @event {Event} change - when a user interacts with the chip of type checkbox.
30
35
  */
31
36
  @safeCustomElement('pie-chip')
32
- export class PieChip extends PieElement implements ChipProps {
37
+ export class PieChip extends DelegatesFocusMixin(PieElement) implements ChipProps {
33
38
  @property({ type: String })
34
39
  @validPropertyValues(componentSelector, variants, defaultProps.variant)
35
40
  public variant = defaultProps.variant;
36
41
 
37
- @property({ type: Boolean })
42
+ @property({ type: String })
43
+ @validPropertyValues(componentSelector, types, defaultProps.type)
44
+ public type = defaultProps.type;
45
+
46
+ @property({ type: Boolean, reflect: true })
38
47
  public disabled = defaultProps.disabled;
39
48
 
40
- @property({ type: Boolean })
49
+ @property({ type: Boolean, reflect: true })
41
50
  public isSelected = defaultProps.isSelected;
42
51
 
43
- @property({ type: Boolean })
52
+ @property({ type: Boolean, reflect: true })
44
53
  public isLoading = defaultProps.isLoading;
45
54
 
46
55
  @property({ type: Boolean })
@@ -50,62 +59,123 @@ export class PieChip extends PieElement implements ChipProps {
50
59
  public aria: ChipProps['aria'];
51
60
 
52
61
  /**
53
- * Handler to prevent click events
54
- * when the chip is disabled or dismissible
55
- *
62
+ * Handles the change event for the native checkbox.
63
+ * This component is controlled, so it does not set its own state.
64
+ * It simply forwards the native change event.
56
65
  * @private
57
66
  */
58
- private onClickHandler (event: Event) {
59
- if (this.disabled || this.isDismissible) {
60
- event.preventDefault();
61
- event.stopPropagation();
62
- }
67
+ private _onCheckboxChange () {
68
+ // The original event from the input does not bubble past the shadow DOM boundary.
69
+ // We create and dispatch a new 'change' event to ensure it bubbles and is composed,
70
+ // allowing consumers to respond to the interaction.
71
+ const changeEvent = new Event('change', { bubbles: true, composed: true });
72
+ this.dispatchEvent(changeEvent);
63
73
  }
64
74
 
65
75
  /**
66
- * Template for the loading state
67
- *
76
+ * Template for the loading state spinner.
68
77
  * @private
69
78
  */
70
- private renderSpinner (): TemplateResult {
71
- const { isSelected } = this;
72
- const spinnerVariant = isSelected ? 'inverse' : 'secondary';
79
+ private _renderSpinner (): TemplateResult {
80
+ const spinnerVariant = this.isSelected ? 'inverse' : 'secondary';
73
81
 
74
82
  return html`
75
- <pie-spinner
76
- class="c-chip-spinner"
77
- size="small"
78
- variant="${spinnerVariant}">
79
- </pie-spinner>`;
83
+ <pie-spinner
84
+ class="c-chip-spinner"
85
+ size="small"
86
+ variant="${spinnerVariant}">
87
+ </pie-spinner>`;
80
88
  }
81
89
 
82
90
  /**
83
- * Handles click on a close button by dispatching a custom event
84
- *
91
+ * Renders the core content of the chip (icon, text, spinner).
85
92
  * @private
86
93
  */
87
- private _handleCloseButtonClick () : void {
88
- dispatchCustomEvent(this, ON_CHIP_CLOSE_EVENT);
94
+ private _renderContent (): TemplateResult {
95
+ return html`
96
+ <slot name="icon"></slot>
97
+ ${this.isLoading ? this._renderSpinner() : nothing}
98
+ <slot></slot>
99
+ `;
89
100
  }
90
101
 
91
102
  /**
92
- * Template for the dismissible state
93
- *
103
+ * Template for the checkbox variant.
104
+ * This uses a visually hidden native checkbox for accessibility and form integration.
94
105
  * @private
95
106
  */
96
- private renderCloseButton (): TemplateResult {
107
+ private _renderCheckbox (): TemplateResult {
108
+ const {
109
+ aria,
110
+ variant,
111
+ disabled,
112
+ isSelected,
113
+ isLoading,
114
+ } = this;
115
+
116
+ const classes = {
117
+ 'c-chip': true,
118
+ [`c-chip--${variant}`]: true,
119
+ 'c-chip--selected': isSelected,
120
+ 'is-disabled': disabled,
121
+ 'is-loading': isLoading,
122
+ };
123
+
97
124
  return html`
98
- <button
99
- @click="${this._handleCloseButtonClick}"
100
- ?disabled=${this.disabled}
101
- aria-label="${ifDefined(this.aria?.close)}"
102
- class="c-chip-closeBtn"
103
- data-test-id="chip-close-button">
104
- <icon-close-circle-filled size="m"></icon-close-circle-filled>
105
- </button>`;
125
+ <input
126
+ data-test-id="chip-checkbox-input"
127
+ type="checkbox"
128
+ id="pie-chip"
129
+ aria-label="${ifDefined(aria?.label)}"
130
+ ?checked=${isSelected}
131
+ ?disabled=${disabled}
132
+ @change="${this._onCheckboxChange}"/>
133
+ <label
134
+ for="pie-chip"
135
+ class=${classMap(classes)}
136
+ data-test-id="pie-chip">
137
+ ${this._renderContent()}
138
+ </label>`;
106
139
  }
107
140
 
108
- render () {
141
+ private _renderButton (): TemplateResult {
142
+ const {
143
+ aria,
144
+ variant,
145
+ disabled,
146
+ isSelected,
147
+ isLoading,
148
+ } = this;
149
+
150
+ const classes = {
151
+ 'c-chip': true,
152
+ [`c-chip--${variant}`]: true,
153
+ 'c-chip--selected': isSelected,
154
+ 'is-disabled': disabled,
155
+ 'is-loading': isLoading,
156
+ };
157
+
158
+ return html`
159
+ <button
160
+ id="pie-chip"
161
+ type="button"
162
+ class=${classMap(classes)}
163
+ aria-busy="${ifDefined(isLoading)}"
164
+ aria-haspopup="${ifDefined(aria?.haspopup)}"
165
+ aria-expanded="${ifDefined(aria?.expanded)}"
166
+ aria-pressed="${isSelected}"
167
+ aria-label="${ifDefined(aria?.label)}"
168
+ ?disabled=${disabled}
169
+ data-test-id="pie-chip">
170
+ ${this._renderContent()}
171
+ </button>`;
172
+ }
173
+
174
+ /**
175
+ * Template for the dismissible variant.
176
+ * @private
177
+ */
178
+ private _renderDismissible (): TemplateResult {
109
179
  const {
110
180
  variant,
111
181
  disabled,
@@ -113,6 +183,7 @@ export class PieChip extends PieElement implements ChipProps {
113
183
  isLoading,
114
184
  isDismissible,
115
185
  } = this;
186
+
116
187
  const showCloseButton = isDismissible && isSelected;
117
188
 
118
189
  const classes = {
@@ -124,26 +195,51 @@ export class PieChip extends PieElement implements ChipProps {
124
195
  'is-loading': isLoading,
125
196
  };
126
197
 
198
+ const handleClick = (event: Event) => {
199
+ if (disabled || isDismissible) {
200
+ event.preventDefault();
201
+ event.stopPropagation();
202
+ }
203
+ };
204
+
205
+ const handleCloseButtonClick = (event: Event) : void => {
206
+ event.stopPropagation();
207
+ const closeEvent = new Event('close', { bubbles: true, composed: true });
208
+ this.dispatchEvent(closeEvent);
209
+ };
210
+
127
211
  return html`
128
212
  <div
129
- role="${ifDefined(showCloseButton ? undefined : 'button')}"
130
- tabindex="${ifDefined(showCloseButton ? undefined : '0')}"
131
- aria-atomic="true"
132
213
  aria-busy="${isLoading}"
133
214
  aria-current="${isSelected}"
134
215
  aria-label="${ifDefined(this.aria?.label)}"
135
- aria-live="polite"
136
216
  class=${classMap(classes)}
137
217
  data-test-id="pie-chip"
138
- @click="${this.onClickHandler}">
139
- <slot name="icon"></slot>
140
- ${isLoading ? this.renderSpinner() : nothing}
141
- <slot></slot>
142
- ${showCloseButton ? this.renderCloseButton() : nothing}
218
+ @click=${handleClick}>
219
+ ${this._renderContent()}
220
+ ${showCloseButton ? html`<button
221
+ @click="${handleCloseButtonClick}"
222
+ ?disabled=${this.disabled}
223
+ aria-label="${ifDefined(this.aria?.close)}"
224
+ class="c-chip-closeBtn"
225
+ data-test-id="chip-close-button">
226
+ <icon-close-circle-filled size="m"></icon-close-circle-filled>
227
+ </button>` : nothing}
143
228
  </div>`;
144
229
  }
145
230
 
146
- // Renders a `CSSResult` generated from SCSS by Vite
231
+ render () {
232
+ if (this.isDismissible) {
233
+ return this._renderDismissible();
234
+ }
235
+
236
+ if (this.type === 'checkbox') {
237
+ return this._renderCheckbox();
238
+ }
239
+
240
+ return this._renderButton();
241
+ }
242
+
147
243
  static styles = unsafeCSS(styles);
148
244
  }
149
245
 
package/src/react.ts CHANGED
@@ -11,14 +11,16 @@ const PieChipReact = createComponent({
11
11
  react: React,
12
12
  tagName: 'pie-chip',
13
13
  events: {
14
- onPieChipClose: 'pie-chip-close' as EventName<CustomEvent>, // when a user clicks close button.
14
+ onClose: 'close' as EventName<Event>, // when a user clicks the close button.
15
+ onChange: 'change' as EventName<Event>, // when a user interacts with the chip of type checkbox.
15
16
  },
16
17
  });
17
18
 
18
19
  type ReactBaseType = React.HTMLAttributes<HTMLElement>
19
20
 
20
21
  type PieChipEvents = {
21
- onPieChipClose?: (event: CustomEvent) => void;
22
+ onClose?: (event: Event) => void;
23
+ onChange?: (event: Event) => void;
22
24
  };
23
25
 
24
26
  export const PieChip = PieChipReact as React.ForwardRefExoticComponent<React.PropsWithoutRef<ChipProps>