@jack-henry/jh-elements 2.0.0-beta.11 → 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,26 +91,33 @@ 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`
99
109
  :host {
100
- font-family: var(--jh-font-helper-regular-font-family);
101
- font-weight: var(--jh-font-helper-regular-font-weight);
102
- font-size: var(--jh-font-helper-regular-font-size);
103
- line-height: var(--jh-font-helper-regular-line-height);
110
+ --input-helper-regular-font-family: var(--jh-font-helper-regular-font-family);
111
+ --input-helper-regular-font-weight: var(--jh-font-helper-regular-font-weight);
112
+ --input-helper-regular-font-size: var(--jh-font-helper-regular-font-size);
113
+ --input-helper-regular-line-height: var(--jh-font-helper-regular-line-height);
114
+ font-family: var(--input-helper-regular-font-family);
115
+ font-weight: var(--input-helper-regular-font-weight);
116
+ font-size: var(--input-helper-regular-font-size);
117
+ line-height: var(--input-helper-regular-line-height);
104
118
  display: inline-block;
105
119
  width: 100%;
106
- --jh-button-size: var(--jh-dimension-800);
107
- /* input padding + slot padding */
108
- --padding-with-left-slotted-content: calc(
109
- var(--jh-dimension-400) + var(--jh-dimension-200)
110
- );
111
- --padding-with-right-slotted-content: calc(
112
- var(--jh-dimension-400) + var(--jh-dimension-200)
113
- );
120
+ --jh-button-size: var(--jh-dimension-800);
114
121
  --input-value-color-text: var(
115
122
  --jh-input-value-color-text,
116
123
  var(--jh-color-content-primary-enabled)
@@ -121,6 +128,10 @@ export class JhInput extends LitElement {
121
128
  --jh-input-label-color-text,
122
129
  var(--jh-color-content-primary-enabled)
123
130
  );
131
+ font-family: var(--jh-font-helper-medium-font-family);
132
+ font-weight: var(--jh-font-helper-medium-font-weight);
133
+ font-size: var(--jh-font-helper-medium-font-size);
134
+ line-height: var(--jh-font-helper-medium-line-height);
124
135
  display: block;
125
136
  }
126
137
  .helper-text {
@@ -138,13 +149,14 @@ export class JhInput extends LitElement {
138
149
  :host([show-char-count]) .helper-text {
139
150
  display: inline-block;
140
151
  }
141
- .input-container {
142
- position: relative;
143
- }
144
152
  :host([label]) .input-container {
145
153
  margin-top: var(--jh-dimension-200);
146
154
  }
147
- input {
155
+
156
+ /* Flex wrapper */
157
+ .input-wrapper {
158
+ display: flex;
159
+ align-items: center;
148
160
  background-color: var(
149
161
  --jh-input-field-color-background,
150
162
  var(--jh-color-container-primary-enabled)
@@ -153,123 +165,78 @@ export class JhInput extends LitElement {
153
165
  border-style: var(--jh-border-control-style);
154
166
  border-color: var(
155
167
  --jh-input-field-color-border-enabled,
156
- var(--jh-border-control-color)
157
- );
168
+ var(--jh-border-control-color)
169
+ );
158
170
  border-radius: var(
159
171
  --jh-input-field-border-radius,
160
172
  var(--jh-border-radius-100)
161
173
  );
162
- color: var(--input-value-color-text);
163
- font-family: var(--jh-font-body-regular-1-font-family);
164
- font-weight: var(--jh-font-body-regular-1-font-weight);
165
- font-size: var(--jh-font-body-regular-1-font-size);
166
- line-height: var(--jh-font-body-regular-1-line-height);
167
- 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);
168
175
  box-sizing: border-box;
169
176
  width: 100%;
170
177
  }
171
- .jh-input-right {
172
- padding-right: calc(var(--padding-with-right-slotted-content) + var(--jh-input-right-width));
173
- }
174
- .jh-input-left {
175
- padding-left: calc(var(--padding-with-left-slotted-content) + var(--jh-input-left-width));
176
- }
177
- /* slot styles */
178
- ::slotted(*),
179
- ::slotted(*) {
180
- position: absolute;
181
- display: flex;
182
- align-items: center;
183
- justify-content: center;
184
- }
185
- ::slotted([slot='jh-input-left']) {
186
- left: var(--jh-dimension-400);
187
- }
188
- ::slotted([slot='jh-input-right']) {
189
- right: var(--jh-dimension-400);
190
- }
191
- ::slotted([slot='jh-input-left']){
192
- top: var(--jh-input-left-top);
193
- }
194
- ::slotted([slot='jh-input-right']) {
195
- top: var(--jh-input-right-top);
196
- }
197
- /* clear button */
198
- .clear-button {
199
- right: var(--jh-dimension-400);
200
- --jh-button-border-radius: var(--jh-input-clear-border-radius);
201
- --jh-button-color-background-tertiary-enabled: var(--jh-input-clear-color-background-enabled);
202
- --jh-button-color-border-tertiary-enabled: var(--jh-input-clear-color-border-enabled);
203
- --jh-button-icon-color-fill-tertiary-enabled: var(--jh-input-clear-icon-color-fill-enabled);
204
- --jh-button-color-background-tertiary-focus: var(--jh-input-clear-color-background-focus);
205
- --jh-button-color-border-tertiary-focus: var(--jh-input-clear-color-border-focus);
206
- --jh-button-color-focus: var(--jh-input-clear-color-focus);
207
- --jh-button-icon-color-fill-tertiary-focus: var(--jh-input-clear-icon-color-fill-focus);
208
- --jh-button-color-background-tertiary-hover: var(--jh-input-clear-color-background-hover);
209
- --jh-button-color-border-tertiary-hover: var(--jh-input-clear-color-border-hover);
210
- --jh-button-icon-color-fill-tertiary-hover: var(--jh-input-clear-icon-color-fill-hover);
211
- --jh-button-color-background-tertiary-active: var(--jh-input-clear-color-background-active);
212
- --jh-button-color-border-tertiary-active: var(--jh-input-clear-color-border-active);
213
- --jh-button-icon-color-fill-tertiary-active: var(--jh-input-clear-icon-color-fill-active);
214
- display: none;
215
- position: absolute;
216
- }
217
- .display-clear-button .clear-button {
218
- display: inherit;
219
- }
220
- :host([size='small']) .clear-button {
221
- top: 4px;
222
- }
223
- :host([size='medium']) .clear-button {
224
- top: 8px;
225
- }
226
- :host([size='large']) .clear-button {
227
- top: 12px;
228
- }
229
- .jh-input-right ~ .clear-button {
230
- right: calc(var(--padding-with-right-slotted-content) + var(--jh-input-right-width));
231
- }
232
- .display-clear-button input {
233
- padding-right: calc(var(--padding-with-right-slotted-content) + var(--jh-dimension-800));
234
- }
235
- .display-clear-button .jh-input-right {
236
- padding-right: calc(var(--padding-with-right-slotted-content) + var(--jh-input-right-width) + var(--jh-dimension-800) + var(--jh-dimension-200));
237
- }
238
- /* Sizes */
239
- :host([size='small']) input {
178
+
179
+ /* Sizes on input wrapper */
180
+ :host([size='small']) .input-wrapper {
240
181
  height: var(--jh-dimension-1000);
241
182
  }
242
- :host([size='medium']) input {
183
+ :host([size='medium']) .input-wrapper {
243
184
  height: var(--jh-dimension-1200);
244
185
  }
245
- :host([size='large']) input {
186
+ :host([size='large']) .input-wrapper {
246
187
  height: var(--jh-dimension-1400);
247
188
  }
248
- .footer-content {
249
- margin: var(--jh-dimension-200) 0 0 0;
250
- gap: var(--jh-dimension-200);
251
- display: flex;
252
- 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%;
253
204
  }
254
- .footer-content:has(.counter):not(:has(.error-text)) {
255
- 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;
256
214
  }
257
- .counter {
258
- color: var(--jh-input-counter-color-text,
259
- var(--jh-color-content-secondary-enabled)
260
- );
215
+ slot[name="jh-input-left"] {
216
+ display: none;
217
+ align-items: center;
218
+ flex-shrink: 0;
219
+ padding-right: var(--jh-dimension-200);
261
220
  }
262
- .error-text {
263
- color: var(
264
- --jh-input-error-color-text,
265
- var(--jh-color-content-negative-enabled)
266
- );
221
+ slot[name="jh-input-right"] {
222
+ display: none;
223
+ align-items: center;
224
+ flex-shrink: 0;
225
+ padding-left: var(--jh-dimension-200);
267
226
  }
268
- p {
269
- margin: 0;
227
+ slot[name="jh-input-left"].display-slot,
228
+ slot[name="jh-input-right"].display-slot {
229
+ display: flex;
270
230
  }
271
- /* Input States */
272
- 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 {
273
240
  border-color: var(
274
241
  --jh-input-field-color-border-active,
275
242
  var(--jh-color-content-brand-active)
@@ -278,13 +245,15 @@ export class JhInput extends LitElement {
278
245
  :host([disabled]) {
279
246
  opacity: var(--jh-input-opacity-disabled, var(--jh-opacity-disabled));
280
247
  }
281
- :host([disabled]) input {
248
+ :host([disabled]) .input-wrapper {
282
249
  border-color: var(
283
250
  --jh-input-field-color-border-disabled,
284
251
  var(--jh-border-control-color)
285
252
  );
286
253
  }
287
- input:focus-visible {
254
+
255
+ /* Focus-visible on wrapper when input is focused */
256
+ .input-wrapper:has(input:focus-visible) {
288
257
  border-color: var(
289
258
  --jh-input-field-color-border-focus,
290
259
  var(--jh-color-content-brand-hover)
@@ -297,13 +266,16 @@ export class JhInput extends LitElement {
297
266
  outline-width: var(--jh-border-focus-width);
298
267
  outline-offset: 1px;
299
268
  }
300
- input:hover {
269
+ input:focus-visible {
270
+ outline: none;
271
+ }
272
+ .input-wrapper:hover {
301
273
  border-color: var(
302
274
  --jh-input-field-color-border-hover,
303
275
  var(--jh-color-content-brand-hover)
304
276
  );
305
277
  }
306
- :host([invalid]) input {
278
+ :host([invalid]) .input-wrapper {
307
279
  border-width: var(--jh-border-error-width);
308
280
  border-style: var(--jh-border-error-style);
309
281
  border-color: var(
@@ -311,26 +283,82 @@ export class JhInput extends LitElement {
311
283
  var(--jh-border-error-color)
312
284
  );
313
285
  }
314
- /* readonly styles */
315
- :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 {
316
313
  height: auto;
317
314
  background-color: transparent;
318
315
  border: none;
319
316
  padding-left: 0;
320
317
  padding-right: 0;
321
318
  }
319
+
322
320
  /* Override Chrome autofill styles */
323
321
  input:autofill {
324
322
  -webkit-text-fill-color: var(--input-value-color-text);
325
323
  caret-color: var(--input-value-color-text);
326
324
  background-clip: text;
327
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
+
328
352
  /* Optional/Required/Show-indicator */
329
353
  :host([show-indicator]) span {
330
354
  color: var(
331
355
  --jh-input-optional-color-text,
332
356
  var(--jh-color-content-primary-enabled)
333
357
  );
358
+ font-family: var(--input-helper-regular-font-family);
359
+ font-weight: var(--input-helper-regular-font-weight);
360
+ font-size: var(--input-helper-regular-font-size);
361
+ line-height: var(--input-helper-regular-line-height);
334
362
  }
335
363
  :host([show-indicator][required]) span {
336
364
  color: var(
@@ -338,7 +366,6 @@ export class JhInput extends LitElement {
338
366
  var(--jh-color-content-negative-enabled)
339
367
  );
340
368
  }
341
-
342
369
  `;
