@grantcodes/ui 2.15.6 → 2.16.0

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 (300) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/custom-elements.json +476 -346
  3. package/package.json +23 -23
  4. package/src/components/accordion/accordion-item.component.js +17 -19
  5. package/src/components/accordion/accordion-item.css +40 -44
  6. package/src/components/accordion/accordion.component.js +7 -7
  7. package/src/components/accordion/accordion.css +5 -5
  8. package/src/components/accordion/accordion.js +6 -6
  9. package/src/components/accordion/accordion.react.js +6 -6
  10. package/src/components/accordion/accordion.stories.js +10 -10
  11. package/src/components/accordion/accordion.test.js +100 -100
  12. package/src/components/accordion/index.js +6 -6
  13. package/src/components/app-bar/app-bar.component.js +86 -92
  14. package/src/components/app-bar/app-bar.css +187 -203
  15. package/src/components/app-bar/app-bar.js +3 -3
  16. package/src/components/app-bar/app-bar.react.js +9 -9
  17. package/src/components/app-bar/app-bar.stories.js +20 -20
  18. package/src/components/app-bar/app-bar.test.js +190 -237
  19. package/src/components/app-bar/index.js +1 -1
  20. package/src/components/app-bar/nav-link.component.js +7 -9
  21. package/src/components/app-bar/nav-link.css +23 -28
  22. package/src/components/app-bar/nav-link.js +3 -3
  23. package/src/components/app-bar/nav-link.react.js +6 -6
  24. package/src/components/avatar/avatar.component.js +79 -68
  25. package/src/components/avatar/avatar.css +18 -18
  26. package/src/components/avatar/avatar.js +3 -3
  27. package/src/components/avatar/avatar.react.js +6 -6
  28. package/src/components/avatar/avatar.stories.js +24 -23
  29. package/src/components/avatar/avatar.test.js +128 -84
  30. package/src/components/avatar/index.js +1 -1
  31. package/src/components/badge/badge.component.js +23 -23
  32. package/src/components/badge/badge.css +39 -52
  33. package/src/components/badge/badge.js +3 -3
  34. package/src/components/badge/badge.react.js +6 -6
  35. package/src/components/badge/badge.stories.js +23 -23
  36. package/src/components/badge/badge.test.js +53 -68
  37. package/src/components/badge/index.js +1 -1
  38. package/src/components/breadcrumb/breadcrumb.component.js +95 -96
  39. package/src/components/breadcrumb/breadcrumb.css +42 -44
  40. package/src/components/breadcrumb/breadcrumb.js +6 -9
  41. package/src/components/breadcrumb/breadcrumb.react.js +9 -12
  42. package/src/components/breadcrumb/breadcrumb.stories.js +5 -5
  43. package/src/components/breadcrumb/breadcrumb.test.js +139 -151
  44. package/src/components/breadcrumb/index.js +1 -1
  45. package/src/components/button/button.component.js +101 -103
  46. package/src/components/button/button.css +90 -94
  47. package/src/components/button/button.js +3 -3
  48. package/src/components/button/button.react.js +6 -6
  49. package/src/components/button/button.stories.js +25 -25
  50. package/src/components/button/button.test.js +324 -344
  51. package/src/components/button/index.js +1 -1
  52. package/src/components/button-group/button-group.component.js +18 -18
  53. package/src/components/button-group/button-group.css +16 -16
  54. package/src/components/button-group/button-group.js +3 -3
  55. package/src/components/button-group/button-group.react.js +6 -6
  56. package/src/components/button-group/button-group.stories.js +23 -23
  57. package/src/components/button-group/button-group.test.js +54 -54
  58. package/src/components/button-group/index.js +1 -1
  59. package/src/components/card/card.component.js +31 -33
  60. package/src/components/card/card.css +85 -85
  61. package/src/components/card/card.js +4 -4
  62. package/src/components/card/card.react.js +6 -6
  63. package/src/components/card/card.stories.js +27 -26
  64. package/src/components/card/card.test.js +56 -56
  65. package/src/components/card/index.js +1 -1
  66. package/src/components/code-preview/code-preview.component.js +47 -48
  67. package/src/components/code-preview/code-preview.css +11 -11
  68. package/src/components/code-preview/code-preview.js +3 -3
  69. package/src/components/code-preview/code-preview.react.js +6 -6
  70. package/src/components/code-preview/code-preview.stories.js +29 -29
  71. package/src/components/code-preview/code-preview.test.js +112 -135
  72. package/src/components/code-preview/index.js +1 -1
  73. package/src/components/container/container.component.js +27 -27
  74. package/src/components/container/container.css +22 -22
  75. package/src/components/container/container.js +3 -3
  76. package/src/components/container/container.react.js +6 -6
  77. package/src/components/container/container.stories.js +44 -44
  78. package/src/components/container/container.test.js +54 -66
  79. package/src/components/container/index.js +1 -1
  80. package/src/components/countdown/countdown.component.js +160 -162
  81. package/src/components/countdown/countdown.css +50 -34
  82. package/src/components/countdown/countdown.js +3 -3
  83. package/src/components/countdown/countdown.react.js +6 -6
  84. package/src/components/countdown/countdown.stories.js +34 -34
  85. package/src/components/countdown/index.js +1 -1
  86. package/src/components/cta/cta.component.js +77 -81
  87. package/src/components/cta/cta.css +24 -24
  88. package/src/components/cta/cta.js +3 -3
  89. package/src/components/cta/cta.react.js +6 -6
  90. package/src/components/cta/cta.stories.js +32 -32
  91. package/src/components/cta/index.js +1 -1
  92. package/src/components/dialog/dialog.component.js +95 -58
  93. package/src/components/dialog/dialog.css +45 -50
  94. package/src/components/dialog/dialog.js +3 -3
  95. package/src/components/dialog/dialog.react.js +6 -6
  96. package/src/components/dialog/dialog.stories.js +38 -29
  97. package/src/components/dialog/dialog.test.js +188 -102
  98. package/src/components/dialog/index.js +1 -1
  99. package/src/components/dropdown/dropdown.component.js +220 -201
  100. package/src/components/dropdown/dropdown.css +60 -84
  101. package/src/components/dropdown/dropdown.js +6 -9
  102. package/src/components/dropdown/dropdown.react.js +15 -15
  103. package/src/components/dropdown/dropdown.stories.js +10 -10
  104. package/src/components/dropdown/dropdown.test.js +211 -157
  105. package/src/components/dropdown/index.js +1 -1
  106. package/src/components/dropzone/dropzone.component.js +126 -129
  107. package/src/components/dropzone/dropzone.css +35 -37
  108. package/src/components/dropzone/dropzone.js +3 -3
  109. package/src/components/dropzone/dropzone.react.js +6 -6
  110. package/src/components/dropzone/dropzone.stories.js +28 -28
  111. package/src/components/dropzone/dropzone.test.js +91 -99
  112. package/src/components/dropzone/index.js +1 -1
  113. package/src/components/feature-list/feature-list.component.js +78 -86
  114. package/src/components/feature-list/feature-list.css +37 -37
  115. package/src/components/feature-list/feature-list.js +3 -3
  116. package/src/components/feature-list/feature-list.react.js +6 -6
  117. package/src/components/feature-list/feature-list.stories.js +86 -90
  118. package/src/components/feature-list/index.js +1 -1
  119. package/src/components/footer/footer-column.component.js +7 -7
  120. package/src/components/footer/footer-column.css +22 -22
  121. package/src/components/footer/footer.component.js +19 -19
  122. package/src/components/footer/footer.css +38 -38
  123. package/src/components/footer/footer.js +8 -8
  124. package/src/components/footer/footer.react.js +9 -9
  125. package/src/components/footer/footer.stories.js +9 -9
  126. package/src/components/footer/footer.test.js +101 -111
  127. package/src/components/footer/index.js +2 -2
  128. package/src/components/form-field/form-field.component.js +167 -125
  129. package/src/components/form-field/form-field.css +24 -24
  130. package/src/components/form-field/form-field.js +3 -3
  131. package/src/components/form-field/form-field.react.js +6 -6
  132. package/src/components/form-field/form-field.stories.js +53 -44
  133. package/src/components/form-field/form-field.test.js +280 -134
  134. package/src/components/form-field/index.js +1 -1
  135. package/src/components/gallery/gallery-image.component.js +30 -30
  136. package/src/components/gallery/gallery-image.js +3 -3
  137. package/src/components/gallery/gallery-image.react.js +6 -6
  138. package/src/components/gallery/gallery.component.js +24 -17
  139. package/src/components/gallery/gallery.css +79 -72
  140. package/src/components/gallery/gallery.js +3 -3
  141. package/src/components/gallery/gallery.react.js +6 -6
  142. package/src/components/gallery/gallery.stories.js +69 -68
  143. package/src/components/gallery/gallery.test.js +95 -88
  144. package/src/components/gallery/index.js +2 -2
  145. package/src/components/hero/hero.component.js +53 -57
  146. package/src/components/hero/hero.css +27 -27
  147. package/src/components/hero/hero.js +3 -3
  148. package/src/components/hero/hero.react.js +6 -6
  149. package/src/components/hero/hero.stories.js +39 -39
  150. package/src/components/hero/index.js +1 -1
  151. package/src/components/icon/icon.component.js +10 -10
  152. package/src/components/icon/icon.css +13 -13
  153. package/src/components/icon/icon.js +4 -4
  154. package/src/components/icon/icon.react.js +6 -6
  155. package/src/components/icon/icon.stories.js +8 -9
  156. package/src/components/icon/icon.test.js +54 -55
  157. package/src/components/icon/index.js +1 -1
  158. package/src/components/loading/index.js +1 -1
  159. package/src/components/loading/loading.component.js +41 -14
  160. package/src/components/loading/loading.css +47 -30
  161. package/src/components/loading/loading.js +4 -4
  162. package/src/components/loading/loading.react.js +6 -6
  163. package/src/components/loading/loading.stories.js +19 -18
  164. package/src/components/loading/loading.test.js +77 -54
  165. package/src/components/logo-cloud/index.js +1 -1
  166. package/src/components/logo-cloud/logo-cloud.component.js +39 -43
  167. package/src/components/logo-cloud/logo-cloud.css +31 -31
  168. package/src/components/logo-cloud/logo-cloud.js +3 -3
  169. package/src/components/logo-cloud/logo-cloud.react.js +6 -6
  170. package/src/components/logo-cloud/logo-cloud.stories.js +80 -80
  171. package/src/components/map/index.js +1 -1
  172. package/src/components/map/map.component.js +103 -107
  173. package/src/components/map/map.css +22 -22
  174. package/src/components/map/map.js +3 -3
  175. package/src/components/map/map.react.js +6 -6
  176. package/src/components/map/map.stories.js +38 -38
  177. package/src/components/media-text/index.js +1 -1
  178. package/src/components/media-text/media-text.component.js +76 -70
  179. package/src/components/media-text/media-text.css +32 -32
  180. package/src/components/media-text/media-text.js +3 -3
  181. package/src/components/media-text/media-text.react.js +6 -6
  182. package/src/components/media-text/media-text.stories.js +45 -45
  183. package/src/components/media-text/media-text.test.js +72 -0
  184. package/src/components/newsletter/index.js +1 -1
  185. package/src/components/newsletter/newsletter.component.js +56 -64
  186. package/src/components/newsletter/newsletter.css +48 -48
  187. package/src/components/newsletter/newsletter.js +3 -3
  188. package/src/components/newsletter/newsletter.react.js +6 -6
  189. package/src/components/newsletter/newsletter.stories.js +35 -35
  190. package/src/components/notice/index.js +1 -1
  191. package/src/components/notice/notice.component.js +66 -55
  192. package/src/components/notice/notice.css +37 -38
  193. package/src/components/notice/notice.js +3 -3
  194. package/src/components/notice/notice.react.js +6 -6
  195. package/src/components/notice/notice.stories.js +54 -53
  196. package/src/components/notice/notice.test.js +210 -156
  197. package/src/components/pagination/index.js +1 -1
  198. package/src/components/pagination/pagination.component.js +44 -44
  199. package/src/components/pagination/pagination.css +6 -6
  200. package/src/components/pagination/pagination.js +4 -4
  201. package/src/components/pagination/pagination.react.js +6 -6
  202. package/src/components/pagination/pagination.stories.js +20 -18
  203. package/src/components/pagination/pagination.test.js +95 -102
  204. package/src/components/person/index.js +1 -1
  205. package/src/components/person/person.component.js +29 -29
  206. package/src/components/person/person.css +17 -17
  207. package/src/components/person/person.js +4 -4
  208. package/src/components/person/person.react.js +6 -6
  209. package/src/components/person/person.stories.js +34 -33
  210. package/src/components/person/person.test.js +55 -68
  211. package/src/components/pricing/index.js +1 -1
  212. package/src/components/pricing/pricing.component.js +58 -70
  213. package/src/components/pricing/pricing.css +65 -65
  214. package/src/components/pricing/pricing.js +3 -3
  215. package/src/components/pricing/pricing.react.js +6 -6
  216. package/src/components/pricing/pricing.stories.js +96 -96
  217. package/src/components/sidebar/index.js +1 -1
  218. package/src/components/sidebar/sidebar.component.js +157 -130
  219. package/src/components/sidebar/sidebar.css +118 -115
  220. package/src/components/sidebar/sidebar.js +4 -4
  221. package/src/components/sidebar/sidebar.react.js +10 -10
  222. package/src/components/sidebar/sidebar.stories.js +10 -10
  223. package/src/components/sidebar/sidebar.test.js +250 -230
  224. package/src/components/stats/index.js +1 -1
  225. package/src/components/stats/stats.component.js +42 -50
  226. package/src/components/stats/stats.css +29 -29
  227. package/src/components/stats/stats.js +3 -3
  228. package/src/components/stats/stats.react.js +6 -6
  229. package/src/components/stats/stats.stories.js +40 -40
  230. package/src/components/tabs/index.js +2 -2
  231. package/src/components/tabs/internal/tabs-button.component.js +16 -27
  232. package/src/components/tabs/internal/tabs-button.js +3 -3
  233. package/src/components/tabs/internal/tabs-button.react.js +6 -6
  234. package/src/components/tabs/internal/tabs-item.component.js +30 -30
  235. package/src/components/tabs/tab.component.js +9 -11
  236. package/src/components/tabs/tab.js +3 -3
  237. package/src/components/tabs/tab.react.js +6 -6
  238. package/src/components/tabs/tabs.component.js +107 -109
  239. package/src/components/tabs/tabs.css +50 -59
  240. package/src/components/tabs/tabs.js +4 -4
  241. package/src/components/tabs/tabs.react.js +6 -6
  242. package/src/components/tabs/tabs.stories.js +23 -22
  243. package/src/components/tabs/tabs.test.js +201 -147
  244. package/src/components/testimonials/index.js +1 -1
  245. package/src/components/testimonials/testimonials.component.js +47 -51
  246. package/src/components/testimonials/testimonials.css +31 -31
  247. package/src/components/testimonials/testimonials.js +3 -3
  248. package/src/components/testimonials/testimonials.react.js +6 -6
  249. package/src/components/testimonials/testimonials.stories.js +49 -51
  250. package/src/components/toast/index.js +1 -1
  251. package/src/components/toast/toast.component.js +217 -148
  252. package/src/components/toast/toast.css +101 -99
  253. package/src/components/toast/toast.js +6 -9
  254. package/src/components/toast/toast.react.js +12 -12
  255. package/src/components/toast/toast.stories.js +75 -49
  256. package/src/components/toast/toast.test.js +260 -221
  257. package/src/components/tooltip/index.js +1 -1
  258. package/src/components/tooltip/tooltip.component.js +56 -49
  259. package/src/components/tooltip/tooltip.css +61 -50
  260. package/src/components/tooltip/tooltip.js +4 -4
  261. package/src/components/tooltip/tooltip.react.js +6 -6
  262. package/src/components/tooltip/tooltip.stories.js +24 -23
  263. package/src/components/tooltip/tooltip.test.js +143 -155
  264. package/src/constructor.test.js +31 -0
  265. package/src/css/base.css +1 -0
  266. package/src/css/colors.stories.js +120 -122
  267. package/src/css/elements/a.css +28 -28
  268. package/src/css/elements/forms/button.css +1 -1
  269. package/src/css/elements/forms/input.css +80 -80
  270. package/src/css/elements/forms/label.css +3 -3
  271. package/src/css/elements/media/image.css +2 -2
  272. package/src/css/elements.stories.js +48 -49
  273. package/src/css/helpers.css +8 -8
  274. package/src/css/layers.stories.js +5 -5
  275. package/src/css/reset.css +102 -102
  276. package/src/css/themes/todomap.css +1 -1
  277. package/src/css/tokens.stories.js +50 -53
  278. package/src/css/typography.css +30 -32
  279. package/src/css/typography.stories.js +92 -92
  280. package/src/css/util/focus-ring.css +12 -20
  281. package/src/css/view-transitions.css +16 -0
  282. package/src/exports.test.js +89 -105
  283. package/src/icons.js +1 -1
  284. package/src/lib/generate-id.js +1 -1
  285. package/src/lib/slot-presence-controller.js +48 -0
  286. package/src/lib/styles/all.css +38 -37
  287. package/src/lib/styles/focus-ring.css +12 -20
  288. package/src/lib/view-transition.js +42 -0
  289. package/src/lib/view-transition.test.js +105 -0
  290. package/src/main.js +29 -29
  291. package/src/pages/agency.stories.js +59 -59
  292. package/src/pages/blog-post.stories.js +48 -48
  293. package/src/pages/saas-landing.stories.js +180 -180
  294. package/src/react.js +44 -44
  295. package/src/react.test.js +42 -51
  296. package/src/test-utils/assert-helpers.js +39 -58
  297. package/src/test-utils/events.js +32 -34
  298. package/src/test-utils/fixture.js +31 -31
  299. package/src/test-utils/index.js +3 -3
  300. package/src/types.d.ts +3 -3
