@noctuatech/uswds 0.0.35 → 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 (75) 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 +11 -8
  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 +19 -13
  17. package/src/lib/radio/radio.element.ts +9 -11
  18. package/src/lib/select/select.element.ts +16 -6
  19. package/src/lib/tag/tag.element.ts +12 -2
  20. package/src/lib/textarea/textarea.element.ts +18 -13
  21. package/src/lib.ts +1 -0
  22. package/target/lib/accordion/accordion.stories.d.ts +1 -1
  23. package/target/lib/accordion/accordion.stories.js +1 -1
  24. package/target/lib/accordion/accordion.stories.js.map +1 -1
  25. package/target/lib/button/button.stories.js +1 -0
  26. package/target/lib/button/button.stories.js.map +1 -1
  27. package/target/lib/card/card.test.js +1 -1
  28. package/target/lib/checkbox/checkbox-group/checkbox-group.element.d.ts +7 -0
  29. package/target/lib/checkbox/checkbox-group/checkbox-group.element.js +36 -0
  30. package/target/lib/checkbox/checkbox-group/checkbox-group.element.js.map +1 -0
  31. package/target/lib/checkbox/checkbox.element.js +8 -7
  32. package/target/lib/checkbox/checkbox.element.js.map +1 -1
  33. package/target/lib/checkbox/checkbox.stories.d.ts +4 -23
  34. package/target/lib/checkbox/checkbox.stories.js +72 -34
  35. package/target/lib/checkbox/checkbox.stories.js.map +1 -1
  36. package/target/lib/collection/collection-item/collection-item.element.js +2 -2
  37. package/target/lib/collection/collection.stories.d.ts +1 -1
  38. package/target/lib/collection/collection.stories.js +19 -3
  39. package/target/lib/collection/collection.stories.js.map +1 -1
  40. package/target/lib/define.d.ts +1 -0
  41. package/target/lib/define.js +1 -0
  42. package/target/lib/define.js.map +1 -1
  43. package/target/lib/file-input/file-input.element.d.ts +2 -0
  44. package/target/lib/file-input/file-input.element.js +23 -2
  45. package/target/lib/file-input/file-input.element.js.map +1 -1
  46. package/target/lib/file-input/file-input.stories.d.ts +1 -1
  47. package/target/lib/file-input/file-input.stories.js +2 -2
  48. package/target/lib/file-input/file-input.stories.js.map +1 -1
  49. package/target/lib/form/validation.d.ts +2 -0
  50. package/target/lib/form/validation.js +27 -0
  51. package/target/lib/form/validation.js.map +1 -0
  52. package/target/lib/icon/icon.element.d.ts +1 -1
  53. package/target/lib/icon/icon.element.js +14 -4
  54. package/target/lib/icon/icon.element.js.map +1 -1
  55. package/target/lib/input/input.element.d.ts +4 -0
  56. package/target/lib/input/input.element.js +47 -8
  57. package/target/lib/input/input.element.js.map +1 -1
  58. package/target/lib/radio/context.d.ts +1 -0
  59. package/target/lib/radio/context.js.map +1 -1
  60. package/target/lib/radio/radio-option/radio-option.element.js +17 -13
  61. package/target/lib/radio/radio-option/radio-option.element.js.map +1 -1
  62. package/target/lib/radio/radio.element.d.ts +0 -1
  63. package/target/lib/radio/radio.element.js +7 -8
  64. package/target/lib/radio/radio.element.js.map +1 -1
  65. package/target/lib/select/select.element.js +13 -4
  66. package/target/lib/select/select.element.js.map +1 -1
  67. package/target/lib/tag/tag.element.d.ts +3 -0
  68. package/target/lib/tag/tag.element.js +17 -3
  69. package/target/lib/tag/tag.element.js.map +1 -1
  70. package/target/lib/textarea/textarea.element.d.ts +1 -1
  71. package/target/lib/textarea/textarea.element.js +12 -11
  72. package/target/lib/textarea/textarea.element.js.map +1 -1
  73. package/target/lib.d.ts +1 -0
  74. package/target/lib.js +1 -0
  75. 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.35",
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,7 +43,8 @@ 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;
@@ -154,6 +155,7 @@ export class USACheckboxElement extends HTMLElement {
154
155
  checked: this.checked,
155
156
  name: this.name,
156
157
  disabled: this.disabled,
158
+ required: this.required,
157
159
  });
158
160
 
159
161
  this.#syncFormState();
