@jack-henry/jh-elements 2.0.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +26 -0
- package/README.md +13 -0
- package/components/badge/badge.js +73 -0
- package/components/button/button.js +953 -0
- package/components/card/card.js +333 -0
- package/components/checkbox/checkbox.js +601 -0
- package/components/checkbox-group/checkbox-group.js +241 -0
- package/components/divider/divider.js +91 -0
- package/components/icon/icon.js +96 -0
- package/components/input/input.js +1245 -0
- package/components/input-email/input-email.js +18 -0
- package/components/input-password/input-password.js +153 -0
- package/components/input-search/input-search.js +41 -0
- package/components/input-telephone/input-telephone.js +18 -0
- package/components/input-textarea/input-textarea.js +374 -0
- package/components/input-url/input-url.js +31 -0
- package/components/list-group/list-group.js +103 -0
- package/components/list-item/list-item.js +350 -0
- package/components/menu/menu.js +60 -0
- package/components/notification/notification.js +327 -0
- package/components/progress/progress.js +417 -0
- package/components/radio/radio.js +422 -0
- package/components/radio-group/radio-group.js +400 -0
- package/components/switch/switch.js +316 -0
- package/components/table/table.js +367 -0
- package/components/table-data-cell/table-data-cell.js +107 -0
- package/components/table-header-cell/table-header-cell.js +321 -0
- package/components/table-row/table-row.js +52 -0
- package/components/tag/tag.js +422 -0
- package/components/tag-group/tag-group.js +57 -0
- package/components/toast/toast.js +172 -0
- package/components/toast-controller/toast-controller.js +122 -0
- package/components/tooltip/tooltip.js +498 -0
- package/custom-elements.json +6864 -0
- package/index.d.ts +69 -0
- package/jsconfig.json +9 -0
- package/package.json +52 -0
- package/utils/themeProvider.js +32 -0
|
@@ -0,0 +1,1245 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Jack Henry
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { LitElement, css, html } from 'lit';
|
|
6
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
7
|
+
import '../button/button.js';
|
|
8
|
+
import '@jack-henry/jh-icons/icons-wc/icon-circle-xmark.js';
|
|
9
|
+
|
|
10
|
+
let id = 0;
|
|
11
|
+
|
|
12
|
+
/**
|
|
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`.
|
|
15
|
+
* @cssprop --jh-input-field-color-border-enabled - The input field border-color. Defaults to `--jh-border-control-color`.
|
|
16
|
+
* @cssprop --jh-input-field-border-radius - The input field border radius. Defaults to `--jh-border-radius-100`.
|
|
17
|
+
* @cssprop --jh-input-color-focus - The input field outline when it receives keyboard focus. Defaults to `--jh-border-focus-color`.
|
|
18
|
+
* @cssprop --jh-input-field-color-border-focus - The input field border-color when in focus. Defaults to `--jh-color-content-brand-hover`.
|
|
19
|
+
* @cssprop --jh-input-field-color-border-hover - The input field border-color when hovered. Defaults to `--jh-color-content-brand-hover`.
|
|
20
|
+
* @cssprop --jh-input-field-color-border-active - The input field border-color when active. Defaults to `--jh-color-content-brand-active`.
|
|
21
|
+
* @cssprop --jh-input-field-color-border-disabled - The input field border-color when disabled. Defaults to `--jh-border-control-color`.
|
|
22
|
+
* @cssprop --jh-input-opacity-disabled - The input opacity when disabled. Defaults to `--jh-opacity-disabled`.
|
|
23
|
+
* @cssprop --jh-input-field-color-border-error - The input field border-color when invalid. Defaults to `--jh-border-error-color`.
|
|
24
|
+
* @cssprop --jh-input-clear-color-background-enabled - The clear search button background-color. Defaults to `transparent`.
|
|
25
|
+
* @cssprop --jh-input-clear-color-border-enabled - The clear search button border-color. Defaults to `transparent`.
|
|
26
|
+
* @cssprop --jh-input-clear-icon-color-fill-enabled - The clear search button icon fill color. Defaults to `--jh-color-content-brand-enabled`.
|
|
27
|
+
* @cssprop --jh-input-clear-color-background-focus - The clear search button background-color when in focus. Defaults to `--jh-color-content-brand-hover`.
|
|
28
|
+
* @cssprop --jh-input-clear-color-border-focus - The clear search button border-color when in focus. Defaults to `transparent`.
|
|
29
|
+
* @cssprop --jh-input-clear-color-focus - The clear search button outline when it receives keyboard focus. Defaults to `--jh-border-focus-color`.
|
|
30
|
+
* @cssprop --jh-input-clear-icon-color-fill-focus - The clear search button icon fill color when in focus. Defaults to `--jh-color-content-on-brand-hover`.
|
|
31
|
+
* @cssprop --jh-input-clear-color-background-hover - The clear search button background-color when hovered. Defaults to `--jh-color-content-brand-hover`.
|
|
32
|
+
* @cssprop --jh-input-clear-color-border-hover - The clear search button border-color when hovered. Defaults to `transparent`.
|
|
33
|
+
* @cssprop --jh-input-clear-icon-color-fill-hover - The clear search button icon fill color when hovered. Defaults to `--jh-color-content-on-brand-hover`.
|
|
34
|
+
* @cssprop --jh-input-clear-color-background-active - The clear search button background-color when active. Defaults to `--jh-color-content-brand-active`.
|
|
35
|
+
* @cssprop --jh-input-clear-color-border-active - The clear search button border-color when active. Defaults to `transparent`.
|
|
36
|
+
* @cssprop --jh-input-clear-icon-color-fill-active - The clear search button icon fill color when active. Defaults to `--jh-color-content-on-brand-active`.
|
|
37
|
+
* @cssprop --jh-input-required-color-text - The required indicator color. Defaults to `jh-color-content-negative-enabled`.
|
|
38
|
+
* @cssprop --jh-input-optional-color-text - The optional indicator text color. Defaults to `jh-color-content-primary-enabled`.
|
|
39
|
+
* @cssprop --jh-input-helper-color-text - The helper-text text color. Defaults to `jh-color-content-secondary-enabled`.
|
|
40
|
+
* @cssprop --jh-input-counter-color-text - The character counter text color. Defaults to `--jh-color-content-secondary-enabled`.
|
|
41
|
+
* @cssprop --jh-input-value-color-text - The value text color. Defaults to `jh-color-content-primary-enabled`.
|
|
42
|
+
* @cssprop --jh-input-error-color-text - The error message text color. Defaults to `jh-color-content-negative-enabled`.
|
|
43
|
+
*
|
|
44
|
+
* @event jh-select - Dispatched when text is selected. Event payload contains the selected text, the starting index of the selection, and the ending index of the selection. These values can be accessed via `e.detail.selected`, `e.detail.selectionStart`, and `e.detail.selectionEnd`.
|
|
45
|
+
* @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`. Payload also includes the raw/unformatted value when an input mask is applied and can be accessed via `e.detail.rawValue`.
|
|
46
|
+
* @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`. Payload also includes the raw/unformatted value when an input mask is applied and can be accessed via `e.detail.rawValue`.
|
|
47
|
+
* @event jh-maxlength - Dispatched when the `maxlength` property is set and it's value is reached.
|
|
48
|
+
* @event jh-input:clear-button-click - Dispatched when the clear button is activated. Event payload contains the previous value of the input field before it was cleared and can be accessed via `e.detail.previousValue`.
|
|
49
|
+
* @slot jh-input-left - Use to insert an element on the left side of the input field, such as an icon or button.
|
|
50
|
+
* @slot jh-input-right - Use to insert an element on the right side of the input field, such as an icon or button.
|
|
51
|
+
* @slot jh-input-clear-button - Use to insert an icon within the clear button.
|
|
52
|
+
*
|
|
53
|
+
* @customElement jh-input
|
|
54
|
+
*/
|
|
55
|
+
export class JhInput extends LitElement {
|
|
56
|
+
static get formAssociated() {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @type {ElementInternals} */
|
|
61
|
+
#internals;
|
|
62
|
+
/** @type {?number} */
|
|
63
|
+
#id;
|
|
64
|
+
/** @type {?string} */
|
|
65
|
+
#value;
|
|
66
|
+
/** @type {string} */
|
|
67
|
+
#rawValue = '';
|
|
68
|
+
/** @type {number} */
|
|
69
|
+
#startLastFixedChar;
|
|
70
|
+
/** @type {boolean} */
|
|
71
|
+
#deletedChar = false;
|
|
72
|
+
/** @type {number} */
|
|
73
|
+
#adjustCaretPositionStart = null;
|
|
74
|
+
/** @type {Object} */
|
|
75
|
+
#selectedText = {
|
|
76
|
+
selected: false,
|
|
77
|
+
selectionStart: null,
|
|
78
|
+
selectionEnd: null,
|
|
79
|
+
};
|
|
80
|
+
/** @type {Array} */
|
|
81
|
+
#maskMetaCharIndexes;
|
|
82
|
+
/** @type {Array} */
|
|
83
|
+
#maskFixedCharIndexes;
|
|
84
|
+
/** @type {Object} */
|
|
85
|
+
#regexSubset = {
|
|
86
|
+
// digits -> phone numbers, CC, etc
|
|
87
|
+
'9': /[0-9]/,
|
|
88
|
+
// alphabetical characters -> names, address, etc
|
|
89
|
+
'a': /[A-Za-z]/,
|
|
90
|
+
// alphanumeric characters -> usernames, product codes, etc.
|
|
91
|
+
'*': /[A-Za-z0-9]/,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
static get styles() {
|
|
95
|
+
return css`
|
|
96
|
+
:host {
|
|
97
|
+
font-family: var(--jh-font-helper-regular-font-family);
|
|
98
|
+
font-weight: var(--jh-font-helper-regular-font-weight);
|
|
99
|
+
font-size: var(--jh-font-helper-regular-font-size);
|
|
100
|
+
line-height: var(--jh-font-helper-regular-line-height);
|
|
101
|
+
display: inline-block;
|
|
102
|
+
width: 100%;
|
|
103
|
+
--jh-button-size: var(--jh-dimension-800);
|
|
104
|
+
/* input padding + slot padding */
|
|
105
|
+
--padding-with-left-slotted-content: calc(
|
|
106
|
+
var(--jh-dimension-400) + var(--jh-dimension-200)
|
|
107
|
+
);
|
|
108
|
+
--padding-with-right-slotted-content: calc(
|
|
109
|
+
var(--jh-dimension-400) + var(--jh-dimension-200)
|
|
110
|
+
);
|
|
111
|
+
--input-value-color-text: var(
|
|
112
|
+
--jh-input-value-color-text,
|
|
113
|
+
var(--jh-color-content-primary-enabled)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
label {
|
|
117
|
+
color: var(
|
|
118
|
+
--jh-input-label-color-text,
|
|
119
|
+
var(--jh-color-content-primary-enabled)
|
|
120
|
+
);
|
|
121
|
+
display: block;
|
|
122
|
+
}
|
|
123
|
+
.helper-text {
|
|
124
|
+
color: var(
|
|
125
|
+
--jh-input-helper-color-text,
|
|
126
|
+
var(--jh-color-content-secondary-enabled)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
label,
|
|
130
|
+
.helper-text,
|
|
131
|
+
.error-text {
|
|
132
|
+
word-break: normal;
|
|
133
|
+
overflow-wrap: anywhere;
|
|
134
|
+
}
|
|
135
|
+
:host([show-char-count]) .helper-text {
|
|
136
|
+
display: inline-block;
|
|
137
|
+
}
|
|
138
|
+
.input-container {
|
|
139
|
+
position: relative;
|
|
140
|
+
}
|
|
141
|
+
:host([label]) .input-container {
|
|
142
|
+
margin-top: var(--jh-dimension-200);
|
|
143
|
+
}
|
|
144
|
+
input {
|
|
145
|
+
background-color: var(
|
|
146
|
+
--jh-input-field-color-background,
|
|
147
|
+
var(--jh-color-container-primary-enabled)
|
|
148
|
+
);
|
|
149
|
+
border-width: var(--jh-border-control-width);
|
|
150
|
+
border-style: var(--jh-border-control-style);
|
|
151
|
+
border-color: var(
|
|
152
|
+
--jh-input-field-color-border-enabled,
|
|
153
|
+
var(--jh-border-control-color)
|
|
154
|
+
);
|
|
155
|
+
border-radius: var(
|
|
156
|
+
--jh-input-field-border-radius,
|
|
157
|
+
var(--jh-border-radius-100)
|
|
158
|
+
);
|
|
159
|
+
color: var(--input-value-color-text);
|
|
160
|
+
font-family: var(--jh-font-body-regular-1-font-family);
|
|
161
|
+
font-weight: var(--jh-font-body-regular-1-font-weight);
|
|
162
|
+
font-size: var(--jh-font-body-regular-1-font-size);
|
|
163
|
+
line-height: var(--jh-font-body-regular-1-line-height);
|
|
164
|
+
padding: var(--jh-dimension-0) var(--jh-dimension-400) var(--jh-dimension-0) var(--jh-dimension-400);
|
|
165
|
+
box-sizing: border-box;
|
|
166
|
+
width: 100%;
|
|
167
|
+
}
|
|
168
|
+
.jh-input-right {
|
|
169
|
+
padding-right: calc(var(--padding-with-right-slotted-content) + var(--jh-input-right-width));
|
|
170
|
+
}
|
|
171
|
+
.jh-input-left {
|
|
172
|
+
padding-left: calc(var(--padding-with-left-slotted-content) + var(--jh-input-left-width));
|
|
173
|
+
}
|
|
174
|
+
/* slot styles */
|
|
175
|
+
::slotted(*),
|
|
176
|
+
::slotted(*) {
|
|
177
|
+
position: absolute;
|
|
178
|
+
display: flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
}
|
|
182
|
+
::slotted([slot='jh-input-left']) {
|
|
183
|
+
left: var(--jh-dimension-400);
|
|
184
|
+
}
|
|
185
|
+
::slotted([slot='jh-input-right']) {
|
|
186
|
+
right: var(--jh-dimension-400);
|
|
187
|
+
}
|
|
188
|
+
::slotted([slot='jh-input-left']){
|
|
189
|
+
top: var(--jh-input-left-top);
|
|
190
|
+
}
|
|
191
|
+
::slotted([slot='jh-input-right']) {
|
|
192
|
+
top: var(--jh-input-right-top);
|
|
193
|
+
}
|
|
194
|
+
/* clear button */
|
|
195
|
+
.clear-button {
|
|
196
|
+
right: var(--jh-dimension-400);
|
|
197
|
+
--jh-button-color-background-tertiary-enabled: var(--jh-input-clear-color-background-enabled);
|
|
198
|
+
--jh-button-color-border-tertiary-enabled: var(--jh-input-clear-color-border-enabled);
|
|
199
|
+
--jh-button-icon-color-fill-tertiary-enabled: var(--jh-input-clear-icon-color-fill-enabled);
|
|
200
|
+
--jh-button-color-background-tertiary-focus: var(--jh-input-clear-color-background-focus);
|
|
201
|
+
--jh-button-color-border-tertiary-focus: var(--jh-input-clear-color-border-focus);
|
|
202
|
+
--jh-button-color-focus: var(--jh-input-clear-color-focus);
|
|
203
|
+
--jh-button-icon-color-fill-tertiary-focus: var(--jh-input-clear-icon-color-fill-focus);
|
|
204
|
+
--jh-button-color-background-tertiary-hover: var(--jh-input-clear-color-background-hover);
|
|
205
|
+
--jh-button-color-border-tertiary-hover: var(--jh-input-clear-color-border-hover);
|
|
206
|
+
--jh-button-icon-color-fill-tertiary-hover: var(--jh-input-clear-icon-color-fill-hover);
|
|
207
|
+
--jh-button-color-background-tertiary-active: var(--jh-input-clear-color-background-active);
|
|
208
|
+
--jh-button-color-border-tertiary-active: var(--jh-input-clear-color-border-active);
|
|
209
|
+
--jh-button-icon-color-fill-tertiary-active: var(--jh-input-clear-icon-color-fill-active);
|
|
210
|
+
display: none;
|
|
211
|
+
position: absolute;
|
|
212
|
+
}
|
|
213
|
+
.display-clear-button .clear-button {
|
|
214
|
+
display: inherit;
|
|
215
|
+
}
|
|
216
|
+
:host([size='small']) .clear-button {
|
|
217
|
+
top: 4px;
|
|
218
|
+
}
|
|
219
|
+
:host([size='medium']) .clear-button {
|
|
220
|
+
top: 8px;
|
|
221
|
+
}
|
|
222
|
+
:host([size='large']) .clear-button {
|
|
223
|
+
top: 12px;
|
|
224
|
+
}
|
|
225
|
+
.jh-input-right ~ .clear-button {
|
|
226
|
+
right: calc(var(--padding-with-right-slotted-content) + var(--jh-input-right-width));
|
|
227
|
+
}
|
|
228
|
+
.display-clear-button input {
|
|
229
|
+
padding-right: calc(var(--padding-with-right-slotted-content) + var(--jh-dimension-800));
|
|
230
|
+
}
|
|
231
|
+
.display-clear-button .jh-input-right {
|
|
232
|
+
padding-right: calc(var(--padding-with-right-slotted-content) + var(--jh-input-right-width) + var(--jh-dimension-800) + var(--jh-dimension-200));
|
|
233
|
+
}
|
|
234
|
+
/* Sizes */
|
|
235
|
+
:host([size='small']) input {
|
|
236
|
+
height: var(--jh-dimension-1000);
|
|
237
|
+
}
|
|
238
|
+
:host([size='medium']) input {
|
|
239
|
+
height: var(--jh-dimension-1200);
|
|
240
|
+
}
|
|
241
|
+
:host([size='large']) input {
|
|
242
|
+
height: var(--jh-dimension-1400);
|
|
243
|
+
}
|
|
244
|
+
.footer-content {
|
|
245
|
+
margin: var(--jh-dimension-200) 0 0 0;
|
|
246
|
+
gap: var(--jh-dimension-200);
|
|
247
|
+
display: flex;
|
|
248
|
+
justify-content: space-between;
|
|
249
|
+
}
|
|
250
|
+
.footer-content:has(.counter):not(:has(.error-text)) {
|
|
251
|
+
justify-content: flex-end;
|
|
252
|
+
}
|
|
253
|
+
.counter {
|
|
254
|
+
color: var(--jh-input-counter-color-text,
|
|
255
|
+
var(--jh-color-content-secondary-enabled)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
.error-text {
|
|
259
|
+
color: var(
|
|
260
|
+
--jh-input-error-color-text,
|
|
261
|
+
var(--jh-color-content-negative-enabled)
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
p {
|
|
265
|
+
margin: 0;
|
|
266
|
+
}
|
|
267
|
+
/* Input States */
|
|
268
|
+
input:active {
|
|
269
|
+
border-color: var(
|
|
270
|
+
--jh-input-field-color-border-active,
|
|
271
|
+
var(--jh-color-content-brand-active)
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
:host([disabled]) {
|
|
275
|
+
opacity: var(--jh-input-opacity-disabled, var(--jh-opacity-disabled));
|
|
276
|
+
}
|
|
277
|
+
:host([disabled]) input {
|
|
278
|
+
border-color: var(
|
|
279
|
+
--jh-input-field-color-border-disabled,
|
|
280
|
+
var(--jh-border-control-color)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
input:focus-visible {
|
|
284
|
+
border-color: var(
|
|
285
|
+
--jh-input-field-color-border-focus,
|
|
286
|
+
var(--jh-color-content-brand-hover)
|
|
287
|
+
);
|
|
288
|
+
outline-color: var(
|
|
289
|
+
--jh-input-color-focus,
|
|
290
|
+
var(--jh-border-focus-color)
|
|
291
|
+
);
|
|
292
|
+
outline-style: var(--jh-border-focus-style);
|
|
293
|
+
outline-width: var(--jh-border-focus-width);
|
|
294
|
+
outline-offset: 1px;
|
|
295
|
+
}
|
|
296
|
+
input:hover {
|
|
297
|
+
border-color: var(
|
|
298
|
+
--jh-input-field-color-border-hover,
|
|
299
|
+
var(--jh-color-content-brand-hover)
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
:host([invalid]) input {
|
|
303
|
+
border-width: var(--jh-border-error-width);
|
|
304
|
+
border-style: var(--jh-border-error-style);
|
|
305
|
+
border-color: var(
|
|
306
|
+
--jh-input-field-color-border-error,
|
|
307
|
+
var(--jh-border-error-color)
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
/* readonly styles */
|
|
311
|
+
:host([readonly]) input {
|
|
312
|
+
height: auto;
|
|
313
|
+
background-color: transparent;
|
|
314
|
+
border: none;
|
|
315
|
+
padding-left: 0;
|
|
316
|
+
padding-right: 0;
|
|
317
|
+
}
|
|
318
|
+
/* Override Chrome autofill styles */
|
|
319
|
+
input:autofill {
|
|
320
|
+
-webkit-text-fill-color: var(--input-value-color-text);
|
|
321
|
+
caret-color: var(--input-value-color-text);
|
|
322
|
+
background-clip: text;
|
|
323
|
+
}
|
|
324
|
+
/* Optional/Required/Show-indicator */
|
|
325
|
+
:host([show-indicator]) span {
|
|
326
|
+
color: var(
|
|
327
|
+
--jh-input-optional-color-text,
|
|
328
|
+
var(--jh-color-content-primary-enabled)
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
:host([show-indicator][required]) span {
|
|
332
|
+
color: var(
|
|
333
|
+
--jh-input-required-color-text,
|
|
334
|
+
var(--jh-color-content-negative-enabled)
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
static get properties() {
|
|
342
|
+
return {
|
|
343
|
+
/** Sets an `aria-label` on the input field to assist screen reader users when no visible label is present. */
|
|
344
|
+
accessibleLabel: { type: String, attribute: 'accessible-label' },
|
|
345
|
+
/** Sets an aria-label on the clear button to assist screen reader users. Indicates that activating the button will clear the input field. */
|
|
346
|
+
accessibleLabelClearButton: { type: String, attribute: 'accessible-label-clear-button'},
|
|
347
|
+
/**
|
|
348
|
+
* Determines whether the browser can provide assistance in filling out the input value and what type of information is expected.
|
|
349
|
+
* This property will override any autocomplete attribute present on the input's parent form element.
|
|
350
|
+
*
|
|
351
|
+
* [Visit MDN for information on supported autocomplete values](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete)
|
|
352
|
+
*/
|
|
353
|
+
autocomplete: { type: String },
|
|
354
|
+
/** Disables the input and prevents all user interactions. May cause the input to be ignored by assistive technologies (AT). */
|
|
355
|
+
disabled: { type: Boolean },
|
|
356
|
+
/** Specifies which action label or icon to present for the enter key on virtual keyboards.
|
|
357
|
+
*
|
|
358
|
+
* [Visit MDN for information on supported enterkeyhint values](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint)
|
|
359
|
+
*/
|
|
360
|
+
enterkeyhint: { type: String },
|
|
361
|
+
/** Text to be displayed when input has failed validation and `invalid` is true. */
|
|
362
|
+
errorText: { type: String, attribute: 'error-text' },
|
|
363
|
+
/** Provides additional context or guidance for using the input. For `helper-text` to be displayed, the `label` property must also be set. */
|
|
364
|
+
helperText: { type: String, attribute: 'helper-text' },
|
|
365
|
+
/** Hides the left slot from input. */
|
|
366
|
+
hideLeftSlot: { type: Boolean, attribute: 'hide-left-slot' },
|
|
367
|
+
/** Hides the right slot from input. */
|
|
368
|
+
hideRightSlot: { type: Boolean, attribute: 'hide-right-slot' },
|
|
369
|
+
/** Formats the user entered data on input. Does not apply to pasted values. See input mask documentation above for implementation details. */
|
|
370
|
+
inputMask: { type: String, attribute: 'input-mask' },
|
|
371
|
+
/** Indicates expected input value type and allows for browsers to display appropriate virtual keyboard.
|
|
372
|
+
*
|
|
373
|
+
* [Visit MDN for information on supported inputmode values](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode)
|
|
374
|
+
*/
|
|
375
|
+
inputmode: { type: String },
|
|
376
|
+
/** Sets an `aria-invalid` attribute on input to indicate the value supplied was invalid. Also displays `error-text` and error state styling when set. */
|
|
377
|
+
invalid: { type: Boolean },
|
|
378
|
+
/** Identifies what data should be entered into the input field. */
|
|
379
|
+
label: { type: String },
|
|
380
|
+
/** Sets the maximum number of characters a user can enter into the field. */
|
|
381
|
+
maxlength: { type: String },
|
|
382
|
+
/** Sets the minimum number of characters a user can enter into the field. */
|
|
383
|
+
minlength: { type: String },
|
|
384
|
+
/** Sets a name for the input control. */
|
|
385
|
+
name: { type: String },
|
|
386
|
+
/** Prevents users from changing the input value. Removes all slotted content. */
|
|
387
|
+
readonly: { type: Boolean },
|
|
388
|
+
/** Indicates a value is required. */
|
|
389
|
+
required: { type: Boolean },
|
|
390
|
+
/** Displays a character counter at the bottom right corner below the input field. */
|
|
391
|
+
showCharCount: { type: Boolean, attribute: 'show-char-count' },
|
|
392
|
+
/** Displays a clear button in the input field when it contains a value and is focused or hovered. Deletes input value when activated. */
|
|
393
|
+
showClearButton: {type: Boolean, attribute: 'show-clear-button'},
|
|
394
|
+
/** Adds a visual indicator next to the label. Indicates that a value is optional(by default) or required if the `required` property is also set. */
|
|
395
|
+
showIndicator: { type: Boolean, attribute: 'show-indicator' },
|
|
396
|
+
/** Sets the size of the input. */
|
|
397
|
+
size: { type: String, reflect: true },
|
|
398
|
+
/** Sets the value of the input. */
|
|
399
|
+
value: { type: String },
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
constructor() {
|
|
404
|
+
super();
|
|
405
|
+
this.#internals = this.attachInternals();
|
|
406
|
+
/** @type {?string} */
|
|
407
|
+
this.accessibleLabel = null;
|
|
408
|
+
/** @type {?string} */
|
|
409
|
+
this.accessibleLabelClearButton = null;
|
|
410
|
+
/** @type {?string} */
|
|
411
|
+
this.autocomplete = null;
|
|
412
|
+
/** @type {boolean} */
|
|
413
|
+
this.disabled = false;
|
|
414
|
+
/** @type {?string} */
|
|
415
|
+
this.enterkeyhint = null;
|
|
416
|
+
/** @type {?string} */
|
|
417
|
+
this.errorText = null;
|
|
418
|
+
/** @type {?string} */
|
|
419
|
+
this.helperText = null;
|
|
420
|
+
/** @type {boolean} */
|
|
421
|
+
this.hideLeftSlot = false;
|
|
422
|
+
/** @type {boolean} */
|
|
423
|
+
this.hideRightSlot = false;
|
|
424
|
+
/** @type {?string} */
|
|
425
|
+
this.inputMask = null;
|
|
426
|
+
/** @type {?string} */
|
|
427
|
+
this.inputmode = null;
|
|
428
|
+
/** @type {boolean} */
|
|
429
|
+
this.invalid = false;
|
|
430
|
+
/** @type {?string} */
|
|
431
|
+
this.label = null;
|
|
432
|
+
/** @type {?string} */
|
|
433
|
+
this.maxlength = null;
|
|
434
|
+
/** @type {?string} */
|
|
435
|
+
this.minlength = null;
|
|
436
|
+
/** @type {?string} */
|
|
437
|
+
this.name = null;
|
|
438
|
+
/** @type {boolean} */
|
|
439
|
+
this.readonly = false;
|
|
440
|
+
/** @type {boolean} */
|
|
441
|
+
this.required = false;
|
|
442
|
+
/** @type {boolean} */
|
|
443
|
+
this.showCharCount = false;
|
|
444
|
+
/** @type {boolean} */
|
|
445
|
+
this.showClearButton = false;
|
|
446
|
+
/** @type {boolean} */
|
|
447
|
+
this.showIndicator = false;
|
|
448
|
+
/** @type {'small'|'medium'|'large'} */
|
|
449
|
+
this.size = 'medium';
|
|
450
|
+
/** @type {?string} */
|
|
451
|
+
this.value = null;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
connectedCallback() {
|
|
455
|
+
super.connectedCallback();
|
|
456
|
+
this.#id = id++;
|
|
457
|
+
this.#captureMaskIndexes();
|
|
458
|
+
let observer = new MutationObserver(this.#captureMaskIndexes.bind(this));
|
|
459
|
+
observer.observe(this, { attributeFilter: ['input-mask'] });
|
|
460
|
+
this.addEventListener('jh-select', this.#setSelection);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
disconnectedCallback() {
|
|
464
|
+
super.disconnectedCallback();
|
|
465
|
+
if (this.inputMask) {
|
|
466
|
+
this.removeEventListener('jh-select', this.#setSelection);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
firstUpdated() {
|
|
471
|
+
// attach event listeners to show/hide clear button
|
|
472
|
+
if (this.showClearButton) {
|
|
473
|
+
['focusin', 'focusout'].forEach(e => {
|
|
474
|
+
this.addEventListener(e, this.#toggleFocus);
|
|
475
|
+
});
|
|
476
|
+
let inputContainer = this.shadowRoot.querySelector('.input-container');
|
|
477
|
+
['mouseenter', 'mouseleave'].forEach(e => {
|
|
478
|
+
inputContainer.addEventListener(e, this.#toggleFocus.bind(this));
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
#toggleFocus(e) {
|
|
484
|
+
if (this.disabled || this.readonly || !this.showClearButton) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// clear button to remain visible on mouseleave if input field or slotted content is focused
|
|
489
|
+
if (this.matches(':focus-within') || e.type === 'mouseenter') {
|
|
490
|
+
this.shadowRoot.querySelector('.input-container').classList.add('display-clear-button');
|
|
491
|
+
} else {
|
|
492
|
+
this.shadowRoot.querySelector('.input-container').classList.remove('display-clear-button');
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// create index of meta and fixed chars of input mask w/ corresponding formatted value indexes
|
|
497
|
+
#captureMaskIndexes() {
|
|
498
|
+
|
|
499
|
+
if (!this.inputMask) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let maskIndex = 0;
|
|
504
|
+
let escapeCharCount = 0;
|
|
505
|
+
let fixedCharCount = 0;
|
|
506
|
+
let mask = this.inputMask;
|
|
507
|
+
let lastCharIsFixed = false;
|
|
508
|
+
this.#maskFixedCharIndexes = [];
|
|
509
|
+
this.#maskMetaCharIndexes = [];
|
|
510
|
+
|
|
511
|
+
while (maskIndex < mask.length) {
|
|
512
|
+
let maskChar = mask[maskIndex];
|
|
513
|
+
let metaChar = this.#regexSubset[maskChar];
|
|
514
|
+
|
|
515
|
+
// escape char located, record fixed char index and skip to next char
|
|
516
|
+
if (maskChar === '\\' && mask[maskIndex + 1] === '\\') {
|
|
517
|
+
escapeCharCount += 2;
|
|
518
|
+
this.#maskFixedCharIndexes.push({'maskIndex' : maskIndex + 2, 'escapeCharCount': escapeCharCount, 'fixedCharCount': fixedCharCount, 'formattedValIndex': maskIndex + 2 - escapeCharCount});
|
|
519
|
+
maskIndex += 3;
|
|
520
|
+
fixedCharCount++;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (!metaChar) {
|
|
525
|
+
lastCharIsFixed = maskIndex === mask.length - 1;
|
|
526
|
+
this.#maskFixedCharIndexes.push({'maskIndex' : maskIndex, 'escapeCharCount': escapeCharCount, 'fixedCharCount': fixedCharCount, 'formattedValIndex': maskIndex - escapeCharCount});
|
|
527
|
+
maskIndex++;
|
|
528
|
+
fixedCharCount++;
|
|
529
|
+
} else {
|
|
530
|
+
this.#maskMetaCharIndexes.push({'maskIndex' : maskIndex, 'escapeCharCount': escapeCharCount, 'fixedCharCount': fixedCharCount, 'formattedValIndex': maskIndex - escapeCharCount });
|
|
531
|
+
maskIndex++;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (lastCharIsFixed) {
|
|
536
|
+
this.#startLastFixedChar = this.#captureLastFixedCharIndex()
|
|
537
|
+
} else {
|
|
538
|
+
this.#startLastFixedChar = -1;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
#setSelection(e) {
|
|
543
|
+
this.#selectedText = {
|
|
544
|
+
selected: true,
|
|
545
|
+
selectionStart: e.detail.selectionStart,
|
|
546
|
+
selectionEnd: e.detail.selectionEnd,
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** @ignore */
|
|
551
|
+
get form() {
|
|
552
|
+
return this.#internals.form;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
get value() {
|
|
556
|
+
return this.#value;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
set value(newValue) {
|
|
560
|
+
const oldValue = this.#value;
|
|
561
|
+
if (newValue !== oldValue) {
|
|
562
|
+
this.#value = newValue;
|
|
563
|
+
this.#internals.setFormValue(this.#value);
|
|
564
|
+
}
|
|
565
|
+
this.requestUpdate('value', oldValue);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
#dispatch(eventName, details) {
|
|
569
|
+
this.dispatchEvent(
|
|
570
|
+
new CustomEvent(eventName, {
|
|
571
|
+
detail: details,
|
|
572
|
+
bubbles: true,
|
|
573
|
+
cancelable: true,
|
|
574
|
+
composed: true,
|
|
575
|
+
})
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
#handleInput(e) {
|
|
580
|
+
this.value = e.target.value;
|
|
581
|
+
let inputType = e.inputType;
|
|
582
|
+
this.#deletedChar = inputType === 'deleteContentBackward' || inputType === 'deleteByCut' || inputType === 'deleteContentForward';
|
|
583
|
+
|
|
584
|
+
if (this.inputMask) {
|
|
585
|
+
// format only typed values; no paste
|
|
586
|
+
if (inputType === 'insertText' || this.#deletedChar) {
|
|
587
|
+
this.#applyInputMask(e);
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
this.#dispatch('jh-input', { value: this.value });
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
#handleKeydown(e) {
|
|
595
|
+
const value = e.target.value;
|
|
596
|
+
let selectionStart = e.target.selectionStart;
|
|
597
|
+
let selectionEnd = e.target.selectionEnd;
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
const testKey = (metaChar, key) => {
|
|
601
|
+
if (!this.#regexSubset[metaChar].test(key)) {
|
|
602
|
+
e.preventDefault();
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// don't validate autofill or keyboard shortcuts ie ctrl + c, ctrl + v, etc
|
|
607
|
+
if (e.key === undefined || e.ctrlKey || e.metaKey) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// only validate single char keys
|
|
612
|
+
if (e.key.length === 1) {
|
|
613
|
+
if (selectionStart < value.length) {
|
|
614
|
+
this.#validateInsertion(e, selectionStart, testKey);
|
|
615
|
+
} else {
|
|
616
|
+
this.#validateAppending(e, testKey);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (e.key === 'Backspace' || e.key === 'Delete') {
|
|
621
|
+
this.#checkForFixedCharRemoval(e, selectionStart, selectionEnd);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
#checkForFixedCharRemoval(e, selectionStart, selectionEnd) {
|
|
626
|
+
|
|
627
|
+
if (e.key === 'Backspace') {
|
|
628
|
+
if (this.#selectedText?.selected) {
|
|
629
|
+
let fixedCharSelected = this.#maskFixedCharIndexes.find((fixedChar) => fixedChar.formattedValIndex === selectionStart);
|
|
630
|
+
let metaCharSelected = this.#maskMetaCharIndexes.find((metaChar) => metaChar.formattedValIndex >= selectionStart && metaChar.formattedValIndex < selectionEnd);
|
|
631
|
+
|
|
632
|
+
// if user only selected fixed char(s), move caret to beginning of selection
|
|
633
|
+
if (fixedCharSelected && !metaCharSelected) {
|
|
634
|
+
this.#setCaretPosition(e.target, 'Backspace', selectionStart);
|
|
635
|
+
this.#selectedText.selected = false;
|
|
636
|
+
e.preventDefault();
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
// if user deleted a fixed char, move caret before fixed char
|
|
640
|
+
let fixedCharDeleted = this.#maskFixedCharIndexes.find((fixedChar) => fixedChar.formattedValIndex === selectionStart - 1);
|
|
641
|
+
|
|
642
|
+
if (fixedCharDeleted) {
|
|
643
|
+
this.#setCaretPosition(e.target, 'Backspace', selectionStart - 1);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (e.key === 'Delete') {
|
|
649
|
+
if (this.#selectedText?.selected) {
|
|
650
|
+
let fixedCharSelected = this.#maskFixedCharIndexes.find((fixedChar) => fixedChar.formattedValIndex === selectionStart);
|
|
651
|
+
let metaCharSelected = this.#maskMetaCharIndexes.find((metaChar) => metaChar.formattedValIndex >= selectionStart && metaChar.formattedValIndex < selectionEnd);
|
|
652
|
+
|
|
653
|
+
// if user only selected fixed char(s), move caret to end of selection
|
|
654
|
+
if (fixedCharSelected && !metaCharSelected) {
|
|
655
|
+
this.#setCaretPosition(e.target, 'Delete', selectionStart, selectionEnd);
|
|
656
|
+
this.#selectedText.selected = false;
|
|
657
|
+
e.preventDefault();
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
// if user deleted a fixed char, move caret behind fixed char
|
|
661
|
+
let fixedCharDeleted = this.#maskFixedCharIndexes.find((fixedChar) => fixedChar.formattedValIndex === selectionStart);
|
|
662
|
+
|
|
663
|
+
if (fixedCharDeleted) {
|
|
664
|
+
this.#setCaretPosition(e.target, 'Delete', selectionStart);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// move caret to prevent deletion of fixed chars
|
|
671
|
+
#setCaretPosition(input, key, selectionStart, selectionEnd) {
|
|
672
|
+
let metaCharIndex;
|
|
673
|
+
let newCaretIndex;
|
|
674
|
+
|
|
675
|
+
// find the first meta char before the fixed char that user attempted to delete
|
|
676
|
+
if (key === "Backspace") {
|
|
677
|
+
|
|
678
|
+
if (this.#selectedText?.selected) {
|
|
679
|
+
// user only selected fixed char(s), move caret to beginning of selection
|
|
680
|
+
newCaretIndex = selectionStart;
|
|
681
|
+
} else {
|
|
682
|
+
// find last meta char before fixed char that user attempted to delete
|
|
683
|
+
metaCharIndex = this.#maskMetaCharIndexes.toReversed().find((metaChar) => metaChar.formattedValIndex <= selectionStart);
|
|
684
|
+
|
|
685
|
+
if (metaCharIndex) {
|
|
686
|
+
// place caret after last meta char before fixed char so it can be deleted
|
|
687
|
+
newCaretIndex = metaCharIndex.formattedValIndex + 1;
|
|
688
|
+
} else {
|
|
689
|
+
// place caret at beginning of input if user attempting to delete fixed char at beginning of input mask
|
|
690
|
+
newCaretIndex = 0;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// find the first meta char after the fixed char that user attempted to delete
|
|
696
|
+
if (key === "Delete") {
|
|
697
|
+
|
|
698
|
+
if (this.#selectedText?.selected) {
|
|
699
|
+
// user only selected fixed char(s), move caret to end of selection
|
|
700
|
+
newCaretIndex = selectionEnd;
|
|
701
|
+
} else {
|
|
702
|
+
// find first meta char after fixed char that user attempted to delete
|
|
703
|
+
metaCharIndex = this.#maskMetaCharIndexes.find((metaChar) => metaChar.formattedValIndex >= selectionStart);
|
|
704
|
+
|
|
705
|
+
if (metaCharIndex) {
|
|
706
|
+
// place caret before first meta char after fixed char so it can be deleted
|
|
707
|
+
newCaretIndex = metaCharIndex.formattedValIndex;
|
|
708
|
+
} else {
|
|
709
|
+
// place caret at end of input if no following meta char
|
|
710
|
+
if (this.#startLastFixedChar >= 0) {
|
|
711
|
+
newCaretIndex = this.#maskFixedCharIndexes[this.#maskFixedCharIndexes.length - 1].formattedValIndex + 1;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
input.setSelectionRange(newCaretIndex, newCaretIndex);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
#validateInsertion(e, selectionStart, testKey) {
|
|
720
|
+
// Find corresponding mask and fixed char indexes
|
|
721
|
+
let metaCharIndex = this.#maskMetaCharIndexes.find((metaChar) => metaChar.formattedValIndex === selectionStart);
|
|
722
|
+
let fixedCharIndex = this.#maskFixedCharIndexes.find((fixedChar) => fixedChar.formattedValIndex === selectionStart);
|
|
723
|
+
|
|
724
|
+
// inserting char at index of a fixed char; find following meta char
|
|
725
|
+
if (fixedCharIndex) {
|
|
726
|
+
// set meta char index to first meta char following fixed char
|
|
727
|
+
metaCharIndex = this.#maskMetaCharIndexes.find((metaChar) => metaChar.formattedValIndex >= fixedCharIndex.formattedValIndex + 1);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// test key against meta char
|
|
731
|
+
if (metaCharIndex) {
|
|
732
|
+
testKey(this.inputMask[metaCharIndex.maskIndex], e.key);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
#validateAppending(e, testKey) {
|
|
737
|
+
let metaCharIndex = this.#maskMetaCharIndexes[this.#rawValue.length]?.maskIndex;
|
|
738
|
+
if (metaCharIndex !== undefined) {
|
|
739
|
+
testKey(this.inputMask[metaCharIndex], e.key);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
#captureLastFixedCharIndex() {
|
|
744
|
+
// initialize index to the last element in the mask fixed character indexes array
|
|
745
|
+
let index = this.#maskFixedCharIndexes.length - 1;
|
|
746
|
+
|
|
747
|
+
for (let i = this.#maskFixedCharIndexes.length - 1; i >= 0; i--) {
|
|
748
|
+
let previousIndex = i - 1;
|
|
749
|
+
|
|
750
|
+
// check if the current fixed char follows immediately after fixed char, ie input-mask="99AA"
|
|
751
|
+
if (this.#maskFixedCharIndexes[i].maskIndex === this.#maskFixedCharIndexes[previousIndex]?.maskIndex + 1) {
|
|
752
|
+
index = previousIndex;
|
|
753
|
+
} else {
|
|
754
|
+
// break the loop if the fixed chars are not consecutive
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return index;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
#removeMask(e, value) {
|
|
762
|
+
let insertedChar = e.target.selectionStart < value.length;
|
|
763
|
+
this.#adjustCaretPositionStart = insertedChar ? e.target.selectionStart : null;
|
|
764
|
+
let replacedChar = this.#selectedText?.selected;
|
|
765
|
+
let valueArray = value.split('');
|
|
766
|
+
|
|
767
|
+
if (replacedChar) {
|
|
768
|
+
// handle deletion or replacement of selected text
|
|
769
|
+
return this.#handleReplacement(valueArray);
|
|
770
|
+
}
|
|
771
|
+
if (this.#deletedChar) {
|
|
772
|
+
// handle deletion of single char
|
|
773
|
+
return this.#handleDeletion(e, valueArray);
|
|
774
|
+
}
|
|
775
|
+
if (insertedChar) {
|
|
776
|
+
// handle insertion of single char
|
|
777
|
+
return this.#handleInsertion(e, valueArray);
|
|
778
|
+
}
|
|
779
|
+
// handle appending of single char
|
|
780
|
+
return this.#handleAppending(valueArray);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
#handleReplacement(valueArray) {
|
|
784
|
+
let selectionStart = this.#selectedText.selectionStart;
|
|
785
|
+
let selectionEnd = this.#selectedText.selectionEnd;
|
|
786
|
+
|
|
787
|
+
// browsers delete blank space before selection start on backspace when user double clicks to select text to be deleted
|
|
788
|
+
if (this.#deletedChar) {
|
|
789
|
+
this.#reinsertBlankSpace(selectionStart, selectionEnd, valueArray);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
this.#maskFixedCharIndexes.toReversed().forEach((fixedChar) => {
|
|
793
|
+
|
|
794
|
+
let selectedFixedChars = fixedChar.formattedValIndex >= selectionStart && fixedChar.formattedValIndex < selectionEnd;
|
|
795
|
+
|
|
796
|
+
// fixed char within selection and deleted, no longer present in valueArray
|
|
797
|
+
if (selectedFixedChars) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// fixed char after selection, adjust index of fixed char to account for deleted/replaced chars
|
|
802
|
+
if (fixedChar.formattedValIndex >= selectionEnd) {
|
|
803
|
+
if (this.#deletedChar) {
|
|
804
|
+
// get new index of fixed char
|
|
805
|
+
let adjustedIndex = fixedChar.formattedValIndex - (selectionEnd - selectionStart);
|
|
806
|
+
return valueArray.splice(adjustedIndex, 1);
|
|
807
|
+
} else {
|
|
808
|
+
// get new index of fixed char, subtract 1 to account for added char within selection
|
|
809
|
+
let adjustedIndex = fixedChar.formattedValIndex - (selectionEnd - selectionStart - 1);
|
|
810
|
+
return valueArray.splice(adjustedIndex, 1);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
// fixed char before selection, no adjustment needed
|
|
814
|
+
valueArray.splice(fixedChar.formattedValIndex, 1);
|
|
815
|
+
});
|
|
816
|
+
this.#selectedText.selected = false;
|
|
817
|
+
return valueArray.join('');
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
#reinsertBlankSpace(selectionStart, selectionEnd, valueArray) {
|
|
821
|
+
let previousFixedChar = this.#maskFixedCharIndexes.find((fixedChar) => fixedChar.formattedValIndex === selectionStart - 1);
|
|
822
|
+
|
|
823
|
+
if (previousFixedChar && this.inputMask[previousFixedChar.maskIndex] === ' ') {
|
|
824
|
+
//check to see if following fixed char is also a blank space
|
|
825
|
+
let followingFixedChar = this.#maskFixedCharIndexes.find((fixedChar) => fixedChar.formattedValIndex === selectionEnd);
|
|
826
|
+
|
|
827
|
+
if (this.inputMask[followingFixedChar.maskIndex] === ' ') {
|
|
828
|
+
// if blank space located at beginning and end of selection, there should be two blank spaces in valueArray
|
|
829
|
+
if (valueArray[selectionStart - 1] !== ' ' || valueArray[selectionStart] !== ' ') {
|
|
830
|
+
valueArray.splice(selectionStart - 1, 0, ' ');
|
|
831
|
+
}
|
|
832
|
+
} else {
|
|
833
|
+
// blank space may be present if user clicked and dragged to select text
|
|
834
|
+
if (valueArray[selectionStart - 1] !== ' ') {
|
|
835
|
+
valueArray.splice(selectionStart - 1, 0, ' ');
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return valueArray;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
#handleDeletion(e, valueArray) {
|
|
843
|
+
let selectionStart = e.target.selectionStart;
|
|
844
|
+
|
|
845
|
+
this.#maskFixedCharIndexes.toReversed().forEach((fixedChar) => {
|
|
846
|
+
|
|
847
|
+
// fixed char deleted, no longer present in valueArray
|
|
848
|
+
if (selectionStart === fixedChar.formattedValIndex) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// deleted char before fixed char, decrease index to account for deleted char
|
|
853
|
+
if (selectionStart < fixedChar.formattedValIndex) {
|
|
854
|
+
return valueArray.splice(fixedChar.formattedValIndex - 1, 1);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
valueArray.splice(fixedChar.formattedValIndex, 1);
|
|
858
|
+
});
|
|
859
|
+
return valueArray.join('');
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
#handleInsertion(e, valueArray) {
|
|
863
|
+
let selectionStart = e.target.selectionStart;
|
|
864
|
+
|
|
865
|
+
this.#maskFixedCharIndexes.toReversed().forEach((fixedChar) => {
|
|
866
|
+
let fixedCharIndex = fixedChar.formattedValIndex;
|
|
867
|
+
|
|
868
|
+
// inserted char at or before fixed char, increase index to account for new char
|
|
869
|
+
if (selectionStart - 1 === fixedCharIndex || selectionStart <= fixedCharIndex) {
|
|
870
|
+
valueArray.splice(fixedChar.formattedValIndex + 1, 1);
|
|
871
|
+
} else {
|
|
872
|
+
// inserted char after fixed char
|
|
873
|
+
valueArray.splice(fixedChar.formattedValIndex, 1);
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
return valueArray.join('');
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
#handleAppending(valueArray) {
|
|
880
|
+
this.#maskFixedCharIndexes.toReversed().forEach((fixedChar) => {
|
|
881
|
+
// remove fixed chars that are located in the value
|
|
882
|
+
if (valueArray.length - 1 > fixedChar.formattedValIndex) {
|
|
883
|
+
valueArray.splice(fixedChar.formattedValIndex, 1);
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
return valueArray.join('');
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
#applyInputMask(e) {
|
|
890
|
+
// only remove mask if fixed chars are present
|
|
891
|
+
this.#rawValue = this.#maskFixedCharIndexes.length ? this.#removeMask(e, this.value) : this.value;
|
|
892
|
+
const mask = this.inputMask;
|
|
893
|
+
|
|
894
|
+
// create array to store formatted result, initialized with null values
|
|
895
|
+
let formattedResult = this.#initializeFormattedResult();
|
|
896
|
+
const initialResultLength = formattedResult.length;
|
|
897
|
+
|
|
898
|
+
// add fixed and meta chars to result array
|
|
899
|
+
this.#populateFormattedResult(mask, formattedResult);
|
|
900
|
+
|
|
901
|
+
// remove null values from result array
|
|
902
|
+
formattedResult = formattedResult.filter(value => value !== null);
|
|
903
|
+
|
|
904
|
+
// if last char of mask is fixed, add it to the formatted result when index is reached
|
|
905
|
+
if (this.#startLastFixedChar >= 0) {
|
|
906
|
+
// get index of last fixed char in the input mask
|
|
907
|
+
let indexOfLastFixedChar = this.#maskFixedCharIndexes[this.#startLastFixedChar]?.formattedValIndex;
|
|
908
|
+
|
|
909
|
+
// if following char is the ending fixed char of the input mask, add it to the formatted result. Account for temp loss of fixed char when deleted
|
|
910
|
+
if (formattedResult.length === indexOfLastFixedChar) {
|
|
911
|
+
this.#appendEndingFixedChars(mask, formattedResult);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// if input mask length is reached, add additional chars to the formatted result
|
|
916
|
+
if (formattedResult.length === initialResultLength) {
|
|
917
|
+
let additionalCharacters = this.#getAdditionalCharacters(formattedResult, mask);
|
|
918
|
+
|
|
919
|
+
if (additionalCharacters) {
|
|
920
|
+
formattedResult.push(additionalCharacters);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// remove chars that exceed maxlength
|
|
925
|
+
if (this.maxlength && formattedResult.length > Number(this.maxlength)) {
|
|
926
|
+
formattedResult = formattedResult.slice(0, this.maxlength);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
this.value = formattedResult.join('');
|
|
930
|
+
|
|
931
|
+
// Dispatch a custom event with the formatted and raw values
|
|
932
|
+
this.#dispatch('jh-input', {
|
|
933
|
+
'value': this.value,
|
|
934
|
+
'rawValue': this.#rawValue
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
#initializeFormattedResult() {
|
|
939
|
+
return Array(Math.max(...this.#maskFixedCharIndexes.map(fixedChar => fixedChar.formattedValIndex).concat(this.#maskMetaCharIndexes.map(metaChar => metaChar.formattedValIndex))) + 1).fill(null);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
#populateFormattedResult(mask, formattedResult) {
|
|
943
|
+
// reinsert fixed chars into formatted result
|
|
944
|
+
this.#maskFixedCharIndexes.forEach((fixedChar) => {
|
|
945
|
+
|
|
946
|
+
// add fixed chars within the length of the total value (raw value + fixed chars)
|
|
947
|
+
if (fixedChar.formattedValIndex < fixedChar.fixedCharCount + this.#rawValue.length) {
|
|
948
|
+
formattedResult[fixedChar.formattedValIndex] = mask[fixedChar.maskIndex];
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
// reinsert meta chars into formatted result
|
|
953
|
+
this.#maskMetaCharIndexes.forEach(metaChar => {
|
|
954
|
+
// add chars that are within the length of the total value (raw value + fixed chars)
|
|
955
|
+
if (metaChar.formattedValIndex < metaChar.fixedCharCount + this.#rawValue.length) {
|
|
956
|
+
formattedResult[metaChar.formattedValIndex] = this.#rawValue[metaChar.formattedValIndex - metaChar.fixedCharCount];
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
#appendEndingFixedChars(mask, formattedResult) {
|
|
962
|
+
let lastFixedCharMaskIndex = this.#maskFixedCharIndexes[this.#startLastFixedChar].maskIndex;
|
|
963
|
+
|
|
964
|
+
let endMaskIndex = mask.length;
|
|
965
|
+
|
|
966
|
+
// capture fixed characters from the input mask
|
|
967
|
+
let endingFixedChars = mask.slice(
|
|
968
|
+
lastFixedCharMaskIndex,
|
|
969
|
+
endMaskIndex
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
endingFixedChars.split('').forEach((char) => {
|
|
973
|
+
formattedResult.push(char);
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
#getAdditionalCharacters(formattedResult, mask) {
|
|
978
|
+
if (this.#maskFixedCharIndexes.length) {
|
|
979
|
+
// account for fixed chars present in the value
|
|
980
|
+
return this.#rawValue.slice(formattedResult.length - this.#maskFixedCharIndexes.length);
|
|
981
|
+
}
|
|
982
|
+
return this.#rawValue.slice(mask.length);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// restore caret position after input mask is applied if change to the value is within the value length
|
|
986
|
+
updated(changedProperties) {
|
|
987
|
+
if (this.#adjustCaretPositionStart) {
|
|
988
|
+
if (changedProperties.has('value')) {
|
|
989
|
+
let input = this.shadowRoot.querySelector('input');
|
|
990
|
+
let selectionStart = this.#selectedText.selectionStart ? this.#selectedText.selectionStart : this.#adjustCaretPositionStart;
|
|
991
|
+
|
|
992
|
+
input.setSelectionRange(selectionStart, selectionStart);
|
|
993
|
+
this.#selectedText.selectionStart = null;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
#handleChange() {
|
|
999
|
+
let payload = {
|
|
1000
|
+
'value': this.value,
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (this.inputMask) {
|
|
1004
|
+
payload.rawValue = this.#rawValue;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
this.#dispatch('jh-change', payload);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
#handleSelect(e) {
|
|
1011
|
+
const selectedString = e.target.value.substring(
|
|
1012
|
+
e.target.selectionStart,
|
|
1013
|
+
e.target.selectionEnd
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
// ensure selected string present before dispatching event. Can be empty due to caret positioning when user attempts to delete fixed char.
|
|
1017
|
+
if (selectedString) {
|
|
1018
|
+
this.#dispatch('jh-select', {
|
|
1019
|
+
selected: selectedString,
|
|
1020
|
+
selectionStart: e.target.selectionStart,
|
|
1021
|
+
selectionEnd: e.target.selectionEnd
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
#handleMaxlength() {
|
|
1027
|
+
this.#dispatch('jh-maxlength');
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
#handleClearButtonClick() {
|
|
1031
|
+
let previousValue = this.value;
|
|
1032
|
+
// clear input value
|
|
1033
|
+
this.value = '';
|
|
1034
|
+
// focus input field
|
|
1035
|
+
this.shadowRoot.querySelector('input').focus();
|
|
1036
|
+
// dispatch clear event
|
|
1037
|
+
this.dispatchEvent(
|
|
1038
|
+
new CustomEvent('jh-input:clear-button-click', {
|
|
1039
|
+
detail: {
|
|
1040
|
+
'previousValue': previousValue
|
|
1041
|
+
},
|
|
1042
|
+
bubbles: true,
|
|
1043
|
+
cancelable: true,
|
|
1044
|
+
composed: true,
|
|
1045
|
+
})
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
#handleSlotChange(e) {
|
|
1050
|
+
let inputEl = this.shadowRoot.querySelector('input');
|
|
1051
|
+
let slottedElement = e.target.assignedElements()[0];
|
|
1052
|
+
let slotName = e.target.name;
|
|
1053
|
+
|
|
1054
|
+
if (slottedElement?.tagName.startsWith('JH-ICON')) {
|
|
1055
|
+
slottedElement.setAttribute('size', 'medium');
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// capture dimensions of slotted content
|
|
1059
|
+
if (slottedElement) {
|
|
1060
|
+
if (slotName === 'jh-input-left' || slotName === 'jh-input-right') {
|
|
1061
|
+
|
|
1062
|
+
// set a CSS variable for the width of the slotted content
|
|
1063
|
+
this.style.setProperty(`--${slottedElement.slot}-width`, `${slottedElement.offsetWidth}px`);
|
|
1064
|
+
|
|
1065
|
+
// set a css variable to vertically center slotted content
|
|
1066
|
+
this.style.setProperty(`--${slottedElement.slot}-top`, `${(inputEl.offsetHeight - slottedElement.offsetHeight) / 2}px`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
this.#addClass(slotName, slottedElement);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// sets class on input element so padding can accomodate slotted content
|
|
1073
|
+
#addClass(slotName, slottedElement) {
|
|
1074
|
+
const inputEl = this.shadowRoot.querySelector('input');
|
|
1075
|
+
|
|
1076
|
+
// add and remove class if slotted element is not present
|
|
1077
|
+
if (!slottedElement) {
|
|
1078
|
+
inputEl.classList.remove(slotName);
|
|
1079
|
+
} else {
|
|
1080
|
+
inputEl.classList.add(slotName);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
#getSlots() {
|
|
1085
|
+
if (this.readonly) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const leftSlot = this.hideLeftSlot
|
|
1090
|
+
? null
|
|
1091
|
+
: html`<slot
|
|
1092
|
+
name="jh-input-left"
|
|
1093
|
+
@slotchange=${this.#handleSlotChange}
|
|
1094
|
+
></slot>
|
|
1095
|
+
`;
|
|
1096
|
+
|
|
1097
|
+
const clearBtn = this.showClearButton && this.value && !this.disabled
|
|
1098
|
+
? html`
|
|
1099
|
+
<jh-button
|
|
1100
|
+
size="small" appearance="tertiary" class="clear-button"
|
|
1101
|
+
accessible-label=${ifDefined(this.accessibleLabelClearButton)}
|
|
1102
|
+
@click=${this.#handleClearButtonClick}>
|
|
1103
|
+
<slot name="jh-input-clear-button" slot="jh-button-icon">
|
|
1104
|
+
<jh-icon-circle-xmark slot="jh-button-icon" aria-hidden="true" size="medium"></jh-icon-circle-xmark>
|
|
1105
|
+
</slot>
|
|
1106
|
+
</jh-button>
|
|
1107
|
+
`
|
|
1108
|
+
: null;
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
const rightSlot = this.hideRightSlot
|
|
1112
|
+
? null
|
|
1113
|
+
: html`<slot
|
|
1114
|
+
name="jh-input-right"
|
|
1115
|
+
@slotchange=${this.#handleSlotChange}
|
|
1116
|
+
></slot>
|
|
1117
|
+
`;
|
|
1118
|
+
|
|
1119
|
+
return html`${leftSlot}${clearBtn}${rightSlot}`;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
#getDescribedby() {
|
|
1123
|
+
let describedbyString = '';
|
|
1124
|
+
|
|
1125
|
+
if (this.errorText) {
|
|
1126
|
+
describedbyString += `jh-input-error-${this.#id}`;
|
|
1127
|
+
}
|
|
1128
|
+
if (this.helperText) {
|
|
1129
|
+
describedbyString += ` jh-input-helper-${this.#id}`;
|
|
1130
|
+
}
|
|
1131
|
+
if (this.showCharCount) {
|
|
1132
|
+
describedbyString += ` jh-input-counter-${this.#id}`;
|
|
1133
|
+
}
|
|
1134
|
+
return describedbyString;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
render() {
|
|
1138
|
+
let label;
|
|
1139
|
+
let indicator;
|
|
1140
|
+
let helperText;
|
|
1141
|
+
let input;
|
|
1142
|
+
let footer;
|
|
1143
|
+
let errorText;
|
|
1144
|
+
let charCount;
|
|
1145
|
+
let describedby;
|
|
1146
|
+
|
|
1147
|
+
if (this.label) {
|
|
1148
|
+
if (this.showIndicator) {
|
|
1149
|
+
if (this.required) {
|
|
1150
|
+
indicator = html`<span class="indicator" aria-hidden="true"> *</span>`;
|
|
1151
|
+
} else {
|
|
1152
|
+
indicator = html`<span class="indicator"> (optional)</span>`;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
if (this.helperText) {
|
|
1157
|
+
helperText = html`
|
|
1158
|
+
<p id="jh-input-helper-${this.#id}" class="helper-text">
|
|
1159
|
+
${this.helperText}
|
|
1160
|
+
</p>
|
|
1161
|
+
`;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
label = html`
|
|
1165
|
+
<label for="jh-input-${this.#id}">${this.label}${indicator}</label>
|
|
1166
|
+
${helperText}
|
|
1167
|
+
`;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (this.showCharCount) {
|
|
1171
|
+
let valueLength = this.value ? this.value.length : 0;
|
|
1172
|
+
|
|
1173
|
+
let charCountValue = `${
|
|
1174
|
+
this.maxlength ? `${valueLength}/${this.maxlength}` : valueLength
|
|
1175
|
+
}`;
|
|
1176
|
+
|
|
1177
|
+
if (valueLength && valueLength === Number(this.maxlength)) {
|
|
1178
|
+
this.#handleMaxlength();
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
charCount = html`
|
|
1182
|
+
<p class="counter" aria-hidden="true">${charCountValue}</p>
|
|
1183
|
+
`;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
if (this.invalid && this.errorText) {
|
|
1187
|
+
errorText = html`
|
|
1188
|
+
<p id="jh-input-error-${this.#id}" class="error-text">
|
|
1189
|
+
${this.errorText}
|
|
1190
|
+
</p>
|
|
1191
|
+
`;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if ((this.invalid && this.errorText) || this.showCharCount) {
|
|
1195
|
+
footer = html`
|
|
1196
|
+
<div class="footer-content">
|
|
1197
|
+
${errorText}
|
|
1198
|
+
${charCount}
|
|
1199
|
+
</div>
|
|
1200
|
+
`;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (helperText || errorText || charCount) {
|
|
1204
|
+
describedby = this.#getDescribedby();
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
input = html`
|
|
1208
|
+
<div class="input-container">
|
|
1209
|
+
<input
|
|
1210
|
+
id="jh-input-${this.#id}"
|
|
1211
|
+
aria-describedby=${describedby}
|
|
1212
|
+
aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
|
|
1213
|
+
aria-label=${ifDefined(
|
|
1214
|
+
this.accessibleLabel === '' ? null : this.accessibleLabel
|
|
1215
|
+
)}
|
|
1216
|
+
autocomplete=${ifDefined(
|
|
1217
|
+
this.autocomplete === '' ? null : this.autocomplete
|
|
1218
|
+
)}
|
|
1219
|
+
?disabled=${this.disabled}
|
|
1220
|
+
enterkeyhint=${ifDefined(
|
|
1221
|
+
this.enterkeyhint === '' ? null : this.enterkeyhint
|
|
1222
|
+
)}
|
|
1223
|
+
inputmode=${ifDefined(this.inputmode === '' ? null : this.inputmode)}
|
|
1224
|
+
maxlength=${ifDefined(this.maxlength === '' ? null : this.maxlength)}
|
|
1225
|
+
minlength=${ifDefined(this.minlength === '' ? null : this.minlength)}
|
|
1226
|
+
name=${ifDefined(this.name === '' ? null : this.name)}
|
|
1227
|
+
?readonly=${this.readonly}
|
|
1228
|
+
?required=${this.required}
|
|
1229
|
+
type="text"
|
|
1230
|
+
.value=${this.value}
|
|
1231
|
+
@keydown=${this.inputMask ? this.#handleKeydown : null}
|
|
1232
|
+
@change=${this.#handleChange}
|
|
1233
|
+
@input=${this.#handleInput}
|
|
1234
|
+
@select=${this.#handleSelect}
|
|
1235
|
+
/>${this.#getSlots()}
|
|
1236
|
+
</div>
|
|
1237
|
+
`;
|
|
1238
|
+
|
|
1239
|
+
return html`
|
|
1240
|
+
${label} ${input} ${footer}
|
|
1241
|
+
`;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
customElements.define('jh-input', JhInput);
|