@noctuatech/uswds 0.0.34 → 0.0.36

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.
Files changed (80) hide show
  1. package/package.json +12 -11
  2. package/src/lib/accordion/accordion.stories.ts +1 -1
  3. package/src/lib/button/button.stories.ts +1 -0
  4. package/src/lib/card/card.test.ts +1 -1
  5. package/src/lib/checkbox/checkbox-group/checkbox-group.element.ts +24 -0
  6. package/src/lib/checkbox/checkbox.element.ts +26 -13
  7. package/src/lib/checkbox/checkbox.stories.ts +73 -35
  8. package/src/lib/collection/collection-item/collection-item.element.ts +2 -2
  9. package/src/lib/collection/collection.stories.ts +19 -3
  10. package/src/lib/define.ts +1 -0
  11. package/src/lib/file-input/file-input.element.ts +27 -1
  12. package/src/lib/file-input/file-input.stories.ts +2 -2
  13. package/src/lib/icon/icon.element.ts +14 -4
  14. package/src/lib/input/input.element.ts +30 -7
  15. package/src/lib/radio/context.ts +1 -0
  16. package/src/lib/radio/radio-option/radio-option.element.ts +24 -15
  17. package/src/lib/radio/radio.element.ts +31 -11
  18. package/src/lib/radio/radio.stories.ts +2 -7
  19. package/src/lib/select/select.element.ts +16 -6
  20. package/src/lib/tag/tag.element.ts +12 -2
  21. package/src/lib/textarea/textarea.element.ts +18 -13
  22. package/src/lib.ts +1 -0
  23. package/target/lib/accordion/accordion.stories.d.ts +1 -1
  24. package/target/lib/accordion/accordion.stories.js +1 -1
  25. package/target/lib/accordion/accordion.stories.js.map +1 -1
  26. package/target/lib/button/button.stories.js +1 -0
  27. package/target/lib/button/button.stories.js.map +1 -1
  28. package/target/lib/card/card.test.js +1 -1
  29. package/target/lib/checkbox/checkbox-group/checkbox-group.element.d.ts +7 -0
  30. package/target/lib/checkbox/checkbox-group/checkbox-group.element.js +36 -0
  31. package/target/lib/checkbox/checkbox-group/checkbox-group.element.js.map +1 -0
  32. package/target/lib/checkbox/checkbox.element.js +23 -12
  33. package/target/lib/checkbox/checkbox.element.js.map +1 -1
  34. package/target/lib/checkbox/checkbox.stories.d.ts +4 -23
  35. package/target/lib/checkbox/checkbox.stories.js +72 -34
  36. package/target/lib/checkbox/checkbox.stories.js.map +1 -1
  37. package/target/lib/collection/collection-item/collection-item.element.js +2 -2
  38. package/target/lib/collection/collection.stories.d.ts +1 -1
  39. package/target/lib/collection/collection.stories.js +19 -3
  40. package/target/lib/collection/collection.stories.js.map +1 -1
  41. package/target/lib/define.d.ts +1 -0
  42. package/target/lib/define.js +1 -0
  43. package/target/lib/define.js.map +1 -1
  44. package/target/lib/file-input/file-input.element.d.ts +2 -0
  45. package/target/lib/file-input/file-input.element.js +23 -2
  46. package/target/lib/file-input/file-input.element.js.map +1 -1
  47. package/target/lib/file-input/file-input.stories.d.ts +1 -1
  48. package/target/lib/file-input/file-input.stories.js +2 -2
  49. package/target/lib/file-input/file-input.stories.js.map +1 -1
  50. package/target/lib/form/validation.d.ts +2 -0
  51. package/target/lib/form/validation.js +27 -0
  52. package/target/lib/form/validation.js.map +1 -0
  53. package/target/lib/icon/icon.element.d.ts +1 -1
  54. package/target/lib/icon/icon.element.js +14 -4
  55. package/target/lib/icon/icon.element.js.map +1 -1
  56. package/target/lib/input/input.element.d.ts +4 -0
  57. package/target/lib/input/input.element.js +47 -8
  58. package/target/lib/input/input.element.js.map +1 -1
  59. package/target/lib/radio/context.d.ts +1 -0
  60. package/target/lib/radio/context.js.map +1 -1
  61. package/target/lib/radio/radio-option/radio-option.element.d.ts +1 -0
  62. package/target/lib/radio/radio-option/radio-option.element.js +28 -16
  63. package/target/lib/radio/radio-option/radio-option.element.js.map +1 -1
  64. package/target/lib/radio/radio.element.d.ts +0 -1
  65. package/target/lib/radio/radio.element.js +29 -8
  66. package/target/lib/radio/radio.element.js.map +1 -1
  67. package/target/lib/radio/radio.stories.d.ts +0 -1
  68. package/target/lib/radio/radio.stories.js +2 -4
  69. package/target/lib/radio/radio.stories.js.map +1 -1
  70. package/target/lib/select/select.element.js +13 -4
  71. package/target/lib/select/select.element.js.map +1 -1
  72. package/target/lib/tag/tag.element.d.ts +3 -0
  73. package/target/lib/tag/tag.element.js +17 -3
  74. package/target/lib/tag/tag.element.js.map +1 -1
  75. package/target/lib/textarea/textarea.element.d.ts +1 -1
  76. package/target/lib/textarea/textarea.element.js +12 -11
  77. package/target/lib/textarea/textarea.element.js.map +1 -1
  78. package/target/lib.d.ts +1 -0
  79. package/target/lib.js +1 -0
  80. package/target/lib.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noctuatech/uswds",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "type": "module",
