@jack-henry/jh-elements 2.0.0-beta.10
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 +343 -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 +1247 -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 +379 -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 +315 -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 +6892 -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,18 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Jack Henry
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { JhInput } from '../input/input.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Input Email
|
|
9
|
+
* @customElement jh-input-email
|
|
10
|
+
*/
|
|
11
|
+
export class JhInputEmail extends JhInput {
|
|
12
|
+
firstUpdated() {
|
|
13
|
+
super.firstUpdated();
|
|
14
|
+
let inputEl = this.shadowRoot.querySelector('input');
|
|
15
|
+
inputEl.setAttribute('type', 'email');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
customElements.define('jh-input-email', JhInputEmail);
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Jack Henry
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { html, render, css } from 'lit';
|
|
6
|
+
import { JhInput } from '../input/input.js';
|
|
7
|
+
import '@jack-henry/jh-icons/icons-wc/icon-eye-slash.js';
|
|
8
|
+
import '@jack-henry/jh-icons/icons-wc/icon-eye.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @slot jh-input-password-hidden - Use to insert a custom icon within the toggle password button when the input value is masked.
|
|
12
|
+
* @slot jh-input-password-visible - Use to insert a custom icon within the toggle password button when the input value is unmasked.
|
|
13
|
+
* @customElement jh-input-password
|
|
14
|
+
*/
|
|
15
|
+
export class JhInputPassword extends JhInput {
|
|
16
|
+
#inputEl;
|
|
17
|
+
|
|
18
|
+
static get styles() {
|
|
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
|
+
}
|
|
47
|
+
|
|
48
|
+
static get properties() {
|
|
49
|
+
return {
|
|
50
|
+
/** Unmasks the input field value when set. */
|
|
51
|
+
passwordVisible: { type: Boolean, attribute: 'password-visible', reflect: true},
|
|
52
|
+
/** Sets an `aria-label` on the toggle password button, which encapsulates the `jh-input-password-visible` slot, to assist screen reader users. The label should indicate that activating the button will mask the password. */
|
|
53
|
+
accessibleLabelHidePassword: {
|
|
54
|
+
type: String,
|
|
55
|
+
attribute: 'accessible-label-hide-password',
|
|
56
|
+
},
|
|
57
|
+
/** Sets an `aria-label` on the toggle password button, which encapsulates the `jh-input-password-hidden` slot, to assist screen reader users. The label should indicate that activating the button will unmask the password. */
|
|
58
|
+
accessibleLabelShowPassword: {
|
|
59
|
+
type: String,
|
|
60
|
+
attribute: 'accessible-label-show-password',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
constructor() {
|
|
66
|
+
super();
|
|
67
|
+
/** @type {?string} */
|
|
68
|
+
this.accessibleLabelHidePassword = null;
|
|
69
|
+
/** @type {?string} */
|
|
70
|
+
this.accessibleLabelShowPassword = null;
|
|
71
|
+
/** @type {boolean} */
|
|
72
|
+
this.hideRightSlot = true;
|
|
73
|
+
/** @type {boolean} */
|
|
74
|
+
this.passwordVisible = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
firstUpdated() {
|
|
78
|
+
super.firstUpdated();
|
|
79
|
+
this.#inputEl = this.shadowRoot.querySelector('input');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
updated(changedProperties) {
|
|
83
|
+
super.updated(changedProperties);
|
|
84
|
+
|
|
85
|
+
if (changedProperties.has('passwordVisible')) {
|
|
86
|
+
if (this.passwordVisible) {
|
|
87
|
+
this.#inputEl.setAttribute('type', 'text');
|
|
88
|
+
} else {
|
|
89
|
+
this.#inputEl.setAttribute('type', 'password');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
this.#insertTogglePasswordBtn();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#insertTogglePasswordBtn() {
|
|
96
|
+
let inputContainerEl = this.shadowRoot.querySelector('.input-container');
|
|
97
|
+
let togglePasswordButton = this.#createTogglePasswordBtn();
|
|
98
|
+
render(togglePasswordButton, inputContainerEl);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#createTogglePasswordBtn() {
|
|
102
|
+
if (this.readonly) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let accessibleLabel = this.passwordVisible
|
|
107
|
+
? this.accessibleLabelHidePassword
|
|
108
|
+
: this.accessibleLabelShowPassword;
|
|
109
|
+
|
|
110
|
+
let passwordBtn = html`
|
|
111
|
+
<jh-button
|
|
112
|
+
class="password-toggle-btn"
|
|
113
|
+
size="small"
|
|
114
|
+
appearance="tertiary"
|
|
115
|
+
?disabled=${this.disabled}
|
|
116
|
+
accessible-label=${accessibleLabel}
|
|
117
|
+
@click=${this.#togglePassword.bind(this)}
|
|
118
|
+
>
|
|
119
|
+
${this.passwordVisible
|
|
120
|
+
? html`
|
|
121
|
+
<slot
|
|
122
|
+
name="jh-input-password-visible"
|
|
123
|
+
slot="jh-button-icon"
|
|
124
|
+
>
|
|
125
|
+
<jh-icon-eye-slash
|
|
126
|
+
slot="jh-button-icon"
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
size="medium"
|
|
129
|
+
></jh-icon-eye-slash>
|
|
130
|
+
</slot>
|
|
131
|
+
`
|
|
132
|
+
: html`
|
|
133
|
+
<slot
|
|
134
|
+
name="jh-input-password-hidden"
|
|
135
|
+
slot="jh-button-icon"
|
|
136
|
+
>
|
|
137
|
+
<jh-icon-eye
|
|
138
|
+
slot="jh-button-icon"
|
|
139
|
+
aria-hidden="true"
|
|
140
|
+
size="medium"
|
|
141
|
+
></jh-icon-eye>
|
|
142
|
+
</slot>
|
|
143
|
+
`}
|
|
144
|
+
</jh-button>
|
|
145
|
+
`;
|
|
146
|
+
return passwordBtn;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#togglePassword() {
|
|
150
|
+
this.passwordVisible = !this.passwordVisible;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
customElements.define('jh-input-password', JhInputPassword);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Jack Henry
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { css } from 'lit';
|
|
6
|
+
import { JhInput } from '../input/input.js';
|
|
7
|
+
import '@jack-henry/jh-icons/icons-wc/icon-magnifying-glass.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Input Search
|
|
11
|
+
* @customElement jh-input-search
|
|
12
|
+
*/
|
|
13
|
+
export class JhInputSearch extends JhInput {
|
|
14
|
+
static get styles() {
|
|
15
|
+
return [
|
|
16
|
+
super.styles,
|
|
17
|
+
css`
|
|
18
|
+
/* removes native clear search button */
|
|
19
|
+
input::-webkit-search-cancel-button {
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
`,
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
firstUpdated() {
|
|
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
|
+
|
|
36
|
+
// set input type to search
|
|
37
|
+
let inputEl = this.shadowRoot.querySelector('input');
|
|
38
|
+
inputEl.setAttribute('type', 'search');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
customElements.define('jh-input-search', JhInputSearch);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Jack Henry
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { JhInput } from '../input/input.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Input Telephone
|
|
9
|
+
* @customElement jh-input-telephone
|
|
10
|
+
*/
|
|
11
|
+
export class JhInputTelephone extends JhInput {
|
|
12
|
+
firstUpdated() {
|
|
13
|
+
super.firstUpdated();
|
|
14
|
+
let inputEl = this.shadowRoot.querySelector('input');
|
|
15
|
+
inputEl.setAttribute('type', 'tel');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
customElements.define('jh-input-telephone', JhInputTelephone);
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Jack Henry
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { css, html } from 'lit';
|
|
6
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
7
|
+
import { JhInput } from '../input/input.js';
|
|
8
|
+
|
|
9
|
+
let id = 0;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @cssprop --jh-input-textarea-field-dimension-min-height - The input field minimum height. Defaults to `--jh-dimension-2000` when `size='small'`, `--jh-dimension-2200` when `size='medium'`, and `--jh-dimension-2400` when `size='large'`.
|
|
13
|
+
*
|
|
14
|
+
* @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`.
|
|
15
|
+
* @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`.
|
|
16
|
+
* @customElement jh-input-textarea
|
|
17
|
+
*/
|
|
18
|
+
export class JhInputTextarea extends JhInput {
|
|
19
|
+
/** @type {?number} */
|
|
20
|
+
#id;
|
|
21
|
+
|
|
22
|
+
static get styles() {
|
|
23
|
+
return [
|
|
24
|
+
super.styles,
|
|
25
|
+
css`
|
|
26
|
+
:host {
|
|
27
|
+
--textarea-small-padding: 10px var(--jh-dimension-400) 10px var(--jh-dimension-400);
|
|
28
|
+
--textarea-medium-padding: 14px var(--jh-dimension-400) 14px var(--jh-dimension-400);
|
|
29
|
+
--textarea-large-padding: 18px var(--jh-dimension-400) 18px var(--jh-dimension-400);
|
|
30
|
+
}
|
|
31
|
+
textarea {
|
|
32
|
+
background-color: var(
|
|
33
|
+
--jh-input-field-color-background,
|
|
34
|
+
var(--jh-color-container-primary-enabled)
|
|
35
|
+
);
|
|
36
|
+
border-radius: var(
|
|
37
|
+
--jh-input-field-border-radius,
|
|
38
|
+
var(--jh-border-radius-100)
|
|
39
|
+
);
|
|
40
|
+
border-width: var(--jh-border-control-width);
|
|
41
|
+
border-style: var(--jh-border-control-style);
|
|
42
|
+
border-color: var(
|
|
43
|
+
--jh-input-field-color-border-enabled,
|
|
44
|
+
var(--jh-border-control-color)
|
|
45
|
+
);
|
|
46
|
+
color: var(--jh-input-value-color-text, var(--jh-color-content-primary-enabled));
|
|
47
|
+
font-family: var(--jh-font-body-regular-1-font-family);
|
|
48
|
+
font-weight: var(--jh-font-body-regular-1-font-weight);
|
|
49
|
+
font-size: var(--jh-font-body-regular-1-font-size);
|
|
50
|
+
line-height: var(--jh-font-body-regular-1-line-height);
|
|
51
|
+
box-sizing: border-box;
|
|
52
|
+
display: block;
|
|
53
|
+
}
|
|
54
|
+
:host([label]) textarea {
|
|
55
|
+
margin-top: var(--jh-dimension-200);
|
|
56
|
+
}
|
|
57
|
+
:host(:not([cols])) textarea {
|
|
58
|
+
min-width: 100%;
|
|
59
|
+
}
|
|
60
|
+
:host([rows]) textarea {
|
|
61
|
+
min-height: 0;
|
|
62
|
+
}
|
|
63
|
+
/** sizes */
|
|
64
|
+
:host([size='small']) textarea {
|
|
65
|
+
min-height: var(
|
|
66
|
+
--jh-input-textarea-field-dimension-min-height,
|
|
67
|
+
var(--jh-dimension-2000)
|
|
68
|
+
);
|
|
69
|
+
padding: var(--textarea-small-padding);
|
|
70
|
+
scroll-padding: var(--textarea-small-padding);
|
|
71
|
+
}
|
|
72
|
+
:host([size='medium']) textarea {
|
|
73
|
+
min-height: var(
|
|
74
|
+
--jh-input-textarea-field-dimension-min-height,
|
|
75
|
+
var(--jh-dimension-2200)
|
|
76
|
+
);
|
|
77
|
+
padding: var(--textarea-medium-padding);
|
|
78
|
+
scroll-padding: var(--textarea-medium-padding);
|
|
79
|
+
}
|
|
80
|
+
:host([size='large']) textarea {
|
|
81
|
+
min-height: var(
|
|
82
|
+
--jh-input-textarea-field-dimension-min-height,
|
|
83
|
+
var(--jh-dimension-2400)
|
|
84
|
+
);
|
|
85
|
+
padding: var(--textarea-large-padding);
|
|
86
|
+
scroll-padding: var(--textarea-large-padding);
|
|
87
|
+
}
|
|
88
|
+
/** textarea states */
|
|
89
|
+
textarea:active {
|
|
90
|
+
border-color: var(
|
|
91
|
+
--jh-input-field-color-border-active,
|
|
92
|
+
var(--jh-color-content-brand-active)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
textarea:hover {
|
|
96
|
+
border-color: var(
|
|
97
|
+
--jh-input-field-color-border-hover,
|
|
98
|
+
var(--jh-color-content-brand-hover)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
textarea:focus-visible {
|
|
102
|
+
border-color: var(
|
|
103
|
+
--jh-input-field-color-border-focus,
|
|
104
|
+
var(--jh-color-content-brand-hover)
|
|
105
|
+
);
|
|
106
|
+
outline-color: var(
|
|
107
|
+
--jh-input-color-focus,
|
|
108
|
+
var(--jh-border-focus-color)
|
|
109
|
+
);
|
|
110
|
+
outline-style: var(--jh-border-focus-style);
|
|
111
|
+
outline-width: var(--jh-border-focus-width);
|
|
112
|
+
outline-offset: 1px;
|
|
113
|
+
}
|
|
114
|
+
:host([disabled]) textarea {
|
|
115
|
+
border-color: var(
|
|
116
|
+
--jh-input-field-color-border-disabled,
|
|
117
|
+
var(--jh-border-control-color)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
:host([readonly]) textarea {
|
|
121
|
+
height: auto;
|
|
122
|
+
border: none;
|
|
123
|
+
padding-left: 0;
|
|
124
|
+
padding-right: 0;
|
|
125
|
+
}
|
|
126
|
+
:host([invalid]) textarea {
|
|
127
|
+
border-color: var(
|
|
128
|
+
--jh-input-field-color-border-error,
|
|
129
|
+
var(--jh-border-error-color)
|
|
130
|
+
);
|
|
131
|
+
border-width: var(--jh-border-error-width);
|
|
132
|
+
border-style: var(--jh-border-error-style);
|
|
133
|
+
}
|
|
134
|
+
/* resize and auto-grow */
|
|
135
|
+
:host([readonly]) textarea,
|
|
136
|
+
:host([no-resize]) textarea {
|
|
137
|
+
resize: none;
|
|
138
|
+
height: 100%;
|
|
139
|
+
}
|
|
140
|
+
:host([auto-grow]) textarea {
|
|
141
|
+
height: auto;
|
|
142
|
+
scrollbar-width: none;
|
|
143
|
+
}
|
|
144
|
+
`,
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static get properties() {
|
|
149
|
+
return {
|
|
150
|
+
/** Enables the input height to grow automatically to accommodate user input. `auto-grow` will also remove the input's native resize capability. */
|
|
151
|
+
autoGrow: {
|
|
152
|
+
type: Boolean,
|
|
153
|
+
attribute: 'auto-grow',
|
|
154
|
+
},
|
|
155
|
+
/** Sets the width of the input field. */
|
|
156
|
+
cols: { type: String },
|
|
157
|
+
/** Removes native resize capability of the input field. */
|
|
158
|
+
noResize: {
|
|
159
|
+
type: Boolean,
|
|
160
|
+
attribute: 'no-resize',
|
|
161
|
+
},
|
|
162
|
+
/** Sets the height of the input field. */
|
|
163
|
+
rows: { type: String },
|
|
164
|
+
/** Specifies how text should be wrapped when submitted in a form. The `cols` property must be set for `wrap='hard'` to take effect. */
|
|
165
|
+
wrap: { type: String },
|
|
166
|
+
/** Prevents users from changing the input value. */
|
|
167
|
+
readonly: { type: Boolean },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
constructor() {
|
|
172
|
+
super();
|
|
173
|
+
/** @type {boolean} */
|
|
174
|
+
this.autoGrow = null;
|
|
175
|
+
/** @type {?string} */
|
|
176
|
+
this.cols = null;
|
|
177
|
+
/** @type {boolean} */
|
|
178
|
+
this.noResize = true;
|
|
179
|
+
/** @type {?string} */
|
|
180
|
+
this.rows = null;
|
|
181
|
+
/** @type {'small'|'medium'|'large'} */
|
|
182
|
+
this.size = 'medium';
|
|
183
|
+
/** @type {'hard'|'soft'} */
|
|
184
|
+
this.wrap = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
connectedCallback() {
|
|
188
|
+
super.connectedCallback();
|
|
189
|
+
this.#id = id++;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
firstUpdated() {
|
|
193
|
+
// add resize observer to update width of footer when textarea width changes
|
|
194
|
+
let footerContent = this.shadowRoot.querySelector('.footer-content');
|
|
195
|
+
let textareaEl = this.shadowRoot.querySelector('textarea');
|
|
196
|
+
|
|
197
|
+
if (footerContent) {
|
|
198
|
+
new ResizeObserver(() => {
|
|
199
|
+
this.#updateFooterWidth(footerContent, textareaEl);
|
|
200
|
+
}).observe(textareaEl);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#updateFooterWidth(footerContent, textareaEl) {
|
|
205
|
+
footerContent.style.width = textareaEl.offsetWidth + 'px';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#dispatch(eventName, details) {
|
|
209
|
+
this.dispatchEvent(
|
|
210
|
+
new CustomEvent(eventName, {
|
|
211
|
+
detail: details,
|
|
212
|
+
bubbles: true,
|
|
213
|
+
cancelable: true,
|
|
214
|
+
composed: true,
|
|
215
|
+
})
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#handleInput(e) {
|
|
220
|
+
this.value = e.target.value;
|
|
221
|
+
|
|
222
|
+
this.#dispatch('jh-input', { value: this.value } );
|
|
223
|
+
|
|
224
|
+
if (this.autoGrow) {
|
|
225
|
+
this.#autoGrowTextarea();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
#autoGrowTextarea() {
|
|
230
|
+
let textareaEl = this.shadowRoot.querySelector('textarea');
|
|
231
|
+
textareaEl.style.height = 'auto';
|
|
232
|
+
textareaEl.style.height = textareaEl.scrollHeight + 'px';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#handleChange() {
|
|
236
|
+
let payload = {
|
|
237
|
+
'value': this.value,
|
|
238
|
+
};
|
|
239
|
+
this.#dispatch('jh-change', payload);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#handleSelect(e) {
|
|
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
|
+
|
|
374
|
+
return html`
|
|
375
|
+
${label} ${input} ${footer}
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
customElements.define('jh-input-textarea', JhInputTextarea);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Jack Henry
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { css } from 'lit';
|
|
6
|
+
import { JhInput } from '../input/input.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Input Url
|
|
10
|
+
* @customElement jh-input-url
|
|
11
|
+
*/
|
|
12
|
+
export class JhInputUrl extends JhInput {
|
|
13
|
+
static get styles() {
|
|
14
|
+
return [
|
|
15
|
+
super.styles,
|
|
16
|
+
css`
|
|
17
|
+
/* removes safari autofill button */
|
|
18
|
+
input::-webkit-textfield-decoration-container {
|
|
19
|
+
display: none;
|
|
20
|
+
}
|
|
21
|
+
`,
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
firstUpdated() {
|
|
26
|
+
super.firstUpdated();
|
|
27
|
+
let inputEl = this.shadowRoot.querySelector('input');
|
|
28
|
+
inputEl.setAttribute('type', 'url');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
customElements.define('jh-input-url', JhInputUrl);
|