@@ -164,6 +166,7 @@ export class USACheckboxElement extends HTMLElement {
164
166
  checked: this.checked,
165
167
  name: this.name,
166
168
  disabled: this.disabled,
169
+ required: this.required,
167
170
  });
168
171
 
169
172
  this.#syncFormState();
@@ -180,20 +183,20 @@ export class USACheckboxElement extends HTMLElement {
180
183
  #syncFormState() {
181
184
  const checkbox = this.#checkbox();
182
185
 
186
+ this.#internals.setValidity({});
187
+
183
188
  if (checkbox.checked) {
184
189
  this.#internals.setFormValue(this.value);
185
190
  } else {
186
191
  this.#internals.setFormValue(null);
187
192
  }
188
193
 
189
- if (this.required && !checkbox.checked) {
194
+ if (checkbox.validationMessage) {
190
195
  this.#internals.setValidity(
191
- { valueMissing: true },
192
- "Please check this box if you want to proceed",
193
- this.#checkbox(),
196
+ { customError: true },
197
+ checkbox.validationMessage,
198
+ checkbox,
194
199
  );
195
- } else {
196
- this.#internals.setValidity({});
197
200
  }
198
201
  }
199
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
 
@@ -43,19 +43,18 @@ export class USARadioOptionElement extends HTMLElement {
43
43
  #label = query("label");
44
44
  #input = query("input");
45
45
  #slot = query("slot");
46
- #radio = inject(RADIO_CTX);
46
+ #radioCtx = inject(RADIO_CTX);
47
47
 
48
48
  #observer = new MutationObserver(() => {
49
- const radio = this.#radio();
49
+ this.#sync();
50
+ });
50
51
 
52
+ attributeChangedCallback() {
51
53
  this.#input({
52
- name: radio.name,
53
- checked: radio.value === this.value,
54
+ value: this.value,
55
+ disabled: this.disabled,
54
56
  });
55
- });
56
57
 
57
- attributeChangedCallback() {
58
- this.#input({ value: this.value, disabled: this.disabled });
59
58
  this.#slot({ name: this.value });
60
59
 
61
60
  this.slot = this.value;
@@ -63,17 +62,14 @@ export class USARadioOptionElement extends HTMLElement {
63
62
 
64
63
  @injected()
65
64
  onInjected() {
66
- const radioCtx = this.#radio();
65
+ const radioCtx = this.#radioCtx();
67
66
 
68
67
  radioCtx.addRadioOption(this.#label());
69
68
 
70
- this.#input({
71
- name: radioCtx.name,
72
- checked: radioCtx.value === this.value,
73
- });
69
+ this.#sync();
74
70
 
75
71
  this.#observer.observe(radioCtx, {
76
- attributeFilter: ["value", "name"],
72
+ attributeFilter: ["value", "name", "required"],
77
73
  });
78
74
  }
79
75
 
@@ -82,4 +78,14 @@ export class USARadioOptionElement extends HTMLElement {
82
78
 
83
79
  this.#observer.disconnect();
84
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
+ }
85
91
  }
@@ -134,15 +134,15 @@ export class USARadioElement extends HTMLElement implements RadioContainer {
134
134
  accessor tiled = false;
135
135
 
136
136
  #internals = this.attachInternals();
137
- #legend = query("#legend");
138
-
139
- connectedCallback() {
140
- this.#syncFormState();
141
- }
137
+ #firstInput: HTMLInputElement | null = null;
142
138
 
143
139
  addRadioOption(el: HTMLElement) {
144
140
  this.shadowRoot?.append(el);
145
141
 
142
+ if (this.#firstInput === null) {
143
+ this.#firstInput = el.querySelector("input");
144
+ }
145
+
146
146
  this.#syncFormState();
147
147
  }
148
148
 
@@ -161,13 +161,11 @@ export class USARadioElement extends HTMLElement implements RadioContainer {
161
161
  this.#internals.setFormValue(this.value);
162
162
  this.#internals.setValidity({});
163
163
 
164
- if (this.required && !this.value) {
165
- const input = this.shadowRoot?.querySelector("input");
166
-
164
+ if (this.#firstInput?.validationMessage) {
167
165
  this.#internals.setValidity(
168
- { valueMissing: true },
169
- "Please select an option if you want to proceed",
170
- input ?? this.#legend(),
166
+ { customError: true },
167
+ this.#firstInput.validationMessage,
168
+ this.#firstInput,
171
169
  );
172
170
  }
173
171
  }