@jack-henry/jh-elements 2.0.0-beta.12 → 2.0.0-beta.13

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.
@@ -11,7 +11,7 @@ let id = 0;
11
11
 
12
12
  /**
13
13
  * @cssprop --jh-input-label-color-text - The label text color. Defaults to `--jh-color-content-primary-enabled`.
14
- * @cssprop --jh-input-field-color-background - The input field background-color. Defaults to `--jh-color-container-primary-enabled`.
14
+ * @cssprop --jh-input-field-color-background - The input field background-color when in an editable state. This property does not apply when the component is set to `readonly`. Defaults to `--jh-color-container-primary-enabled`.
15
15
  * @cssprop --jh-input-field-color-border-enabled - The input field border-color. Defaults to `--jh-border-control-color`.
16
16
  * @cssprop --jh-input-field-border-radius - The input field border radius. Defaults to `--jh-border-radius-100`.
17
17
  * @cssprop --jh-input-color-focus - The input field outline when it receives keyboard focus. Defaults to `--jh-border-focus-color`.
@@ -91,8 +91,18 @@ export class JhInput extends LitElement {
91
91
  // alphanumeric characters -> usernames, product codes, etc.
92
92
  '*': /[A-Za-z0-9]/,
93
93
  };
94
- /** @type {Map} */
95
- #activeSlottedElement = new Map();
94
+
95
+ get #inputEl() {
96
+ return this.renderRoot?.querySelector('input');
97
+ }
98
+
99
+ get #leftSlot() {
100
+ return this.renderRoot?.querySelector('slot[name="jh-input-left"]');
101
+ }
102
+
103
+ get #rightSlot() {
104
+ return this.renderRoot?.querySelector('slot[name="jh-input-right"]');
105
+ }
96
106
 
