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

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