@noctuatech/uswds 0.0.36 → 0.1.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/assets/flags/alabama.svg +5 -0
- package/assets/flags/alaska.svg +14 -0
- package/assets/flags/arizona.svg +7 -0
- package/assets/flags/arkansas.svg +15 -0
- package/assets/flags/california.svg +8 -0
- package/assets/flags/colorado.svg +8 -0
- package/assets/flags/connecticut.svg +5 -0
- package/assets/flags/delaware.svg +7 -0
- package/assets/flags/florida.svg +5 -0
- package/assets/flags/georgia.svg +5 -0
- package/assets/flags/hawaii.svg +19 -0
- package/assets/flags/idaho.svg +5 -0
- package/assets/flags/illinois.svg +5 -0
- package/assets/flags/indiana.svg +15 -0
- package/assets/flags/iowa.svg +13 -0
- package/assets/flags/kansas.svg +5 -0
- package/assets/flags/kentucky.svg +16 -0
- package/assets/flags/louisiana.svg +14 -0
- package/assets/flags/maine.svg +12 -0
- package/assets/flags/maryland.svg +14 -0
- package/assets/flags/massachusetts.svg +15 -0
- package/assets/flags/michigan.svg +5 -0
- package/assets/flags/minnesota.svg +15 -0
- package/assets/flags/mississippi.svg +10 -0
- package/assets/flags/missouri.svg +16 -0
- package/assets/flags/montana.svg +11 -0
- package/assets/flags/nebraska.svg +13 -0
- package/assets/flags/nevada.svg +14 -0
- package/assets/flags/new_hampshire.svg +13 -0
- package/assets/flags/new_jersey.svg +13 -0
- package/assets/flags/new_mexico.svg +7 -0
- package/assets/flags/new_york.svg +13 -0
- package/assets/flags/north_carolina.svg +5 -0
- package/assets/flags/north_dakota.svg +13 -0
- package/assets/flags/ohio.svg +16 -0
- package/assets/flags/oklahoma.svg +12 -0
- package/assets/flags/oregon.svg +13 -0
- package/assets/flags/pennsylvania.svg +15 -0
- package/assets/flags/rhode_island.svg +13 -0
- package/assets/flags/south_carolina.svg +11 -0
- package/assets/flags/south_dakota.svg +15 -0
- package/assets/flags/tennessee.svg +13 -0
- package/assets/flags/texas.svg +8 -0
- package/assets/flags/utah.svg +9 -0
- package/assets/flags/vermont.svg +13 -0
- package/assets/flags/virginia.svg +5 -0
- package/assets/flags/washington.svg +5 -0
- package/assets/flags/west_virginia.svg +24 -0
- package/assets/flags/wisconsin.svg +5 -0
- package/assets/flags/wyoming.svg +5 -0
- package/package.json +13 -5
- package/src/lib/accordion/accordion.test.ts +12 -10
- package/src/lib/button/button.stories.ts +1 -1
- package/src/lib/card/card.stories.ts +0 -1
- package/src/lib/checkbox/checkbox.element.ts +1 -17
- package/src/lib/checkbox/checkbox.stories.ts +0 -27
- package/src/lib/combo-box/combo-box-option/combo-box-option.element.ts +68 -0
- package/src/lib/combo-box/combo-box.element.ts +222 -0
- package/src/lib/combo-box/combo-box.stories.ts +236 -0
- package/src/lib/combo-box/combo-box.test.ts +150 -0
- package/src/lib/combo-box/context.ts +10 -0
- package/src/lib/define.ts +3 -0
- package/src/lib/input/input.element.ts +4 -0
- package/src/lib/input/input.test.ts +2 -4
- package/src/lib/radio/radio-option/radio-option.element.ts +2 -6
- package/src/lib/radio/radio.element.ts +1 -1
- package/src/lib/radio/radio.stories.ts +0 -1
- package/src/lib/range-slider/range-slider.element.ts +111 -0
- package/src/lib/range-slider/range-slider.stories.ts +24 -0
- package/src/lib/range-slider/range-slider.test.ts +52 -0
- package/src/lib/select/select.stories.ts +1 -1
- package/src/lib/select/select.test.ts +2 -4
- package/src/lib/side-nav/side-nav.stories.ts +1 -1
- package/src/lib/summary-box/summary-box.stories.ts +1 -1
- package/src/lib/textarea/textarea.test.ts +2 -4
- package/src/lib.ts +3 -0
- package/target/lib/accordion/accordion.test.js +12 -10
- package/target/lib/accordion/accordion.test.js.map +1 -1
- package/target/lib/button/button.stories.d.ts +1 -1
- package/target/lib/button/button.stories.js +1 -1
- package/target/lib/button/button.stories.js.map +1 -1
- package/target/lib/card/card.stories.js.map +1 -1
- package/target/lib/checkbox/checkbox.element.d.ts +0 -1
- package/target/lib/checkbox/checkbox.element.js +1 -15
- package/target/lib/checkbox/checkbox.element.js.map +1 -1
- package/target/lib/checkbox/checkbox.stories.d.ts +0 -1
- package/target/lib/checkbox/checkbox.stories.js +0 -24
- package/target/lib/checkbox/checkbox.stories.js.map +1 -1
- package/target/lib/combo-box/combo-box-option/combo-box-option.element.d.ts +12 -0
- package/target/lib/combo-box/combo-box-option/combo-box-option.element.js +72 -0
- package/target/lib/combo-box/combo-box-option/combo-box-option.element.js.map +1 -0
- package/target/lib/combo-box/combo-box.element.d.ts +22 -0
- package/target/lib/combo-box/combo-box.element.js +209 -0
- package/target/lib/combo-box/combo-box.element.js.map +1 -0
- package/target/lib/combo-box/combo-box.stories.d.ts +12 -0
- package/target/lib/combo-box/combo-box.stories.js +229 -0
- package/target/lib/combo-box/combo-box.stories.js.map +1 -0
- package/target/lib/combo-box/combo-box.test.d.ts +3 -0
- package/target/lib/combo-box/combo-box.test.js +88 -0
- package/target/lib/combo-box/combo-box.test.js.map +1 -0
- package/target/lib/combo-box/context.d.ts +6 -0
- package/target/lib/combo-box/context.js +3 -0
- package/target/lib/combo-box/context.js.map +1 -0
- package/target/lib/define.d.ts +3 -0
- package/target/lib/define.js +3 -0
- package/target/lib/define.js.map +1 -1
- package/target/lib/input/input.element.d.ts +1 -0
- package/target/lib/input/input.element.js +3 -0
- package/target/lib/input/input.element.js.map +1 -1
- package/target/lib/input/input.test.js +2 -3
- package/target/lib/input/input.test.js.map +1 -1
- package/target/lib/radio/radio-option/radio-option.element.js +2 -5
- package/target/lib/radio/radio-option/radio-option.element.js.map +1 -1
- package/target/lib/radio/radio.element.js.map +1 -1
- package/target/lib/radio/radio.stories.js.map +1 -1
- package/target/lib/range-slider/range-slider.element.d.ts +16 -0
- package/target/lib/range-slider/range-slider.element.js +146 -0
- package/target/lib/range-slider/range-slider.element.js.map +1 -0
- package/target/lib/range-slider/range-slider.stories.d.ts +12 -0
- package/target/lib/range-slider/range-slider.stories.js +17 -0
- package/target/lib/range-slider/range-slider.stories.js.map +1 -0
- package/target/lib/range-slider/range-slider.test.d.ts +1 -0
- package/target/lib/range-slider/range-slider.test.js +39 -0
- package/target/lib/range-slider/range-slider.test.js.map +1 -0
- package/target/lib/select/select.stories.d.ts +1 -1
- package/target/lib/select/select.stories.js +1 -1
- package/target/lib/select/select.stories.js.map +1 -1
- package/target/lib/select/select.test.js +2 -3
- package/target/lib/select/select.test.js.map +1 -1
- package/target/lib/side-nav/side-nav.stories.d.ts +1 -1
- package/target/lib/side-nav/side-nav.stories.js +1 -1
- package/target/lib/side-nav/side-nav.stories.js.map +1 -1
- package/target/lib/summary-box/summary-box.stories.d.ts +1 -1
- package/target/lib/summary-box/summary-box.stories.js +1 -1
- package/target/lib/summary-box/summary-box.stories.js.map +1 -1
- package/target/lib/textarea/textarea.test.js +2 -3
- package/target/lib/textarea/textarea.test.js.map +1 -1
- package/target/lib.d.ts +3 -0
- package/target/lib.js +3 -0
- package/target/lib.js.map +1 -1
- package/target/lib/form/validation.d.ts +0 -2
- package/target/lib/form/validation.js +0 -27
- package/target/lib/form/validation.js.map +0 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<rect width="24" height="16" fill="#FFFFFF"/>
|
|
4
|
+
<rect x="0" y="0" width="24" height="16" fill="#002868"/>
|
|
5
|
+
|
|
6
|
+
<!-- State seal circle -->
|
|
7
|
+
<circle cx="12" cy="8" r="6" fill="#FFFFFF"/>
|
|
8
|
+
<circle cx="12" cy="8" r="5" fill="#002868"/>
|
|
9
|
+
|
|
10
|
+
<!-- Rhododendron flowers -->
|
|
11
|
+
<circle cx="8" cy="6" r="1" fill="#FFFFFF"/>
|
|
12
|
+
<circle cx="16" cy="6" r="1" fill="#FFFFFF"/>
|
|
13
|
+
<circle cx="8" cy="10" r="1" fill="#FFFFFF"/>
|
|
14
|
+
<circle cx="16" cy="10" r="1" fill="#FFFFFF"/>
|
|
15
|
+
<circle cx="12" cy="8" r="1" fill="#FFFFFF"/>
|
|
16
|
+
|
|
17
|
+
<!-- Crossed rifles -->
|
|
18
|
+
<path d="M10 6L14 10" stroke="#FFFFFF" stroke-width="1"/>
|
|
19
|
+
<path d="M14 6L10 10" stroke="#FFFFFF" stroke-width="1"/>
|
|
20
|
+
|
|
21
|
+
<!-- Mining tools -->
|
|
22
|
+
<path d="M8 8L16 8" stroke="#FFFFFF" stroke-width="1"/>
|
|
23
|
+
<path d="M12 6L12 10" stroke="#FFFFFF" stroke-width="1"/>
|
|
24
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noctuatech/uswds",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": ["packages/**"],
|
|
6
6
|
"main": "./target/lib.js",
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
},
|
|
27
27
|
"test": {
|
|
28
28
|
"command": "wtr",
|
|
29
|
-
"dependencies": ["tsc"]
|
|
29
|
+
"dependencies": ["tsc", "build_testing_library"]
|
|
30
30
|
},
|
|
31
31
|
"build": {
|
|
32
32
|
"dependencies": ["build_storybook", "build_website"]
|
|
33
33
|
},
|
|
34
34
|
"preview": {
|
|
35
|
-
"command": "eleventy --serve --incremental
|
|
35
|
+
"command": "eleventy --serve --incremental",
|
|
36
36
|
"service": true,
|
|
37
37
|
"dependencies": [
|
|
38
38
|
{
|
|
@@ -48,14 +48,19 @@
|
|
|
48
48
|
"command": "eleventy --pathprefix=uswds",
|
|
49
49
|
"dependencies": ["tsc", "copy_icons"]
|
|
50
50
|
},
|
|
51
|
+
"build_testing_library": {
|
|
52
|
+
"command": "./scripts/build_testing_library.sh",
|
|
53
|
+
"files": ["node_modules/@testing-library/**"],
|
|
54
|
+
"output": ["testing-library/**"]
|
|
55
|
+
},
|
|
51
56
|
"tsc": {
|
|
52
57
|
"command": "tsc --build --pretty",
|
|
53
58
|
"clean": "if-file-deleted",
|
|
54
59
|
"files": ["src/**", "tsconfig.json"],
|
|
55
|
-
"
|
|
60
|
+
"output": ["target/**"]
|
|
56
61
|
},
|
|
57
62
|
"copy_icons": {
|
|
58
|
-
"command": "
|
|
63
|
+
"command": "./scripts/copy_usa_icons.sh",
|
|
59
64
|
"files": ["node_modules/@uswds/uswds/dist/img/usa-icons/**"],
|
|
60
65
|
"output": ["assets/usa-icon/**"]
|
|
61
66
|
}
|
|
@@ -76,9 +81,12 @@
|
|
|
76
81
|
"@storybook/addon-essentials": "^8.6.0",
|
|
77
82
|
"@storybook/web-components": "^8.6.0",
|
|
78
83
|
"@storybook/web-components-vite": "^8.6.0",
|
|
84
|
+
"@testing-library/dom": "^10.4.0",
|
|
85
|
+
"@testing-library/user-event": "^14.6.1",
|
|
79
86
|
"@types/mocha": "^10.0.7",
|
|
80
87
|
"@types/node": "^22.0.0",
|
|
81
88
|
"@uswds/uswds": "^3.10.0",
|
|
89
|
+
"@web/dev-server-import-maps": "^0.2.1",
|
|
82
90
|
"@web/test-runner": "^0.20.0",
|
|
83
91
|
"husky": "^9.0.11",
|
|
84
92
|
"js-beautify": "^1.15.1",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import "./accordion.element.js";
|
|
2
2
|
|
|
3
3
|
import { assert, fixture, html } from "@open-wc/testing";
|
|
4
|
+
import { screen } from "@testing-library/dom";
|
|
5
|
+
import { userEvent } from "@testing-library/user-event";
|
|
4
6
|
|
|
5
7
|
import type { USAAccordionElement } from "./accordion.element.js";
|
|
6
8
|
|
|
@@ -36,10 +38,10 @@ describe("usa-accordion", () => {
|
|
|
36
38
|
</usa-accordion>
|
|
37
39
|
`);
|
|
38
40
|
|
|
39
|
-
const heading =
|
|
41
|
+
const heading = await screen.findByRole("heading");
|
|
40
42
|
const content = accordion.querySelector<HTMLDivElement>(".content");
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
await userEvent.click(heading);
|
|
43
45
|
|
|
44
46
|
assert.isTrue(content?.checkVisibility());
|
|
45
47
|
});
|
|
@@ -58,12 +60,12 @@ describe("usa-accordion", () => {
|
|
|
58
60
|
</usa-accordion>
|
|
59
61
|
`);
|
|
60
62
|
|
|
61
|
-
const heading =
|
|
63
|
+
const heading = await screen.findByRole("heading");
|
|
62
64
|
const content = accordion.querySelector<HTMLDivElement>(".content");
|
|
63
65
|
|
|
64
66
|
assert.isFalse(content?.checkVisibility());
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
await userEvent.click(heading);
|
|
67
69
|
|
|
68
70
|
assert.isTrue(content.checkVisibility());
|
|
69
71
|
});
|
|
@@ -91,21 +93,21 @@ describe("usa-accordion", () => {
|
|
|
91
93
|
const headings = el.querySelectorAll("h4");
|
|
92
94
|
const content = Array.from(el.querySelectorAll<HTMLDivElement>(".content"));
|
|
93
95
|
|
|
94
|
-
headings[0]
|
|
96
|
+
await userEvent.click(headings[0]);
|
|
95
97
|
|
|
96
98
|
assert.deepEqual(
|
|
97
99
|
content.map((el) => el.checkVisibility()),
|
|
98
100
|
[true, false, false],
|
|
99
101
|
);
|
|
100
102
|
|
|
101
|
-
headings[1]
|
|
103
|
+
await userEvent.click(headings[1]);
|
|
102
104
|
|
|
103
105
|
assert.deepEqual(
|
|
104
106
|
content.map((el) => el.checkVisibility()),
|
|
105
107
|
[false, true, false],
|
|
106
108
|
);
|
|
107
109
|
|
|
108
|
-
headings[2]
|
|
110
|
+
await userEvent.click(headings[2]);
|
|
109
111
|
|
|
110
112
|
assert.deepEqual(
|
|
111
113
|
content.map((el) => el.checkVisibility()),
|
|
@@ -136,21 +138,21 @@ describe("usa-accordion", () => {
|
|
|
136
138
|
const headings = el.querySelectorAll("h4");
|
|
137
139
|
const content = Array.from(el.querySelectorAll<HTMLDivElement>(".content"));
|
|
138
140
|
|
|
139
|
-
headings[0]
|
|
141
|
+
await userEvent.click(headings[0]);
|
|
140
142
|
|
|
141
143
|
assert.deepEqual(
|
|
142
144
|
content.map((el) => el.checkVisibility()),
|
|
143
145
|
[true, false, false],
|
|
144
146
|
);
|
|
145
147
|
|
|
146
|
-
headings[1]
|
|
148
|
+
await userEvent.click(headings[1]);
|
|
147
149
|
|
|
148
150
|
assert.deepEqual(
|
|
149
151
|
content.map((el) => el.checkVisibility()),
|
|
150
152
|
[false, true, false],
|
|
151
153
|
);
|
|
152
154
|
|
|
153
|
-
headings[2]
|
|
155
|
+
await userEvent.click(headings[2]);
|
|
154
156
|
|
|
155
157
|
assert.deepEqual(
|
|
156
158
|
content.map((el) => el.checkVisibility()),
|
|
@@ -7,7 +7,7 @@ import { BUTTON_VARIANTS, type USAButtonElement } from "./button.element.js";
|
|
|
7
7
|
const meta = {
|
|
8
8
|
title: "usa-button",
|
|
9
9
|
tags: ["autodocs"],
|
|
10
|
-
render(
|
|
10
|
+
render() {
|
|
11
11
|
return html`
|
|
12
12
|
<div style="display: inline-flex; flex-direction: column; gap: 1rem">
|
|
13
13
|
${BUTTON_VARIANTS.map(
|
|
@@ -150,17 +150,6 @@ export class USACheckboxElement extends HTMLElement {
|
|
|
150
150
|
|
|
151
151
|
#internals = this.attachInternals();
|
|
152
152
|
|
|
153
|
-
connectedCallback() {
|
|
154
|
-
this.#checkbox({
|
|
155
|
-
checked: this.checked,
|
|
156
|
-
name: this.name,
|
|
157
|
-
disabled: this.disabled,
|
|
158
|
-
required: this.required,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
this.#syncFormState();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
153
|
attributeChangedCallback() {
|
|
165
154
|
this.#checkbox({
|
|
166
155
|
checked: this.checked,
|
|
@@ -184,12 +173,7 @@ export class USACheckboxElement extends HTMLElement {
|
|
|
184
173
|
const checkbox = this.#checkbox();
|
|
185
174
|
|
|
186
175
|
this.#internals.setValidity({});
|
|
187
|
-
|
|
188
|
-
if (checkbox.checked) {
|
|
189
|
-
this.#internals.setFormValue(this.value);
|
|
190
|
-
} else {
|
|
191
|
-
this.#internals.setFormValue(null);
|
|
192
|
-
}
|
|
176
|
+
this.#internals.setFormValue(checkbox.checked ? this.value : null);
|
|
193
177
|
|
|
194
178
|
if (checkbox.validationMessage) {
|
|
195
179
|
this.#internals.setValidity(
|
|
@@ -1,39 +1,12 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/web-components";
|
|
2
2
|
import { html } from "lit";
|
|
3
3
|
|
|
4
|
-
import { ifDefined } from "lit/directives/if-defined.js";
|
|
5
|
-
import { when } from "lit/directives/when.js";
|
|
6
|
-
|
|
7
4
|
import type { USACheckboxElement } from "./checkbox.element.js";
|
|
8
5
|
|
|
9
6
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
|
|
10
7
|
const meta = {
|
|
11
8
|
title: "usa-checkbox",
|
|
12
9
|
tags: ["autodocs"],
|
|
13
|
-
render() {
|
|
14
|
-
return html`
|
|
15
|
-
<usa-checkbox-group>
|
|
16
|
-
<legend class="usa-legend">Select any historical figure</legend>
|
|
17
|
-
|
|
18
|
-
<usa-checkbox name="historical-figure" value="sojurner-truth" tiled>
|
|
19
|
-
Sojourner Truth
|
|
20
|
-
<usa-description>This is optional text that can be used to describe the label in more detail.</usa-description>
|
|
21
|
-
</usa-checkbox>
|
|
22
|
-
|
|
23
|
-
<usa-checkbox name="historical-figure" value="frederick-douglass" tiled>
|
|
24
|
-
Frederick Douglass
|
|
25
|
-
</usa-checkbox>
|
|
26
|
-
|
|
27
|
-
<usa-checkbox name="historical-figure" value="booker-t-washington" tiled>
|
|
28
|
-
Booker T. Washington
|
|
29
|
-
</usa-checkbox>
|
|
30
|
-
|
|
31
|
-
<usa-checkbox name="historical-figure" value="gw-carver" tiled disabled>
|
|
32
|
-
George Washington Carver
|
|
33
|
-
</usa-checkbox>
|
|
34
|
-
</usa-checkbox-group>
|
|
35
|
-
`;
|
|
36
|
-
},
|
|
37
10
|
argTypes: {},
|
|
38
11
|
args: {},
|
|
39
12
|
} satisfies Meta<USACheckboxElement & { description: string }>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { inject, injectable } from "@joist/di";
|
|
2
|
+
import { attr, css, element, html, query } from "@joist/element";
|
|
3
|
+
|
|
4
|
+
import { COMBO_BOX_CTX } from "../context.js";
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface HTMLElementTagNameMap {
|
|
8
|
+
"usa-combo-box-option": USAComboBoxOptionElement;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const template = document.createElement("template");
|
|
13
|
+
|
|
14
|
+
template.innerHTML = /*html*/ `
|
|
15
|
+
<li tabindex="-1" role="option">
|
|
16
|
+
<slot></slot>
|
|
17
|
+
</li>
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
@injectable({
|
|
21
|
+
name: "usa-combo-box-option-ctx",
|
|
22
|
+
})
|
|
23
|
+
@element({
|
|
24
|
+
tagName: "usa-combo-box-option",
|
|
25
|
+
shadowDom: [
|
|
26
|
+
css`
|
|
27
|
+
:host {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: 0.5rem;
|
|
31
|
+
padding: 0.5rem;
|
|
32
|
+
}
|
|
33
|
+
`,
|
|
34
|
+
html`<slot></slot>`,
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
export class USAComboBoxOptionElement extends HTMLElement {
|
|
38
|
+
@attr()
|
|
39
|
+
accessor value = "";
|
|
40
|
+
|
|
41
|
+
#listItem = template.content.cloneNode(true) as HTMLLIElement;
|
|
42
|
+
#li = query("li", this.#listItem);
|
|
43
|
+
#slot = query("slot", this.#listItem);
|
|
44
|
+
#ctx = inject(COMBO_BOX_CTX);
|
|
45
|
+
|
|
46
|
+
attributeChangedCallback() {
|
|
47
|
+
const value = this.value.split(" ").join("-").toLocaleLowerCase();
|
|
48
|
+
|
|
49
|
+
this.#li().dataset.value = this.value;
|
|
50
|
+
this.#slot().name = value;
|
|
51
|
+
|
|
52
|
+
this.slot = value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
connectedCallback() {
|
|
56
|
+
const ctx = this.#ctx();
|
|
57
|
+
|
|
58
|
+
ctx.addOption(this.#li());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
disconnectedCallback() {
|
|
62
|
+
const ctx = this.#ctx();
|
|
63
|
+
|
|
64
|
+
ctx.removeOption(this.#li());
|
|
65
|
+
|
|
66
|
+
this.#li().remove();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { injectable } from "@joist/di";
|
|
2
|
+
import { css, element, html, listen, query } from "@joist/element";
|
|
3
|
+
|
|
4
|
+
import { COMBO_BOX_CTX, type ComboBoxContainer } from "./context.js";
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface HTMLElementTagNameMap {
|
|
8
|
+
"usa-combo-box": USAComboBoxElement;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@injectable({
|
|
13
|
+
name: "usa-combo-box-ctx",
|
|
14
|
+
provideSelfAs: [COMBO_BOX_CTX],
|
|
15
|
+
})
|
|
16
|
+
@element({
|
|
17
|
+
tagName: "usa-combo-box",
|
|
18
|
+
shadowDom: [
|
|
19
|
+
css`
|
|
20
|
+
:host {
|
|
21
|
+
--usa-combo-max-height: 12.5em;
|
|
22
|
+
|
|
23
|
+
display: block;
|
|
24
|
+
max-width: 30rem;
|
|
25
|
+
position: relative;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ul {
|
|
29
|
+
padding: 0;
|
|
30
|
+
position: absolute;
|
|
31
|
+
bottom: 0;
|
|
32
|
+
left: 0;
|
|
33
|
+
right: 0;
|
|
34
|
+
transform: translateY(100%);
|
|
35
|
+
margin: 0;
|
|
36
|
+
border: 1px solid rgb(92, 92, 92);
|
|
37
|
+
max-height: var(--usa-combo-max-height);
|
|
38
|
+
overflow-y: scroll;
|
|
39
|
+
overflow-x: visible;
|
|
40
|
+
z-index: 1001;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ul:empty {
|
|
44
|
+
border: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ul li {
|
|
48
|
+
background: #ffff;
|
|
49
|
+
list-style: none;
|
|
50
|
+
border-bottom: 1px solid #e6e6e6;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
display: block;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
ul li:hover {
|
|
56
|
+
background-color: #f0f0f0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
li:focus {
|
|
60
|
+
outline: 0.25rem solid #2491ff;
|
|
61
|
+
outline-offset: -0.25rem;
|
|
62
|
+
}
|
|
63
|
+
`,
|
|
64
|
+
html`
|
|
65
|
+
<slot name="input"></slot>
|
|
66
|
+
<ul tabindex="-1" role="listbox"></ul>
|
|
67
|
+
`,
|
|
68
|
+
],
|
|
69
|
+
})
|
|
70
|
+
export class USAComboBoxElement
|
|
71
|
+
extends HTMLElement
|
|
72
|
+
implements ComboBoxContainer
|
|
73
|
+
{
|
|
74
|
+
list = query("ul");
|
|
75
|
+
input = query<HTMLInputElement>('[slot="input"]', this);
|
|
76
|
+
currentItemEl: Element | null = null;
|
|
77
|
+
#allListItems = new Set<HTMLLIElement>();
|
|
78
|
+
|
|
79
|
+
listItems() {
|
|
80
|
+
return this.list().querySelectorAll("li");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
addOption(el: HTMLLIElement) {
|
|
84
|
+
this.#allListItems.add(el);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
removeOption(el: HTMLLIElement) {
|
|
88
|
+
this.#allListItems.delete(el);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@listen("focus", (host) => host.input())
|
|
92
|
+
onFocusIn() {
|
|
93
|
+
this.currentItemEl = null;
|
|
94
|
+
|
|
95
|
+
const list = this.list();
|
|
96
|
+
|
|
97
|
+
const fragment = document.createDocumentFragment();
|
|
98
|
+
|
|
99
|
+
for (const item of this.#allListItems) {
|
|
100
|
+
fragment.append(item);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
list.replaceChildren(fragment);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@listen("input", (host) => host)
|
|
107
|
+
async onInput() {
|
|
108
|
+
const input = this.input();
|
|
109
|
+
const list = this.list();
|
|
110
|
+
|
|
111
|
+
this.currentItemEl = null;
|
|
112
|
+
|
|
113
|
+
const fragment = document.createDocumentFragment();
|
|
114
|
+
|
|
115
|
+
for (const item of this.#allListItems) {
|
|
116
|
+
if (
|
|
117
|
+
item.dataset.value?.toLowerCase().startsWith(input.value.toLowerCase())
|
|
118
|
+
) {
|
|
119
|
+
fragment.append(item);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
list.replaceChildren(fragment);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@listen("focusout")
|
|
127
|
+
onFocusOut() {
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
// This needs to be in a timeout so that it runs as part of the next loop.
|
|
130
|
+
// the active element will not be set until after all of the focus and blur events are done
|
|
131
|
+
if (!this.contains(document.activeElement)) {
|
|
132
|
+
this.list({ innerHTML: "" });
|
|
133
|
+
this.currentItemEl = null;
|
|
134
|
+
}
|
|
135
|
+
}, 0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@listen("keydown")
|
|
139
|
+
onArrowDown(e: KeyboardEvent): void {
|
|
140
|
+
if (e.key.toUpperCase() !== "ARROWDOWN") {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
|
|
146
|
+
if (this.currentItemEl === null) {
|
|
147
|
+
// if there is no current item, set the first item as the current item
|
|
148
|
+
const list = this.list();
|
|
149
|
+
|
|
150
|
+
this.currentItemEl = list.firstElementChild;
|
|
151
|
+
} else if (this.currentItemEl.nextSibling) {
|
|
152
|
+
// if there is a current item, set the next item as the current item
|
|
153
|
+
this.currentItemEl = this.currentItemEl.nextElementSibling;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (this.currentItemEl instanceof HTMLElement) {
|
|
157
|
+
this.currentItemEl.focus();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@listen("keydown")
|
|
162
|
+
onArrowUp(e: KeyboardEvent): void {
|
|
163
|
+
if (e.key.toUpperCase() !== "ARROWUP") {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
e.preventDefault();
|
|
168
|
+
|
|
169
|
+
if (this.currentItemEl?.previousElementSibling) {
|
|
170
|
+
this.currentItemEl = this.currentItemEl.previousElementSibling;
|
|
171
|
+
|
|
172
|
+
if (this.currentItemEl instanceof HTMLElement) {
|
|
173
|
+
this.currentItemEl.focus();
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
this.input().focus();
|
|
177
|
+
this.currentItemEl = null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@listen("keydown")
|
|
182
|
+
onEnter(e: KeyboardEvent): void {
|
|
183
|
+
if (e.key.toUpperCase() !== "ENTER") {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
|
|
189
|
+
const target = e.target as HTMLElement;
|
|
190
|
+
|
|
191
|
+
this.currentItemEl = null;
|
|
192
|
+
|
|
193
|
+
const value = target.dataset.value || "";
|
|
194
|
+
|
|
195
|
+
this.input({
|
|
196
|
+
value,
|
|
197
|
+
selectionStart: value.length,
|
|
198
|
+
selectionEnd: value.length,
|
|
199
|
+
}).focus();
|
|
200
|
+
|
|
201
|
+
this.list({ innerHTML: "" });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@listen("click")
|
|
205
|
+
onClick(e: MouseEvent) {
|
|
206
|
+
if (e.target instanceof HTMLElement) {
|
|
207
|
+
const value = e.target.getAttribute("value");
|
|
208
|
+
|
|
209
|
+
if (value) {
|
|
210
|
+
this.input({
|
|
211
|
+
value,
|
|
212
|
+
selectionStart: value.length,
|
|
213
|
+
selectionEnd: value.length,
|
|
214
|
+
}).focus();
|
|
215
|
+
|
|
216
|
+
this.list({ innerHTML: "" });
|
|
217
|
+
|
|
218
|
+
this.currentItemEl = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|