@jack-henry/jh-elements 2.0.0-beta.8

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.
Files changed (39) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +26 -0
  3. package/README.md +13 -0
  4. package/components/badge/badge.js +73 -0
  5. package/components/button/button.js +953 -0
  6. package/components/card/card.js +333 -0
  7. package/components/checkbox/checkbox.js +601 -0
  8. package/components/checkbox-group/checkbox-group.js +241 -0
  9. package/components/divider/divider.js +91 -0
  10. package/components/icon/icon.js +96 -0
  11. package/components/input/input.js +1245 -0
  12. package/components/input-email/input-email.js +18 -0
  13. package/components/input-password/input-password.js +153 -0
  14. package/components/input-search/input-search.js +41 -0
  15. package/components/input-telephone/input-telephone.js +18 -0
  16. package/components/input-textarea/input-textarea.js +374 -0
  17. package/components/input-url/input-url.js +31 -0
  18. package/components/list-group/list-group.js +103 -0
  19. package/components/list-item/list-item.js +350 -0
  20. package/components/menu/menu.js +60 -0
  21. package/components/notification/notification.js +327 -0
  22. package/components/progress/progress.js +417 -0
  23. package/components/radio/radio.js +422 -0
  24. package/components/radio-group/radio-group.js +400 -0
  25. package/components/switch/switch.js +316 -0
  26. package/components/table/table.js +367 -0
  27. package/components/table-data-cell/table-data-cell.js +107 -0
  28. package/components/table-header-cell/table-header-cell.js +321 -0
  29. package/components/table-row/table-row.js +52 -0
  30. package/components/tag/tag.js +422 -0
  31. package/components/tag-group/tag-group.js +57 -0
  32. package/components/toast/toast.js +172 -0
  33. package/components/toast-controller/toast-controller.js +122 -0
  34. package/components/tooltip/tooltip.js +498 -0
  35. package/custom-elements.json +6864 -0
  36. package/index.d.ts +69 -0
  37. package/jsconfig.json +9 -0
  38. package/package.json +52 -0
  39. package/utils/themeProvider.js +32 -0