5
5
  "workspaces": ["packages/**"],
6
6
  "main": "./target/lib.js",
@@ -15,7 +15,7 @@
15
15
  "build": "wireit",
16
16
  "preview": "wireit",
17
17
  "storybook": "storybook dev -p 6006",
18
- "build-storybook": "wireit",
18
+ "build_storybook": "wireit",
19
19
  "gen": "plop",
20
20
  "prepare": "wireit"
21
21
  },
@@ -29,10 +29,10 @@
29
29
  "dependencies": ["tsc"]
30
30
  },
31
31
  "build": {
32
- "dependencies": ["tsc", "build-storybook", "copy_icons"]
32
+ "dependencies": ["build_storybook", "build_website"]
33
33
  },
34
34
  "preview": {
35
- "command": "browser-sync start --server --files \"target/**\" \"index.html\"",
35
+ "command": "eleventy --serve --incremental --quiet",
36
36
  "service": true,
37
37
  "dependencies": [
38
38
  {
@@ -41,14 +41,17 @@
41
41
  }
42
42
  ]
43
43
  },
44
- "build-storybook": {
45
- "command": "storybook build"
44
+ "build_storybook": {
45
+ "command": "storybook build -o _site/storybook"
46
+ },
47
+ "build_website": {
48
+ "command": "eleventy --pathprefix=uswds",
49
+ "dependencies": ["tsc", "copy_icons"]
46
50
  },
47
51
  "tsc": {
48
52
  "command": "tsc --build --pretty",
49
53
  "clean": "if-file-deleted",
50
54
  "files": ["src/**", "tsconfig.json"],
51
- "output": ["target/**", "tsconfig.tsbuildinfo"],
52
55
  "dependencies": ["./packages/testing:build"]
53
56
  },
54
57
  "copy_icons": {
@@ -67,10 +70,9 @@
67
70
  "tslib": "2.8.1"
68
71
  },
69
72
  "devDependencies": {
73
+ "@11ty/eleventy": "^3.0.0",
70
74
  "@biomejs/biome": "1.9.4",
71
75
  "@open-wc/testing": "^4.0.0",
72
- "@rollup/plugin-node-resolve": "^16.0.0",
73
- "@rollup/plugin-terser": "^0.4.4",
74
76
  "@storybook/addon-essentials": "^8.6.0",
75
77
  "@storybook/web-components": "^8.6.0",
76
78
  "@storybook/web-components-vite": "^8.6.0",
@@ -78,7 +80,6 @@
78
80
  "@types/node": "^22.0.0",
79
81
  "@uswds/uswds": "^3.10.0",
80
82
  "@web/test-runner": "^0.20.0",
81
- "browser-sync": "^3.0.3",
82
83
  "husky": "^9.0.11",
83
84
  "js-beautify": "^1.15.1",
84
85
  "lint-staged": "^15.2.2",
@@ -86,7 +87,7 @@
86
87
  "mocha": "^11.0.0",
87
88
  "plop": "^4.0.1",
88
89
  "storybook": "^8.6.0",
89
- "typescript": "^5.8.0-beta",
90
+ "typescript": "^5.8.0",
90
91
  "wireit": "^0.14.9"
91
92
  },
92
93
  "lint-staged": {
@@ -7,7 +7,7 @@ import type { USAAccordionElement } from "./accordion.element.js";
7
7
  const meta = {
8
8
  title: "usa-accordion",
9
9
  tags: ["autodocs"],
10
- render(args) {
10
+ render() {
11
11
  return html`
12
12
  <usa-accordion id="first" name="ammendment">
13
13
  <h4 slot="heading">First Ammendment</h4>
@@ -16,6 +16,7 @@ const meta = {
16
16
  i === BUTTON_VARIANTS.length - 1 ? "" : "\n\n"
17
17
  }`,
18
18
  )}
19
+ <usa-button disabled>Disabled</usa-button>
19
20
  </div>
20
21
  `;
21
22
  },
@@ -15,7 +15,7 @@ describe("usa-card", () => {
15
15
  <usa-card-group>
16
16
  <usa-card>
17
17
  <usa-card-media>
18
- <img src="/img/built-to-grow--alt.jpg" alt="A placeholder image" />
18
+ <img alt="A placeholder image" />
19
19
  </usa-card-media>
20
20
 
21
21
  <usa-card-header>Card with media</usa-card-header>
@@ -0,0 +1,24 @@
1
+ import { css, element, html } from "@joist/element";
2
+
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ "usa-checkbox-group": USACheckboxGroupElement;
6
+ }
7
+ }
8
+
9
+ @element({
10
+ tagName: "usa-checkbox-group",
11
+ shadowDom: [
12
+ css`
13
+ :host {
14
+ display: flex;
15
+ gap: 0.75rem;
16
+ flex-direction: column;
17
+ }
18
+ `,
19
+ html`
20
+ <slot></slot>
21
+ `,
22
+ ],
23
+ })
24
+ export class USACheckboxGroupElement extends HTMLElement {}
@@ -33,7 +33,7 @@ declare global {
33
33
  cursor: pointer;
34
34
  font-size: 1.06rem;
35
35
  line-height: 1.3;
36
- flex-wrap: wrap;
36
+ width: 100%;
37
37
  }
38
38
 
39
39
  .checkbox {
@@ -43,19 +43,20 @@ declare global {
43
43
  align-items: center;
44
44
  justify-content: center;
45
45
  height: 1.25rem;
46
- width: 1.25rem;
46
+ min-width: 1.25rem;
47
+ max-width: 1.25rem;
47
48
  border-radius: 2px;
48
49
  position: relative;
49
50
  margin-right: 0.75rem;
50
51
  }
51
52
 
52
- input:checked + .checkbox {
53
- background-color: #005ea2;
54
- box-shadow: 0 0 0 2px #005ea2;
53
+ input:disabled + .checkbox {
54
+ background-color: #fff;
55
+ box-shadow: 0 0 0 2px #757575;
55
56
  }
56
57
 
57
- :host([disabled]) .checkbox {
58
- background-color: #fff;
58
+ input:disabled:is(:checked) + .checkbox {
59
+ background-color: #757575;
59
60
  box-shadow: 0 0 0 2px #757575;
60
61
  }
61
62
 
@@ -63,6 +64,11 @@ declare global {
63
64
  color: #757575;
64
65
  cursor: not-allowed;
65
66
  }
67
+
68
+ input:checked + .checkbox {
69
+ background-color: #005ea2;
70
+ box-shadow: 0 0 0 2px #005ea2;
71
+ }
66
72
 
67
73
  input:checked + .checkbox::after {
68
74
  content: " ";
@@ -98,6 +104,11 @@ declare global {
98
104
  background-color: rgba(0, 94, 162, 0.1);
99
105
  border-color: #005ea2;
100
106
  }
107
+
108
+ :host([tiled]) label:has(input:checked:is(:disabled)) {
109
+ background-color: #fff;
110
+ border-color: #757575;
111
+ }
101
112
  `,
102
113
  html`
103
114
  <label>
@@ -144,6 +155,7 @@ export class USACheckboxElement extends HTMLElement {
144
155
  checked: this.checked,
145
156
  name: this.name,
146
157
  disabled: this.disabled,
158
+ required: this.required,
147
159
  });
148
160
 
149
161
  this.#syncFormState();
@@ -154,6 +166,7 @@ export class USACheckboxElement extends HTMLElement {
154
166
  checked: this.checked,
155
167
  name: this.name,
156
168
  disabled: this.disabled,
169
+ required: this.required,
157
170
  });
158
171
 
159
172
  this.#syncFormState();
@@ -170,20 +183,20 @@ export class USACheckboxElement extends HTMLElement {
170
183
  #syncFormState() {
171
184
  const checkbox = this.#checkbox();
172
185
 
186
+ this.#internals.setValidity({});
187
+
173
188
  if (checkbox.checked) {
174
189
  this.#internals.setFormValue(this.value);
175
190
  } else {
176
191
  this.#internals.setFormValue(null);
177
192
  }
178
193
 
179
- if (this.required && !checkbox.checked) {
194
+ if (checkbox.validationMessage) {
180
195
  this.#internals.setValidity(
181
- { valueMissing: true },
182
- "Please check this box if you want to proceed",
183
- this.#checkbox(),
196
+ { customError: true },
197
+ checkbox.validationMessage,
198
+ checkbox,
184
199
  );
185
- } else {
186
- this.#internals.setValidity({});
187
200
  }
188
201
  }
189
202
  }
@@ -10,43 +10,32 @@ import type { USACheckboxElement } from "./checkbox.element.js";
10
10
  const meta = {
11
11
  title: "usa-checkbox",
12
12
  tags: ["autodocs"],
13
- render(args) {
13
+ render() {
14
14
  return html`
15
- <usa-checkbox
16
- name=${args.name}
17
- value=${ifDefined(args.value)}
18
- checked=${ifDefined(args.checked)}
19
- ?tiled=${args.tiled}
20
- ?disabled=${args.disabled}
21
- >
22
- Hello World
23
- ${when(
24
- args.description,
25
- () => html`<usa-description>${args.description}</usa-description>`,
26
- )}
27
- </usa-checkbox>
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>
28
35
  `;
29
36
  },
30
- argTypes: {
31
- name: {
32
- control: "text",
33
- },
34
- value: {
35
- control: "text",
36
- },
37
- description: {
38
- control: "text",
39
- },
40
- tiled: {
41
- control: "boolean",
42
- },
43
- },
44
- args: {
45
- name: "toc",
46
- value: "agree",
47
- tiled: false,
48
- disabled: false,
49
- },
37
+ argTypes: {},
38
+ args: {},
50
39
  } satisfies Meta<USACheckboxElement & { description: string }>;
51
40
 
52
41
  export default meta;
@@ -55,5 +44,54 @@ type Story = StoryObj<USACheckboxElement>;
55
44
 
56
45
  // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
57
46
  export const Primary: Story = {
58
- args: {},
47
+ render() {
48
+ return html`
49
+ <usa-checkbox-group>
50
+ <legend class="usa-legend">Select any historical figure</legend>
51
+
52
+ <usa-checkbox name="historical-figure" value="sojurner-truth">
53
+ Sojourner Truth
54
+ </usa-checkbox>
55
+
56
+ <usa-checkbox name="historical-figure" value="frederick-douglass">
57
+ Frederick Douglass
58
+ </usa-checkbox>
59
+
60
+ <usa-checkbox name="historical-figure" value="booker-t-washington">
61
+ Booker T. Washington
62
+ </usa-checkbox>
63
+
64
+ <usa-checkbox name="historical-figure" value="gw-carver" disabled>
65
+ George Washington Carver
66
+ </usa-checkbox>
67
+ </usa-checkbox-group>
68
+ `;
69
+ },
70
+ };
71
+
72
+ export const Tiled: Story = {
73
+ render() {
74
+ return html`
75
+ <usa-checkbox-group>
76
+ <legend class="usa-legend">Select any historical figure</legend>
77
+
78
+ <usa-checkbox name="historical-figure" value="sojurner-truth" tiled>
79
+ Sojourner Truth
80
+ <usa-description>This is optional text that can be used to describe the label in more detail.</usa-description>
81
+ </usa-checkbox>
82
+
83
+ <usa-checkbox name="historical-figure" value="frederick-douglass" tiled>
84
+ Frederick Douglass
85
+ </usa-checkbox>
86
+
87
+ <usa-checkbox name="historical-figure" value="booker-t-washington" tiled>
88
+ Booker T. Washington
89
+ </usa-checkbox>
90
+
91
+ <usa-checkbox name="historical-figure" value="gw-carver" tiled disabled>
92
+ George Washington Carver
93
+ </usa-checkbox>
94
+ </usa-checkbox-group>
95
+ `;
96
+ },
59
97
  };
@@ -44,8 +44,8 @@ declare global {
44
44
  <slot name="description"></slot>
45
45
  </div>
46
46
 
47
- <div class="meta">
48
- <slot name="meta"></slot>
47
+ <div class="slots">
48
+ <slot name="tags"></slot>
49
49
  </div>
50
50
  </div>
51
51
  `,
@@ -7,7 +7,7 @@ import type { USACollectionElement } from "./collection.element.js";
7
7
  const meta = {
8
8
  title: "usa-collection",
9
9
  tags: ["autodocs"],
10
- render(args) {
10
+ render() {
11
11
  return html`
12
12
  <usa-collection>
13
13
  <usa-collection-item>
@@ -16,8 +16,12 @@ const meta = {
16
16
  </h4>
17
17
 
18
18
  <div slot="description">
19
- Today, the Administration announces the winners of the Gears of Government Presidents Award. This program recognizes the contributions of individuals and teams across the federal workforce who make a profound difference in the lives of the American people.
19
+ Today, the Administration announces the winners of the Gears of Government President's Award. This program recognizes the contributions of individuals and teams across the federal workforce who make a profound difference in the lives of the American people.
20
20
  </div>
21
+
22
+ <usa-tag slot="tags" type="new">NEW</usa-tag>
23
+ <usa-tag slot="tags">PMA</usa-tag>
24
+ <usa-tag slot="tags">OMB</usa-tag>
21
25
  </usa-collection-item>
22
26
 
23
27
  <usa-collection-item>
@@ -28,8 +32,12 @@ const meta = {
28
32
  </h4>
29
33
 
30
34
  <div slot="description">
31
- Today, the Administration announces the winners of the Gears of Government Presidents Award. This program recognizes the contributions of individuals and teams across the federal workforce who make a profound difference in the lives of the American people.
35
+ Today, the Administration announces the winners of the Gears of Government President's Award. This program recognizes the contributions of individuals and teams across the federal workforce who make a profound difference in the lives of the American people.
32
36
  </div>
37
+
38
+ <usa-tag slot="tags" type="new">NEW</usa-tag>
39
+ <usa-tag slot="tags">PMA</usa-tag>
40
+ <usa-tag slot="tags">OMB</usa-tag>
33
41
  </usa-collection-item>
34
42
 
35
43
  <usa-collection-item>
@@ -40,6 +48,10 @@ const meta = {
40
48
  <div slot="description">
41
49
  In honor of National Women's Small Business Month, we've partnered with SBA's Office of Government Contracting and Business Development and Office of Program Performance, Analysis, and Evaluation to highlight the Women-Owned Small Businesses (WOSBs) data dashboard!
42
50
  </div>
51
+
52
+ <usa-tag slot="tags" type="new">NEW</usa-tag>
53
+ <usa-tag slot="tags">PMA</usa-tag>
54
+ <usa-tag slot="tags">OMB</usa-tag>
43
55
  </usa-collection-item>
44
56
 
45
57
  <usa-collection-item>
@@ -52,6 +64,10 @@ const meta = {
52
64
  <div slot="description">
53
65
  In honor of National Women's Small Business Month, we've partnered with SBA's Office of Government Contracting and Business Development and Office of Program Performance, Analysis, and Evaluation to highlight the Women-Owned Small Businesses (WOSBs) data dashboard!
54
66
  </div>
67
+
68
+ <usa-tag slot="tags" type="new">NEW</usa-tag>
69
+ <usa-tag slot="tags">PMA</usa-tag>
70
+ <usa-tag slot="tags">OMB</usa-tag>
55
71
  </usa-collection-item>
56
72
  </usa-collection>
57
73
  `;
package/src/lib/define.ts CHANGED
@@ -32,3 +32,4 @@ import "./card/card-group/card-group.element.js";
32
32
  import "./textarea/textarea.element.js";
33
33
  import "./collection/collection.element.js";
34
34
  import "./collection/collection-item/collection-item.element.js";
35
+ import "./checkbox/checkbox-group/checkbox-group.element.js";
@@ -106,6 +106,9 @@ export class USAFileInputElement extends HTMLElement {
106
106
  @attr()
107
107
  accessor accept = "";
108
108
 
109
+ @attr()
110
+ accessor required = false;
111
+
109
112
  @observe()
110
113
  accessor files: FileList | null = null;
111
114
 
@@ -119,12 +122,25 @@ export class USAFileInputElement extends HTMLElement {
119
122
  name: this.name,
120
123
  multiple: this.multiple,
121
124
  accept: this.accept,
125
+ required: this.required,
122
126
  });
123
127
  }
124
128
 
129
+ connectedCallback() {
130
+ const input = this.#input();
131
+
132
+ if (input.validationMessage) {
133
+ this.#internals.setValidity(
134
+ { customError: true },
135
+ input.validationMessage,
136
+ input,
137
+ );
138
+ }
139
+ }
140
+
125
141
  @effect()
126
142
  onChange() {
127
- this.#input({ files: this.files });
143
+ const input = this.#input({ files: this.files });
128
144
  this.#preview({ files: this.files });
129
145
 
130
146
  const box = this.#box();
@@ -140,6 +156,16 @@ export class USAFileInputElement extends HTMLElement {
140
156
  }
141
157
 
142
158
  this.#internals.setFormValue(formData);
159
+
160
+ if (input.validationMessage) {
161
+ this.#internals.setValidity(
162
+ { customError: true },
163
+ input.validationMessage,
164
+ input,
165
+ );
166
+ } else {
167
+ this.#internals.setValidity({});
168
+ }
143
169
  }
144
170
 
145
171
  @listen("change")
@@ -7,7 +7,7 @@ import type { USAFileInputElement } from "./file-input.element.js";
7
7
  const meta = {
8
8
  title: "usa-file-input",
9
9
  tags: ["autodocs"],
10
- render(args) {
10
+ render() {
11
11
  function onSubmit(e: Event) {
12
12
  e.preventDefault();
13
13
 
@@ -18,7 +18,7 @@ const meta = {
18
18
 
19
19
  return html`
20
20
  <form @submit=${onSubmit}>
21
- <usa-file-input name="upload">
21
+ <usa-file-input name="upload" required>
22
22
  Input accepts a single file
23
23
 
24
24
  <div slot="description">
@@ -29,7 +29,9 @@ declare global {
29
29
  `,
30
30
  ],
31
31
  })
32
- @injectable()
32
+ @injectable({
33
+ name: "usa-icon-ctx",
34
+ })
33
35
  export class USAIconElement extends HTMLElement {
34
36
  @attr()
35
37
  accessor icon: USAIcon = "accessibility_new";
@@ -45,9 +47,11 @@ export class USAIconElement extends HTMLElement {
45
47
  this.#updateIcon();
46
48
  }
47
49
 
48
- attributeChangedCallback() {
50
+ attributeChangedCallback(_: string, newVal: string, oldVal: string) {
49
51
  if (this.#injected) {
50
- this.#updateIcon();
52
+ if (newVal !== oldVal) {
53
+ this.#updateIcon();
54
+ }
51
55
  }
52
56
  }
53
57
 
@@ -55,7 +59,13 @@ export class USAIconElement extends HTMLElement {
55
59
  const icon = this.#icon();
56
60
 
57
61
  if (this.shadowRoot) {
58
- this.shadowRoot.append(await icon.getIcon(this.icon));
62
+ const currentIcon = await icon.getIcon(this.icon);
63
+
64
+ if (this.shadowRoot.firstElementChild) {
65
+ this.shadowRoot.firstElementChild.replaceWith(currentIcon);
66
+ } else {
67
+ this.shadowRoot.append(currentIcon);
68
+ }
59
69
  }
60
70
  }
61
71
  }
@@ -102,6 +102,18 @@ export class USATextInputElement
102
102
  @attr()
103
103
  accessor placeholder = "";
104
104
 
105
+ @attr()
106
+ accessor min = "";
107
+
108
+ @attr()
109
+ accessor max = "";
110
+
111
+ @attr()
112
+ accessor minLength = -1;
113
+
114
+ @attr()
115
+ accessor maxLength = -1;
116
+
105
117
  @attr()
106
118
  accessor required = false;
107
119
 
@@ -134,9 +146,17 @@ export class USATextInputElement
134
146
  }
135
147
 
136
148
  attributeChangedCallback() {
137
- const { autocomplete, placeholder, name, type } = this;
138
-
139
- this.#input({ autocomplete, placeholder, name, type });
149
+ this.#input({
150
+ autocomplete: this.autocomplete,
151
+ placeholder: this.placeholder,
152
+ name: this.name,
153
+ type: this.type,
154
+ required: this.required,
155
+ min: this.min,
156
+ max: this.max,
157
+ minLength: this.minLength,
158
+ maxLength: this.maxLength,
159
+ });
140
160
  }
141
161
 
142
162
  connectedCallback() {
@@ -182,12 +202,15 @@ export class USATextInputElement
182
202
  #syncFormState() {
183
203
  const input = this.#input();
184
204
 
205
+ this.#internals.setValidity({});
185
206
  this.#internals.setFormValue(input.value);
186
207
 
187
- if (this.required && !input.value) {
188
- this.#internals.setValidity({ valueMissing: true }, "Required", input);
189
- } else {
190
- this.#internals.setValidity({});
208
+ if (input.validationMessage) {
209
+ this.#internals.setValidity(
210
+ { customError: true },
211
+ input.validationMessage,
212
+ input,
213
+ );
191
214
  }
192
215
  }
193
216
 
@@ -3,6 +3,7 @@ import { StaticToken } from "@joist/di";
3
3
  export interface RadioContainer extends Node {
4
4
  name: string;
5
5
  value: string;
6
+ required: boolean;
6
7
  addRadioOption(el: HTMLElement): void;
7
8
  }
8
9
 
@@ -37,22 +37,24 @@ export class USARadioOptionElement extends HTMLElement {
37
37
  @attr()
38
38
  accessor value = "";
39
39
 
40
+ @attr()
41
+ accessor disabled = false;
42
+
40
43
  #label = query("label");
41
44
  #input = query("input");
42
45
  #slot = query("slot");
43
- #radio = inject(RADIO_CTX);
46
+ #radioCtx = inject(RADIO_CTX);
44
47
 
45
48
  #observer = new MutationObserver(() => {
46
- const radio = this.#radio();
49
+ this.#sync();
50
+ });
47
51
 
52
+ attributeChangedCallback() {
48
53
  this.#input({
49
- name: radio.name,
50
- checked: radio.value === this.value,
54
+ value: this.value,
55
+ disabled: this.disabled,
51
56
  });
52
- });
53
57
 