343
370
  }
344
371
 
@@ -370,7 +397,7 @@ export class JhInput extends LitElement {
370
397
  hideLeftSlot: { type: Boolean, attribute: 'hide-left-slot' },
371
398
  /** Hides the right slot from input. */
372
399
  hideRightSlot: { type: Boolean, attribute: 'hide-right-slot' },
373
- /** Formats the user entered data on input. Does not apply to pasted values. See input mask documentation above for implementation details. */
400
+ /** Formats user entered data on input based on fixed lengths. This property does not support dynamic formatting or pasted values. See the input mask documentation above for implementation details. */
374
401
  inputMask: { type: String, attribute: 'input-mask' },
375
402
  /** Indicates expected input value type and allows for browsers to display appropriate virtual keyboard.
376
403
  *
@@ -466,12 +493,15 @@ export class JhInput extends LitElement {
466
493
 
467
494
  disconnectedCallback() {
468
495
  super.disconnectedCallback();
469
- this.#resizeObserver.disconnect();
470
496
  if (this.inputMask) {
471
497
  this.removeEventListener('jh-select', this.#setSelection);
472
498
  }
473
499
  }
474
500
 
501
+ get uniqueId() {
502
+ return this.#id;
503
+ }
504
+
475
505
  firstUpdated() {
476
506
  // attach event listeners to show/hide clear button
477
507
  if (this.showClearButton) {
@@ -482,9 +512,43 @@ export class JhInput extends LitElement {
482
512
  ['mouseenter', 'mouseleave'].forEach(e => {
483
513
  inputContainer.addEventListener(e, this.#toggleFocus.bind(this));
484
514
  });
485
- }
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
+ });
486
534
  }
487
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
+
488
552
  #toggleFocus(e) {
489
553
  if (this.disabled || this.readonly || !this.showClearButton) {
490
554
  return;
@@ -581,7 +645,7 @@ export class JhInput extends LitElement {
581
645
  );
582
646
  }
583
647
 
584
- #handleInput(e) {
648
+ _handleInput(e) {
585
649
  this.value = e.target.value;
586
650
  let inputType = e.inputType;
587
651
  this.#deletedChar = inputType === 'deleteContentBackward' || inputType === 'deleteByCut' || inputType === 'deleteContentForward';
@@ -596,7 +660,7 @@ export class JhInput extends LitElement {
596
660
  }
597
661
  }
598
662
 
599
- #handleKeydown(e) {
663
+ _handleKeydown(e) {
600
664
  const value = e.target.value;
601
665
  let selectionStart = e.target.selectionStart;
602
666
  let selectionEnd = e.target.selectionEnd;
@@ -998,9 +1062,27 @@ export class JhInput extends LitElement {
998
1062
  this.#selectedText.selectionStart = null;
999
1063
  }
1000
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
+ }
1001
1083
  }
1002
1084
 
1003
- #handleChange() {
1085
+ _handleChange() {
1004
1086
  let payload = {
1005
1087
  'value': this.value,
1006
1088
  }
@@ -1012,7 +1094,7 @@ export class JhInput extends LitElement {
1012
1094
  this.#dispatch('jh-change', payload);
1013
1095
  }
1014
1096
 
1015
- #handleSelect(e) {
1097
+ _handleSelect(e) {
1016
1098
  const selectedString = e.target.value.substring(
1017
1099
  e.target.selectionStart,
1018
1100
  e.target.selectionEnd
@@ -1028,11 +1110,11 @@ export class JhInput extends LitElement {
1028
1110
  }
1029
1111
  }
1030
1112
 
1031
- #handleMaxlength() {
1113
+ _handleMaxlength() {
1032
1114
  this.#dispatch('jh-maxlength');
1033
1115
  }
1034
1116
 
1035
- #handleClearButtonClick() {
1117
+ _handleClearButtonClick() {
1036
1118
  let previousValue = this.value;
1037
1119
  // clear input value
1038
1120
  this.value = '';
@@ -1051,115 +1133,67 @@ export class JhInput extends LitElement {
1051
1133
  );
1052
1134
  }
1053
1135
 
1054
- // capture dimensions of slotted content and set CSS variables to adjust input padding and vertically center slotted content
1055
- #resizeObserver = new ResizeObserver((entries) => {
1056
- let inputEl = this.shadowRoot.querySelector('input');
1057
-
1058
- for (let entry of entries) {
1059
- let slottedEl = entry.target;
1060
- let slottedElWidth = entry.borderBoxSize[0].inlineSize;
1061
- let slottedElHeight = entry.borderBoxSize[0].blockSize;
1062
-
1063
- this.style.setProperty(`--${slottedEl.slot}-width`, `${slottedElWidth}px`);
1064
-
1065
- this.style.setProperty(`--${slottedEl.slot}-top`, `${(inputEl.offsetHeight - slottedElHeight) / 2}px`);
1066
- }
1067
- });
1068
-
1069
- #handleSlotChange(e) {
1136
+ _handleSlotChange(e) {
1070
1137
  let newSlottedElement = e.target.assignedElements()[0];
1071
- let slotName = e.target.name;
1138
+ let slot = e.target;
1072
1139
 
1073
- // stop observing previous slotted element for each slot
1074
- if (this.#activeSlottedElement.has(slotName)) {
1075
- this.#resizeObserver.unobserve(this.#activeSlottedElement.get(slotName));
1140
+ if (slot.name !== 'jh-input-left' && slot.name !== 'jh-input-right') {
1141
+ return;
1076
1142
  }
1143
+
1144
+ let hasContent = this.#checkSlotContent(slot);
1145
+ slot.classList.toggle('display-slot', hasContent);
1077
1146
 
1078
- if (newSlottedElement) {
1079
- this.#activeSlottedElement.set(slotName, newSlottedElement);
1080
-
1081
- if (newSlottedElement.tagName.startsWith('JH-ICON')) {
1082
- newSlottedElement.setAttribute('size', 'medium');
1083
- }
1084
- this.#resizeObserver.observe(newSlottedElement);
1147
+ // Set icon size if applicable
1148
+ if (newSlottedElement?.tagName.startsWith('JH-ICON')) {
1149
+ newSlottedElement.setAttribute('size', 'medium');
1085
1150
  }
1086
- this.#addClass(slotName, newSlottedElement);
1087
1151
  }
1088
1152
 
1089
- // sets class on input element so padding can accomodate slotted content
1090
- #addClass(slotName, slottedElement) {
1091
- 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
+ }
1092
1159
 
1093
- // add and remove class if slotted element is not present
1094
- if (!slottedElement) {
1095
- inputEl.classList.remove(slotName);
1096
- } else {
1097
- inputEl.classList.add(slotName);
1098
- }
1160
+ renderRightSlot() {
1161
+ if (this.hideRightSlot) return null;
1162
+ return html`
1163
+ <slot name="jh-input-right" @slotchange=${this._handleSlotChange}></slot>
1164
+ `;
1099
1165
  }
1100
1166
 
1101
- #getSlots() {
1102
- if (this.readonly) {
1103
- return;
1104
- }
1105
-
1106
- const leftSlot = this.hideLeftSlot
1107
- ? null
1108
- : html`<slot
1109
- name="jh-input-left"
1110
- @slotchange=${this.#handleSlotChange}
1111
- ></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>
1112
1178
  `;
1113
-
1114
- const clearBtn = this.showClearButton && this.value && !this.disabled
1115
- ? html`
1116
- <jh-button
1117
- size="small" appearance="tertiary" class="clear-button"
1118
- accessible-label=${ifDefined(this.accessibleLabelClearButton)}
1119
- @click=${this.#handleClearButtonClick}>
1120
- <slot name="jh-input-clear-button" slot="jh-button-icon">
1121
- <jh-icon-circle-xmark slot="jh-button-icon" aria-hidden="true" size="medium"></jh-icon-circle-xmark>
1122
- </slot>
1123
- </jh-button>
1124
- `
1125
- : null;
1126
-
1127
-
1128
- const rightSlot = this.hideRightSlot
1129
- ? null
1130
- : html`<slot
1131
- name="jh-input-right"
1132
- @slotchange=${this.#handleSlotChange}
1133
- ></slot>
1134
- `;
1135
-
1136
- return html`${leftSlot}${clearBtn}${rightSlot}`;
1137
1179
  }
1138
1180
 
1139
- #getDescribedby() {
1181
+ _getDescribedby() {
1140
1182
  let describedbyString = '';
1141
1183
 
1142
1184
  if (this.errorText) {
1143
- describedbyString += `jh-input-error-${this.#id}`;
1185
+ describedbyString += `jh-input-error-${this.uniqueId}`;
1144
1186
  }
1145
1187
  if (this.helperText) {
1146
- describedbyString += ` jh-input-helper-${this.#id}`;
1147
- }
1148
- if (this.showCharCount) {
1149
- describedbyString += ` jh-input-counter-${this.#id}`;
1188
+ describedbyString += ` jh-input-helper-${this.uniqueId}`;
1150
1189
  }
1151
1190
  return describedbyString;
1152
1191
  }
1153
1192
 
1154
- render() {
1193
+ renderLabel() {
1155
1194
  let label;
1156
1195
  let indicator;
1157
1196
  let helperText;
1158
- let input;
1159
- let footer;
1160
- let errorText;
1161
- let charCount;
1162
- let describedby;
1163
1197
 
1164
1198
  if (this.label) {
1165
1199
  if (this.showIndicator) {
@@ -1172,17 +1206,24 @@ export class JhInput extends LitElement {
1172
1206
 
1173
1207
  if (this.helperText) {
1174
1208
  helperText = html`
1175
- <p id="jh-input-helper-${this.#id}" class="helper-text">
1209
+ <p id="jh-input-helper-${this.uniqueId}" class="helper-text">
1176
1210
  ${this.helperText}
1177
1211
  </p>
1178
1212
  `;
1179
1213
  }
1180
1214
 
1181
1215
  label = html`
1182
- <label for="jh-input-${this.#id}">${this.label}${indicator}</label>
1216
+ <label for="jh-input-${this.uniqueId}">${this.label}${indicator}</label>
1183
1217
  ${helperText}
1184
1218
  `;
1185
1219
  }
1220
+ return label;
1221
+ }
1222
+
1223
+ renderFooter() {
1224
+ let footer;
1225
+ let errorText;
1226
+ let charCount;
1186
1227
 
1187
1228
  if (this.showCharCount) {
1188
1229
  let valueLength = this.value ? this.value.length : 0;
@@ -1192,7 +1233,7 @@ export class JhInput extends LitElement {
1192
1233
  }`;
1193
1234
 
1194
1235
  if (valueLength && valueLength === Number(this.maxlength)) {
1195
- this.#handleMaxlength();
1236
+ this._handleMaxlength();
1196
1237
  }
1197
1238
 
1198
1239
  charCount = html`
@@ -1202,7 +1243,7 @@ export class JhInput extends LitElement {
1202
1243
 
1203
1244
  if (this.invalid && this.errorText) {
1204
1245
  errorText = html`
1205
- <p id="jh-input-error-${this.#id}" class="error-text">
1246
+ <p id="jh-input-error-${this.uniqueId}" class="error-text">
1206
1247
  ${this.errorText}
1207
1248
  </p>
1208
1249
  `;
@@ -1216,42 +1257,62 @@ export class JhInput extends LitElement {
1216
1257
  </div>
1217
1258
  `;
1218
1259
  }
1260
+ return footer;
1261
+ }
1262
+
1263
+ renderInput() {
1264
+ let describedby;
1219
1265
 
1220
- if (helperText || errorText || charCount) {
1221
- describedby = this.#getDescribedby();
1266
+ if (this.helperText || (this.errorText && this.invalid)) {
1267
+ describedby = this._getDescribedby();
1222
1268
  }
1223
1269
 
1224
- 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`
1225
1275
  <div class="input-container">
1226
- <input
1227
- id="jh-input-${this.#id}"
1228
- aria-describedby=${describedby}
1229
- aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
1230
- aria-label=${ifDefined(
1231
- this.accessibleLabel === '' ? null : this.accessibleLabel
1232
- )}
1233
- autocomplete=${ifDefined(
1234
- this.autocomplete === '' ? null : this.autocomplete
1235
- )}
1236
- ?disabled=${this.disabled}
1237
- enterkeyhint=${ifDefined(
1238
- this.enterkeyhint === '' ? null : this.enterkeyhint
1239
- )}
1240
- inputmode=${ifDefined(this.inputmode === '' ? null : this.inputmode)}
1241
- maxlength=${ifDefined(this.maxlength === '' ? null : this.maxlength)}
1242
- minlength=${ifDefined(this.minlength === '' ? null : this.minlength)}
1243
- name=${ifDefined(this.name === '' ? null : this.name)}
1244
- ?readonly=${this.readonly}
1245
- ?required=${this.required}
1246
- type="text"
1247
- .value=${this.value}
1248
- @keydown=${this.inputMask ? this.#handleKeydown : null}
1249
- @change=${this.#handleChange}
1250
- @input=${this.#handleInput}
1251
- @select=${this.#handleSelect}
1252
- />${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>
1253
1308
  </div>
1254
1309
  `;
1310
+ }
1311
+
1312
+ render() {
1313
+ const label = this.renderLabel();
1314
+ const input = this.renderInput();
1315
+ const footer = this.renderFooter();
1255
1316
 
1256
1317
  return html`
1257
1318
  ${label} ${input} ${footer}