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

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 +343 -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 +1247 -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 +379 -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 +315 -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 +6892 -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,18 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { JhInput } from '../input/input.js';
6
+
7
+ /**
8
+ * Input Email
9
+ * @customElement jh-input-email
10
+ */
11
+ export class JhInputEmail extends JhInput {
12
+ firstUpdated() {
13
+ super.firstUpdated();
14
+ let inputEl = this.shadowRoot.querySelector('input');
15
+ inputEl.setAttribute('type', 'email');
16
+ }
17
+ }
18
+ customElements.define('jh-input-email', JhInputEmail);
@@ -0,0 +1,153 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { html, render, css } from 'lit';
6
+ import { JhInput } from '../input/input.js';
7
+ import '@jack-henry/jh-icons/icons-wc/icon-eye-slash.js';
8
+ import '@jack-henry/jh-icons/icons-wc/icon-eye.js';
9
+
10
+ /**
11
+ * @slot jh-input-password-hidden - Use to insert a custom icon within the toggle password button when the input value is masked.
12
+ * @slot jh-input-password-visible - Use to insert a custom icon within the toggle password button when the input value is unmasked.
13
+ * @customElement jh-input-password
14
+ */
15
+ export class JhInputPassword extends JhInput {
16
+ #inputEl;
17
+
18
+ static get styles() {
19
+ return [
20
+ super.styles,
21
+ css`
22
+ .password-toggle-btn {
23
+ position: absolute;
24
+ right: var(--jh-dimension-400);
25
+ }
26
+ .clear-button {
27
+ right: var(--jh-dimension-1400);
28
+ }
29
+ .display-clear-button input {
30
+ padding-right: var(--jh-dimension-2400);
31
+ }
32
+ input {
33
+ padding-right: var(--jh-dimension-1400);
34
+ }
35
+ :host([size='small']) .password-toggle-btn {
36
+ top: 4px;
37
+ }
38
+ :host([size='medium']) .password-toggle-btn {
39
+ top: 8px;
40
+ }
41
+ :host([size='large']) .password-toggle-btn {
42
+ top: 12px;
43
+ }
44
+ `
45
+ ];
46
+ }
47
+
48
+ static get properties() {
49
+ return {
50
+ /** Unmasks the input field value when set. */
51
+ passwordVisible: { type: Boolean, attribute: 'password-visible', reflect: true},
52
+ /** Sets an `aria-label` on the toggle password button, which encapsulates the `jh-input-password-visible` slot, to assist screen reader users. The label should indicate that activating the button will mask the password. */
53
+ accessibleLabelHidePassword: {
54
+ type: String,
55
+ attribute: 'accessible-label-hide-password',
56
+ },
57
+ /** Sets an `aria-label` on the toggle password button, which encapsulates the `jh-input-password-hidden` slot, to assist screen reader users. The label should indicate that activating the button will unmask the password. */
58
+ accessibleLabelShowPassword: {
59
+ type: String,
60
+ attribute: 'accessible-label-show-password',
61
+ },
62
+ };
63
+ }
64
+
65
+ constructor() {
66
+ super();
67
+ /** @type {?string} */
68
+ this.accessibleLabelHidePassword = null;
69
+ /** @type {?string} */
70
+ this.accessibleLabelShowPassword = null;
71
+ /** @type {boolean} */
72
+ this.hideRightSlot = true;
73
+ /** @type {boolean} */
74
+ this.passwordVisible = false;
75
+ }
76
+
77
+ firstUpdated() {
78
+ super.firstUpdated();
79
+ this.#inputEl = this.shadowRoot.querySelector('input');
80
+ }
81
+
82
+ updated(changedProperties) {
83
+ super.updated(changedProperties);
84
+
85
+ if (changedProperties.has('passwordVisible')) {
86
+ if (this.passwordVisible) {
87
+ this.#inputEl.setAttribute('type', 'text');
88
+ } else {
89
+ this.#inputEl.setAttribute('type', 'password');
90
+ }
91
+ }
92
+ this.#insertTogglePasswordBtn();
93
+ }
94
+
95
+ #insertTogglePasswordBtn() {
96
+ let inputContainerEl = this.shadowRoot.querySelector('.input-container');
97
+ let togglePasswordButton = this.#createTogglePasswordBtn();
98
+ render(togglePasswordButton, inputContainerEl);
99
+ }
100
+
101
+ #createTogglePasswordBtn() {
102
+ if (this.readonly) {
103
+ return;
104
+ }
105
+
106
+ let accessibleLabel = this.passwordVisible
107
+ ? this.accessibleLabelHidePassword
108
+ : this.accessibleLabelShowPassword;
109
+
110
+ let passwordBtn = html`
111
+ <jh-button
112
+ class="password-toggle-btn"
113
+ size="small"
114
+ appearance="tertiary"
115
+ ?disabled=${this.disabled}
116
+ accessible-label=${accessibleLabel}
117
+ @click=${this.#togglePassword.bind(this)}
118
+ >
119
+ ${this.passwordVisible
120
+ ? html`
121
+ <slot
122
+ name="jh-input-password-visible"
123
+ slot="jh-button-icon"
124
+ >
125
+ <jh-icon-eye-slash
126
+ slot="jh-button-icon"
127
+ aria-hidden="true"
128
+ size="medium"
129
+ ></jh-icon-eye-slash>
130
+ </slot>
131
+ `
132
+ : html`
133
+ <slot
134
+ name="jh-input-password-hidden"
135
+ slot="jh-button-icon"
136
+ >
137
+ <jh-icon-eye
138
+ slot="jh-button-icon"
139
+ aria-hidden="true"
140
+ size="medium"
141
+ ></jh-icon-eye>
142
+ </slot>
143
+ `}
144
+ </jh-button>
145
+ `;
146
+ return passwordBtn;
147
+ }
148
+
149
+ #togglePassword() {
150
+ this.passwordVisible = !this.passwordVisible;
151
+ }
152
+ }
153
+ customElements.define('jh-input-password', JhInputPassword);
@@ -0,0 +1,41 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { css } from 'lit';
6
+ import { JhInput } from '../input/input.js';
7
+ import '@jack-henry/jh-icons/icons-wc/icon-magnifying-glass.js';
8
+
9
+ /**
10
+ * Input Search
11
+ * @customElement jh-input-search
12
+ */
13
+ export class JhInputSearch extends JhInput {
14
+ static get styles() {
15
+ return [
16
+ super.styles,
17
+ css`
18
+ /* removes native clear search button */
19
+ input::-webkit-search-cancel-button {
20
+ display: none;
21
+ }
22
+ `,
23
+ ];
24
+ }
25
+
26
+ firstUpdated() {
27
+ super.firstUpdated();
28
+ let leftSlot = this.shadowRoot.querySelector('slot[name="jh-input-left"]');
29
+
30
+ // insert fallback content in left slot if empty
31
+ if (leftSlot && leftSlot.assignedElements().length === 0) {
32
+ this.innerHTML +=
33
+ '<jh-icon-magnifying-glass slot="jh-input-left" aria-hidden="true"></jh-icon-magnifying-glass>';
34
+ }
35
+
36
+ // set input type to search
37
+ let inputEl = this.shadowRoot.querySelector('input');
38
+ inputEl.setAttribute('type', 'search');
39
+ }
40
+ }
41
+ customElements.define('jh-input-search', JhInputSearch);
@@ -0,0 +1,18 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { JhInput } from '../input/input.js';
6
+
7
+ /**
8
+ * Input Telephone
9
+ * @customElement jh-input-telephone
10
+ */
11
+ export class JhInputTelephone extends JhInput {
12
+ firstUpdated() {
13
+ super.firstUpdated();
14
+ let inputEl = this.shadowRoot.querySelector('input');
15
+ inputEl.setAttribute('type', 'tel');
16
+ }
17
+ }
18
+ customElements.define('jh-input-telephone', JhInputTelephone);
@@ -0,0 +1,379 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { css, html } from 'lit';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
7
+ import { JhInput } from '../input/input.js';
8
+
9
+ let id = 0;
10
+
11
+ /**
12
+ * @cssprop --jh-input-textarea-field-dimension-min-height - The input field minimum height. Defaults to `--jh-dimension-2000` when `size='small'`, `--jh-dimension-2200` when `size='medium'`, and `--jh-dimension-2400` when `size='large'`.
13
+ *
14
+ * @event jh-change - Dispatched when the value of the input has changed and input loses focus. Event payload includes the value of the input and can be accessed via `e.detail.value`.
15
+ * @event jh-input - Dispatched when the value of the input has changed. Event payload includes the value of the input and can be accessed via `e.detail.value`.
16
+ * @customElement jh-input-textarea
17
+ */
18
+ export class JhInputTextarea extends JhInput {
19
+ /** @type {?number} */
20
+ #id;
21
+
22
+ static get styles() {
23
+ return [
24
+ super.styles,
25
+ css`
26
+ :host {
27
+ --textarea-small-padding: 10px var(--jh-dimension-400) 10px var(--jh-dimension-400);
28
+ --textarea-medium-padding: 14px var(--jh-dimension-400) 14px var(--jh-dimension-400);
29
+ --textarea-large-padding: 18px var(--jh-dimension-400) 18px var(--jh-dimension-400);
30
+ }
31
+ textarea {
32
+ background-color: var(
33
+ --jh-input-field-color-background,
34
+ var(--jh-color-container-primary-enabled)
35
+ );
36
+ border-radius: var(
37
+ --jh-input-field-border-radius,
38
+ var(--jh-border-radius-100)
39
+ );
40
+ border-width: var(--jh-border-control-width);
41
+ border-style: var(--jh-border-control-style);
42
+ border-color: var(
43
+ --jh-input-field-color-border-enabled,
44
+ var(--jh-border-control-color)
45
+ );
46
+ color: var(--jh-input-value-color-text, var(--jh-color-content-primary-enabled));
47
+ font-family: var(--jh-font-body-regular-1-font-family);
48
+ font-weight: var(--jh-font-body-regular-1-font-weight);
49
+ font-size: var(--jh-font-body-regular-1-font-size);
50
+ line-height: var(--jh-font-body-regular-1-line-height);
51
+ box-sizing: border-box;
52
+ display: block;
53
+ }
54
+ :host([label]) textarea {
55
+ margin-top: var(--jh-dimension-200);
56
+ }
57
+ :host(:not([cols])) textarea {
58
+ min-width: 100%;
59
+ }
60
+ :host([rows]) textarea {
61
+ min-height: 0;
62
+ }
63
+ /** sizes */
64
+ :host([size='small']) textarea {
65
+ min-height: var(
66
+ --jh-input-textarea-field-dimension-min-height,
67
+ var(--jh-dimension-2000)
68
+ );
69
+ padding: var(--textarea-small-padding);
70
+ scroll-padding: var(--textarea-small-padding);
71
+ }
72
+ :host([size='medium']) textarea {
73
+ min-height: var(
74
+ --jh-input-textarea-field-dimension-min-height,
75
+ var(--jh-dimension-2200)
76
+ );
77
+ padding: var(--textarea-medium-padding);
78
+ scroll-padding: var(--textarea-medium-padding);
79
+ }
80
+ :host([size='large']) textarea {
81
+ min-height: var(
82
+ --jh-input-textarea-field-dimension-min-height,
83
+ var(--jh-dimension-2400)
84
+ );
85
+ padding: var(--textarea-large-padding);
86
+ scroll-padding: var(--textarea-large-padding);
87
+ }
88
+ /** textarea states */
89
+ textarea:active {
90
+ border-color: var(
91
+ --jh-input-field-color-border-active,
92
+ var(--jh-color-content-brand-active)
93
+ );
94
+ }
95
+ textarea:hover {
96
+ border-color: var(
97
+ --jh-input-field-color-border-hover,
98
+ var(--jh-color-content-brand-hover)
99
+ );
100
+ }
101
+ textarea:focus-visible {
102
+ border-color: var(
103
+ --jh-input-field-color-border-focus,
104
+ var(--jh-color-content-brand-hover)
105
+ );
106
+ outline-color: var(
107
+ --jh-input-color-focus,
108
+ var(--jh-border-focus-color)
109
+ );
110
+ outline-style: var(--jh-border-focus-style);
111
+ outline-width: var(--jh-border-focus-width);
112
+ outline-offset: 1px;
113
+ }
114
+ :host([disabled]) textarea {
115
+ border-color: var(
116
+ --jh-input-field-color-border-disabled,
117
+ var(--jh-border-control-color)
118
+ );
119
+ }
120
+ :host([readonly]) textarea {
121
+ height: auto;
122
+ border: none;
123
+ padding-left: 0;
124
+ padding-right: 0;
125
+ }
126
+ :host([invalid]) textarea {
127
+ border-color: var(
128
+ --jh-input-field-color-border-error,
129
+ var(--jh-border-error-color)
130
+ );
131
+ border-width: var(--jh-border-error-width);
132
+ border-style: var(--jh-border-error-style);
133
+ }
134
+ /* resize and auto-grow */
135
+ :host([readonly]) textarea,
136
+ :host([no-resize]) textarea {
137
+ resize: none;
138
+ height: 100%;
139
+ }
140
+ :host([auto-grow]) textarea {
141
+ height: auto;
142
+ scrollbar-width: none;
143
+ }
144
+ `,
145
+ ];
146
+ }
147
+
148
+ static get properties() {
149
+ return {
150
+ /** Enables the input height to grow automatically to accommodate user input. `auto-grow` will also remove the input's native resize capability. */
151
+ autoGrow: {
152
+ type: Boolean,
153
+ attribute: 'auto-grow',
154
+ },
155
+ /** Sets the width of the input field. */
156
+ cols: { type: String },
157
+ /** Removes native resize capability of the input field. */
158
+ noResize: {
159
+ type: Boolean,
160
+ attribute: 'no-resize',
161
+ },
162
+ /** Sets the height of the input field. */
163
+ rows: { type: String },
164
+ /** Specifies how text should be wrapped when submitted in a form. The `cols` property must be set for `wrap='hard'` to take effect. */
165
+ wrap: { type: String },
166
+ /** Prevents users from changing the input value. */
167
+ readonly: { type: Boolean },
168
+ };
169
+ }
170
+
171
+ constructor() {
172
+ super();
173
+ /** @type {boolean} */
174
+ this.autoGrow = null;
175
+ /** @type {?string} */
176
+ this.cols = null;
177
+ /** @type {boolean} */
178
+ this.noResize = true;
179
+ /** @type {?string} */
180
+ this.rows = null;
181
+ /** @type {'small'|'medium'|'large'} */
182
+ this.size = 'medium';
183
+ /** @type {'hard'|'soft'} */
184
+ this.wrap = null;
185
+ }
186
+
187
+ connectedCallback() {
188
+ super.connectedCallback();
189
+ this.#id = id++;
190
+ }
191
+
192
+ firstUpdated() {
193
+ // add resize observer to update width of footer when textarea width changes
194
+ let footerContent = this.shadowRoot.querySelector('.footer-content');
195
+ let textareaEl = this.shadowRoot.querySelector('textarea');
196
+
197
+ if (footerContent) {
198
+ new ResizeObserver(() => {
199
+ this.#updateFooterWidth(footerContent, textareaEl);
200
+ }).observe(textareaEl);
201
+ }
202
+ }
203
+
204
+ #updateFooterWidth(footerContent, textareaEl) {
205
+ footerContent.style.width = textareaEl.offsetWidth + 'px';
206
+ }
207
+
208
+ #dispatch(eventName, details) {
209
+ this.dispatchEvent(
210
+ new CustomEvent(eventName, {
211
+ detail: details,
212
+ bubbles: true,
213
+ cancelable: true,
214
+ composed: true,
215
+ })
216
+ );
217
+ }
218
+
219
+ #handleInput(e) {
220
+ this.value = e.target.value;
221
+
222
+ this.#dispatch('jh-input', { value: this.value } );
223
+
224
+ if (this.autoGrow) {
225
+ this.#autoGrowTextarea();
226
+ }
227
+ }
228
+
229
+ #autoGrowTextarea() {
230
+ let textareaEl = this.shadowRoot.querySelector('textarea');
231
+ textareaEl.style.height = 'auto';
232
+ textareaEl.style.height = textareaEl.scrollHeight + 'px';
233
+ }
234
+
235
+ #handleChange() {
236
+ let payload = {
237
+ 'value': this.value,
238
+ };
239
+ this.#dispatch('jh-change', payload);
240
+ }
241
+
242
+ #handleSelect(e) {
243
+ const selectedString = e.target.value.substring(
244
+ e.target.selectionStart,
245
+ e.target.selectionEnd
246
+ );
247
+
248
+ if (selectedString) {
249
+ this.#dispatch('jh-select', {
250
+ selected: selectedString,
251
+ selectionStart: e.target.selectionStart,
252
+ selectionEnd: e.target.selectionEnd,
253
+ });
254
+ }
255
+ }
256
+
257
+ #handleMaxlength() {
258
+ this.#dispatch('jh-maxlength');
259
+ }
260
+
261
+ #getDescribedby() {
262
+ let describedbyString = '';
263
+
264
+ if (this.errorText) {
265
+ describedbyString += `jh-input-error-${this.#id}`;
266
+ }
267
+ if (this.helperText) {
268
+ describedbyString += ` jh-input-helper-${this.#id}`;
269
+ }
270
+ if (this.showCharCount) {
271
+ describedbyString += ` jh-input-counter-${this.#id}`;
272
+ }
273
+ return describedbyString;
274
+ }
275
+
276
+ render() {
277
+ let label;
278
+ let indicator;
279
+ let helperText;
280
+ let input;
281
+ let footer;
282
+ let errorText;
283
+ let charCount;
284
+ let describedby;
285
+
286
+ if (this.label) {
287
+ if (this.showIndicator) {
288
+ if (this.required) {
289
+ indicator = html`<span class="indicator" aria-hidden="true"> *</span>`;
290
+ } else {
291
+ indicator = html`<span class="indicator"> (optional)</span>`;
292
+ }
293
+ }
294
+
295
+ if (this.helperText) {
296
+ helperText = html`
297
+ <p id="jh-input-helper-${this.#id}" class="helper-text">
298
+ ${this.helperText}
299
+ </p>
300
+ `;
301
+ }
302
+
303
+ label = html`
304
+ <label for="jh-input-${this.#id}">${this.label}${indicator}</label>
305
+ ${helperText}
306
+ `;
307
+ }
308
+
309
+ if (this.showCharCount) {
310
+ let valueLength = this.value ? this.value.length : 0;
311
+ let charCountValue = `${this.maxlength ? `${valueLength}/${this.maxlength}` : valueLength}`;
312
+
313
+ if (valueLength && valueLength === Number(this.maxlength)) {
314
+ this.#handleMaxlength();
315
+ }
316
+
317
+ charCount = html`
318
+ <p class="counter" aria-hidden="true">${charCountValue}</p>
319
+ `;
320
+ }
321
+
322
+ if (this.invalid && this.errorText) {
323
+ errorText = html`
324
+ <p id="jh-input-error-${this.#id}" class="error-text">
325
+ ${this.errorText}
326
+ </p>
327
+ `;
328
+ }
329
+
330
+ if ((this.invalid && this.errorText) || this.showCharCount) {
331
+ footer = html`
332
+ <div class="footer-content">
333
+ ${errorText}
334
+ ${charCount}
335
+ </div>
336
+ `;
337
+ }
338
+
339
+ if (helperText || errorText || charCount) {
340
+ describedby = this.#getDescribedby();
341
+ }
342
+
343
+ input = html`
344
+ <textarea
345
+ id="jh-input-${this.#id}"
346
+ aria-describedby=${describedby}
347
+ aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
348
+ aria-label=${ifDefined(
349
+ this.accessibleLabel === '' ? null : this.accessibleLabel
350
+ )}
351
+ autocomplete=${ifDefined(
352
+ this.autocomplete === '' ? null : this.autocomplete
353
+ )}
354
+ cols=${ifDefined(this.cols === '' ? null : this.cols)}
355
+ ?disabled=${this.disabled}
356
+ enterkeyhint=${ifDefined(
357
+ this.enterkeyhint === '' ? null : this.enterkeyhint
358
+ )}
359
+ inputmode=${ifDefined(this.inputmode === '' ? null : this.inputmode)}
360
+ maxlength=${ifDefined(this.maxlength === '' ? null : this.maxlength)}
361
+ minlength=${ifDefined(this.minlength === '' ? null : this.minlength)}
362
+ name=${ifDefined(this.name === '' ? null : this.name)}
363
+ ?readonly=${this.readonly}
364
+ ?required=${this.required}
365
+ rows=${ifDefined(this.rows === '' ? null : this.rows)}
366
+ .value=${this.value}
367
+ wrap=${ifDefined(this.wrap === '' ? null : this.wrap)}
368
+ @change=${this.#handleChange}
369
+ @select=${this.#handleSelect}
370
+ @input=${this.#handleInput}
371
+ ></textarea>
372
+ `;
373
+
374
+ return html`
375
+ ${label} ${input} ${footer}
376
+ `;
377
+ }
378
+ }
379
+ customElements.define('jh-input-textarea', JhInputTextarea);
@@ -0,0 +1,31 @@
1
+ // SPDX-FileCopyrightText: 2025 Jack Henry
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ import { css } from 'lit';
6
+ import { JhInput } from '../input/input.js';
7
+
8
+ /**
9
+ * Input Url
10
+ * @customElement jh-input-url
11
+ */
12
+ export class JhInputUrl extends JhInput {
13
+ static get styles() {
14
+ return [
15
+ super.styles,
16
+ css`
17
+ /* removes safari autofill button */
18
+ input::-webkit-textfield-decoration-container {
19
+ display: none;
20
+ }
21
+ `,
22
+ ];
23
+ }
24
+
25
+ firstUpdated() {
26
+ super.firstUpdated();
27
+ let inputEl = this.shadowRoot.querySelector('input');
28
+ inputEl.setAttribute('type', 'url');
29
+ }
30
+ }
31
+ customElements.define('jh-input-url', JhInputUrl);