@noctuatech/uswds 1.3.0 → 1.3.2
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/package.json +56 -21
- package/src/lib/accordion/accordion.element.ts +18 -26
- package/src/lib/alert/alert.element.ts +21 -20
- package/src/lib/alert/alert.stories.ts +10 -15
- package/src/lib/file-input/file-input-preview/file-input-preview.element.ts +17 -14
- package/src/lib/file-input/file-input.element.ts +56 -61
- package/src/lib/icon/icon.element.ts +37 -27
- package/src/lib/input/input.element.ts +30 -39
- package/src/lib/input/input.test.ts +14 -16
- package/src/lib/input-mask/input-mask.element.ts +13 -13
- package/src/lib/input-mask/input-mask.stories.ts +7 -12
- package/src/lib/input-mask/input-mask.test.ts +45 -50
- package/src/lib/services/icon.service.test.ts +14 -14
- package/src/lib/services/icon.service.ts +18 -17
- package/src/lib/textarea/textarea.element.ts +14 -18
- package/target/lib/accordion/accordion.element.d.ts +4 -3
- package/target/lib/accordion/accordion.element.js +20 -12
- package/target/lib/accordion/accordion.element.js.map +1 -1
- package/target/lib/alert/alert.element.d.ts +4 -4
- package/target/lib/alert/alert.element.js +20 -12
- package/target/lib/alert/alert.element.js.map +1 -1
- package/target/lib/alert/alert.stories.d.ts +2 -2
- package/target/lib/alert/alert.stories.js +3 -3
- package/target/lib/alert/alert.stories.js.map +1 -1
- package/target/lib/file-input/file-input-preview/file-input-preview.element.d.ts +2 -2
- package/target/lib/file-input/file-input-preview/file-input-preview.element.js +8 -8
- package/target/lib/file-input/file-input-preview/file-input-preview.element.js.map +1 -1
- package/target/lib/file-input/file-input.element.d.ts +2 -3
- package/target/lib/file-input/file-input.element.js +23 -31
- package/target/lib/file-input/file-input.element.js.map +1 -1
- package/target/lib/icon/icon.element.d.ts +4 -4
- package/target/lib/icon/icon.element.js +35 -25
- package/target/lib/icon/icon.element.js.map +1 -1
- package/target/lib/input/input.element.d.ts +5 -5
- package/target/lib/input/input.element.js +20 -22
- package/target/lib/input/input.element.js.map +1 -1
- package/target/lib/input/input.test.d.ts +1 -1
- package/target/lib/input/input.test.js +13 -13
- package/target/lib/input/input.test.js.map +1 -1
- package/target/lib/input-mask/input-mask.element.d.ts +1 -1
- package/target/lib/input-mask/input-mask.element.js +11 -11
- package/target/lib/input-mask/input-mask.element.js.map +1 -1
- package/target/lib/input-mask/input-mask.stories.d.ts +2 -2
- package/target/lib/input-mask/input-mask.stories.js +5 -5
- package/target/lib/input-mask/input-mask.stories.js.map +1 -1
- package/target/lib/input-mask/input-mask.test.d.ts +2 -2
- package/target/lib/input-mask/input-mask.test.js +43 -43
- package/target/lib/input-mask/input-mask.test.js.map +1 -1
- package/target/lib/services/icon.service.d.ts +2 -1
- package/target/lib/services/icon.service.js +15 -15
- package/target/lib/services/icon.service.js.map +1 -1
- package/target/lib/services/icon.service.test.js +14 -14
- package/target/lib/services/icon.service.test.js.map +1 -1
- package/target/lib/textarea/textarea.element.d.ts +1 -1
- package/target/lib/textarea/textarea.element.js +10 -10
- package/target/lib/textarea/textarea.element.js.map +1 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import '@joist/templating/define.js';
|
|
2
2
|
|
|
3
|
-
import { attr, css, element, html, listen, query } from
|
|
4
|
-
import { effect } from
|
|
5
|
-
import { bind } from
|
|
3
|
+
import { attr, css, element, html, listen, query } from '@joist/element';
|
|
4
|
+
import { effect } from '@joist/observable';
|
|
5
|
+
import { bind } from '@joist/templating';
|
|
6
6
|
|
|
7
7
|
declare global {
|
|
8
8
|
interface HTMLElementTagNameMap {
|
|
9
|
-
|
|
9
|
+
'usa-file-input': USAFileInputElement;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
@element({
|
|
14
|
-
tagName:
|
|
14
|
+
tagName: 'usa-file-input',
|
|
15
15
|
shadowDom: [
|
|
16
16
|
css`
|
|
17
17
|
* {
|
|
@@ -77,31 +77,39 @@ declare global {
|
|
|
77
77
|
}
|
|
78
78
|
`,
|
|
79
79
|
html`
|
|
80
|
-
<label>
|
|
80
|
+
<label for="file-input">
|
|
81
81
|
<slot class="label"></slot>
|
|
82
|
-
|
|
83
|
-
<div class="container">
|
|
84
|
-
<input type="file" tabindex="0"/>
|
|
85
|
-
|
|
86
|
-
<j-if bind="filesVisible">
|
|
87
|
-
<template>
|
|
88
|
-
<j-props>
|
|
89
|
-
<usa-file-input-preview $.files="files" part="preview">
|
|
90
|
-
Selected file <usa-link>Change file</usa-link>
|
|
91
|
-
</usa-file-input-preview>
|
|
92
|
-
</j-props>
|
|
93
|
-
</template>
|
|
94
|
-
|
|
95
|
-
<template else>
|
|
96
|
-
<div class="box" part="input">
|
|
97
|
-
<slot name="description">
|
|
98
|
-
Drag file here or <usa-link>choose from folder</usa-link>
|
|
99
|
-
</slot>
|
|
100
|
-
</div>
|
|
101
|
-
</template>
|
|
102
|
-
</j-if>
|
|
103
|
-
</div>
|
|
104
82
|
</label>
|
|
83
|
+
|
|
84
|
+
<div class="container">
|
|
85
|
+
<j-props>
|
|
86
|
+
<input
|
|
87
|
+
id="file-input"
|
|
88
|
+
type="file"
|
|
89
|
+
tabindex="0"
|
|
90
|
+
$.name="name"
|
|
91
|
+
$.multiple="multiple"
|
|
92
|
+
$.accept="accept"
|
|
93
|
+
$.required="required"
|
|
94
|
+
/>
|
|
95
|
+
</j-props>
|
|
96
|
+
|
|
97
|
+
<j-if bind="filesVisible">
|
|
98
|
+
<template>
|
|
99
|
+
<j-props>
|
|
100
|
+
<usa-file-input-preview $.files="files" part="preview" exportparts="heading, item">
|
|
101
|
+
Selected file <usa-link>Change file</usa-link>
|
|
102
|
+
</usa-file-input-preview>
|
|
103
|
+
</j-props>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<template else>
|
|
107
|
+
<div class="box" part="input">
|
|
108
|
+
<slot name="description"> Drag file here or <usa-link>choose from folder</usa-link> </slot>
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
111
|
+
</j-if>
|
|
112
|
+
</div>
|
|
105
113
|
`,
|
|
106
114
|
],
|
|
107
115
|
})
|
|
@@ -109,15 +117,19 @@ export class USAFileInputElement extends HTMLElement {
|
|
|
109
117
|
static formAssociated = true;
|
|
110
118
|
|
|
111
119
|
@attr()
|
|
112
|
-
|
|
120
|
+
@bind()
|
|
121
|
+
accessor name = '';
|
|
113
122
|
|
|
114
123
|
@attr()
|
|
124
|
+
@bind()
|
|
115
125
|
accessor multiple = true;
|
|
116
126
|
|
|
117
127
|
@attr()
|
|
118
|
-
|
|
128
|
+
@bind()
|
|
129
|
+
accessor accept = '';
|
|
119
130
|
|
|
120
131
|
@attr()
|
|
132
|
+
@bind()
|
|
121
133
|
accessor required = false;
|
|
122
134
|
|
|
123
135
|
@bind()
|
|
@@ -127,32 +139,19 @@ export class USAFileInputElement extends HTMLElement {
|
|
|
127
139
|
accessor filesVisible = false;
|
|
128
140
|
|
|
129
141
|
#internals = this.attachInternals();
|
|
130
|
-
#input = query(
|
|
131
|
-
|
|
132
|
-
attributeChangedCallback() {
|
|
133
|
-
this.#input({
|
|
134
|
-
name: this.name,
|
|
135
|
-
multiple: this.multiple,
|
|
136
|
-
accept: this.accept,
|
|
137
|
-
required: this.required,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
142
|
+
#input = query('input');
|
|
140
143
|
|
|
141
144
|
connectedCallback() {
|
|
142
145
|
const input = this.#input();
|
|
143
146
|
|
|
144
147
|
if (input.validationMessage) {
|
|
145
|
-
this.#internals.setValidity(
|
|
146
|
-
{ customError: true },
|
|
147
|
-
input.validationMessage,
|
|
148
|
-
input,
|
|
149
|
-
);
|
|
148
|
+
this.#internals.setValidity({ customError: true }, input.validationMessage, input);
|
|
150
149
|
}
|
|
151
150
|
}
|
|
152
151
|
|
|
153
152
|
@effect()
|
|
154
153
|
syncFormValues() {
|
|
155
|
-
const input = this.#input(
|
|
154
|
+
const input = this.#input();
|
|
156
155
|
|
|
157
156
|
const formData = new FormData();
|
|
158
157
|
|
|
@@ -165,38 +164,34 @@ export class USAFileInputElement extends HTMLElement {
|
|
|
165
164
|
this.#internals.setFormValue(formData);
|
|
166
165
|
|
|
167
166
|
if (input.validationMessage) {
|
|
168
|
-
this.#internals.setValidity(
|
|
169
|
-
{ customError: true },
|
|
170
|
-
input.validationMessage,
|
|
171
|
-
input,
|
|
172
|
-
);
|
|
167
|
+
this.#internals.setValidity({ customError: true }, input.validationMessage, input);
|
|
173
168
|
} else {
|
|
174
169
|
this.#internals.setValidity({});
|
|
175
170
|
}
|
|
176
171
|
}
|
|
177
172
|
|
|
178
|
-
@listen(
|
|
173
|
+
@listen('change')
|
|
179
174
|
onInputChange() {
|
|
180
175
|
const input = this.#input();
|
|
181
176
|
|
|
182
177
|
this.files = input.files;
|
|
183
178
|
|
|
184
|
-
this.dispatchEvent(new Event(
|
|
179
|
+
this.dispatchEvent(new Event('change'));
|
|
185
180
|
}
|
|
186
181
|
|
|
187
|
-
@listen(
|
|
182
|
+
@listen('dragenter')
|
|
188
183
|
onDragEnter() {
|
|
189
|
-
this.classList.add(
|
|
184
|
+
this.classList.add('dragenter');
|
|
190
185
|
}
|
|
191
186
|
|
|
192
|
-
@listen(
|
|
187
|
+
@listen('dragleave')
|
|
193
188
|
onDragLeave() {
|
|
194
|
-
this.classList.remove(
|
|
189
|
+
this.classList.remove('dragenter');
|
|
195
190
|
}
|
|
196
191
|
|
|
197
|
-
@listen(
|
|
192
|
+
@listen('drop')
|
|
198
193
|
onDrop(e: DragEvent) {
|
|
199
|
-
this.classList.remove(
|
|
194
|
+
this.classList.remove('dragenter');
|
|
200
195
|
|
|
201
196
|
if (e.dataTransfer?.items) {
|
|
202
197
|
e.preventDefault();
|
|
@@ -204,7 +199,7 @@ export class USAFileInputElement extends HTMLElement {
|
|
|
204
199
|
const data = new DataTransfer();
|
|
205
200
|
|
|
206
201
|
for (const item of e.dataTransfer.items) {
|
|
207
|
-
if (item.kind ===
|
|
202
|
+
if (item.kind === 'file') {
|
|
208
203
|
const file = item.getAsFile();
|
|
209
204
|
|
|
210
205
|
if (file) {
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { inject, injectable } from
|
|
2
|
-
import { attr, css, element } from
|
|
1
|
+
import { inject, injectable, injected } from '@joist/di';
|
|
2
|
+
import { attr, css, element } from '@joist/element';
|
|
3
3
|
|
|
4
|
-
import { IconService } from
|
|
5
|
-
import type { USAIcon } from
|
|
4
|
+
import { IconService } from '../services/icon.service.js';
|
|
5
|
+
import type { USAIcon } from './icon-types.js';
|
|
6
|
+
import { effect, observe } from '@joist/observable';
|
|
6
7
|
|
|
7
8
|
declare global {
|
|
8
9
|
interface HTMLElementTagNameMap {
|
|
9
|
-
|
|
10
|
+
'usa-icon': USAIconElement;
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
@element({
|
|
14
|
-
tagName:
|
|
15
|
+
tagName: 'usa-icon',
|
|
15
16
|
shadowDom: [
|
|
16
17
|
css`
|
|
17
18
|
:host {
|
|
@@ -30,41 +31,50 @@ declare global {
|
|
|
30
31
|
],
|
|
31
32
|
})
|
|
32
33
|
@injectable({
|
|
33
|
-
name:
|
|
34
|
+
name: 'usa-icon-ctx',
|
|
34
35
|
})
|
|
35
36
|
export class USAIconElement extends HTMLElement {
|
|
36
37
|
@attr()
|
|
37
|
-
|
|
38
|
+
@observe()
|
|
39
|
+
accessor icon: USAIcon | '' = '';
|
|
38
40
|
|
|
39
|
-
ariaHidden: string | null =
|
|
41
|
+
ariaHidden: string | null = 'true';
|
|
40
42
|
|
|
41
43
|
#icon = inject(IconService);
|
|
42
|
-
#
|
|
44
|
+
#abortController: AbortController | null = null;
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (newVal !== oldVal) {
|
|
47
|
-
this.#updateIcon();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
46
|
+
connectedCallback() {
|
|
47
|
+
this.#updateIcon();
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
@effect()
|
|
51
|
+
onIconUpdate() {
|
|
52
|
+
console.log('onIconUpdate', this.icon);
|
|
53
|
+
|
|
54
54
|
this.#updateIcon();
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
async #updateIcon() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (this.shadowRoot) {
|
|
61
|
-
const currentIcon = await icon.getIcon(this.icon);
|
|
58
|
+
this.#abortController?.abort();
|
|
59
|
+
this.#abortController = new AbortController();
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
} else {
|
|
66
|
-
this.shadowRoot.append(currentIcon);
|
|
67
|
-
}
|
|
61
|
+
if (!this.icon) {
|
|
62
|
+
return;
|
|
68
63
|
}
|
|
64
|
+
|
|
65
|
+
const icon = this.#icon();
|
|
66
|
+
|
|
67
|
+
icon
|
|
68
|
+
.getIcon(this.icon, this.#abortController?.signal)
|
|
69
|
+
.then((currentIcon) => {
|
|
70
|
+
if (this.shadowRoot) {
|
|
71
|
+
if (this.shadowRoot.firstElementChild) {
|
|
72
|
+
this.shadowRoot.firstElementChild.replaceWith(currentIcon);
|
|
73
|
+
} else {
|
|
74
|
+
this.shadowRoot.append(currentIcon);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.catch(() => {});
|
|
69
79
|
}
|
|
70
80
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { attr, css, element, html, listen, query, ready } from
|
|
2
|
-
import { effect, observe } from
|
|
1
|
+
import { attr, css, element, html, listen, query, ready } from '@joist/element';
|
|
2
|
+
import { effect, observe } from '@joist/observable';
|
|
3
3
|
|
|
4
|
-
import type { MaskableElement } from
|
|
4
|
+
import type { MaskableElement } from '../input-mask/maskable.element.js';
|
|
5
5
|
|
|
6
6
|
declare global {
|
|
7
7
|
interface HTMLElementTagNameMap {
|
|
8
|
-
|
|
8
|
+
'usa-input': USATextInputElement;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
@element({
|
|
13
|
-
tagName:
|
|
13
|
+
tagName: 'usa-input',
|
|
14
14
|
shadowDom: [
|
|
15
15
|
css`
|
|
16
16
|
* {
|
|
@@ -54,31 +54,31 @@ declare global {
|
|
|
54
54
|
color: #757575;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
slot[name=
|
|
57
|
+
slot[name='detail']::slotted(*) {
|
|
58
58
|
color: #757575;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
slot[name=
|
|
61
|
+
slot[name='detail']::slotted(usa-icon) {
|
|
62
62
|
width: 1.5rem;
|
|
63
63
|
height: 1.5rem;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
slot[name=
|
|
66
|
+
slot[name='detail'] {
|
|
67
67
|
display: block;
|
|
68
68
|
position: absolute;
|
|
69
69
|
bottom: 0.21rem;
|
|
70
70
|
left: 0.5rem;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
:host([detail=
|
|
73
|
+
:host([detail='pfx']) input {
|
|
74
74
|
padding-left: 2.5rem;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
:host([detail=
|
|
77
|
+
:host([detail='sfx']) input {
|
|
78
78
|
padding-right: 2.5rem;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
:host([detail=
|
|
81
|
+
:host([detail='sfx']) slot[name='detail'] {
|
|
82
82
|
right: 0.5rem;
|
|
83
83
|
left: auto;
|
|
84
84
|
}
|
|
@@ -94,26 +94,23 @@ declare global {
|
|
|
94
94
|
`,
|
|
95
95
|
],
|
|
96
96
|
})
|
|
97
|
-
export class USATextInputElement
|
|
98
|
-
extends HTMLElement
|
|
99
|
-
implements MaskableElement
|
|
100
|
-
{
|
|
97
|
+
export class USATextInputElement extends HTMLElement implements MaskableElement {
|
|
101
98
|
static formAssociated = true;
|
|
102
99
|
|
|
103
100
|
@attr()
|
|
104
|
-
accessor name =
|
|
101
|
+
accessor name = '';
|
|
105
102
|
|
|
106
103
|
@attr()
|
|
107
|
-
accessor autocomplete: AutoFill =
|
|
104
|
+
accessor autocomplete: AutoFill = 'on';
|
|
108
105
|
|
|
109
106
|
@attr()
|
|
110
|
-
accessor placeholder =
|
|
107
|
+
accessor placeholder = '';
|
|
111
108
|
|
|
112
109
|
@attr()
|
|
113
|
-
accessor min =
|
|
110
|
+
accessor min = '';
|
|
114
111
|
|
|
115
112
|
@attr()
|
|
116
|
-
accessor max =
|
|
113
|
+
accessor max = '';
|
|
117
114
|
|
|
118
115
|
@attr()
|
|
119
116
|
accessor minLength = -1;
|
|
@@ -128,18 +125,16 @@ export class USATextInputElement
|
|
|
128
125
|
accessor disabled = false;
|
|
129
126
|
|
|
130
127
|
@attr()
|
|
131
|
-
accessor type:
|
|
128
|
+
accessor type: 'text' | 'password' | 'number' = 'text';
|
|
132
129
|
|
|
133
|
-
@attr(
|
|
134
|
-
|
|
135
|
-
})
|
|
136
|
-
accessor detail: "pfx" | "sfx" | "" = "";
|
|
130
|
+
@attr()
|
|
131
|
+
accessor detail: 'pfx' | 'sfx' | '' = '';
|
|
137
132
|
|
|
138
133
|
@attr({
|
|
139
134
|
reflect: false,
|
|
140
135
|
})
|
|
141
136
|
@observe()
|
|
142
|
-
accessor value =
|
|
137
|
+
accessor value = '';
|
|
143
138
|
|
|
144
139
|
@observe()
|
|
145
140
|
accessor selectionStart: number | null = null;
|
|
@@ -152,14 +147,14 @@ export class USATextInputElement
|
|
|
152
147
|
}
|
|
153
148
|
|
|
154
149
|
#internals = this.attachInternals();
|
|
155
|
-
#input = query(
|
|
150
|
+
#input = query('input');
|
|
156
151
|
|
|
157
152
|
@ready()
|
|
158
153
|
onReady() {
|
|
159
154
|
this.#input({ autofocus: this.autofocus });
|
|
160
155
|
}
|
|
161
156
|
|
|
162
|
-
attributeChangedCallback(
|
|
157
|
+
attributeChangedCallback() {
|
|
163
158
|
this.#input({
|
|
164
159
|
autocomplete: this.autocomplete,
|
|
165
160
|
placeholder: this.placeholder,
|
|
@@ -191,14 +186,14 @@ export class USATextInputElement
|
|
|
191
186
|
this.#syncFormState();
|
|
192
187
|
}
|
|
193
188
|
|
|
194
|
-
@listen(
|
|
189
|
+
@listen('keydown')
|
|
195
190
|
onKeyDown(e: KeyboardEvent) {
|
|
196
191
|
const form = this.#internals.form;
|
|
197
192
|
|
|
198
193
|
if (form) {
|
|
199
194
|
const hasModifier = e.metaKey || e.ctrlKey || e.shiftKey || e.altKey;
|
|
200
195
|
|
|
201
|
-
if (e.key.toUpperCase() ===
|
|
196
|
+
if (e.key.toUpperCase() === 'ENTER' && !hasModifier) {
|
|
202
197
|
// this makes sure that the user has a chance to cancel the event before submitting
|
|
203
198
|
setTimeout(() => {
|
|
204
199
|
if (!e.defaultPrevented && !e.isComposing) {
|
|
@@ -209,7 +204,7 @@ export class USATextInputElement
|
|
|
209
204
|
}
|
|
210
205
|
}
|
|
211
206
|
|
|
212
|
-
@listen(
|
|
207
|
+
@listen('input')
|
|
213
208
|
onInputChange() {
|
|
214
209
|
const input = this.#input();
|
|
215
210
|
|
|
@@ -222,20 +217,16 @@ export class USATextInputElement
|
|
|
222
217
|
const input = this.#input();
|
|
223
218
|
|
|
224
219
|
this.#internals.setValidity({});
|
|
225
|
-
this.#internals.setFormValue(
|
|
220
|
+
this.#internals.setFormValue(this.value);
|
|
226
221
|
|
|
227
222
|
if (input.validationMessage) {
|
|
228
|
-
this.#internals.setValidity(
|
|
229
|
-
{ customError: true },
|
|
230
|
-
input.validationMessage,
|
|
231
|
-
input,
|
|
232
|
-
);
|
|
223
|
+
this.#internals.setValidity({ customError: true }, input.validationMessage, input);
|
|
233
224
|
}
|
|
234
225
|
}
|
|
235
226
|
|
|
236
227
|
#submit(form: HTMLFormElement) {
|
|
237
|
-
const btn = document.createElement(
|
|
238
|
-
btn.type =
|
|
228
|
+
const btn = document.createElement('button');
|
|
229
|
+
btn.type = 'submit';
|
|
239
230
|
form.append(btn);
|
|
240
231
|
|
|
241
232
|
btn.click();
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import './input.element.js';
|
|
2
2
|
|
|
3
|
-
import { assert, fixture, html } from
|
|
4
|
-
import { userEvent } from
|
|
3
|
+
import { assert, fixture, html } from '@open-wc/testing';
|
|
4
|
+
import { userEvent } from '@testing-library/user-event';
|
|
5
5
|
|
|
6
|
-
describe(
|
|
7
|
-
it(
|
|
8
|
-
const form = await fixture<HTMLFormElement>(html`
|
|
9
|
-
<usa-input name="fname" value="Foo">Hello World</usa-input>
|
|
10
|
-
`);
|
|
6
|
+
describe('usa-input', () => {
|
|
7
|
+
it('should be accessible', async () => {
|
|
8
|
+
const form = await fixture<HTMLFormElement>(html` <usa-input name="fname" value="Foo">Hello World</usa-input> `);
|
|
11
9
|
|
|
12
10
|
return assert.isAccessible(form);
|
|
13
11
|
});
|
|
14
12
|
|
|
15
|
-
it(
|
|
13
|
+
it('should submit form with default values', async () => {
|
|
16
14
|
const form = await fixture<HTMLFormElement>(html`
|
|
17
15
|
<form>
|
|
18
16
|
<usa-input name="fname" value="Foo">Hello World</usa-input>
|
|
@@ -23,10 +21,10 @@ describe("usa-input", () => {
|
|
|
23
21
|
|
|
24
22
|
const value = new FormData(form);
|
|
25
23
|
|
|
26
|
-
assert.equal(value.get(
|
|
24
|
+
assert.equal(value.get('fname'), 'Foo');
|
|
27
25
|
});
|
|
28
26
|
|
|
29
|
-
it(
|
|
27
|
+
it('should update form value as input value changed', async () => {
|
|
30
28
|
const form = await fixture<HTMLFormElement>(html`
|
|
31
29
|
<form>
|
|
32
30
|
<usa-input name="fname">Hello World</usa-input>
|
|
@@ -35,19 +33,19 @@ describe("usa-input", () => {
|
|
|
35
33
|
</form>
|
|
36
34
|
`);
|
|
37
35
|
|
|
38
|
-
const input = form.querySelector(
|
|
39
|
-
const nativeInput = input?.shadowRoot?.querySelector(
|
|
36
|
+
const input = form.querySelector('usa-input');
|
|
37
|
+
const nativeInput = input?.shadowRoot?.querySelector('input');
|
|
40
38
|
|
|
41
39
|
if (nativeInput) {
|
|
42
|
-
await userEvent.type(nativeInput,
|
|
40
|
+
await userEvent.type(nativeInput, 'Bar');
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
const value = new FormData(form);
|
|
46
44
|
|
|
47
|
-
assert.equal(value.get(
|
|
45
|
+
assert.equal(value.get('fname'), 'Bar');
|
|
48
46
|
});
|
|
49
47
|
|
|
50
|
-
it(
|
|
48
|
+
it('should not submit when not valid', async () => {
|
|
51
49
|
const form = await fixture<HTMLFormElement>(html`
|
|
52
50
|
<form>
|
|
53
51
|
<usa-input name="fname" required>Hello World</usa-input>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { attr, css, element, html, listen, queryAll } from
|
|
1
|
+
import { attr, css, element, html, listen, queryAll } from '@joist/element';
|
|
2
2
|
|
|
3
|
-
import { PATTERN_CHARS, type PatternChar, REG_EXPS, format } from
|
|
4
|
-
import type { MaskableElement } from
|
|
3
|
+
import { PATTERN_CHARS, type PatternChar, REG_EXPS, format } from './format.js';
|
|
4
|
+
import type { MaskableElement } from './maskable.element.js';
|
|
5
5
|
|
|
6
6
|
declare global {
|
|
7
7
|
interface HTMLElementTagNameMap {
|
|
8
|
-
|
|
8
|
+
'usa-input-mask': USAInputMaskElement;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
@element({
|
|
13
|
-
tagName:
|
|
13
|
+
tagName: 'usa-input-mask',
|
|
14
14
|
shadowDom: [
|
|
15
15
|
css`
|
|
16
16
|
:host {
|
|
@@ -22,13 +22,13 @@ declare global {
|
|
|
22
22
|
})
|
|
23
23
|
export class USAInputMaskElement extends HTMLElement {
|
|
24
24
|
@attr()
|
|
25
|
-
accessor mask =
|
|
25
|
+
accessor mask = '';
|
|
26
26
|
|
|
27
|
-
#maskables = queryAll<MaskableElement>(
|
|
27
|
+
#maskables = queryAll<MaskableElement>('[mask]', this);
|
|
28
28
|
|
|
29
29
|
connectedCallback() {
|
|
30
30
|
for (const input of this.#maskables()) {
|
|
31
|
-
const { formatted } = format(input.value, this.#getMaskFor(input));
|
|
31
|
+
const { formatted } = format(input.value || input.getAttribute('value') || '', this.#getMaskFor(input));
|
|
32
32
|
|
|
33
33
|
if (formatted) {
|
|
34
34
|
input.value = formatted;
|
|
@@ -36,7 +36,7 @@ export class USAInputMaskElement extends HTMLElement {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
@listen(
|
|
39
|
+
@listen('input')
|
|
40
40
|
onInput(e: Event) {
|
|
41
41
|
const input = e.target as MaskableElement;
|
|
42
42
|
const selectionStart = input.selectionStart || 0;
|
|
@@ -60,7 +60,7 @@ export class USAInputMaskElement extends HTMLElement {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
@listen(
|
|
63
|
+
@listen('keydown')
|
|
64
64
|
onKeyDown(e: KeyboardEvent) {
|
|
65
65
|
const input = e.target as MaskableElement;
|
|
66
66
|
const mask = this.#getMaskFor(input);
|
|
@@ -72,12 +72,12 @@ export class USAInputMaskElement extends HTMLElement {
|
|
|
72
72
|
if (input.value.length >= mask.length) {
|
|
73
73
|
// prevent default once value is the same as the mask length
|
|
74
74
|
e.preventDefault();
|
|
75
|
-
} else if (patternChar ===
|
|
75
|
+
} else if (patternChar === '9') {
|
|
76
76
|
if (!REG_EXPS.Numbers.test(e.key)) {
|
|
77
77
|
// if pattern char specifies number and is not
|
|
78
78
|
e.preventDefault();
|
|
79
79
|
}
|
|
80
|
-
} else if (patternChar ===
|
|
80
|
+
} else if (patternChar === 'A') {
|
|
81
81
|
if (!REG_EXPS.Letters.test(e.key)) {
|
|
82
82
|
// if pattern char specifies letter and is not
|
|
83
83
|
e.preventDefault();
|
|
@@ -87,6 +87,6 @@ export class USAInputMaskElement extends HTMLElement {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
#getMaskFor(input: MaskableElement) {
|
|
90
|
-
return this.mask || input.getAttribute(
|
|
90
|
+
return this.mask || input.getAttribute('mask') || '';
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from
|
|
2
|
-
import { html } from
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
2
|
+
import { html } from 'lit';
|
|
3
3
|
|
|
4
|
-
import type { USAInputMaskElement } from
|
|
4
|
+
import type { USAInputMaskElement } from './input-mask.element.js';
|
|
5
5
|
|
|
6
6
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
|
|
7
7
|
const meta = {
|
|
8
|
-
title:
|
|
9
|
-
tags: [
|
|
8
|
+
title: 'input-mask',
|
|
9
|
+
tags: ['autodocs'],
|
|
10
10
|
render(args) {
|
|
11
11
|
return html`
|
|
12
12
|
<usa-input-mask>
|
|
13
|
-
<usa-input
|
|
14
|
-
name="phone"
|
|
15
|
-
placeholder=${args.mask}
|
|
16
|
-
autocomplete="off"
|
|
17
|
-
mask=${args.mask}
|
|
18
|
-
>
|
|
13
|
+
<usa-input name="phone" placeholder=${args.mask} autocomplete="off" mask=${args.mask} value="1234567890">
|
|
19
14
|
Phone:
|
|
20
15
|
</usa-input>
|
|
21
16
|
</usa-input-mask>
|
|
@@ -23,7 +18,7 @@ const meta = {
|
|
|
23
18
|
},
|
|
24
19
|
argTypes: {},
|
|
25
20
|
args: {
|
|
26
|
-
mask:
|
|
21
|
+
mask: '(999) 999-9999',
|
|
27
22
|
},
|
|
28
23
|
} satisfies Meta<USAInputMaskElement>;
|
|
29
24
|
|