@@ -0,0 +1,400 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { LitElement, css, html } from 'lit';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
7
+
8
+ let id = 0;
9
+ /**
10
+ *
11
+ * @cssprop --jh-radio-group-label-color-text - The label text color. Defaults to `--jh-color-content-primary-enabled`.
12
+ * @cssprop --jh-radio-group-required-color-text - The required indicator color.
13
+ * Defaults to `--jh-color-content-negative-enabled`.
14
+ * @cssprop --jh-radio-group-required-color-text-optional - The optional indicator text color.
15
+ * Defaults to `--jh-color-content-primary-enabled`.
16
+ * @cssprop --jh-radio-group-helper-color-text - The helper-text text color.
17
+ * Defaults to `--jh-color-content-secondary-enabled`.
18
+ * @cssprop --jh-radio-group-error-color-text - The error-text text color.
19
+ * Defaults to `--jh-color-content-negative-enabled`.
20
+ *
21
+ * @slot default - Use to insert `<jh-radio>` components(s).
22
+ *
23
+ * @event jh-change - Dispatched when the value of the radio group has changed.
24
+ *
25
+ * @customElement jh-radio-group
26
+ */
27
+ export class JhRadioGroup extends LitElement {
28
+ static get formAssociated() {
29
+ return true;
30
+ }
31
+ #checked;
32
+ /** @type {?Number} */
33
+ #id;
34
+ /** @type {ElementInternals} */
35
+ #internals;
36
+ /** @type {?string} */
37
+ #value;
38
+
39
+ static get styles() {
40
+ return css`
41
+ :host {
42
+ font-family: var(--jh-font-helper-regular-font-family);
43
+ font-weight: var(--jh-font-helper-regular-font-weight);
44
+ font-size: var(--jh-font-helper-regular-font-size);
45
+ line-height: var(--jh-font-helper-regular-line-height);
46
+ display: block;
47
+ }
48
+ /* reset styling on fieldset and legend */
49
+ :host fieldset {
50
+ border: none;
51
+ padding: 0;
52
+ margin: 0;
53
+ }
54
+ :host legend {
55
+ padding: 0;
56
+ }
57
+ :host([label]) .controls {
58
+ margin-top: var(--jh-dimension-200);
59
+ }
60
+ :host([orientation='vertical']) .controls {
61
+ display: flex;
62
+ flex-direction: column;
63
+ }
64
+ :host([orientation='vertical']) ::slotted(*) {
65
+ margin-bottom: var(--jh-dimension-200);
66
+ flex: 1;
67
+ }
68
+ :host([orientation='vertical']) ::slotted(:last-of-type) {
69
+ margin-bottom: 0;
70
+ }
71
+ :host([orientation='horizontal']) .controls {
72
+ display: flex;
73
+ flex-direction: row;
74
+ }
75
+ :host([orientation='horizontal']) ::slotted(*) {
76
+ margin-right: var(--jh-dimension-400);
77
+ margin-bottom: 0;
78
+ flex: 1;
79
+ }
80
+ :host([orientation='horizontal']) ::slotted(:last-of-type) {
81
+ margin-right: 0;
82
+ }
83
+ .label {
84
+ color: var(
85
+ --jh-radio-group-label-color-text,
86
+ var(--jh-color-content-primary-enabled)
87
+ );
88
+ }
89
+ .helper-text {
90
+ color: var(
91
+ --jh-radio-group-helper-color-text,
92
+ var(--jh-color-content-secondary-enabled)
93
+ );
94
+ margin: 0;
95
+ }
96
+ .label-text,
97
+ .helper-text,
98
+ :host([invalid]) .error-text {
99
+ word-break: break-word;
100
+ }
101
+ :host([invalid]) .error-text {
102
+ color: var(
103
+ --jh-radio-group-error-color-text,
104
+ var(--jh-color-content-negative-enabled)
105
+ );
106
+ margin: var(--jh-dimension-200) 0 0 0;
107
+ }
108
+ :host([show-indicator]) .indicator {
109
+ color: var(
110
+ --jh-radio-group-required-color-text-optional,
111
+ var(--jh-color-content-primary-enabled)
112
+ );
113
+ }
114
+ :host([show-indicator][required]) .indicator {
115
+ color: var(
116
+ --jh-radio-group-required-color-text,
117
+ var(--jh-color-content-negative-enabled)
118
+ );
119
+ }
120
+ `;
121
+ }
122
+ static get properties() {
123
+ return {
124
+ /** Sets an `aria-label` to assist screen reader users when no visible label is present. */
125
+ accessibleLabel: {
126
+ type: String,
127
+ attribute: 'accessible-label',
128
+ },
129
+ /** Text to be displayed when radio group has failed validation and `invalid` is true. */
130
+ errorText: {
131
+ type: String,
132
+ attribute: 'error-text',
133
+ },
134
+ /**
135
+ * Provides additional context or guidance for using the radio group. For `helper-text` to be displayed, the `label` property must also be set.
136
+ */
137
+ helperText: {
138
+ attribute: 'helper-text',
139
+ },
140
+ /** Sets an `aria-invalid` on the radio group to indicate the value supplied was invalid and displays `error-text` when set. */
141
+ invalid: {
142
+ type: Boolean,
143
+ reflect: true,
144
+ },
145
+ /**
146
+ * Describes the type of data to be collected.
147
+ */
148
+ label: {
149
+ type: String,
150
+ },
151
+ /** Sets the name of the radio group data when submitted in a form. */
152
+ name: {
153
+ type: String,
154
+ },
155
+ /** Indicates a value is required. */
156
+ required: {
157
+ type: Boolean,
158
+ reflect: true,
159
+ },
160
+ /** Determines the orientation of the radio group. */
161
+ orientation: {
162
+ type: String,
163
+ reflect: true,
164
+ },
165
+ /** Adds a visual indicator next to the label. Indicates that a value is optional (by default) or required if the `required`
166
+ * property is also set. For the indicator to be displayed, the `label` property must also be set.*/
167
+ showIndicator: {
168
+ type: Boolean,
169
+ reflect: true,
170
+ attribute: 'show-indicator',
171
+ },
172
+ /** Sets the value of the radio group. */
173
+ value: {
174
+ type: String,
175
+ reflect: true,
176
+ },
177
+ };
178
+ }
179
+ constructor() {
180
+ super();
181
+ this.#internals = this.attachInternals();
182
+ /** @type {?string} */
183
+ this.accessibleLabel = null;
184
+ /** @type {?string} */
185
+ this.errorText = null;
186
+ /** @type {?string} */
187
+ this.helperText = null;
188
+ /** @type {?Boolean} */
189
+ this.invalid = false;
190
+ /** @type {?string} */
191
+ this.label = null;
192
+ /** @type {?string} */
193
+ this.name = null;
194
+ /** @type {?Boolean} */
195
+ this.required = false;
196
+ /** @type {'vertical'|'horizontal'} */
197
+ this.orientation = 'vertical';
198
+ /** @type {?boolean} */
199
+ this.showIndicator = false;
200
+ /** @type {?string} */
201
+ this.value = null;
202
+
203
+ this.addEventListener('jh-change', this.#handleChange);
204
+ this.addEventListener('keydown', this.#handleKeydown);
205
+ this.addEventListener('focusout', this.#handleFocusOut);
206
+ }
207
+
208
+ connectedCallback() {
209
+ super.connectedCallback();
210
+ this.#id = id++;
211
+ }
212
+
213
+ /**
214
+ * Returns the radio group's parent form element.
215
+ * @type {?HTMLFormElement}
216
+ */
217
+ get form() {
218
+ return this.#internals.form;
219
+ }
220
+
221
+ /** @ignore */
222
+ get validity() {
223
+ return this.#internals.validity;
224
+ }
225
+
226
+ /** @type {?string} */
227
+ get value() {
228
+ return this.#value;
229
+ }
230
+
231
+ set value(newValue) {
232
+ const oldValue = this.#value;
233
+ if (newValue !== oldValue) {
234
+ this.#value = newValue;
235
+ this.#internals.setFormValue(newValue);
236
+ }
237
+ this.requestUpdate('value', oldValue);
238
+ }
239
+
240
+ #getRadios() {
241
+ return [...this.querySelectorAll('jh-radio')];
242
+ }
243
+
244
+ #handleSlotChange() {
245
+ const radios = this.#getRadios();
246
+
247
+ this.#checked = radios.find((radio) => radio.value === this.value && this.value !== null);
248
+
249
+ if (!this.#checked) {
250
+ const checkedRadio = radios.find((radio) => radio.checked);
251
+ if (checkedRadio) {
252
+ this.value = checkedRadio.value;
253
+ this.#checked = checkedRadio;
254
+ } else {
255
+ this.value = null;
256
+ this.#checked = null;
257
+ }
258
+ }
259
+ this.#updateChecked();
260
+ if (!this.#checked) {
261
+ radios[0].tabIndex = 0;
262
+ }
263
+ }
264
+
265
+ #handleChange(e) {
266
+ if (e.target.tagName === 'JH-RADIO') {
267
+ this.value = e.target.value;
268
+ this.#checked = e.target;
269
+ this.#updateChecked();
270
+
271
+ const options = {
272
+ bubbles: true,
273
+ composed: true,
274
+ cancelable: true,
275
+ };
276
+ this.dispatchEvent(new CustomEvent('jh-change', options));
277
+ }
278
+ }
279
+
280
+ #handleKeydown(e) {
281
+ const keyCodes = [
282
+ 'ArrowDown',
283
+ 'ArrowRight',
284
+ 'ArrowUp',
285
+ 'ArrowLeft',
286
+ 'Space',
287
+ ];
288
+ if (!keyCodes.includes(e.code)) return;
289
+
290
+ const radios = this.#getRadios().filter((radio) => !radio.disabled);
291
+ const numberOfItems = radios.length;
292
+ let index = radios.findIndex((radio) => radio.checked);
293
+ //if no radio is checked use the first radio as starting point.
294
+ index = index === -1 ? 0 : index;
295
+
296
+ if (e.code === 'ArrowDown' || e.code === 'ArrowRight') {
297
+ e.preventDefault();
298
+ if (index + 1 < numberOfItems) {
299
+ index++;
300
+ } else {
301
+ index = 0;
302
+ }
303
+ } else if (e.code === 'ArrowUp' || e.code === 'ArrowLeft') {
304
+ e.preventDefault();
305
+ if (index - 1 >= 0) {
306
+ index--;
307
+ } else {
308
+ index = numberOfItems - 1;
309
+ }
310
+ }
311
+ this.value = radios[index].value;
312
+ radios[index].click();
313
+ this.#checked = radios[index];
314
+ this.#updateChecked();
315
+ radios[index].focus();
316
+ }
317
+
318
+ #handleFocusOut() {
319
+ const radios = this.#getRadios().filter((radio) => !radio.disabled);
320
+
321
+ if (!radios.some((radio) => radio.checked)) return;
322
+
323
+ radios.forEach((radio) =>
324
+ radio.checked ? (radio.tabIndex = 0) : (radio.tabIndex = -1)
325
+ );
326
+ }
327
+
328
+ #updateChecked() {
329
+ const radios = this.#getRadios();
330
+
331
+ radios.forEach((radio) => {
332
+ radio.checked = this.#checked === radio;
333
+ radio.checked ? (radio.tabIndex = 0) : (radio.tabIndex = -1);
334
+ });
335
+ }
336
+
337
+ #getAriaDescribedBy() {
338
+ if (this.errorText && this.invalid && this.helperText && this.label) {
339
+ return `radio-group-error-${this.#id} radio-group-helper-${this.#id}`;
340
+ } else if (this.errorText && this.invalid) {
341
+ return `radio-group-error-${this.#id}`;
342
+ } else if (this.helperText && this.label) {
343
+ return `radio-group-helper-${this.#id}`;
344
+ }
345
+ }
346
+
347
+ render() {
348
+ let indicator;
349
+ let helperText;
350
+ let label;
351
+ let errorText;
352
+
353
+ if (this.showIndicator) {
354
+ if (this.required) {
355
+ indicator = html`<span class="indicator" aria-hidden="true"> *</span>`;
356
+ } else {
357
+ indicator = html`<span class="indicator"> (optional)</span>`;
358
+ }
359
+ }
360
+
361
+ if (this.helperText) {
362
+ helperText = html`<p
363
+ class="helper-text"
364
+ id="radio-group-helper-${this.#id}"
365
+ >
366
+ ${this.helperText}
367
+ </p>`;
368
+ }
369
+
370
+ if (this.label) {
371
+ label = html`<legend class="label" for="radio-group-label-${this.#id}">
372
+ ${this.label}${indicator}
373
+ </legend>
374
+ ${helperText}`;
375
+ }
376
+
377
+ if (this.invalid && this.errorText) {
378
+ errorText = html`<p class="error-text" id="radio-group-error-${this.#id}">
379
+ ${this.errorText}
380
+ </p>`;
381
+ }
382
+ return html`
383
+ <fieldset
384
+ role="radiogroup"
385
+ id=${ifDefined(this.label ? `radio-group-label-${this.#id}` : null)}
386
+ aria-describedby=${ifDefined(this.#getAriaDescribedBy())}
387
+ aria-required=${ifDefined(this.required ? 'true' : 'false')}
388
+ aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
389
+ aria-label=${ifDefined(this.accessibleLabel)}
390
+ >
391
+ ${label}
392
+ <div class="controls">
393
+ <slot @slotchange=${this.#handleSlotChange}></slot>
394
+ </div>
395
+ ${errorText}
396
+ </fieldset>
397
+ `;
398
+ }
399
+ }
400
+ customElements.define('jh-radio-group', JhRadioGroup);
@@ -0,0 +1,316 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { LitElement, css, html } from 'lit';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
7
+
8
+ let id = 0;
9
+
10
+ /**
11
+ * @cssprop --jh-switch-opacity-disabled - The switch opacity when disabled. Defaults to `--jh-opacity-disabled`.
12
+ * @cssprop --jh-switch-thumb-color-background - The thumb background-color. Defaults to `--jh-color-container-primary-enabled`.
13
+ * @cssprop --jh-switch-color-focus - The switch outline when it receives keyboard focus. Defaults to `--jh-border-focus-color`.
14
+ * @cssprop --jh-switch-helper-color-text - The helper-text text color. Defaults to `--jh-color-content-secondary-enabled`.
15
+ * @cssprop --jh-switch-label-color-text - The label text color. Defaults to `--jh-color-content-primary-enabled`.
16
+ * @cssprop --jh-switch-track-color-background-unselected-enabled - The track color when unselected. Defaults to `--jh-color-control-enabled`.
17
+ * @cssprop --jh-switch-track-color-background-unselected-focus - The track color when unselected and focused. Defaults to `--jh-color-control-hover`.
18
+ * @cssprop --jh-switch-track-color-background-unselected-hover - The track color when unselected and hovered. Defaults to `--jh-color-control-hover`.
19
+ * @cssprop --jh-switch-track-color-background-unselected-active - The track color when unselected and active. Defaults to `--jh-color-control-active`.
20
+ * @cssprop --jh-switch-track-color-background-unselected-disabled - The track color when unselected and disabled. Defaults to `--jh-color-control-enabled`.
21
+ * @cssprop --jh-switch-track-color-background-selected-enabled - The track color when selected. Defaults to `--jh-color-content-brand-enabled`.
22
+ * @cssprop --jh-switch-track-color-background-selected-focus - The track color when selected and focused. Defaults to `--jh-color-content-brand-hover`.
23
+ * @cssprop --jh-switch-track-color-background-selected-hover - The track color when selected and hovered. Defaults to `--jh-color-content-brand-hover`.
24
+ * @cssprop --jh-switch-track-color-background-selected-active - The track color when selected and active. Defaults to `--jh-color-content-brand-active`.
25
+ * @cssprop --jh-switch-track-color-background-selected-disabled - The track color when selected and disabled. Defaults to `--jh-color-content-brand-enabled`.
26
+ *
27
+ * @event jh-change - Dispatched when the state of the switch has changed.
28
+ *
29
+ * @customElement jh-switch
30
+ */
31
+ export class JhSwitch extends LitElement {
32
+ /** @type {?Number} */
33
+ #id;
34
+
35
+ static get styles() {
36
+ return css`
37
+ :host {
38
+ font-family: var(--jh-font-body-regular-1-font-family);
39
+ font-weight: var(--jh-font-body-regular-1-font-weight);
40
+ font-size: var(--jh-font-body-regular-1-font-size);
41
+ line-height: var(--jh-font-body-regular-1-line-height);
42
+ display: inline-flex;
43
+ position: relative;
44
+ }
45
+ button {
46
+ position: absolute;
47
+ top: 0;
48
+ left: 0;
49
+ width: 100%;
50
+ height: 100%;
51
+ opacity: 0;
52
+ cursor: pointer;
53
+ z-index: 2;
54
+ }
55
+ span {
56
+ width: var(--jh-dimension-1400);
57
+ height: var(--jh-dimension-600);
58
+ display: inline-block;
59
+ position: relative;
60
+ }
61
+ span::before,
62
+ span::after {
63
+ content: '';
64
+ position: absolute;
65
+ }
66
+ /* track */
67
+ span::before {
68
+ background-color: var(
69
+ --jh-switch-track-color-background-unselected-enabled,
70
+ var(--jh-color-control-enabled)
71
+ );
72
+ top: 0;
73
+ bottom: 0;
74
+ left: 0;
75
+ right: 0;
76
+ border-radius: var(--jh-border-radius-300);
77
+ transition: background-color 0.3s cubic-bezier(0.1, 0.5, 0.1, 1);
78
+ }
79
+ /* thumb */
80
+ span::after {
81
+ background-color: var(
82
+ --jh-switch-thumb-color-background,
83
+ var(--jh-color-container-primary-enabled)
84
+ );
85
+ border-radius: var(--jh-border-radius-circle);
86
+ top: 2px;
87
+ left: 2px;
88
+ width: var(--jh-dimension-500);
89
+ height: var(--jh-dimension-500);
90
+ box-shadow: var(--jh-shadow-low);
91
+ transition: all 0.3s cubic-bezier(0.1, 0.5, 0.1, 1);
92
+ }
93
+ /* Unselected states */
94
+ button:focus-visible + span::before {
95
+ outline-color: var(
96
+ --jh-switch-color-focus,
97
+ var(--jh-border-focus-color)
98
+ );
99
+ outline-style: var(--jh-border-focus-style);
100
+ outline-width: var(--jh-border-focus-width);
101
+ outline-offset: 1px;
102
+ background-color: var(
103
+ --jh-switch-track-color-background-unselected-focus,
104
+ var(--jh-color-control-hover)
105
+ );
106
+ }
107
+ button:hover + span::before {
108
+ background-color: var(
109
+ --jh-switch-track-color-background-unselected-hover,
110
+ var(--jh-color-control-hover)
111
+ );
112
+ }
113
+ button:active + span::before {
114
+ background-color: var(
115
+ --jh-switch-track-color-background-unselected-active,
116
+ var(--jh-color-control-active)
117
+ );
118
+ }
119
+ button:disabled + span::before,
120
+ :host([accessible-disabled='true']) span::before {
121
+ background-color: var(
122
+ --jh-switch-track-color-background-unselected-disabled,
123
+ var(--jh-color-control-enabled)
124
+ );
125
+ }
126
+ /* Selected states */
127
+ button[checked] + span::before {
128
+ background-color: var(
129
+ --jh-switch-track-color-background-selected-enabled,
130
+ var(--jh-color-content-brand-enabled)
131
+ );
132
+ }
133
+ button[checked]:focus-visible + span::before {
134
+ background-color: var(
135
+ --jh-switch-track-color-background-selected-focus,
136
+ var(--jh-color-content-brand-hover)
137
+ );
138
+ }
139
+ button[checked]:hover + span::before {
140
+ background-color: var(
141
+ --jh-switch-track-color-background-selected-hover,
142
+ var(--jh-color-content-brand-hover)
143
+ );
144
+ }
145
+ button[checked]:active + span::before {
146
+ background-color: var(
147
+ --jh-switch-track-color-background-selected-active,
148
+ var(--jh-color-content-brand-active)
149
+ );
150
+ }
151
+ button[checked]:disabled + span::before,
152
+ :host([accessible-disabled='true'][checked]) span::before {
153
+ background-color: var(
154
+ --jh-switch-track-color-background-selected-disabled,
155
+ var(--jh-color-content-brand-enabled)
156
+ );
157
+ }
158
+ :host([disabled]),
159
+ :host([accessible-disabled='true']) {
160
+ opacity: var(--jh-switch-opacity-disabled, var(--jh-opacity-disabled));
161
+ }
162
+ :host([accessible-disabled='true']:focus) span::before {
163
+ outline: none;
164
+ }
165
+ button:disabled,
166
+ :host([accessible-disabled='true']) button {
167
+ cursor: default;
168
+ pointer-events: none;
169
+ }
170
+ button[checked] + span::after {
171
+ transform: translateX(32px);
172
+ }
173
+ .label-container {
174
+ margin-top: var(--jh-dimension-50);
175
+ margin-left: var(--jh-dimension-200);
176
+ flex: 1;
177
+ }
178
+ .label-text,
179
+ .helper-text {
180
+ word-break: break-word;
181
+ }
182
+ .label-text {
183
+ color: var(
184
+ --jh-switch-label-color-text,
185
+ var(--jh-color-content-primary-enabled)
186
+ );
187
+ }
188
+ .helper-text {
189
+ color: var(
190
+ --jh-switch-helper-color-text,
191
+ var(--jh-color-content-secondary-enabled)
192
+ );
193
+ font-family: var(--jh-font-helper-regular-font-family);
194
+ font-weight: var(--jh-font-helper-regular-font-weight);
195
+ font-size: var(--jh-font-helper-regular-font-size);
196
+ line-height: var(--jh-font-helper-regular-line-height);
197
+ margin: 0;
198
+ }
199
+ `;
200
+ }
201
+
202
+ static get properties() {
203
+ return {
204
+ /** Sets an `aria-disabled` to signify to screen readers that the disabled switch should remain perceivable while disabled. */
205
+ accessibleDisabled: {
206
+ type: String,
207
+ attribute: 'accessible-disabled',
208
+ reflect: true,
209
+ },
210
+ /** Sets an `aria-label` to assist screen reader users when no visible label is present. */
211
+ accessibleLabel: {
212
+ type: String,
213
+ attribute: 'accessible-label',
214
+ },
215
+ /** Sets the selected or 'checked' state on the switch */
216
+ checked: {
217
+ type: Boolean,
218
+ reflect: true,
219
+ },
220
+ /** Disables the switch and prevents all user interactions. May cause switch to be ignored by assistive technologies(AT). See `accessible-disabled` if switch should remain perceivable to AT. */
221
+ disabled: {
222
+ type: Boolean,
223
+ reflect: true,
224
+ },
225
+ /**
226
+ * Provides additional context or guidance for using the switch. For `helper-text` to be displayed, the `label` property must also be set.
227
+ */
228
+ helperText: {
229
+ type: String,
230
+ attribute: 'helper-text',
231
+ },
232
+ /**
233
+ * Describes the intent of the switch.
234
+ */
235
+ label: {
236
+ type: String,
237
+ },
238
+ };
239
+ }
240
+
241
+ constructor() {
242
+ super();
243
+ /** @type {'true'|'false'} */
244
+ this.accessibleDisabled = null;
245
+ /** @type {?string} */
246
+ this.accessibleLabel = null;
247
+ /** @type {?boolean} */
248
+ this.checked = false;
249
+ /** @type {?boolean} */
250
+ this.disabled = false;
251
+ /** @type {?string} */
252
+ this.helperText = null;
253
+ /** @type {?string} */
254
+ this.label = null;
255
+ }
256
+
257
+ connectedCallback() {
258
+ super.connectedCallback();
259
+ this.#id = id++;
260
+ }
261
+
262
+ #handleClick() {
263
+ if (!this.disabled && this.accessibleDisabled !== 'true') {
264
+ this.checked = !this.checked;
265
+ const options = {
266
+ bubbles: true,
267
+ composed: true,
268
+ cancelable: true,
269
+ };
270
+ this.dispatchEvent(new CustomEvent('jh-change', options));
271
+ }
272
+ }
273
+
274
+ render() {
275
+ let helperText;
276
+ let label;
277
+
278
+ if (this.helperText) {
279
+ helperText = html`
280
+ <p class="helper-text" id="switch-helper-text-${this.#id}">
281
+ ${this.helperText}
282
+ </p>
283
+ `;
284
+ }
285
+
286
+ if (this.label) {
287
+ label = html`
288
+ <div class="label-container">
289
+ <label class="label-text" for="switch-label-${this.#id}">
290
+ ${this.label}
291
+ </label>
292
+ ${helperText}
293
+ </div>
294
+ `;
295
+ }
296
+
297
+ return html`
298
+ <button
299
+ tabindex="0"
300
+ @click=${this.#handleClick}
301
+ aria-label="${ifDefined(this.accessibleLabel)}"
302
+ aria-disabled="${ifDefined(this.accessibleDisabled)}"
303
+ type="button"
304
+ aria-describedby=${this.helperText ? `switch-helper-text-${this.#id}` : null}
305
+ ?checked=${this.checked}
306
+ ?disabled=${this.disabled}
307
+ aria-pressed="${this.checked}"
308
+ id="switch-label-${this.#id}"
309
+ ></button>
310
+ <span aria-hidden="true"></span>
311
+ ${label}
312
+ `;
313
+ }
314
+ }
315
+
316
+ customElements.define('jh-switch', JhSwitch);