@justeattakeaway/pie-switch 2.4.2 → 2.5.1

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/dist/react.d.ts CHANGED
@@ -36,11 +36,13 @@ declare class PieSwitch_2 extends PieSwitch_base implements SwitchProps, PIEInpu
36
36
  labelPlacement: "leading" | "trailing";
37
37
  aria: SwitchProps['aria'];
38
38
  checked: boolean;
39
+ defaultChecked: boolean;
39
40
  required: boolean;
40
41
  value: string;
41
42
  name: SwitchProps['name'];
42
43
  disabled: boolean;
43
44
  private input;
45
+ private switchBody;
44
46
  focusTarget: HTMLElement;
45
47
  private _abortController;
46
48
  private _isAnimationAllowed;
@@ -75,6 +77,11 @@ declare class PieSwitch_2 extends PieSwitch_base implements SwitchProps, PIEInpu
75
77
  * https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setCustomValidity
76
78
  */
77
79
  setCustomValidity(message: string): void;
80
+ /**
81
+ * Called when the containing form is reset.
82
+ * Resets checked state back to defaultChecked and emits a change event when needed.
83
+ */
84
+ formResetCallback(): void;
78
85
  /**
79
86
  * (Read-only) returns a ValidityState with the validity states that this element is in.
80
87
  * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
@@ -86,15 +93,9 @@ declare class PieSwitch_2 extends PieSwitch_base implements SwitchProps, PIEInpu
86
93
  * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validationMessage
87
94
  */
88
95
  get validationMessage(): string;
89
- /**
90
- * If a label is provided, renders it if `labelPlacement` matches the given position.
91
- * If no label is provided, or `labelPlacement` does not match the given position, nothing is rendered.
92
- *
93
- * @private
94
- */
95
- private renderSwitchLabel;
96
96
  private renderAriaDescription;
97
- render(): TemplateResult<1>;
97
+ private renderSwitchLabel;
98
+ render(): TemplateResult;
98
99
  }
99
100
 
100
101
  declare const PieSwitch_base: GenericConstructor<FormControlInterface> & typeof PieElement;
