@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.
- package/components/button/button.js +98 -39
- package/components/input/input.js +295 -247
- package/components/input-password/input-password.js +71 -61
- package/components/input-search/input-search.js +11 -8
- package/components/input-textarea/input-textarea.js +67 -175
- package/components/list-item/list-item.js +4 -1
- package/components/notification/notification.js +14 -6
- package/components/select/filtering.js +92 -0
- package/components/select/select.js +660 -0
- package/components/table/table.js +16 -17
- package/components/table-data-cell/table-data-cell.js +2 -1
- package/components/table-header-cell/table-header-cell.js +1 -1
- package/components/tag/tag.js +7 -4
- package/components/toast/toast.js +1 -5
- package/custom-elements.json +819 -171
- package/index.d.ts +2 -0
- package/jsconfig.json +1 -1
- package/package.json +10 -14
|
@@ -2,48 +2,23 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
import { html
|
|
5
|
+
import { html } from 'lit';
|
|
6
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
6
7
|
import { JhInput } from '../input/input.js';
|
|
7
8
|
import '@jack-henry/jh-icons/icons-wc/icon-eye-slash.js';
|
|
8
9
|
import '@jack-henry/jh-icons/icons-wc/icon-eye.js';
|
|
9
10
|
|
|
11
|
+
let id = 0;
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* @slot jh-input-password-hidden - Use to insert a custom icon within the toggle password button when the input value is masked.
|
|
12
15
|
* @slot jh-input-password-visible - Use to insert a custom icon within the toggle password button when the input value is unmasked.
|
|
13
16
|
* @customElement jh-input-password
|
|
14
17
|
*/
|
|
15
18
|
export class JhInputPassword extends JhInput {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return [
|
|
20
|
-
super.styles,
|
|
21
|
-
css`
|
|
22
|
-
.password-toggle-btn {
|
|
23
|
-
position: absolute;
|
|
24
|
-
right: var(--jh-dimension-400);
|
|
25
|
-
}
|
|
26
|
-
.clear-button {
|
|
27
|
-
right: var(--jh-dimension-1400);
|
|
28
|
-
}
|
|
29
|
-
.display-clear-button input {
|
|
30
|
-
padding-right: var(--jh-dimension-2400);
|
|
31
|
-
}
|
|
32
|
-
input {
|
|
33
|
-
padding-right: var(--jh-dimension-1400);
|
|
34
|
-
}
|
|
35
|
-
:host([size='small']) .password-toggle-btn {
|
|
36
|
-
top: 4px;
|
|
37
|
-
}
|
|
38
|
-
:host([size='medium']) .password-toggle-btn {
|
|
39
|
-
top: 8px;
|
|
40
|
-
}
|
|
41
|
-
:host([size='large']) .password-toggle-btn {
|
|
42
|
-
top: 12px;
|
|
43
|
-
}
|
|
44
|
-
`
|
|
45
|
-
];
|
|
46
|
-
}
|
|
19
|
+
|
|
20
|
+
/** @type {?number} */
|
|
21
|
+
#id;
|
|
47
22
|
|
|
48
23
|
static get properties() {
|
|
49
24
|
return {
|
|
@@ -69,39 +44,74 @@ export class JhInputPassword extends JhInput {
|
|
|
69
44
|
/** @type {?string} */
|
|
70
45
|
this.accessibleLabelShowPassword = null;
|
|
71
46
|
/** @type {boolean} */
|
|
72
|
-
this.hideRightSlot = true;
|
|
73
|
-
/** @type {boolean} */
|
|
74
47
|
this.passwordVisible = false;
|
|
75
48
|
}
|
|
76
49
|
|
|
77
|
-
|
|
78
|
-
super.
|
|
79
|
-
this.#
|
|
50
|
+
connectedCallback() {
|
|
51
|
+
super.connectedCallback();
|
|
52
|
+
this.#id = id++;
|
|
80
53
|
}
|
|
81
54
|
|
|
82
|
-
|
|
83
|
-
|
|
55
|
+
renderInput() {
|
|
56
|
+
let describedby;
|
|
84
57
|
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
this.#inputEl.setAttribute('type', 'text');
|
|
88
|
-
} else {
|
|
89
|
-
this.#inputEl.setAttribute('type', 'password');
|
|
90
|
-
}
|
|
58
|
+
if (this.helperText || (this.errorText && this.invalid)) {
|
|
59
|
+
describedby = this._getDescribedby();
|
|
91
60
|
}
|
|
92
|
-
|
|
61
|
+
|
|
62
|
+
const leftSlot = this.readonly ? null : this.renderLeftSlot();
|
|
63
|
+
const rightSlot = this.readonly ? null : this.renderRightSlot();
|
|
64
|
+
const clearButton = this.readonly ? null : this.renderClearButton();
|
|
65
|
+
|
|
66
|
+
return html`
|
|
67
|
+
<div class="input-container">
|
|
68
|
+
<div class="input-wrapper">
|
|
69
|
+
${leftSlot}
|
|
70
|
+
<input
|
|
71
|
+
id="jh-input-${this.#id}"
|
|
72
|
+
aria-describedby=${describedby}
|
|
73
|
+
aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
|
|
74
|
+
aria-label=${ifDefined(
|
|
75
|
+
this.accessibleLabel === '' ? null : this.accessibleLabel
|
|
76
|
+
)}
|
|
77
|
+
autocomplete=${ifDefined(
|
|
78
|
+
this.autocomplete === '' ? null : this.autocomplete
|
|
79
|
+
)}
|
|
80
|
+
?disabled=${this.disabled}
|
|
81
|
+
enterkeyhint=${ifDefined(
|
|
82
|
+
this.enterkeyhint === '' ? null : this.enterkeyhint
|
|
83
|
+
)}
|
|
84
|
+
inputmode=${ifDefined(this.inputmode === '' ? null : this.inputmode)}
|
|
85
|
+
maxlength=${ifDefined(this.maxlength === '' ? null : this.maxlength)}
|
|
86
|
+
minlength=${ifDefined(this.minlength === '' ? null : this.minlength)}
|
|
87
|
+
name=${ifDefined(this.name === '' ? null : this.name)}
|
|
88
|
+
?readonly=${this.readonly}
|
|
89
|
+
?required=${this.required}
|
|
90
|
+
type=${this.passwordVisible ? 'text' : 'password'}
|
|
91
|
+
.value=${this.value}
|
|
92
|
+
@keydown=${this.inputMask ? this._handleKeydown : null}
|
|
93
|
+
@change=${this._handleChange}
|
|
94
|
+
@input=${this._handleInput}
|
|
95
|
+
@select=${this._handleSelect}
|
|
96
|
+
/>
|
|
97
|
+
${clearButton}
|
|
98
|
+
${rightSlot}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
`;
|
|
93
102
|
}
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
renderRightSlot() {
|
|
105
|
+
if (this.hideRightSlot) return;
|
|
106
|
+
|
|
107
|
+
return html`
|
|
108
|
+
<slot name="jh-input-right" @slotchange=${this._handleSlotChange}>${this.#renderTogglePasswordBtn()}</slot>
|
|
109
|
+
|
|
110
|
+
`;
|
|
99
111
|
}
|
|
100
112
|
|
|
101
|
-
#
|
|
102
|
-
if (this.readonly)
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
113
|
+
#renderTogglePasswordBtn() {
|
|
114
|
+
if (this.readonly) return;
|
|
105
115
|
|
|
106
116
|
let accessibleLabel = this.passwordVisible
|
|
107
117
|
? this.accessibleLabelHidePassword
|
|
@@ -110,7 +120,7 @@ export class JhInputPassword extends JhInput {
|
|
|
110
120
|
let passwordBtn = html`
|
|
111
121
|
<jh-button
|
|
112
122
|
class="password-toggle-btn"
|
|
113
|
-
size="small"
|
|
123
|
+
size="x-small"
|
|
114
124
|
appearance="tertiary"
|
|
115
125
|
?disabled=${this.disabled}
|
|
116
126
|
accessible-label=${accessibleLabel}
|
|
@@ -120,24 +130,24 @@ export class JhInputPassword extends JhInput {
|
|
|
120
130
|
? html`
|
|
121
131
|
<slot
|
|
122
132
|
name="jh-input-password-visible"
|
|
123
|
-
slot="jh-button-icon"
|
|
133
|
+
slot="jh-button-icon-left"
|
|
124
134
|
>
|
|
125
135
|
<jh-icon-eye-slash
|
|
126
|
-
slot="jh-button-icon"
|
|
136
|
+
slot="jh-button-icon-left"
|
|
127
137
|
aria-hidden="true"
|
|
128
|
-
size="
|
|
138
|
+
size="x-small"
|
|
129
139
|
></jh-icon-eye-slash>
|
|
130
140
|
</slot>
|
|
131
141
|
`
|
|
132
142
|
: html`
|
|
133
143
|
<slot
|
|
134
144
|
name="jh-input-password-hidden"
|
|
135
|
-
slot="jh-button-icon"
|
|
145
|
+
slot="jh-button-icon-left"
|
|
136
146
|
>
|
|
137
147
|
<jh-icon-eye
|
|
138
|
-
slot="jh-button-icon"
|
|
148
|
+
slot="jh-button-icon-left"
|
|
139
149
|
aria-hidden="true"
|
|
140
|
-
size="
|
|
150
|
+
size="x-small"
|
|
141
151
|
></jh-icon-eye>
|
|
142
152
|
</slot>
|
|
143
153
|
`}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
import { css } from 'lit';
|
|
5
|
+
import { css, html } from 'lit';
|
|
6
6
|
import { JhInput } from '../input/input.js';
|
|
7
7
|
import '@jack-henry/jh-icons/icons-wc/icon-magnifying-glass.js';
|
|
8
8
|
|
|
@@ -25,17 +25,20 @@ export class JhInputSearch extends JhInput {
|
|
|
25
25
|
|
|
26
26
|
firstUpdated() {
|
|
27
27
|
super.firstUpdated();
|
|
28
|
-
let leftSlot = this.shadowRoot.querySelector('slot[name="jh-input-left"]');
|
|
29
|
-
|
|
30
|
-
// insert fallback content in left slot if empty
|
|
31
|
-
if (leftSlot && leftSlot.assignedElements().length === 0) {
|
|
32
|
-
this.innerHTML +=
|
|
33
|
-
'<jh-icon-magnifying-glass slot="jh-input-left" aria-hidden="true"></jh-icon-magnifying-glass>';
|
|
34
|
-
}
|
|
35
28
|
|
|
36
29
|
// set input type to search
|
|
37
30
|
let inputEl = this.shadowRoot.querySelector('input');
|
|
38
31
|
inputEl.setAttribute('type', 'search');
|
|
39
32
|
}
|
|
33
|
+
|
|
34
|
+
renderLeftSlot() {
|
|
35
|
+
if (this.hideLeftSlot) return null;
|
|
36
|
+
|
|
37
|
+
return html`
|
|
38
|
+
<slot name="jh-input-left" @slotchange=${this._handleSlotChange}>
|
|
39
|
+
<jh-icon-magnifying-glass aria-hidden="true" size="x-small"></jh-icon-magnifying-glass>
|
|
40
|
+
</slot>
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
customElements.define('jh-input-search', JhInputSearch);
|
|
@@ -18,6 +18,16 @@ let id = 0;
|
|
|
18
18
|
export class JhInputTextarea extends JhInput {
|
|
19
19
|
/** @type {?number} */
|
|
20
20
|
#id;
|
|
21
|
+
/** @type {?ResizeObserver} */
|
|
22
|
+
#resizeObserver;
|
|
23
|
+
|
|
24
|
+
get #textareaEl() {
|
|
25
|
+
return this.renderRoot?.querySelector('textarea');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get #footerContent() {
|
|
29
|
+
return this.renderRoot?.querySelector('.footer-content');
|
|
30
|
+
}
|
|
21
31
|
|
|
22
32
|
static get styles() {
|
|
23
33
|
return [
|
|
@@ -118,6 +128,7 @@ export class JhInputTextarea extends JhInput {
|
|
|
118
128
|
);
|
|
119
129
|
}
|
|
120
130
|
:host([readonly]) textarea {
|
|
131
|
+
background-color: transparent;
|
|
121
132
|
height: auto;
|
|
122
133
|
border: none;
|
|
123
134
|
padding-left: 0;
|
|
@@ -153,14 +164,14 @@ export class JhInputTextarea extends JhInput {
|
|
|
153
164
|
attribute: 'auto-grow',
|
|
154
165
|
},
|
|
155
166
|
/** Sets the width of the input field. */
|
|
156
|
-
cols: { type:
|
|
167
|
+
cols: { type: Number },
|
|
157
168
|
/** Removes native resize capability of the input field. */
|
|
158
169
|
noResize: {
|
|
159
170
|
type: Boolean,
|
|
160
171
|
attribute: 'no-resize',
|
|
161
172
|
},
|
|
162
173
|
/** Sets the height of the input field. */
|
|
163
|
-
rows: { type:
|
|
174
|
+
rows: { type: Number },
|
|
164
175
|
/** Specifies how text should be wrapped when submitted in a form. The `cols` property must be set for `wrap='hard'` to take effect. */
|
|
165
176
|
wrap: { type: String },
|
|
166
177
|
/** Prevents users from changing the input value. */
|
|
@@ -171,12 +182,12 @@ export class JhInputTextarea extends JhInput {
|
|
|
171
182
|
constructor() {
|
|
172
183
|
super();
|
|
173
184
|
/** @type {boolean} */
|
|
174
|
-
this.autoGrow =
|
|
175
|
-
/** @type {?
|
|
185
|
+
this.autoGrow = false;
|
|
186
|
+
/** @type {?number} */
|
|
176
187
|
this.cols = null;
|
|
177
188
|
/** @type {boolean} */
|
|
178
189
|
this.noResize = true;
|
|
179
|
-
/** @type {?
|
|
190
|
+
/** @type {?number} */
|
|
180
191
|
this.rows = null;
|
|
181
192
|
/** @type {'small'|'medium'|'large'} */
|
|
182
193
|
this.size = 'medium';
|
|
@@ -189,191 +200,72 @@ export class JhInputTextarea extends JhInput {
|
|
|
189
200
|
this.#id = id++;
|
|
190
201
|
}
|
|
191
202
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
new ResizeObserver(() => {
|
|
199
|
-
this.#updateFooterWidth(footerContent, textareaEl);
|
|
200
|
-
}).observe(textareaEl);
|
|
203
|
+
disconnectedCallback() {
|
|
204
|
+
super.disconnectedCallback();
|
|
205
|
+
if (this.#resizeObserver) {
|
|
206
|
+
this.#resizeObserver.unobserve(this.#textareaEl);
|
|
207
|
+
this.#resizeObserver.disconnect();
|
|
208
|
+
this.#resizeObserver = null;
|
|
201
209
|
}
|
|
202
210
|
}
|
|
203
211
|
|
|
204
|
-
|
|
205
|
-
|
|
212
|
+
firstUpdated() {
|
|
213
|
+
// add resize observer to update width of footer when textarea width changes
|
|
214
|
+
if (this.#footerContent) {
|
|
215
|
+
this.#resizeObserver = new ResizeObserver(() => {
|
|
216
|
+
this.#updateFooterWidth();
|
|
217
|
+
});
|
|
218
|
+
this.#resizeObserver.observe(this.#textareaEl);
|
|
219
|
+
}
|
|
206
220
|
}
|
|
207
221
|
|
|
208
|
-
#
|
|
209
|
-
this
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
bubbles: true,
|
|
213
|
-
cancelable: true,
|
|
214
|
-
composed: true,
|
|
215
|
-
})
|
|
216
|
-
);
|
|
222
|
+
#updateFooterWidth() {
|
|
223
|
+
if (this.#footerContent) {
|
|
224
|
+
this.#footerContent.style.width = this.#textareaEl.offsetWidth + 'px';
|
|
225
|
+
}
|
|
217
226
|
}
|
|
218
227
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
_handleInput(e) {
|
|
229
|
+
super._handleInput(e);
|
|
230
|
+
|
|
231
|
+
// add textarea specific autogrow
|
|
224
232
|
if (this.autoGrow) {
|
|
225
233
|
this.#autoGrowTextarea();
|
|
226
234
|
}
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
#autoGrowTextarea() {
|
|
230
|
-
|
|
231
|
-
textareaEl.style.height = 'auto';
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
#handleChange() {
|
|
236
|
-
let payload = {
|
|
237
|
-
'value': this.value,
|
|
238
|
-
};
|
|
239
|
-
this.#dispatch('jh-change', payload);
|
|
238
|
+
// Reset height to auto to get the correct scrollHeight
|
|
239
|
+
this.#textareaEl.style.height = 'auto';
|
|
240
|
+
// Set height to scrollHeight to accommodate content
|
|
241
|
+
this.#textareaEl.style.height = `${this.#textareaEl.scrollHeight}px`;
|
|
240
242
|
}
|
|
241
243
|
|
|
242
|
-
|
|
243
|
-
const selectedString = e.target.value.substring(
|
|
244
|
-
e.target.selectionStart,
|
|
245
|
-
e.target.selectionEnd
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
if (selectedString) {
|
|
249
|
-
this.#dispatch('jh-select', {
|
|
250
|
-
selected: selectedString,
|
|
251
|
-
selectionStart: e.target.selectionStart,
|
|
252
|
-
selectionEnd: e.target.selectionEnd,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
#handleMaxlength() {
|
|
258
|
-
this.#dispatch('jh-maxlength');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
#getDescribedby() {
|
|
262
|
-
let describedbyString = '';
|
|
263
|
-
|
|
264
|
-
if (this.errorText) {
|
|
265
|
-
describedbyString += `jh-input-error-${this.#id}`;
|
|
266
|
-
}
|
|
267
|
-
if (this.helperText) {
|
|
268
|
-
describedbyString += ` jh-input-helper-${this.#id}`;
|
|
269
|
-
}
|
|
270
|
-
if (this.showCharCount) {
|
|
271
|
-
describedbyString += ` jh-input-counter-${this.#id}`;
|
|
272
|
-
}
|
|
273
|
-
return describedbyString;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
render() {
|
|
277
|
-
let label;
|
|
278
|
-
let indicator;
|
|
279
|
-
let helperText;
|
|
280
|
-
let input;
|
|
281
|
-
let footer;
|
|
282
|
-
let errorText;
|
|
283
|
-
let charCount;
|
|
284
|
-
let describedby;
|
|
285
|
-
|
|
286
|
-
if (this.label) {
|
|
287
|
-
if (this.showIndicator) {
|
|
288
|
-
if (this.required) {
|
|
289
|
-
indicator = html`<span class="indicator" aria-hidden="true"> *</span>`;
|
|
290
|
-
} else {
|
|
291
|
-
indicator = html`<span class="indicator"> (optional)</span>`;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (this.helperText) {
|
|
296
|
-
helperText = html`
|
|
297
|
-
<p id="jh-input-helper-${this.#id}" class="helper-text">
|
|
298
|
-
${this.helperText}
|
|
299
|
-
</p>
|
|
300
|
-
`;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
label = html`
|
|
304
|
-
<label for="jh-input-${this.#id}">${this.label}${indicator}</label>
|
|
305
|
-
${helperText}
|
|
306
|
-
`;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (this.showCharCount) {
|
|
310
|
-
let valueLength = this.value ? this.value.length : 0;
|
|
311
|
-
let charCountValue = `${this.maxlength ? `${valueLength}/${this.maxlength}` : valueLength}`;
|
|
312
|
-
|
|
313
|
-
if (valueLength && valueLength === Number(this.maxlength)) {
|
|
314
|
-
this.#handleMaxlength();
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
charCount = html`
|
|
318
|
-
<p class="counter" aria-hidden="true">${charCountValue}</p>
|
|
319
|
-
`;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (this.invalid && this.errorText) {
|
|
323
|
-
errorText = html`
|
|
324
|
-
<p id="jh-input-error-${this.#id}" class="error-text">
|
|
325
|
-
${this.errorText}
|
|
326
|
-
</p>
|
|
327
|
-
`;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if ((this.invalid && this.errorText) || this.showCharCount) {
|
|
331
|
-
footer = html`
|
|
332
|
-
<div class="footer-content">
|
|
333
|
-
${errorText}
|
|
334
|
-
${charCount}
|
|
335
|
-
</div>
|
|
336
|
-
`;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (helperText || errorText || charCount) {
|
|
340
|
-
describedby = this.#getDescribedby();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
input = html`
|
|
344
|
-
<textarea
|
|
345
|
-
id="jh-input-${this.#id}"
|
|
346
|
-
aria-describedby=${describedby}
|
|
347
|
-
aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
|
|
348
|
-
aria-label=${ifDefined(
|
|
349
|
-
this.accessibleLabel === '' ? null : this.accessibleLabel
|
|
350
|
-
)}
|
|
351
|
-
autocomplete=${ifDefined(
|
|
352
|
-
this.autocomplete === '' ? null : this.autocomplete
|
|
353
|
-
)}
|
|
354
|
-
cols=${ifDefined(this.cols === '' ? null : this.cols)}
|
|
355
|
-
?disabled=${this.disabled}
|
|
356
|
-
enterkeyhint=${ifDefined(
|
|
357
|
-
this.enterkeyhint === '' ? null : this.enterkeyhint
|
|
358
|
-
)}
|
|
359
|
-
inputmode=${ifDefined(this.inputmode === '' ? null : this.inputmode)}
|
|
360
|
-
maxlength=${ifDefined(this.maxlength === '' ? null : this.maxlength)}
|
|
361
|
-
minlength=${ifDefined(this.minlength === '' ? null : this.minlength)}
|
|
362
|
-
name=${ifDefined(this.name === '' ? null : this.name)}
|
|
363
|
-
?readonly=${this.readonly}
|
|
364
|
-
?required=${this.required}
|
|
365
|
-
rows=${ifDefined(this.rows === '' ? null : this.rows)}
|
|
366
|
-
.value=${this.value}
|
|
367
|
-
wrap=${ifDefined(this.wrap === '' ? null : this.wrap)}
|
|
368
|
-
@change=${this.#handleChange}
|
|
369
|
-
@select=${this.#handleSelect}
|
|
370
|
-
@input=${this.#handleInput}
|
|
371
|
-
></textarea>
|
|
372
|
-
`;
|
|
373
|
-
|
|
244
|
+
renderInput() {
|
|
374
245
|
return html`
|
|
375
|
-
|
|
376
|
-
|
|
246
|
+
<textarea
|
|
247
|
+
id="jh-input-${this.#id}"
|
|
248
|
+
aria-describedby=${this._getDescribedby()}
|
|
249
|
+
aria-invalid=${ifDefined(this.invalid ? 'true' : null)}
|
|
250
|
+
aria-label=${ifDefined(this.accessibleLabel === '' ? null : this.accessibleLabel)}
|
|
251
|
+
autocomplete=${ifDefined(this.autocomplete === '' ? null : this.autocomplete)}
|
|
252
|
+
cols=${ifDefined(this.cols ? Number(this.cols) : null)}
|
|
253
|
+
?disabled=${this.disabled}
|
|
254
|
+
enterkeyhint=${ifDefined(this.enterkeyhint || null)}
|
|
255
|
+
inputmode=${ifDefined(this.inputmode || null)}
|
|
256
|
+
maxlength=${ifDefined(this.maxlength ? Number(this.maxlength) : null)}
|
|
257
|
+
minlength=${ifDefined(this.minlength ? Number(this.minlength) : null)}
|
|
258
|
+
name=${ifDefined(this.name || null)}
|
|
259
|
+
?readonly=${this.readonly}
|
|
260
|
+
?required=${this.required}
|
|
261
|
+
rows=${ifDefined(this.rows ? Number(this.rows) : null)}
|
|
262
|
+
.value=${this.value}
|
|
263
|
+
wrap=${ifDefined(this.wrap || null)}
|
|
264
|
+
@change=${this._handleChange}
|
|
265
|
+
@input=${this._handleInput}
|
|
266
|
+
@select=${this._handleSelect}
|
|
267
|
+
></textarea>
|
|
268
|
+
`
|
|
377
269
|
}
|
|
378
270
|
}
|
|
379
|
-
customElements.define('jh-input-textarea', JhInputTextarea);
|
|
271
|
+
customElements.define('jh-input-textarea', JhInputTextarea);
|
|
@@ -120,7 +120,10 @@ export class JhListItem extends LitElement {
|
|
|
120
120
|
border-left-width: var(--jh-border-selected-width);
|
|
121
121
|
}
|
|
122
122
|
:host([tabindex][selected]) .list-item {
|
|
123
|
-
padding-left: var(
|
|
123
|
+
padding-left: calc(var(
|
|
124
|
+
--jh-list-item-space-padding-left,
|
|
125
|
+
var(--jh-dimension-600)
|
|
126
|
+
) - var(--jh-border-selected-width));
|
|
124
127
|
}
|
|
125
128
|
:host([tabindex][disabled]) {
|
|
126
129
|
opacity: var(--jh-opacity-disabled);
|
|
@@ -136,7 +136,7 @@ export class JhNotification extends LitElement {
|
|
|
136
136
|
}
|
|
137
137
|
/* Default slot styling */
|
|
138
138
|
.display-default-slot {
|
|
139
|
-
padding: var(--jh-dimension-
|
|
139
|
+
padding: var(--jh-dimension-100) 0;
|
|
140
140
|
margin: var(--jh-dimension-50) 0;
|
|
141
141
|
font: var(--jh-font-body-regular-1);
|
|
142
142
|
display: flex;
|
|
@@ -149,7 +149,7 @@ export class JhNotification extends LitElement {
|
|
|
149
149
|
}
|
|
150
150
|
slot[name="jh-notification-icon"]::slotted(*) {
|
|
151
151
|
margin-right: var(--jh-dimension-400);
|
|
152
|
-
padding: var(--jh-dimension-
|
|
152
|
+
padding: var(--jh-dimension-100) 0;
|
|
153
153
|
}
|
|
154
154
|
:host([appearance='neutral']) slot[name="jh-notification-icon"] {
|
|
155
155
|
--jh-icon-color-fill: var(--jh-notification-icon-color-fill-neutral, var(--jh-color-content-on-primary-enabled));
|
|
@@ -204,7 +204,8 @@ export class JhNotification extends LitElement {
|
|
|
204
204
|
.stacked-container,
|
|
205
205
|
.inline-container {
|
|
206
206
|
display: flex;
|
|
207
|
-
align-items:
|
|
207
|
+
align-items: center;
|
|
208
|
+
line-height: 0;
|
|
208
209
|
}
|
|
209
210
|
`;
|
|
210
211
|
}
|
|
@@ -295,6 +296,13 @@ export class JhNotification extends LitElement {
|
|
|
295
296
|
}
|
|
296
297
|
}
|
|
297
298
|
|
|
299
|
+
#handleIconSlotChange(e) {
|
|
300
|
+
let newSlottedElement = e.target.assignedElements()[0];
|
|
301
|
+
if (newSlottedElement?.tagName.startsWith('JH-ICON')) {
|
|
302
|
+
newSlottedElement.setAttribute('size', 'medium');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
298
306
|
render() {
|
|
299
307
|
let dismissBtn;
|
|
300
308
|
|
|
@@ -305,8 +313,8 @@ export class JhNotification extends LitElement {
|
|
|
305
313
|
appearance="secondary"
|
|
306
314
|
accessible-label=${this.dismissButtonAccessibleLabel}
|
|
307
315
|
@click=${this.#handleDismissal}
|
|
308
|
-
><slot name="jh-notification-dismiss-icon" slot="jh-button-icon">
|
|
309
|
-
<jh-icon-xmark slot="jh-button-icon"></jh-icon-xmark>
|
|
316
|
+
><slot name="jh-notification-dismiss-icon" slot="jh-button-icon-left">
|
|
317
|
+
<jh-icon-xmark slot="jh-button-icon-left"></jh-icon-xmark>
|
|
310
318
|
</slot>
|
|
311
319
|
</jh-button>
|
|
312
320
|
`;
|
|
@@ -314,7 +322,7 @@ export class JhNotification extends LitElement {
|
|
|
314
322
|
|
|
315
323
|
return html`
|
|
316
324
|
<div class=${this.stacked ? 'stacked-container' : 'inline-container'}>
|
|
317
|
-
<slot aria-hidden="true" name="jh-notification-icon"></slot>
|
|
325
|
+
<slot aria-hidden="true" name="jh-notification-icon" @slotchange=${this.#handleIconSlotChange}></slot>
|
|
318
326
|
<slot @slotchange=${this.#handleSlotChange}></slot>
|
|
319
327
|
${this.stacked ? null : this.#getActionButtons()}
|
|
320
328
|
${dismissBtn}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Jack Henry
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// TODO for tagged multi-select we will need to filter out already selected items from the list of options.
|
|
8
|
+
|
|
9
|
+
// returns a filtered list of terms that include the search term in the specified key (default is 'label')
|
|
10
|
+
export const JhFilter = {
|
|
11
|
+
filterList(items, searchTerm, key = 'label') {
|
|
12
|
+
if (!items || items.length === 0) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
if (!searchTerm) {
|
|
16
|
+
return items;
|
|
17
|
+
}
|
|
18
|
+
const lowerSearchTerm = searchTerm.toLowerCase();
|
|
19
|
+
return items.filter((item) =>
|
|
20
|
+
String(item[key] || '')
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.includes(lowerSearchTerm),
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// returns the first term that includes the search term in the specified key (default is 'label'), or null if not found.
|
|
27
|
+
// Skip disabled items in the inline suggestions as they are not selectable.
|
|
28
|
+
filterInline(items, searchTerm, key = 'label') {
|
|
29
|
+
if (!searchTerm || !items || items.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const lowerSearchTerm = searchTerm.toLowerCase();
|
|
33
|
+
return (
|
|
34
|
+
items.find(
|
|
35
|
+
(item) =>
|
|
36
|
+
!item.disabled &&
|
|
37
|
+
String(item[key] || '')
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.startsWith(lowerSearchTerm),
|
|
40
|
+
) || null
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
//returns the index of the first term that starts with the buffer, or -1 if not found
|
|
44
|
+
// exclude disabled items from the search as they are not selectable.
|
|
45
|
+
// when it is a multi-select, return selected items as well, so users can deselect easily.
|
|
46
|
+
|
|
47
|
+
jumpAhead(items, buffer, activeIndex, key = 'label') {
|
|
48
|
+
if (!buffer || !items || items.length === 0) {
|
|
49
|
+
return -1;
|
|
50
|
+
}
|
|
51
|
+
// more than one character that are same.
|
|
52
|
+
const allCharsSame = (str) => {
|
|
53
|
+
if (str.length <= 1) return false;
|
|
54
|
+
return str.split('').every((char) => char === str[0]);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const safeActiveIndex = activeIndex ?? -1;
|
|
58
|
+
const lowerBuffer = buffer.toLowerCase();
|
|
59
|
+
const char = lowerBuffer[0];
|
|
60
|
+
const isCycling = allCharsSame(lowerBuffer);
|
|
61
|
+
|
|
62
|
+
if (isCycling) {
|
|
63
|
+
// Find the next item that starts with the character after the active index
|
|
64
|
+
const nextIndex = items.findIndex(
|
|
65
|
+
(item, index) =>
|
|
66
|
+
index > safeActiveIndex &&
|
|
67
|
+
!item.disabled &&
|
|
68
|
+
String(item[key] || '')
|
|
69
|
+
.toLowerCase()
|
|
70
|
+
.startsWith(char),
|
|
71
|
+
);
|
|
72
|
+
if (nextIndex !== -1) {
|
|
73
|
+
return nextIndex;
|
|
74
|
+
}
|
|
75
|
+
// If not found, wrap around and search from the beginning up to the active index
|
|
76
|
+
return items.findIndex(
|
|
77
|
+
(item) =>
|
|
78
|
+
!item.disabled &&
|
|
79
|
+
String(item[key] || '')
|
|
80
|
+
.toLowerCase()
|
|
81
|
+
.startsWith(char),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return items.findIndex(
|
|
85
|
+
(item) =>
|
|
86
|
+
!item.disabled &&
|
|
87
|
+
String(item[key] || '')
|
|
88
|
+
.toLowerCase()
|
|
89
|
+
.startsWith(lowerBuffer),
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
};
|