@@ -1,349 +1,329 @@
1
- import { describe, it, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { fixture, cleanup, click } from "../../test-utils/index.js";
4
- import { GrantCodesButton } from "./button.component.js";
5
- import "./button.js";
6
-
7
- describe("Button Component", () => {
8
- let element;
9
-
10
- afterEach(() => {
11
- cleanup(element);
12
- });
13
-
14
- it("should render with default properties", async () => {
15
- element = await fixture("grantcodes-button");
16
- assert.ok(element, "Element should be created");
17
- assert.ok(element.shadowRoot, "Element should have shadow root");
18
- });
19
-
20
- it("should render button element by default", async () => {
21
- element = await fixture("grantcodes-button");
22
- const button = element.shadowRoot.querySelector("button");
23
- assert.ok(button, "Button element should exist");
24
- });
25
-
26
- it("should render link when href is provided", async () => {
27
- element = await fixture("grantcodes-button", {
28
- href: "https://example.com",
29
- });
30
- await element.updateComplete;
31
-
32
- const link = element.shadowRoot.querySelector("a");
33
- assert.ok(link, "Link element should exist");
34
- const href = link.getAttribute("href");
35
- assert.ok(href, "Link should have href attribute");
36
- assert.ok(
37
- href.includes("example.com") || href === "https://example.com",
38
- "Link href should contain example.com",
39
- );
40
- });
41
-
42
- it("should be disabled when disabled property is set", async () => {
43
- element = await fixture("grantcodes-button", {
44
- disabled: true,
45
- });
46
-
47
- const button = element.shadowRoot.querySelector("button");
48
- assert.ok(button.disabled, "Button should be disabled");
49
- assert.ok(
50
- button.hasAttribute("disabled"),
51
- "Button should have disabled attribute",
52
- );
53
- });
54
-
55
- it("should emit click event when clicked", async () => {
56
- element = await fixture("grantcodes-button");
57
- const button = element.shadowRoot.querySelector("button");
58
-
59
- let clicked = false;
60
- element.addEventListener("click", () => {
61
- clicked = true;
62
- });
63
-
64
- click(button);
65
- assert.ok(clicked, "Click event should have fired");
66
- });
67
-
68
- it("should not emit click event when disabled", async () => {
69
- element = await fixture("grantcodes-button", {
70
- disabled: true,
71
- });
72
- const button = element.shadowRoot.querySelector("button");
73
-
74
- let clicked = false;
75
- element.addEventListener("click", () => {
76
- clicked = true;
77
- });
78
-
79
- click(button);
80
- assert.ok(!clicked, "Click event should not fire when disabled");
81
- });
82
-
83
- it("should support different button types", async () => {
84
- const types = ["button", "submit", "reset"];
85
-
86
- for (const type of types) {
87
- const testElement = await fixture("grantcodes-button", { type });
88
- // Internal button is always type="button" — form behavior is handled
89
- // by the host element via FACE (requestSubmit/reset on ElementInternals)
90
- const button = testElement.shadowRoot.querySelector("button");
91
- assert.strictEqual(button.type, "button", `Internal button should always be type="button"`);
92
- assert.strictEqual(testElement.type, type, `Host type property should be ${type}`);
93
- cleanup(testElement);
94
- }
95
- });
96
-
97
- it("should render slotted content", async () => {
98
- element = await fixture("grantcodes-button");
99
- element.textContent = "Click me";
100
-
101
- await element.updateComplete;
102
-
103
- const slot = element.shadowRoot.querySelector("slot");
104
- assert.ok(slot, "Slot should exist");
105
- });
1
+ import { strict as assert } from 'node:assert';
2
+ import { afterEach, describe, it } from 'node:test';
3
+ import { cleanup, click, fixture } from '../../test-utils/index.js';
4
+ import { GrantCodesButton } from './button.component.js';
5
+ import './button.js';
6
+
7
+ describe('Button Component', () => {
8
+ let element;
9
+
10
+ afterEach(() => {
11
+ cleanup(element);
12
+ });
13
+
14
+ it('should render with default properties', async () => {
15
+ element = await fixture('grantcodes-button');
16
+ assert.ok(element, 'Element should be created');
17
+ assert.ok(element.shadowRoot, 'Element should have shadow root');
18
+ });
19
+
20
+ it('should render button element by default', async () => {
21
+ element = await fixture('grantcodes-button');
22
+ const button = element.shadowRoot.querySelector('button');
23
+ assert.ok(button, 'Button element should exist');
24
+ });
25
+
26
+ it('should render link when href is provided', async () => {
27
+ element = await fixture('grantcodes-button', {
28
+ href: 'https://example.com',
29
+ });
30
+ await element.updateComplete;
31
+
32
+ const link = element.shadowRoot.querySelector('a');
33
+ assert.ok(link, 'Link element should exist');
34
+ const href = link.getAttribute('href');
35
+ assert.ok(href, 'Link should have href attribute');
36
+ assert.ok(
37
+ href.includes('example.com') || href === 'https://example.com',
38
+ 'Link href should contain example.com',
39
+ );
40
+ });
41
+
42
+ it('should be disabled when disabled property is set', async () => {
43
+ element = await fixture('grantcodes-button', {
44
+ disabled: true,
45
+ });
46
+
47
+ const button = element.shadowRoot.querySelector('button');
48
+ assert.ok(button.disabled, 'Button should be disabled');
49
+ assert.ok(button.hasAttribute('disabled'), 'Button should have disabled attribute');
50
+ });
51
+
52
+ it('should emit click event when clicked', async () => {
53
+ element = await fixture('grantcodes-button');
54
+ const button = element.shadowRoot.querySelector('button');
55
+
56
+ let clicked = false;
57
+ element.addEventListener('click', () => {
58
+ clicked = true;
59
+ });
60
+
61
+ click(button);
62
+ assert.ok(clicked, 'Click event should have fired');
63
+ });
64
+
65
+ it('should not emit click event when disabled', async () => {
66
+ element = await fixture('grantcodes-button', {
67
+ disabled: true,
68
+ });
69
+ const button = element.shadowRoot.querySelector('button');
70
+
71
+ let clicked = false;
72
+ element.addEventListener('click', () => {
73
+ clicked = true;
74
+ });
75
+
76
+ click(button);
77
+ assert.ok(!clicked, 'Click event should not fire when disabled');
78
+ });
79
+
80
+ it('should support different button types', async () => {
81
+ const types = ['button', 'submit', 'reset'];
82
+
83
+ for (const type of types) {
84
+ const testElement = await fixture('grantcodes-button', { type });
85
+ // Internal button is always type="button" — form behavior is handled
86
+ // by the host element via FACE (requestSubmit/reset on ElementInternals)
87
+ const button = testElement.shadowRoot.querySelector('button');
88
+ assert.strictEqual(button.type, 'button', `Internal button should always be type="button"`);
89
+ assert.strictEqual(testElement.type, type, `Host type property should be ${type}`);
90
+ cleanup(testElement);
91
+ }
92
+ });
93
+
94
+ it('should render slotted content', async () => {
95
+ element = await fixture('grantcodes-button');
96
+ element.textContent = 'Click me';
97
+
98
+ await element.updateComplete;
99
+
100
+ const slot = element.shadowRoot.querySelector('slot');
101
+ assert.ok(slot, 'Slot should exist');
102
+ });
106
103
  });
107
104
 
108
- describe("Form Context", () => {
109
- let element;
110
- let form;
111
-
112
- afterEach(() => {
113
- cleanup(element);
114
- cleanup(form);
115
- });
116
-
117
- it("should render button inside a form element", async () => {
118
- form = document.createElement("form");
119
- document.body.appendChild(form);
120
-
121
- element = document.createElement("grantcodes-button");
122
- form.appendChild(element);
123
- await element.updateComplete;
124
-
125
- const button = element.shadowRoot.querySelector("button");
126
- assert.ok(button, "Button should exist in shadow DOM when inside form");
127
- const link = element.shadowRoot.querySelector("a");
128
- assert.strictEqual(link, null, "Should not render a link when inside form");
129
- });
130
-
131
- it("should use type='button' as default on internal button", async () => {
132
- element = await fixture("grantcodes-button");
133
-
134
- const button = element.shadowRoot.querySelector("button");
135
- assert.strictEqual(button.type, "button", "Default type should be 'button'");
136
- });
137
-
138
- it("should keep internal button as type='button' when host type is submit", async () => {
139
- element = await fixture("grantcodes-button", { type: "submit" });
140
-
141
- const button = element.shadowRoot.querySelector("button");
142
- // Internal button is always type="button" — FACE handles submit via requestSubmit()
143
- assert.strictEqual(button.type, "button", "Internal button should be type='button'");
144
- assert.strictEqual(element.type, "submit", "Host type property should be 'submit'");
145
- });
146
-
147
- it("should keep internal button as type='button' when host type is reset", async () => {
148
- element = await fixture("grantcodes-button", { type: "reset" });
149
-
150
- const button = element.shadowRoot.querySelector("button");
151
- // Internal button is always type="button" — FACE handles reset via form.reset()
152
- assert.strictEqual(button.type, "button", "Internal button should be type='button'");
153
- assert.strictEqual(element.type, "reset", "Host type property should be 'reset'");
154
- });
155
-
156
- it("should reflect name property on internal button", async () => {
157
- element = await fixture("grantcodes-button", { name: "myButton" });
158
-
159
- const button = element.shadowRoot.querySelector("button");
160
- assert.strictEqual(
161
- button.getAttribute("name"),
162
- "myButton",
163
- "Internal button name attribute should be 'myButton'",
164
- );
165
- });
166
-
167
- it("should reflect value property on internal button", async () => {
168
- element = await fixture("grantcodes-button", { value: "send" });
169
-
170
- const button = element.shadowRoot.querySelector("button");
171
- assert.strictEqual(
172
- button.getAttribute("value"),
173
- "send",
174
- "Internal button value attribute should be 'send'",
175
- );
176
- });
177
-
178
- it("should reflect disabled property on internal button", async () => {
179
- element = await fixture("grantcodes-button", { disabled: true });
180
-
181
- const button = element.shadowRoot.querySelector("button");
182
- assert.strictEqual(button.disabled, true, "Internal button should be disabled");
183
- assert.ok(
184
- button.hasAttribute("disabled"),
185
- "Internal button should have disabled attribute",
186
- );
187
- });
188
-
189
- it("should render as link not button when href is set", async () => {
190
- element = await fixture("grantcodes-button", {
191
- href: "https://example.com",
192
- });
193
- await element.updateComplete;
194
-
195
- const button = element.shadowRoot.querySelector("button");
196
- const link = element.shadowRoot.querySelector("a");
197
- assert.strictEqual(button, null, "Should not render a button when href is set");
198
- assert.ok(link, "Should render a link when href is set");
199
- const href = link.getAttribute("href");
200
- assert.ok(
201
- href === "https://example.com" || href.includes("example.com"),
202
- "Link href should contain example.com",
203
- );
204
- });
105
+ describe('Form Context', () => {
106
+ let element;
107
+ let form;
108
+
109
+ afterEach(() => {
110
+ cleanup(element);
111
+ cleanup(form);
112
+ });
113
+
114
+ it('should render button inside a form element', async () => {
115
+ form = document.createElement('form');
116
+ document.body.appendChild(form);
117
+
118
+ element = document.createElement('grantcodes-button');
119
+ form.appendChild(element);
120
+ await element.updateComplete;
121
+
122
+ const button = element.shadowRoot.querySelector('button');
123
+ assert.ok(button, 'Button should exist in shadow DOM when inside form');
124
+ const link = element.shadowRoot.querySelector('a');
125
+ assert.strictEqual(link, null, 'Should not render a link when inside form');
126
+ });
127
+
128
+ it("should use type='button' as default on internal button", async () => {
129
+ element = await fixture('grantcodes-button');
130
+
131
+ const button = element.shadowRoot.querySelector('button');
132
+ assert.strictEqual(button.type, 'button', "Default type should be 'button'");
133
+ });
134
+
135
+ it("should keep internal button as type='button' when host type is submit", async () => {
136
+ element = await fixture('grantcodes-button', { type: 'submit' });
137
+
138
+ const button = element.shadowRoot.querySelector('button');
139
+ // Internal button is always type="button" — FACE handles submit via requestSubmit()
140
+ assert.strictEqual(button.type, 'button', "Internal button should be type='button'");
141
+ assert.strictEqual(element.type, 'submit', "Host type property should be 'submit'");
142
+ });
143
+
144
+ it("should keep internal button as type='button' when host type is reset", async () => {
145
+ element = await fixture('grantcodes-button', { type: 'reset' });
146
+
147
+ const button = element.shadowRoot.querySelector('button');
148
+ // Internal button is always type="button" — FACE handles reset via form.reset()
149
+ assert.strictEqual(button.type, 'button', "Internal button should be type='button'");
150
+ assert.strictEqual(element.type, 'reset', "Host type property should be 'reset'");
151
+ });
152
+
153
+ it('should reflect name property on internal button', async () => {
154
+ element = await fixture('grantcodes-button', { name: 'myButton' });
155
+
156
+ const button = element.shadowRoot.querySelector('button');
157
+ assert.strictEqual(
158
+ button.getAttribute('name'),
159
+ 'myButton',
160
+ "Internal button name attribute should be 'myButton'",
161
+ );
162
+ });
163
+
164
+ it('should reflect value property on internal button', async () => {
165
+ element = await fixture('grantcodes-button', { value: 'send' });
166
+
167
+ const button = element.shadowRoot.querySelector('button');
168
+ assert.strictEqual(
169
+ button.getAttribute('value'),
170
+ 'send',
171
+ "Internal button value attribute should be 'send'",
172
+ );
173
+ });
174
+
175
+ it('should reflect disabled property on internal button', async () => {
176
+ element = await fixture('grantcodes-button', { disabled: true });
177
+
178
+ const button = element.shadowRoot.querySelector('button');
179
+ assert.strictEqual(button.disabled, true, 'Internal button should be disabled');
180
+ assert.ok(button.hasAttribute('disabled'), 'Internal button should have disabled attribute');
181
+ });
182
+
183
+ it('should render as link not button when href is set', async () => {
184
+ element = await fixture('grantcodes-button', {
185
+ href: 'https://example.com',
186
+ });
187
+ await element.updateComplete;
188
+
189
+ const button = element.shadowRoot.querySelector('button');
190
+ const link = element.shadowRoot.querySelector('a');
191
+ assert.strictEqual(button, null, 'Should not render a button when href is set');
192
+ assert.ok(link, 'Should render a link when href is set');
193
+ const href = link.getAttribute('href');
194
+ assert.ok(
195
+ href === 'https://example.com' || href.includes('example.com'),
196
+ 'Link href should contain example.com',
197
+ );
198
+ });
205
199
  });
206
200
 
207
- describe("FACE Infrastructure", () => {
208
- let element;
209
-
210
- afterEach(() => {
211
- cleanup(element);
212
- });
213
-
214
- it("should declare formAssociated as true", () => {
215
- assert.strictEqual(
216
- GrantCodesButton.formAssociated,
217
- true,
218
- "GrantCodesButton.formAssociated should be true",
219
- );
220
- });
221
-
222
- it("should have internals property after construction", async () => {
223
- element = await fixture("grantcodes-button");
224
- // internals may be null in happy-dom (attachInternals throws), but the
225
- // property must exist — it was assigned in the constructor's try/catch.
226
- assert.ok(
227
- "internals" in element,
228
- "Element should have internals property",
229
- );
230
- // In happy-dom: element.internals === null (expected — DOM shim limitation)
231
- // In real browser: element.internals instanceof ElementInternals
232
- });
233
-
234
- it("should reflect form attribute when set as property", async () => {
235
- element = await fixture("grantcodes-button", {
236
- form: "myForm",
237
- });
238
- await element.updateComplete;
239
-
240
- assert.strictEqual(
241
- element.getAttribute("form"),
242
- "myForm",
243
- "form attribute should reflect property value 'myForm'",
244
- );
245
- assert.strictEqual(
246
- element.form,
247
- "myForm",
248
- "element.form property should be 'myForm'",
249
- );
250
- });
251
-
252
- it("should not have form attribute by default", async () => {
253
- element = await fixture("grantcodes-button");
254
- await element.updateComplete;
255
-
256
- const formAttr = element.getAttribute("form");
257
- assert.ok(
258
- formAttr === null || formAttr === "",
259
- "form attribute should be null or empty by default",
260
- );
261
- });
262
-
263
- it("should use type='button' on internal button after FACE changes", async () => {
264
- element = await fixture("grantcodes-button", { type: "submit" });
265
-
266
- const button = element.shadowRoot.querySelector("button");
267
- assert.ok(button, "Internal button should exist");
268
- assert.strictEqual(
269
- button.type,
270
- "button",
271
- "Internal button should be type='button' — FACE handles submit via requestSubmit()",
272
- );
273
- });
274
-
275
- it("should still render as link not button when href is set", async () => {
276
- element = await fixture("grantcodes-button", {
277
- href: "https://example.com",
278
- });
279
- await element.updateComplete;
280
-
281
- const button = element.shadowRoot.querySelector("button");
282
- const link = element.shadowRoot.querySelector("a");
283
- assert.strictEqual(button, null, "Should not render a button when href is set");
284
- assert.ok(link, "Should render a link when href is set");
285
- });
286
-
287
- it("should have disabled attribute on internal button when disabled", async () => {
288
- element = await fixture("grantcodes-button", {
289
- disabled: true,
290
- });
291
-
292
- const button = element.shadowRoot.querySelector("button");
293
- assert.ok(button, "Internal button should exist");
294
- assert.strictEqual(
295
- button.disabled,
296
- true,
297
- "Internal button should be disabled when element.disabled is true",
298
- );
299
- assert.ok(
300
- button.hasAttribute("disabled"),
301
- "Internal button should have disabled attribute",
302
- );
303
- });
304
-
305
- it("should not throw when clicking internal button while disabled", async () => {
306
- element = await fixture("grantcodes-button", {
307
- disabled: true,
308
- type: "submit",
309
- });
310
- await element.updateComplete;
311
-
312
- const button = element.shadowRoot.querySelector("button");
313
- assert.ok(button, "Internal button should exist");
314
-
315
- // _handleFormClick should early-return when this.disabled is true.
316
- // Verify clicking does not throw (no internals.form access attempted).
317
- let threw = false;
318
- try {
319
- button.click();
320
- } catch (e) {
321
- threw = true;
322
- }
323
- assert.strictEqual(threw, false, "Clicking disabled button should not throw");
324
- });
325
-
326
- it("should keep internal button as type='button' when host type is reset", async () => {
327
- element = await fixture("grantcodes-button", { type: "reset" });
328
-
329
- const button = element.shadowRoot.querySelector("button");
330
- assert.ok(button, "Internal button should exist");
331
- assert.strictEqual(
332
- button.type,
333
- "button",
334
- "Internal button should be type='button' — FACE handles reset via form.reset()",
335
- );
336
- });
337
-
338
- it("should have type='button' as default on internal button", async () => {
339
- element = await fixture("grantcodes-button");
340
-
341
- const button = element.shadowRoot.querySelector("button");
342
- assert.ok(button, "Internal button should exist");
343
- assert.strictEqual(
344
- button.type,
345
- "button",
346
- "Default internal button type should be 'button'",
347
- );
348
- });
201
+ describe('FACE Infrastructure', () => {
202
+ let element;
203
+
204
+ afterEach(() => {
205
+ cleanup(element);
206
+ });
207
+
208
+ it('should declare formAssociated as true', () => {
209
+ assert.strictEqual(
210
+ GrantCodesButton.formAssociated,
211
+ true,
212
+ 'GrantCodesButton.formAssociated should be true',
213
+ );
214
+ });
215
+
216
+ it('should have internals property after construction', async () => {
217
+ element = await fixture('grantcodes-button');
218
+ // internals may be null in happy-dom (attachInternals throws), but the
219
+ // property must exist — it was assigned in the constructor's try/catch.
220
+ assert.ok('internals' in element, 'Element should have internals property');
221
+ // In happy-dom: element.internals === null (expected — DOM shim limitation)
222
+ // In real browser: element.internals instanceof ElementInternals
223
+ });
224
+
225
+ it('should reflect form attribute when set as property', async () => {
226
+ element = await fixture('grantcodes-button', {
227
+ form: 'myForm',
228
+ });
229
+ await element.updateComplete;
230
+
231
+ assert.strictEqual(
232
+ element.getAttribute('form'),
233
+ 'myForm',
234
+ "form attribute should reflect property value 'myForm'",
235
+ );
236
+ assert.strictEqual(element.form, 'myForm', "element.form property should be 'myForm'");
237
+ });
238
+
239
+ it('should not have form attribute by default', async () => {
240
+ element = await fixture('grantcodes-button');
241
+ await element.updateComplete;
242
+
243
+ const formAttr = element.getAttribute('form');
244
+ assert.ok(
245
+ formAttr === null || formAttr === '',
246
+ 'form attribute should be null or empty by default',
247
+ );
248
+ });
249
+
250
+ it("should use type='button' on internal button after FACE changes", async () => {
251
+ element = await fixture('grantcodes-button', { type: 'submit' });
252
+
253
+ const button = element.shadowRoot.querySelector('button');
254
+ assert.ok(button, 'Internal button should exist');
255
+ assert.strictEqual(
256
+ button.type,
257
+ 'button',
258
+ "Internal button should be type='button' — FACE handles submit via requestSubmit()",
259
+ );
260
+ });
261
+
262
+ it('should still render as link not button when href is set', async () => {
263
+ element = await fixture('grantcodes-button', {
264
+ href: 'https://example.com',
265
+ });
266
+ await element.updateComplete;
267
+
268
+ const button = element.shadowRoot.querySelector('button');
269
+ const link = element.shadowRoot.querySelector('a');
270
+ assert.strictEqual(button, null, 'Should not render a button when href is set');
271
+ assert.ok(link, 'Should render a link when href is set');
272
+ });
273
+
274
+ it('should have disabled attribute on internal button when disabled', async () => {
275
+ element = await fixture('grantcodes-button', {
276
+ disabled: true,
277
+ });
278
+
279
+ const button = element.shadowRoot.querySelector('button');
280
+ assert.ok(button, 'Internal button should exist');
281
+ assert.strictEqual(
282
+ button.disabled,
283
+ true,
284
+ 'Internal button should be disabled when element.disabled is true',
285
+ );
286
+ assert.ok(button.hasAttribute('disabled'), 'Internal button should have disabled attribute');
287
+ });
288
+
289
+ it('should not throw when clicking internal button while disabled', async () => {
290
+ element = await fixture('grantcodes-button', {
291
+ disabled: true,
292
+ type: 'submit',
293
+ });
294
+ await element.updateComplete;
295
+
296
+ const button = element.shadowRoot.querySelector('button');
297
+ assert.ok(button, 'Internal button should exist');
298
+
299
+ // _handleFormClick should early-return when this.disabled is true.
300
+ // Verify clicking does not throw (no internals.form access attempted).
301
+ let threw = false;
302
+ try {
303
+ button.click();
304
+ } catch (e) {
305
+ threw = true;
306
+ }
307
+ assert.strictEqual(threw, false, 'Clicking disabled button should not throw');
308
+ });
309
+
310
+ it("should keep internal button as type='button' when host type is reset", async () => {
311
+ element = await fixture('grantcodes-button', { type: 'reset' });
312
+
313
+ const button = element.shadowRoot.querySelector('button');
314
+ assert.ok(button, 'Internal button should exist');
315
+ assert.strictEqual(
316
+ button.type,
317
+ 'button',
318
+ "Internal button should be type='button' — FACE handles reset via form.reset()",
319
+ );
320
+ });
321
+
322
+ it("should have type='button' as default on internal button", async () => {
323
+ element = await fixture('grantcodes-button');
324
+
325
+ const button = element.shadowRoot.querySelector('button');
326
+ assert.ok(button, 'Internal button should exist');
327
+ assert.strictEqual(button.type, 'button', "Default internal button type should be 'button'");
328
+ });
349
329
  });