@@ -115,6 +116,11 @@ export declare interface SwitchProps {
115
116
  * Same as the HTML checked attribute - indicates whether the switch is on or off
116
117
  */
117
118
  checked?: boolean;
119
+ /**
120
+ * The default checked state of the switch (not necessarily the same as the current checked state).
121
+ * Used when the switch is part of a form that is reset.
122
+ */
123
+ defaultChecked?: boolean;
118
124
  /**
119
125
  * Same as the HTML required attribute - indicates whether the switch must be turned or not
120
126
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-switch",
3
3
  "description": "PIE Design System Switch built using Web Components",
4
- "version": "2.4.2",
4
+ "version": "2.5.1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/justeattakeaway/pie",
@@ -45,12 +45,12 @@
45
45
  "devDependencies": {
46
46
  "@justeattakeaway/pie-components-config": "0.21.3",
47
47
  "@justeattakeaway/pie-css": "1.2.0",
48
- "@justeattakeaway/pie-monorepo-utils": "0.9.5",
48
+ "@justeattakeaway/pie-monorepo-utils": "0.9.6",
49
49
  "@justeattakeaway/pie-wrapper-react": "0.14.5"
50
50
  },
51
51
  "dependencies": {
52
- "@justeattakeaway/pie-icons-webc": "1.25.1",
53
- "@justeattakeaway/pie-webc-core": "15.0.0",
52
+ "@justeattakeaway/pie-icons-webc": "1.25.2",
53
+ "@justeattakeaway/pie-webc-core": "15.0.1",
54
54
  "element-internals-polyfill": "1.3.11"
55
55
  },
56
56
  "customElements": "custom-elements.json",
package/src/defs.ts CHANGED
@@ -16,6 +16,11 @@ export interface SwitchProps {
16
16
  * Same as the HTML checked attribute - indicates whether the switch is on or off
17
17
  */
18
18
  checked?: boolean;
19
+ /**
20
+ * The default checked state of the switch (not necessarily the same as the current checked state).
21
+ * Used when the switch is part of a form that is reset.
22
+ */
23
+ defaultChecked?: boolean;
19
24
  /**
20
25
  * Same as the HTML required attribute - indicates whether the switch must be turned or not
21
26
  */
@@ -53,6 +58,7 @@ export type DefaultProps = ComponentDefaultProps<SwitchProps, keyof Omit<SwitchP
53
58
 
54
59
  export const defaultProps: DefaultProps = {
55
60
  checked: false,
61
+ defaultChecked: false,
56
62
  disabled: false,
57
63
  labelPlacement: 'leading',
58
64
  required: false,
package/src/index.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  html, unsafeCSS, nothing,
3
3
  } from 'lit';
4
+ import { html as staticHtml, unsafeStatic } from 'lit/static-html.js';
4
5
  import { PieElement } from '@justeattakeaway/pie-webc-core/src/internals/PieElement';
5
6
  import { property, query, state } from 'lit/decorators.js';
6
7
  import { classMap } from 'lit/directives/class-map.js';
7
8
  import { ifDefined } from 'lit/directives/if-defined.js';
9
+ import { live } from 'lit/directives/live.js';
8
10
  import 'element-internals-polyfill';
9
11
 
10
12
  import {
@@ -44,6 +46,9 @@ export class PieSwitch extends FormControlMixin(DelegatesFocusMixin(PieElement))
44
46
  @property({ type: Boolean, reflect: true })
45
47
  public checked = defaultProps.checked;
46
48
 
49
+ @property({ type: Boolean, reflect: true })
50
+ public defaultChecked = defaultProps.defaultChecked;
51
+
47
52
  @property({ type: Boolean, reflect: true })
48
53
  public required = defaultProps.required;
49
54
 
@@ -59,7 +64,10 @@ export class PieSwitch extends FormControlMixin(DelegatesFocusMixin(PieElement))
59
64
  @query('input[type="checkbox"]')
60
65
  private input!: HTMLInputElement;
61
66
 
62
- @query('label')
67
+ @query('.c-switch')
68
+ private switchBody!: HTMLElement;
69
+
70
+ @query('label, input[type="checkbox"]')
63
71
  public focusTarget!: HTMLElement;
64
72
 
65
73
  private _abortController!: AbortController;
@@ -83,10 +91,18 @@ export class PieSwitch extends FormControlMixin(DelegatesFocusMixin(PieElement))
83
91
  const { signal } = this._abortController;
84
92
 
85
93
  this.addEventListener('click', (event: Event) => {
94
+ const [source] = event.composedPath();
95
+
96
+ if (this.disabled || source === this.input) {
97
+ return;
98
+ }
99
+
86
100
  // Only programmatically click the input if the explicit target
87
101
  // of the click was the host element itself (e.g., via an external label).
88
102
  // This ignores clicks bubbling up from the internal shadow DOM and prevents loops.
89
- if (event.composedPath()[0] === this) {
103
+ // Also forward clicks from the visual switch body when no internal label exists.
104
+ const isInsideSwitchBody = !this.label && event.composedPath().includes(this.switchBody);
105
+ if (source === this || isInsideSwitchBody) {
90
106
  this.input.click();
91
107
  }
92
108
  }, { signal });
@@ -166,6 +182,23 @@ export class PieSwitch extends FormControlMixin(DelegatesFocusMixin(PieElement))
166
182
  this._internals.setValidity(this.validity, this.validationMessage, this.input);
167
183
  }
168
184
 
185
+ /**
186
+ * Called when the containing form is reset.
187
+ * Resets checked state back to defaultChecked and emits a change event when needed.
188
+ */
189
+ public formResetCallback () : void {
190
+ if (this.checked === this.defaultChecked) {
191
+ return;
192
+ }
193
+
194
+ this.checked = this.defaultChecked;
195
+
196
+ const changeEvent = new Event('change', { bubbles: true, composed: true });
197
+ this.dispatchEvent(changeEvent);
198
+
199
+ this.handleFormAssociation();
200
+ }
201
+
169
202
  /**
170
203
  * (Read-only) returns a ValidityState with the validity states that this element is in.
171
204
  * https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
@@ -183,36 +216,12 @@ export class PieSwitch extends FormControlMixin(DelegatesFocusMixin(PieElement))
183
216
  return this.input.validationMessage;
184
217
  }
185
218
 
186
- /**
187
- * If a label is provided, renders it if `labelPlacement` matches the given position.
188
- * If no label is provided, or `labelPlacement` does not match the given position, nothing is rendered.
189
- *
190
- * @private
191
- */
192
- private renderSwitchLabel (placement : SwitchProps['labelPlacement']) {
193
- const { label, labelPlacement } = this;
194
-
195
- if (!label || labelPlacement !== placement) {
196
- return nothing;
197
- }
198
-
199
- // Using aria-hidden here to prevent the label from potentially being narrated twice by screen readers such as Apple VoiceOver.
200
- // Instead, we apply the label as an aria-label attribute on the input (if no aria.label prop is provided).
201
- return html`
202
- <span
203
- aria-hidden="true"
204
- data-test-id="switch-label-${labelPlacement}"
205
- class="c-switch-label">
206
- ${label}
207
- </span>`;
208
- }
209
-
210
219
  private renderAriaDescription () {
211
220
  if (!this.aria?.describedBy) {
212
221
  return nothing;
213
222
  }
214
223
 
215
- // we apply aria-hidden to the element containing the description because it prevent some screen readers such as Apple VoiceOver from announcing the description once
224
+ // we apply aria-hidden to the element containing the description because it prevents some screen readers such as Apple VoiceOver from announcing the description once
216
225
  // on the input and again separately. The description is still announced once, when the input is focused/selected.
217
226
  return html`
218
227
  <div
@@ -224,9 +233,28 @@ export class PieSwitch extends FormControlMixin(DelegatesFocusMixin(PieElement))
224
233
  </div>`;
225
234
  }
226
235
 
236
+ private renderSwitchLabel () {
237
+ const { label, labelPlacement } = this;
238
+
239
+ if (!label) {
240
+ return nothing;
241
+ }
242
+
243
+ // Using aria-hidden here to prevent the label from potentially being narrated twice by screen readers such as Apple VoiceOver.
244
+ // Instead, we apply the label as an aria-label attribute on the input (if no aria.label prop is provided).
245
+ return html`
246
+ <span
247
+ aria-hidden="true"
248
+ data-test-id="switch-label-${labelPlacement}"
249
+ class="c-switch-label">
250
+ ${label}
251
+ </span>`;
252
+ }
253
+
227
254
  render () {
228
255
  const {
229
256
  label,
257
+ labelPlacement,
230
258
  aria,
231
259
  checked,
232
260
  disabled,
@@ -237,13 +265,15 @@ export class PieSwitch extends FormControlMixin(DelegatesFocusMixin(PieElement))
237
265
  const classes = {
238
266
  'c-switch-wrapper': true,
239
267
  'c-switch-wrapper--allow-animation': _isAnimationAllowed,
268
+ [`c-switch-wrapper--label-${labelPlacement}`]: true,
240
269
  };
241
270
 
242
- return html`
243
- <label
271
+ const tag = unsafeStatic(label ? 'label' : 'div');
272
+
273
+ return staticHtml`
274
+ <${tag}
244
275
  class="${classMap(classes)}"
245
276
  ?disabled=${disabled}>
246
- ${this.renderSwitchLabel('leading')}
247
277
  <div
248
278
  data-test-id="switch-component"
249
279
  class="c-switch"
@@ -254,7 +284,7 @@ export class PieSwitch extends FormControlMixin(DelegatesFocusMixin(PieElement))
254
284
  type="checkbox"
255
285
  class="c-switch-input"
256
286
  .required=${required}
257
- .checked="${checked}"
287
+ .checked="${live(checked)}"
258
288
  .disabled="${disabled}"
259
289
  @change="${this.handleChange}"
260
290
  aria-label="${ifDefined(aria?.label || label)}"
@@ -263,9 +293,9 @@ export class PieSwitch extends FormControlMixin(DelegatesFocusMixin(PieElement))
263
293
  ${checked ? html`<icon-check></icon-check>` : nothing}
264
294
  </div>
265
295
  </div>
266
- ${this.renderSwitchLabel('trailing')}
296
+ ${this.renderSwitchLabel()}
267
297
  ${this.renderAriaDescription()}
268
- </label>`;
298
+ </${tag}>`;
269
299
  }
270
300
  }
271
301
 
package/src/switch.scss CHANGED
@@ -121,6 +121,13 @@
121
121
  @include p.visually-hidden;
122
122
  }
123
123
 
124
+ .c-switch-wrapper--label-leading {
125
+ flex-direction: row-reverse;
126
+ }
127
+
128
+ .c-switch-wrapper--label-trailing {
129
+ flex-direction: row;
130
+ }
124
131
 
125
132
  .c-switch-wrapper--allow-animation {
126
133
  /* stylelint-disable-next-line no-descending-specificity */