97
107
  static get styles() {
98
108
  return css`
@@ -107,14 +117,7 @@ export class JhInput extends LitElement {
107
117
  line-height: var(--input-helper-regular-line-height);
108
118
  display: inline-block;
109
119
  width: 100%;
110
- --jh-button-size: var(--jh-dimension-800);
111
- /* input padding + slot padding */
112
- --padding-with-left-slotted-content: calc(
113
- var(--jh-dimension-400) + var(--jh-dimension-200)
114
- );
115
- --padding-with-right-slotted-content: calc(
116
- var(--jh-dimension-400) + var(--jh-dimension-200)
117
- );
120
+ --jh-button-size: var(--jh-dimension-800);
118
121
  --input-value-color-text: var(
119
122
  --jh-input-value-color-text,
120
123
  var(--jh-color-content-primary-enabled)
@@ -146,13 +149,14 @@ export class JhInput extends LitElement {
146
149
  :host([show-char-count]) .helper-text {
147
150
  display: inline-block;
148
151
  }
149
- .input-container {
150
- position: relative;
151
- }
152
152
  :host([label]) .input-container {
153
153
  margin-top: var(--jh-dimension-200);
154
154
  }
155
- input {
155
+
156
+ /* Flex wrapper */
157
+ .input-wrapper {
158
+ display: flex;
159
+ align-items: center;
156
160
  background-color: var(
157
161
  --jh-input-field-color-background,
158
162
  var(--jh-color-container-primary-enabled)
@@ -161,123 +165,78 @@ export class JhInput extends LitElement {
161
165
  border-style: var(--jh-border-control-style);
162
166
  border-color: var(
163
167
  --jh-input-field-color-border-enabled,
164
- var(--jh-border-control-color)
165
- );
168
+ var(--jh-border-control-color)
169
+ );
166
170
  border-radius: var(
167
171
  --jh-input-field-border-radius,
168
172
  var(--jh-border-radius-100)
169
173
  );
170
- color: var(--input-value-color-text);
171
- font-family: var(--jh-font-body-regular-1-font-family);
172
- font-weight: var(--jh-font-body-regular-1-font-weight);
173
- font-size: var(--jh-font-body-regular-1-font-size);
174
- line-height: var(--jh-font-body-regular-1-line-height);
175
- padding: var(--jh-dimension-0) var(--jh-dimension-400) var(--jh-dimension-0) var(--jh-dimension-400);
174
+ padding: var(--jh-dimension-0) var(--jh-dimension-400);
176
175
  box-sizing: border-box;
177
176
  width: 100%;
178
177
  }
179
- .jh-input-right {
180
- padding-right: calc(var(--padding-with-right-slotted-content) + var(--jh-input-right-width));
181
- }
182
- .jh-input-left {
183
- padding-left: calc(var(--padding-with-left-slotted-content) + var(--jh-input-left-width));
184
- }
185
- /* slot styles */
186
- ::slotted(*),
187
- ::slotted(*) {
188
- position: absolute;
189
- display: flex;
190
- align-items: center;
191
- justify-content: center;
192
- }
193
- ::slotted([slot='jh-input-left']) {
194
- left: var(--jh-dimension-400);
195
- }
196
- ::slotted([slot='jh-input-right']) {
197
- right: var(--jh-dimension-400);
198
- }
199
- ::slotted([slot='jh-input-left']){
200
- top: var(--jh-input-left-top);
201
- }
202
- ::slotted([slot='jh-input-right']) {
203
- top: var(--jh-input-right-top);
204
- }
205
- /* clear button */
206
- .clear-button {
207
- right: var(--jh-dimension-400);
208
- --jh-button-border-radius: var(--jh-input-clear-border-radius);
209
- --jh-button-color-background-tertiary-enabled: var(--jh-input-clear-color-background-enabled);
210
- --jh-button-color-border-tertiary-enabled: var(--jh-input-clear-color-border-enabled);
211
- --jh-button-icon-color-fill-tertiary-enabled: var(--jh-input-clear-icon-color-fill-enabled);
212
- --jh-button-color-background-tertiary-focus: var(--jh-input-clear-color-background-focus);
213
- --jh-button-color-border-tertiary-focus: var(--jh-input-clear-color-border-focus);
214
- --jh-button-color-focus: var(--jh-input-clear-color-focus);
215
- --jh-button-icon-color-fill-tertiary-focus: var(--jh-input-clear-icon-color-fill-focus);
216
- --jh-button-color-background-tertiary-hover: var(--jh-input-clear-color-background-hover);
217
- --jh-button-color-border-tertiary-hover: var(--jh-input-clear-color-border-hover);
218
- --jh-button-icon-color-fill-tertiary-hover: var(--jh-input-clear-icon-color-fill-hover);
219
- --jh-button-color-background-tertiary-active: var(--jh-input-clear-color-background-active);
220
- --jh-button-color-border-tertiary-active: var(--jh-input-clear-color-border-active);
221
- --jh-button-icon-color-fill-tertiary-active: var(--jh-input-clear-icon-color-fill-active);
222
- display: none;
223
- position: absolute;
224
- }
225
- .display-clear-button .clear-button {
226
- display: inherit;
227
- }
228
- :host([size='small']) .clear-button {
229
- top: 4px;
230
- }
231
- :host([size='medium']) .clear-button {
232
- top: 8px;
233
- }
234
- :host([size='large']) .clear-button {
235
- top: 12px;
236
- }
237
- .jh-input-right ~ .clear-button {
238
- right: calc(var(--padding-with-right-slotted-content) + var(--jh-input-right-width));
239
- }
240
- .display-clear-button input {
241
- padding-right: calc(var(--padding-with-right-slotted-content) + var(--jh-dimension-800));
242
- }
243
- .display-clear-button .jh-input-right {
244
- padding-right: calc(var(--padding-with-right-slotted-content) + var(--jh-input-right-width) + var(--jh-dimension-800) + var(--jh-dimension-200));
245
- }
246
- /* Sizes */
247
- :host([size='small']) input {
178
+
179
+ /* Sizes on input wrapper */
180
+ :host([size='small']) .input-wrapper {
248
181
  height: var(--jh-dimension-1000);
249
182
  }
250
- :host([size='medium']) input {
183
+ :host([size='medium']) .input-wrapper {
251
184
  height: var(--jh-dimension-1200);
252
185
  }
253
- :host([size='large']) input {
186
+ :host([size='large']) .input-wrapper {
254
187
  height: var(--jh-dimension-1400);
255
188
  }
256
- .footer-content {
257
- margin: var(--jh-dimension-200) 0 0 0;
258
- gap: var(--jh-dimension-200);
259
- display: flex;
260
- justify-content: space-between;
189
+
190
+ /* Input element no border, grows to fill */
191
+ input {
192
+ flex: 1;
193
+ min-width: 0;
194
+ border: none;
195
+ background: transparent;
196
+ outline: none;
197
+ padding: 0;
198
+ color: var(--input-value-color-text);
199
+ font-family: var(--jh-font-body-regular-1-font-family);
200
+ font-weight: var(--jh-font-body-regular-1-font-weight);
201
+ font-size: var(--jh-font-body-regular-1-font-size);
202
+ line-height: var(--jh-font-body-regular-1-line-height);
203
+ height: 100%;
261
204
  }
262
- .footer-content:has(.counter):not(:has(.error-text)) {
263
- justify-content: flex-end;
205
+ :host([readonly]) input {
206
+ height: auto;
207
+ }
208
+
209
+ /* Slot wrappers */
210
+ .slot-wrapper {
211
+ display: none;
212
+ align-items: center;
213
+ flex-shrink: 0;
264
214
  }
265
- .counter {
266
- color: var(--jh-input-counter-color-text,
267
- var(--jh-color-content-secondary-enabled)
268
- );
215
+ slot[name="jh-input-left"] {
216
+ display: none;
217
+ align-items: center;
218
+ flex-shrink: 0;
219
+ padding-right: var(--jh-dimension-200);
269
220
  }
270
- .error-text {
271
- color: var(
272
- --jh-input-error-color-text,
273
- var(--jh-color-content-negative-enabled)
274
- );
221
+ slot[name="jh-input-right"] {
222
+ display: none;
223
+ align-items: center;
224
+ flex-shrink: 0;
225
+ padding-left: var(--jh-dimension-200);
275
226
  }
276
- p {
277
- margin: 0;
227
+ slot[name="jh-input-left"].display-slot,
228
+ slot[name="jh-input-right"].display-slot {
229
+ display: flex;
278
230
  }
279
- /* Input States */
280
- input:active {
231
+
232
+ /* Slotted content alignment */
233
+ ::slotted(*) {
234
+ display: flex;
235
+ align-items: center;
236
+ }
237
+
238
+ /* States on input wrapper */
239
+ .input-wrapper:active {
281
240
  border-color: var(
282
241
  --jh-input-field-color-border-active,
283
242
  var(--jh-color-content-brand-active)
@@ -286,13 +245,15 @@ export class JhInput extends LitElement {
286
245
  :host([disabled]) {
287
246
  opacity: var(--jh-input-opacity-disabled, var(--jh-opacity-disabled));
288
247
  }
289
- :host([disabled]) input {
248
+ :host([disabled]) .input-wrapper {
290
249
  border-color: var(
291
250
  --jh-input-field-color-border-disabled,
292
251
  var(--jh-border-control-color)
293
252
  );
294
253
  }
295
- input:focus-visible {
254
+
255
+ /* Focus-visible on wrapper when input is focused */
256
+ .input-wrapper:has(input:focus-visible) {
296
257
  border-color: var(
297
258
  --jh-input-field-color-border-focus,
298
259
  var(--jh-color-content-brand-hover)
@@ -305,13 +266,16 @@ export class JhInput extends LitElement {
305
266
  outline-width: var(--jh-border-focus-width);
306
267
  outline-offset: 1px;
307
268
  }
308
- input:hover {
269
+ input:focus-visible {
270
+ outline: none;
271
+ }
272
+ .input-wrapper:hover {
309
273
  border-color: var(
310
274
  --jh-input-field-color-border-hover,
311
275
  var(--jh-color-content-brand-hover)
312
276
  );
313
277
  }
314
- :host([invalid]) input {
278
+ :host([invalid]) .input-wrapper {
315
279
  border-width: var(--jh-border-error-width);
316
280
  border-style: var(--jh-border-error-style);
317
281
  border-color: var(
@@ -319,20 +283,72 @@ export class JhInput extends LitElement {
319
283
  var(--jh-border-error-color)
320
284
  );
321
285
  }
322
- /* readonly styles */
323
- :host([readonly]) input {
286
+
287
+ /* Clear button */
288
+ .clear-button {
289
+ --jh-button-border-radius: var(--jh-input-clear-border-radius);
290
+ --jh-button-color-background-tertiary-enabled: var(--jh-input-clear-color-background-enabled);
291
+ --jh-button-color-border-tertiary-enabled: var(--jh-input-clear-color-border-enabled);
292
+ --jh-button-icon-color-fill-tertiary-enabled: var(--jh-input-clear-icon-color-fill-enabled);
293
+ --jh-button-color-background-tertiary-focus: var(--jh-input-clear-color-background-focus);
294
+ --jh-button-color-border-tertiary-focus: var(--jh-input-clear-color-border-focus);
295
+ --jh-button-color-focus: var(--jh-input-clear-color-focus);
296
+ --jh-button-icon-color-fill-tertiary-focus: var(--jh-input-clear-icon-color-fill-focus);
297
+ --jh-button-color-background-tertiary-hover: var(--jh-input-clear-color-background-hover);
298
+ --jh-button-color-border-tertiary-hover: var(--jh-input-clear-color-border-hover);
299
+ --jh-button-icon-color-fill-tertiary-hover: var(--jh-input-clear-icon-color-fill-hover);
300
+ --jh-button-color-background-tertiary-active: var(--jh-input-clear-color-background-active);
301
+ --jh-button-color-border-tertiary-active: var(--jh-input-clear-color-border-active);
302
+ --jh-button-icon-color-fill-tertiary-active: var(--jh-input-clear-icon-color-fill-active);
303
+ display: none;
304
+ flex-shrink: 0;
305
+ }
306
+ .display-clear-button .clear-button {
307
+ display: flex;
308
+ margin-left: var(--jh-dimension-200);
309
+ }
310
+
311
+ /* Readonly styles */
312
+ :host([readonly]) .input-wrapper {
324
313
  height: auto;
325
314
  background-color: transparent;
326
315
  border: none;
327
316
  padding-left: 0;
328
317
  padding-right: 0;
329
318
  }
319
+
330
320
  /* Override Chrome autofill styles */
331
321
  input:autofill {
332
322
  -webkit-text-fill-color: var(--input-value-color-text);
333
323
  caret-color: var(--input-value-color-text);
334
324
  background-clip: text;
335
325
  }
326
+
327
+ /* Footer */
328
+ .footer-content {
329
+ margin: var(--jh-dimension-200) 0 0 0;
330
+ gap: var(--jh-dimension-200);
331
+ display: flex;
332
+ justify-content: space-between;
333
+ }
334
+ .footer-content:has(.counter):not(:has(.error-text)) {
335
+ justify-content: flex-end;
336
+ }
337
+ .counter {
338
+ color: var(--jh-input-counter-color-text,
339
+ var(--jh-color-content-secondary-enabled)
340
+ );
341
+ }
342
+ .error-text {
343
+ color: var(
344
+ --jh-input-error-color-text,
345
+ var(--jh-color-content-negative-enabled)
346
+ );
347
+ }
348
+ p {
349
+ margin: 0;
350
+ }
351
+
336
352
  /* Optional/Required/Show-indicator */
337
353
  :host([show-indicator]) span {
338
354
  color: var(
@@ -350,7 +366,6 @@ export class JhInput extends LitElement {
350
366
  var(--jh-color-content-negative-enabled)
351
367
  );
352
368
  }
353
-
354
369
  `;
355
370
  }