54
- attributeChangedCallback() {
55
- this.#input({ value: this.value });
56
58
  this.#slot({ name: this.value });
57
59
 
58
60
  this.slot = this.value;
@@ -60,17 +62,14 @@ export class USARadioOptionElement extends HTMLElement {
60
62
 
61
63
  @injected()
62
64
  onInjected() {
63
- const radio = this.#radio();
65
+ const radioCtx = this.#radioCtx();
64
66
 
65
- radio.addRadioOption(this.#label());
67
+ radioCtx.addRadioOption(this.#label());
66
68
 
67
- this.#input({
68
- name: radio.name,
69
- checked: radio.value === this.value,
70
- });
69
+ this.#sync();
71
70
 
72
- this.#observer.observe(radio, {
73
- attributeFilter: ["value", "name"],
71
+ this.#observer.observe(radioCtx, {
72
+ attributeFilter: ["value", "name", "required"],
74
73
  });
75
74
  }
76
75
 
@@ -79,4 +78,14 @@ export class USARadioOptionElement extends HTMLElement {
79
78
 
80
79
  this.#observer.disconnect();
81
80
  }
81
+
82
+ #sync() {
83
+ const radioCtx = this.#radioCtx();
84
+
85
+ this.#input({
86
+ name: radioCtx.name,
87
+ checked: radioCtx.value === this.value,
88
+ required: radioCtx.required,
89
+ });
90
+ }
82
91
  }