356
371
 
@@ -478,12 +493,15 @@ export class JhInput extends LitElement {
478
493
 
479
494
  disconnectedCallback() {
480
495
  super.disconnectedCallback();
481
- this.#resizeObserver.disconnect();
482
496
  if (this.inputMask) {
483
497
  this.removeEventListener('jh-select', this.#setSelection);
484
498
  }
485
499
  }
486
500
 
501
+ get uniqueId() {
502
+ return this.#id;
503
+ }
504
+
487
505
  firstUpdated() {
488
506
  // attach event listeners to show/hide clear button
489
507
  if (this.showClearButton) {
@@ -494,9 +512,43 @@ export class JhInput extends LitElement {
494
512
  ['mouseenter', 'mouseleave'].forEach(e => {
495
513
  inputContainer.addEventListener(e, this.#toggleFocus.bind(this));
496
514
  });
497
- }
515
+ }
516
+
517
+ if (this.#leftSlot) this.#leftSlot.classList.toggle('display-slot', this.#checkSlotContent(this.#leftSlot));
518
+ if (this.#rightSlot) this.#rightSlot.classList.toggle('display-slot', this.#checkSlotContent(this.#rightSlot));
519
+
520
+ // clicking the wrapper should focus the input
521
+ const wrapper = this.shadowRoot.querySelector('.input-wrapper');
522
+ wrapper?.addEventListener('mousedown', (e) => {
523
+ if (e.target === wrapper || e.target.tagName === 'SLOT') {
524
+ //if the input already has focus, don't do anything. Prevent default to avoid flickering of the focus ring.
525
+ if (this.shadowRoot.activeElement === this.#inputEl) {
526
+ e.preventDefault();
527
+ } else {
528
+ //otherwise set focus to the input.
529
+ e.preventDefault();
530
+ this.#inputEl?.focus();
531
+ }
532
+ }
533
+ });
498
534
  }
499
535
 
536
+ #checkSlotContent(slot) {
537
+ // Slotted and fallback elements
538
+ const slottedElements = slot.assignedElements({ flatten: true });
539
+ if (slottedElements.length > 0) {
540
+ return true;
541
+ }
542
+
543
+ // Slotted and fallback text nodes that are not just whitespace
544
+ if (slot.assignedNodes({ flatten: true }).some(
545
+ (node) => node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== ''
546
+ )) {
547
+ return true;
548
+ }
549
+ return false;
550
+ }
551
+
500
552
  #toggleFocus(e) {
501
553
  if (this.disabled || this.readonly || !this.showClearButton) {
502
554
  return;
@@ -593,7 +645,7 @@ export class JhInput extends LitElement {
593
645
  );
594
646
  }
595
647
 
596
- #handleInput(e) {
648
+ _handleInput(e) {
597
649
  this.value = e.target.value;
598
650
  let inputType = e.inputType;
599
651
  this.#deletedChar = inputType === 'deleteContentBackward' || inputType === 'deleteByCut' || inputType === 'deleteContentForward';
@@ -608,7 +660,7 @@ export class JhInput extends LitElement {
608
660
  }
609
661
  }
610
662
 
611
- #handleKeydown(e) {
663
+ _handleKeydown(e) {
612
664
  const value = e.target.value;
613
665
  let selectionStart = e.target.selectionStart;
614
666
  let selectionEnd = e.target.selectionEnd;
@@ -1010,9 +1062,27 @@ export class JhInput extends LitElement {
1010
1062
  this.#selectedText.selectionStart = null;
1011
1063
  }
1012
1064
  }
1065
+
1066
+ // Handle slot visibility updates
1067
+ if (changedProperties.has('hideLeftSlot')) {
1068
+ this.#updateSlotVisibility(this.#leftSlot);
1069
+ }
1070
+ if (changedProperties.has('hideRightSlot')) {
1071
+ this.#updateSlotVisibility(this.#rightSlot);
1072
+ }
1073
+ if (changedProperties.has('readonly')) {
1074
+ this.#updateSlotVisibility(this.#leftSlot);
1075
+ this.#updateSlotVisibility(this.#rightSlot);
1076
+ }
1077
+ }
1078
+
1079
+ #updateSlotVisibility(slot) {
1080
+ if (slot) {
1081
+ slot.classList.toggle('display-slot', this.#checkSlotContent(slot));
1082
+ }
1013
1083
  }
1014
1084
 
1015
- #handleChange() {
1085
+ _handleChange() {
1016
1086
  let payload = {
1017
1087
  'value': this.value,
1018
1088
  }
@@ -1024,7 +1094,7 @@ export class JhInput extends LitElement {
1024
1094
  this.#dispatch('jh-change', payload);
1025
1095
  }
1026
1096
 
1027
- #handleSelect(e) {
1097
+ _handleSelect(e) {
1028
1098
  const selectedString = e.target.value.substring(
1029
1099
  e.target.selectionStart,
1030
1100
  e.target.selectionEnd
@@ -1040,11 +1110,11 @@ export class JhInput extends LitElement {
1040
1110
  }
1041
1111
  }
1042
1112
 
1043
- #handleMaxlength() {
1113
+ _handleMaxlength() {
1044
1114
  this.#dispatch('jh-maxlength');
1045
1115
  }
1046
1116
 
1047
- #handleClearButtonClick() {
1117
+ _handleClearButtonClick() {
1048
1118
  let previousValue = this.value;
1049
1119
  // clear input value
1050
1120
  this.value = '';
@@ -1063,115 +1133,67 @@ export class JhInput extends LitElement {
1063
1133
  );
1064
1134
  }
1065
1135
 
1066
- // capture dimensions of slotted content and set CSS variables to adjust input padding and vertically center slotted content
1067
- #resizeObserver = new ResizeObserver((entries) => {
1068
- let inputEl = this.shadowRoot.querySelector('input');
1069
-
1070
- for (let entry of entries) {
1071
- let slottedEl = entry.target;
1072
- let slottedElWidth = entry.borderBoxSize[0].inlineSize;
1073
- let slottedElHeight = entry.borderBoxSize[0].blockSize;
1074
-
1075
- this.style.setProperty(`--${slottedEl.slot}-width`, `${slottedElWidth}px`);
1076
-
1077
- this.style.setProperty(`--${slottedEl.slot}-top`, `${(inputEl.offsetHeight - slottedElHeight) / 2}px`);
1078
- }
1079
- });
1080
-
1081
- #handleSlotChange(e) {
1136
+ _handleSlotChange(e) {
1082
1137
  let newSlottedElement = e.target.assignedElements()[0];
1083
- let slotName = e.target.name;
1138
+ let slot = e.target;
1084
1139
 
1085
- // stop observing previous slotted element for each slot
1086
- if (this.#activeSlottedElement.has(slotName)) {
1087
- this.#resizeObserver.unobserve(this.#activeSlottedElement.get(slotName));
1140
+ if (slot.name !== 'jh-input-left' && slot.name !== 'jh-input-right') {
1141
+ return;
1088
1142
  }
1143
+
1144
+ let hasContent = this.#checkSlotContent(slot);
1145
+ slot.classList.toggle('display-slot', hasContent);
1089
1146
 
1090
- if (newSlottedElement) {
1091
- this.#activeSlottedElement.set(slotName, newSlottedElement);
1092
-
1093
- if (newSlottedElement.tagName.startsWith('JH-ICON')) {
1094
- newSlottedElement.setAttribute('size', 'medium');
1095
- }
1096
- this.#resizeObserver.observe(newSlottedElement);
1147
+ // Set icon size if applicable
1148
+ if (newSlottedElement?.tagName.startsWith('JH-ICON')) {
1149
+ newSlottedElement.setAttribute('size', 'medium');
1097
1150
  }
1098
- this.#addClass(slotName, newSlottedElement);
1099
1151
  }
1100
1152
 
1101
- // sets class on input element so padding can accomodate slotted content
1102
- #addClass(slotName, slottedElement) {
1103
- const inputEl = this.shadowRoot.querySelector('input');
1153
+ renderLeftSlot() {
1154
+ if (this.hideLeftSlot) return null;
1155
+ return html`
1156
+ <slot name="jh-input-left" @slotchange=${this._handleSlotChange}></slot>
1157
+ `;
1158
+ }
1104
1159
 
1105
- // add and remove class if slotted element is not present
1106
- if (!slottedElement) {
1107
- inputEl.classList.remove(slotName);
1108
- } else {
1109
- inputEl.classList.add(slotName);
1110
- }
1160
+ renderRightSlot() {
1161
+ if (this.hideRightSlot) return null;
1162
+ return html`
1163
+ <slot name="jh-input-right" @slotchange=${this._handleSlotChange}></slot>
1164
+ `;
1111
1165
  }
1112
1166
 
1113
- #getSlots() {
1114
- if (this.readonly) {
1115
- return;
1116
- }
1117
-
1118
- const leftSlot = this.hideLeftSlot
1119
- ? null
1120
- : html`<slot
1121
- name="jh-input-left"
1122
- @slotchange=${this.#handleSlotChange}
1123
- ></slot>
1167
+ renderClearButton() {
1168
+ if (!this.showClearButton || !this.value || this.disabled) return null;
1169
+ return html`
1170
+ <jh-button
1171
+ size="small" appearance="tertiary" class="clear-button"
1172
+ accessible-label=${ifDefined(this.accessibleLabelClearButton)}
1173
+ @click=${this._handleClearButtonClick}>
1174
+ <slot name="jh-input-clear-button" slot="jh-button-icon">
1175
+ <jh-icon-circle-xmark slot="jh-button-icon" aria-hidden="true" size="medium"></jh-icon-circle-xmark>
1176
+ </slot>
1177
+ </jh-button>
1124
1178
  `;
1125
-
1126
- const clearBtn = this.showClearButton && this.value && !this.disabled
1127
- ? html`
1128
- <jh-button
1129
- size="small" appearance="tertiary" class="clear-button"
1130
- accessible-label=${ifDefined(this.accessibleLabelClearButton)}
1131
- @click=${this.#handleClearButtonClick}>
1132
- <slot name="jh-input-clear-button" slot="jh-button-icon">
1133
- <jh-icon-circle-xmark slot="jh-button-icon" aria-hidden="true" size="medium"></jh-icon-circle-xmark>
1134
- </slot>
1135
- </jh-button>
1136
- `
1137
- : null;
1138
-
1139
-
1140
- const rightSlot = this.hideRightSlot
1141
- ? null
1142
- : html`<slot
1143
- name="jh-input-right"
1144
- @slotchange=${this.#handleSlotChange}
1145
- ></slot>
1146
- `;
1147
-
1148
- return html`${leftSlot}${clearBtn}${rightSlot}`;
1149
1179
  }
1150
1180
 
1151
- #getDescribedby() {
1181
+ _getDescribedby() {
1152
1182
  let describedbyString = '';
1153
1183
 
1154
1184
  if (this.errorText) {
1155
- describedbyString += `jh-input-error-${this.#id}`;
1185
+ describedbyString += `jh-input-error-${this.uniqueId}`;
1156
1186
  }
1157
1187
  if (this.helperText) {
1158
- describedbyString += ` jh-input-helper-${this.#id}`;
1159
- }
1160
- if (this.showCharCount) {
1161
- describedbyString += ` jh-input-counter-${this.#id}`;
1188
+ describedbyString += ` jh-input-helper-${this.uniqueId}`;
1162
1189
  }
1163
1190
  return describedbyString;
1164
1191
  }
1165
1192
 
1166
- render() {
1193
+ renderLabel() {
1167
1194
  let label;
1168
1195
  let indicator;
1169
1196
  let helperText;
1170
- let input;
1171
- let footer;
1172
- let errorText;
1173
- let charCount;
1174
- let describedby;
1175
1197
 
1176
1198
  if (this.label) {
1177
1199
  if (this.showIndicator) {
@@ -1184,17 +1206,24 @@ export class JhInput extends LitElement {
1184
1206
 
1185
1207
  if (this.helperText) {
1186
1208
  helperText = html`
1187
- <p id="jh-input-helper-${this.#id}" class="helper-text">
1209
+ <p id="jh-input-helper-${this.uniqueId}" class="helper-text">
1188
1210
  ${this.helperText}
1189
1211
  </p>
1190
1212
  `;
1191
1213
  }
1192
1214
 
1193
1215
  label = html`
1194
- <label for="jh-input-${this.#id}">${this.label}${indicator}</label>
1216
+ <label for="jh-input-${this.uniqueId}">${this.label}${indicator}</label>
1195
1217
  ${helperText}
1196
1218
  `;
1197
1219
  }
1220
+ return label;
1221
+ }
1222
+
1223
+ renderFooter() {
1224
+ let footer;
1225
+ let errorText;
1226
+ let charCount;
1198
1227
 
1199
1228
  if (this.showCharCount) {
1200
1229
  let valueLength = this.value ? this.value.length : 0;
@@ -1204,7 +1233,7 @@ export class JhInput extends LitElement {
1204
1233
  }`;
1205
1234
 
1206
1235
  if (valueLength && valueLength === Number(this.maxlength)) {
1207
- this.#handleMaxlength();
1236
+ this._handleMaxlength();
1208
1237
  }
1209
1238
 
1210
1239
  charCount = html`
@@ -1214,7 +1243,7 @@ export class JhInput extends LitElement {
1214
1243
 
1215
1244
  if (this.invalid && this.errorText) {
1216
1245
  errorText = html`
1217
- <p id="jh-input-error-${this.#id}" class="error-text">
1246
+ <p id="jh-input-error-${this.uniqueId}" class="error-text">
1218
1247
  ${this.errorText}
1219
1248
  </p>
1220
1249
  `;
@@ -1228,42 +1257,62 @@ export class JhInput extends LitElement {
1228
1257
  </div>
1229
1258
  `;
1230
1259
  }
1260
+ return footer;
1261
+ }
1262
+
1263
+ renderInput() {
1264
+ let describedby;
1231
1265
 
1232
- if (helperText || errorText || charCount) {
1233
- describedby = this.#getDescribedby();
1266
+ if (this.helperText || (this.errorText && this.invalid)) {
1267
+ describedby = this._getDescribedby();
1234
1268
  }
1235
1269
 
1236
- input = html`
1270
+ const leftSlot = this.readonly ? null : this.renderLeftSlot();
1271
+ const rightSlot = this.readonly ? null : this.renderRightSlot();
1272
+ const clearButton = this.readonly ? null : this.renderClearButton();
1273
+
1274
+ return html`
1237
1275
  <div class="input-container">
1238
- <input
1239
- id="jh-input-${this.#id}"
1240
- aria-describedby=${describedby}
1241
- aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
1242
- aria-label=${ifDefined(
1243
- this.accessibleLabel === '' ? null : this.accessibleLabel
1244
- )}
1245
- autocomplete=${ifDefined(
1246
- this.autocomplete === '' ? null : this.autocomplete
1247
- )}
1248
- ?disabled=${this.disabled}
1249
- enterkeyhint=${ifDefined(
1250
- this.enterkeyhint === '' ? null : this.enterkeyhint
1251
- )}
1252
- inputmode=${ifDefined(this.inputmode === '' ? null : this.inputmode)}
1253
- maxlength=${ifDefined(this.maxlength === '' ? null : this.maxlength)}
1254
- minlength=${ifDefined(this.minlength === '' ? null : this.minlength)}
1255
- name=${ifDefined(this.name === '' ? null : this.name)}
1256
- ?readonly=${this.readonly}
1257
- ?required=${this.required}
1258
- type="text"
1259
- .value=${this.value}
1260
- @keydown=${this.inputMask ? this.#handleKeydown : null}
1261
- @change=${this.#handleChange}
1262
- @input=${this.#handleInput}
1263
- @select=${this.#handleSelect}
1264
- />${this.#getSlots()}
1276
+ <div class="input-wrapper">
1277
+ ${leftSlot}
1278
+ <input
1279
+ id="jh-input-${this.uniqueId}"
1280
+ aria-describedby=${describedby}
1281
+ aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
1282
+ aria-label=${ifDefined(
1283
+ this.accessibleLabel === '' ? null : this.accessibleLabel
1284
+ )}
1285
+ autocomplete=${ifDefined(
1286
+ this.autocomplete === '' ? null : this.autocomplete
1287
+ )}
1288
+ ?disabled=${this.disabled}
1289
+ enterkeyhint=${ifDefined(
1290
+ this.enterkeyhint === '' ? null : this.enterkeyhint
1291
+ )}
1292
+ inputmode=${ifDefined(this.inputmode === '' ? null : this.inputmode)}
1293
+ maxlength=${ifDefined(this.maxlength === '' ? null : this.maxlength)}
1294
+ minlength=${ifDefined(this.minlength === '' ? null : this.minlength)}
1295
+ name=${ifDefined(this.name === '' ? null : this.name)}
1296
+ ?readonly=${this.readonly}
1297
+ ?required=${this.required}
1298
+ type="text"
1299
+ .value=${this.value}
1300
+ @keydown=${this.inputMask ? this._handleKeydown : null}
1301
+ @change=${this._handleChange}
1302
+ @input=${this._handleInput}
1303
+ @select=${this._handleSelect}
1304
+ />
1305
+ ${clearButton}
1306
+ ${rightSlot}
1307
+ </div>
1265
1308
  </div>
1266
1309
  `;
1310
+ }
1311
+
1312
+ render() {
1313
+ const label = this.renderLabel();
1314
+ const input = this.renderInput();
1315
+ const footer = this.renderFooter();
1267
1316
 
1268
1317
  return html`
1269
1318
  ${label} ${input} ${footer}