srcdev-nuxt-components 9.6.6 → 9.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/consumer/ids-from-useid.md +14 -0
- package/.claude/skills/component-compliance-checklist.md +6 -0
- package/.claude/skills/component-naming.md +2 -2
- package/.claude/skills/components/input-checkbox-radio.md +19 -3
- package/.claude/skills/components/input-checkbox.md +14 -3
- package/.claude/skills/components/input-description.md +1 -1
- package/.claude/skills/components/input-error.md +1 -1
- package/.claude/skills/components/input-label.md +2 -2
- package/.claude/skills/components/input-number.md +44 -13
- package/.claude/skills/components/input-radio.md +15 -1
- package/.claude/skills/components/{input-range-core.md → input-range.md} +53 -18
- package/.claude/skills/components/input-textarea-core.md +4 -4
- package/.claude/skills/components/page-hero-highlights.md +22 -18
- package/.claude/skills/index.md +6 -6
- package/.claude/skills/testing-add-unit-test.md +17 -0
- package/.vscode/srcdev-component-input-checkbox-radio.code-snippets +0 -1
- package/.vscode/srcdev-component-input-range.code-snippets +38 -18
- package/.vscode/srcdev-component-page-hero-highlights.code-snippets +2 -1
- package/app/components/04.templates/page-hero-highlights/CONSUMER-STYLING.md +32 -12
- package/app/components/04.templates/page-hero-highlights/PageHeroHighlights.vue +56 -47
- package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlights.stories.ts +158 -307
- package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlightsHeader.stories.ts +10 -0
- package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlights.spec.ts +49 -27
- package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlightsHeader.spec.ts +42 -7
- package/app/components/04.templates/page-hero-highlights/tests/__snapshots__/PageHeroHighlights.spec.ts.snap +12 -10
- package/app/components/05.forms/input-checkbox/CONSUMER-STYLING.md +10 -0
- package/app/components/05.forms/input-checkbox/MultipleCheckboxes.vue +18 -4
- package/app/components/05.forms/input-checkbox/SingleCheckbox.vue +14 -2
- package/app/components/05.forms/input-checkbox/stories/MultipleCheckboxes.stories.ts +146 -37
- package/app/components/05.forms/input-checkbox/stories/SingleCheckbox.stories.ts +107 -30
- package/app/components/05.forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +38 -4
- package/app/components/05.forms/input-checkbox/tests/SingleCheckbox.spec.ts +5 -0
- package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +10 -3
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +1 -0
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +11 -10
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioField.vue +3 -0
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadio.stories.ts +44 -17
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +164 -58
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioField.stories.ts +94 -21
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadio.spec.ts +14 -3
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioButton.spec.ts +38 -3
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioField.spec.ts +14 -3
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +42 -2
- package/app/components/05.forms/input-number/InputNumber.vue +37 -25
- package/app/components/05.forms/input-number/InputNumberField.vue +29 -16
- package/app/components/05.forms/input-number/stories/InputNumber.stories.ts +76 -25
- package/app/components/05.forms/input-number/stories/InputNumberField.stories.ts +116 -50
- package/app/components/05.forms/input-number/tests/InputNumber.spec.ts +49 -13
- package/app/components/05.forms/input-number/tests/InputNumberField.spec.ts +59 -5
- package/app/components/05.forms/input-radio/CONSUMER-STYLING.md +11 -0
- package/app/components/05.forms/input-radio/MultipleRadiobuttons.vue +14 -3
- package/app/components/05.forms/input-radio/stories/MultipleRadiobuttons.stories.ts +138 -38
- package/app/components/05.forms/input-radio/tests/MultipleRadioButtons.spec.ts +50 -3
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +68 -26
- package/app/components/05.forms/input-range/InputRange.vue +247 -0
- package/app/components/05.forms/input-range/{InputRangeDefault.vue → InputRangeField.vue} +41 -18
- package/app/components/05.forms/input-range/stories/InputRange.stories.ts +187 -0
- package/app/components/05.forms/input-range/stories/InputRangeField.stories.ts +196 -0
- package/app/components/05.forms/input-range/tests/{InputRangeCore.spec.ts → InputRange.spec.ts} +39 -15
- package/app/components/05.forms/input-range/tests/{InputRangeDefault.spec.ts → InputRangeField.spec.ts} +71 -7
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +5 -5
- package/package.json +1 -1
- package/app/components/05.forms/input-range/InputRangeCore.vue +0 -169
- package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +0 -126
- package/app/components/05.forms/input-range/stories/InputRangeDefault.stories.ts +0 -108
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
2
|
import { h } from "vue";
|
|
3
3
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
4
4
|
import PageHeroHighlights from "../PageHeroHighlights.vue";
|
|
5
5
|
|
|
6
|
+
const mounted: { unmount: () => void }[] = [];
|
|
7
|
+
|
|
8
|
+
const mountTracked = (async (...args: Parameters<typeof mountSuspended>) => {
|
|
9
|
+
const wrapper = await mountSuspended(...args);
|
|
10
|
+
mounted.push(wrapper);
|
|
11
|
+
return wrapper;
|
|
12
|
+
}) as typeof mountSuspended;
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
6
19
|
describe("PageHeroHighlights", () => {
|
|
7
20
|
it("mounts without error", async () => {
|
|
8
|
-
const wrapper = await
|
|
21
|
+
const wrapper = await mountTracked(PageHeroHighlights);
|
|
9
22
|
expect(wrapper.vm).toBeTruthy();
|
|
10
23
|
});
|
|
11
24
|
|
|
12
25
|
it("renders correct HTML structure", async () => {
|
|
13
|
-
const wrapper = await
|
|
26
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
14
27
|
slots: {
|
|
15
28
|
header: "<h1>Page Title</h1>",
|
|
16
29
|
highlights: "<div class='highlight'>Highlight 1</div>",
|
|
@@ -20,14 +33,23 @@ describe("PageHeroHighlights", () => {
|
|
|
20
33
|
expect(wrapper.html()).toMatchSnapshot();
|
|
21
34
|
});
|
|
22
35
|
|
|
36
|
+
it("wraps the rows in .page-hero-highlights-grid, so the root can be the gutter query container", async () => {
|
|
37
|
+
const wrapper = await mountTracked(PageHeroHighlights);
|
|
38
|
+
const grid = wrapper.find(".page-hero-highlights > .page-hero-highlights-grid");
|
|
39
|
+
expect(grid.exists()).toBe(true);
|
|
40
|
+
expect(grid.find(".page-hero-highlights-header-row").exists()).toBe(true);
|
|
41
|
+
expect(grid.find(".page-hero-highlights-strip").exists()).toBe(true);
|
|
42
|
+
expect(grid.find(".page-hero-highlights-content-row").exists()).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
23
45
|
it("renders as a div by default", async () => {
|
|
24
|
-
const wrapper = await
|
|
46
|
+
const wrapper = await mountTracked(PageHeroHighlights);
|
|
25
47
|
expect(wrapper.element.tagName.toLowerCase()).toBe("div");
|
|
26
48
|
});
|
|
27
49
|
|
|
28
50
|
it("renders with the correct tag when tag prop is provided", async () => {
|
|
29
51
|
for (const tag of ["section", "main"] as const) {
|
|
30
|
-
const wrapper = await
|
|
52
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
31
53
|
props: { tag },
|
|
32
54
|
});
|
|
33
55
|
expect(wrapper.element.tagName.toLowerCase()).toBe(tag);
|
|
@@ -35,7 +57,7 @@ describe("PageHeroHighlights", () => {
|
|
|
35
57
|
});
|
|
36
58
|
|
|
37
59
|
it("renders header slot content", async () => {
|
|
38
|
-
const wrapper = await
|
|
60
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
39
61
|
slots: { header: "<h1 class='page-title'>Dashboard</h1>" },
|
|
40
62
|
});
|
|
41
63
|
expect(wrapper.find(".page-title").exists()).toBe(true);
|
|
@@ -43,7 +65,7 @@ describe("PageHeroHighlights", () => {
|
|
|
43
65
|
});
|
|
44
66
|
|
|
45
67
|
it("renders highlights slot content", async () => {
|
|
46
|
-
const wrapper = await
|
|
68
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
47
69
|
slots: {
|
|
48
70
|
highlights: ["<div class='highlight-card'>Card 1</div>", "<div class='highlight-card'>Card 2</div>"].join(""),
|
|
49
71
|
},
|
|
@@ -52,7 +74,7 @@ describe("PageHeroHighlights", () => {
|
|
|
52
74
|
});
|
|
53
75
|
|
|
54
76
|
it("renders content slot content", async () => {
|
|
55
|
-
const wrapper = await
|
|
77
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
56
78
|
slots: { content: "<p class='body-text'>Page body</p>" },
|
|
57
79
|
});
|
|
58
80
|
expect(wrapper.find(".body-text").exists()).toBe(true);
|
|
@@ -60,42 +82,42 @@ describe("PageHeroHighlights", () => {
|
|
|
60
82
|
});
|
|
61
83
|
|
|
62
84
|
it("content slot is rendered inside .content-slot", async () => {
|
|
63
|
-
const wrapper = await
|
|
85
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
64
86
|
slots: { content: "<p class='body-text'>Page body</p>" },
|
|
65
87
|
});
|
|
66
88
|
expect(wrapper.find(".page-hero-highlights-content-slot .body-text").exists()).toBe(true);
|
|
67
89
|
});
|
|
68
90
|
|
|
69
91
|
it("header slot is rendered inside .header-slot", async () => {
|
|
70
|
-
const wrapper = await
|
|
92
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
71
93
|
slots: { header: "<h1 class='page-title'>Dashboard</h1>" },
|
|
72
94
|
});
|
|
73
95
|
expect(wrapper.find(".page-hero-highlights-header-slot .page-title").exists()).toBe(true);
|
|
74
96
|
});
|
|
75
97
|
|
|
76
98
|
it("adds aria-labelledby when tag is section", async () => {
|
|
77
|
-
const wrapper = await
|
|
99
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
78
100
|
props: { tag: "section" },
|
|
79
101
|
});
|
|
80
102
|
expect(wrapper.find(".page-hero-highlights").attributes("aria-labelledby")).toBeTruthy();
|
|
81
103
|
});
|
|
82
104
|
|
|
83
105
|
it("does not add aria-labelledby when tag is main (main never needs an accessible name)", async () => {
|
|
84
|
-
const wrapper = await
|
|
106
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
85
107
|
props: { tag: "main" },
|
|
86
108
|
});
|
|
87
109
|
expect(wrapper.find(".page-hero-highlights").attributes("aria-labelledby")).toBeUndefined();
|
|
88
110
|
});
|
|
89
111
|
|
|
90
112
|
it("does not add aria-labelledby when tag is div", async () => {
|
|
91
|
-
const wrapper = await
|
|
113
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
92
114
|
props: { tag: "div" },
|
|
93
115
|
});
|
|
94
116
|
expect(wrapper.find(".page-hero-highlights").attributes("aria-labelledby")).toBeUndefined();
|
|
95
117
|
});
|
|
96
118
|
|
|
97
119
|
it("exposes headingId via header scoped slot and aria-labelledby matches", async () => {
|
|
98
|
-
const wrapper = await
|
|
120
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
99
121
|
props: { tag: "section" },
|
|
100
122
|
slots: {
|
|
101
123
|
header: (props: Record<string, unknown>) => h("h1", { id: props.headingId, class: "page-title" }, "Dashboard"),
|
|
@@ -107,13 +129,13 @@ describe("PageHeroHighlights", () => {
|
|
|
107
129
|
});
|
|
108
130
|
|
|
109
131
|
it("applies flexible-widths class by default", async () => {
|
|
110
|
-
const wrapper = await
|
|
132
|
+
const wrapper = await mountTracked(PageHeroHighlights);
|
|
111
133
|
expect(wrapper.find(".page-hero-highlights-strip").classes()).toContain("flexible-widths");
|
|
112
134
|
expect(wrapper.find(".page-hero-highlights-strip").classes()).not.toContain("equal-widths");
|
|
113
135
|
});
|
|
114
136
|
|
|
115
137
|
it("applies equal-widths class when highlightsEqualWidths is true", async () => {
|
|
116
|
-
const wrapper = await
|
|
138
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
117
139
|
props: { highlightsEqualWidths: true },
|
|
118
140
|
});
|
|
119
141
|
expect(wrapper.find(".page-hero-highlights-strip").classes()).toContain("equal-widths");
|
|
@@ -121,14 +143,14 @@ describe("PageHeroHighlights", () => {
|
|
|
121
143
|
});
|
|
122
144
|
|
|
123
145
|
it("applies justify-start class by default", async () => {
|
|
124
|
-
const wrapper = await
|
|
146
|
+
const wrapper = await mountTracked(PageHeroHighlights);
|
|
125
147
|
expect(wrapper.find(".page-hero-highlights-strip").classes()).toContain("justify-start");
|
|
126
148
|
});
|
|
127
149
|
|
|
128
150
|
it("applies the correct justify class for each highlightsJustify value", async () => {
|
|
129
151
|
const values = ["start", "center", "end", "space-between", "space-around"] as const;
|
|
130
152
|
for (const value of values) {
|
|
131
|
-
const wrapper = await
|
|
153
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
132
154
|
props: { highlightsJustify: value },
|
|
133
155
|
});
|
|
134
156
|
expect(wrapper.find(".page-hero-highlights-strip").classes()).toContain(`justify-${value}`);
|
|
@@ -136,31 +158,31 @@ describe("PageHeroHighlights", () => {
|
|
|
136
158
|
});
|
|
137
159
|
|
|
138
160
|
it("applies has-content-panel class by default", async () => {
|
|
139
|
-
const wrapper = await
|
|
161
|
+
const wrapper = await mountTracked(PageHeroHighlights);
|
|
140
162
|
expect(wrapper.find(".page-hero-highlights").classes()).toContain("has-content-panel");
|
|
141
163
|
});
|
|
142
164
|
|
|
143
165
|
it("does not apply has-content-panel class when contentPanel is false", async () => {
|
|
144
|
-
const wrapper = await
|
|
166
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
145
167
|
props: { contentPanel: false },
|
|
146
168
|
});
|
|
147
169
|
expect(wrapper.find(".page-hero-highlights").classes()).not.toContain("has-content-panel");
|
|
148
170
|
});
|
|
149
171
|
|
|
150
172
|
it("does not apply highlight-title-baseline class by default", async () => {
|
|
151
|
-
const wrapper = await
|
|
173
|
+
const wrapper = await mountTracked(PageHeroHighlights);
|
|
152
174
|
expect(wrapper.find(".page-hero-highlights").classes()).not.toContain("highlight-title-baseline");
|
|
153
175
|
});
|
|
154
176
|
|
|
155
177
|
it("applies highlight-title-baseline class when highlightTitleBaseline is true", async () => {
|
|
156
|
-
const wrapper = await
|
|
178
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
157
179
|
props: { highlightTitleBaseline: true },
|
|
158
180
|
});
|
|
159
181
|
expect(wrapper.find(".page-hero-highlights").classes()).toContain("highlight-title-baseline");
|
|
160
182
|
});
|
|
161
183
|
|
|
162
184
|
it("applies styleClassPassthrough classes", async () => {
|
|
163
|
-
const wrapper = await
|
|
185
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
164
186
|
props: { styleClassPassthrough: ["extra-class", "another-class"] },
|
|
165
187
|
});
|
|
166
188
|
const el = wrapper.find(".page-hero-highlights");
|
|
@@ -170,24 +192,24 @@ describe("PageHeroHighlights", () => {
|
|
|
170
192
|
|
|
171
193
|
describe("grid layout classes", () => {
|
|
172
194
|
it("applies center class by default", async () => {
|
|
173
|
-
const wrapper = await
|
|
195
|
+
const wrapper = await mountTracked(PageHeroHighlights);
|
|
174
196
|
expect(wrapper.find(".page-hero-highlights").classes()).toContain("content-align-center");
|
|
175
197
|
});
|
|
176
198
|
|
|
177
199
|
it("does not apply width-constrained class by default", async () => {
|
|
178
|
-
const wrapper = await
|
|
200
|
+
const wrapper = await mountTracked(PageHeroHighlights);
|
|
179
201
|
expect(wrapper.find(".page-hero-highlights").classes()).not.toContain("width-constrained");
|
|
180
202
|
});
|
|
181
203
|
|
|
182
204
|
it("applies width-constrained class when widthConstrained is true", async () => {
|
|
183
|
-
const wrapper = await
|
|
205
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
184
206
|
props: { widthConstrained: true },
|
|
185
207
|
});
|
|
186
208
|
expect(wrapper.find(".page-hero-highlights").classes()).toContain("width-constrained");
|
|
187
209
|
});
|
|
188
210
|
|
|
189
211
|
it("applies start class when contentAlign is start", async () => {
|
|
190
|
-
const wrapper = await
|
|
212
|
+
const wrapper = await mountTracked(PageHeroHighlights, {
|
|
191
213
|
props: { contentAlign: "start" },
|
|
192
214
|
});
|
|
193
215
|
expect(wrapper.find(".page-hero-highlights").classes()).toContain("content-align-start");
|
package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlightsHeader.spec.ts
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { defineComponent } from "vue";
|
|
2
3
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
4
|
import PageHeroHighlightsHeader from "../PageHeroHighlightsHeader.vue";
|
|
4
5
|
|
|
6
|
+
const mounted: { unmount: () => void }[] = [];
|
|
7
|
+
|
|
8
|
+
const mountTracked = (async (...args: Parameters<typeof mountSuspended>) => {
|
|
9
|
+
const wrapper = await mountSuspended(...args);
|
|
10
|
+
mounted.push(wrapper);
|
|
11
|
+
return wrapper;
|
|
12
|
+
}) as typeof mountSuspended;
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
5
19
|
describe("PageHeroHighlightsHeader", () => {
|
|
6
20
|
it("mounts without error", async () => {
|
|
7
|
-
const wrapper = await
|
|
21
|
+
const wrapper = await mountTracked(PageHeroHighlightsHeader);
|
|
8
22
|
expect(wrapper.vm).toBeTruthy();
|
|
9
23
|
});
|
|
10
24
|
|
|
11
25
|
it("renders start slot content", async () => {
|
|
12
|
-
const wrapper = await
|
|
26
|
+
const wrapper = await mountTracked(PageHeroHighlightsHeader, {
|
|
13
27
|
slots: { start: "<h1 class='page-title'>Surplus needs</h1>" },
|
|
14
28
|
});
|
|
15
29
|
expect(wrapper.find(".page-hero-highlights-header-start .page-title").exists()).toBe(true);
|
|
@@ -17,7 +31,7 @@ describe("PageHeroHighlightsHeader", () => {
|
|
|
17
31
|
});
|
|
18
32
|
|
|
19
33
|
it("renders end slot content when provided", async () => {
|
|
20
|
-
const wrapper = await
|
|
34
|
+
const wrapper = await mountTracked(PageHeroHighlightsHeader, {
|
|
21
35
|
slots: {
|
|
22
36
|
start: "<h1>Title</h1>",
|
|
23
37
|
end: "<button class='cta'>Create new need</button>",
|
|
@@ -27,14 +41,14 @@ describe("PageHeroHighlightsHeader", () => {
|
|
|
27
41
|
});
|
|
28
42
|
|
|
29
43
|
it("does not render .page-hero-highlights-header-end when no end slot is provided", async () => {
|
|
30
|
-
const wrapper = await
|
|
44
|
+
const wrapper = await mountTracked(PageHeroHighlightsHeader, {
|
|
31
45
|
slots: { start: "<h1>Title</h1>" },
|
|
32
46
|
});
|
|
33
47
|
expect(wrapper.find(".page-hero-highlights-header-end").exists()).toBe(false);
|
|
34
48
|
});
|
|
35
49
|
|
|
36
50
|
it("renders .page-hero-highlights-header-start as full width when no end slot is provided", async () => {
|
|
37
|
-
const wrapper = await
|
|
51
|
+
const wrapper = await mountTracked(PageHeroHighlightsHeader, {
|
|
38
52
|
slots: { start: "<h1>Title</h1>" },
|
|
39
53
|
});
|
|
40
54
|
expect(wrapper.find(".page-hero-highlights-header-start").exists()).toBe(true);
|
|
@@ -42,10 +56,31 @@ describe("PageHeroHighlightsHeader", () => {
|
|
|
42
56
|
});
|
|
43
57
|
|
|
44
58
|
it("applies styleClassPassthrough classes", async () => {
|
|
45
|
-
const wrapper = await
|
|
59
|
+
const wrapper = await mountTracked(PageHeroHighlightsHeader, {
|
|
46
60
|
props: { styleClassPassthrough: ["extra-class", "another-class"] },
|
|
47
61
|
});
|
|
48
62
|
expect(wrapper.find(".page-hero-highlights-header").classes()).toContain("extra-class");
|
|
49
63
|
expect(wrapper.find(".page-hero-highlights-header").classes()).toContain("another-class");
|
|
50
64
|
});
|
|
51
65
|
});
|
|
66
|
+
|
|
67
|
+
describe("PageHeroHighlightsHeader end slot toggled after mount", () => {
|
|
68
|
+
it("adds and removes .page-hero-highlights-header-end with a v-if'd end slot", async () => {
|
|
69
|
+
const Host = defineComponent({
|
|
70
|
+
components: { PageHeroHighlightsHeader },
|
|
71
|
+
props: { showEnd: { type: Boolean, default: false } },
|
|
72
|
+
template: `
|
|
73
|
+
<PageHeroHighlightsHeader>
|
|
74
|
+
<template #start><h1>Title</h1></template>
|
|
75
|
+
<template v-if="showEnd" #end><button class="cta">Go</button></template>
|
|
76
|
+
</PageHeroHighlightsHeader>
|
|
77
|
+
`,
|
|
78
|
+
});
|
|
79
|
+
const wrapper = await mountTracked(Host);
|
|
80
|
+
expect(wrapper.find(".page-hero-highlights-header-end").exists()).toBe(false);
|
|
81
|
+
await wrapper.setProps({ showEnd: true });
|
|
82
|
+
expect(wrapper.find(".page-hero-highlights-header-end .cta").exists()).toBe(true);
|
|
83
|
+
await wrapper.setProps({ showEnd: false });
|
|
84
|
+
expect(wrapper.find(".page-hero-highlights-header-end").exists()).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`PageHeroHighlights > renders correct HTML structure 1`] = `
|
|
4
4
|
"<div class="page-hero-highlights content-align-center has-content-panel">
|
|
5
|
-
<div class="page-hero-highlights-
|
|
6
|
-
<div class="page-hero-highlights-header-
|
|
7
|
-
<
|
|
5
|
+
<div class="page-hero-highlights-grid">
|
|
6
|
+
<div class="page-hero-highlights-header-row">
|
|
7
|
+
<div class="page-hero-highlights-header-slot">
|
|
8
|
+
<h1>Page Title</h1>
|
|
9
|
+
</div>
|
|
8
10
|
</div>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
<div class="page-hero-highlights-strip flexible-widths justify-start">
|
|
12
|
+
<div class="highlight">Highlight 1</div>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="page-hero-highlights-content-row">
|
|
15
|
+
<div class="page-hero-highlights-content-slot">
|
|
16
|
+
<p>Content</p>
|
|
17
|
+
</div>
|
|
16
18
|
</div>
|
|
17
19
|
</div>
|
|
18
20
|
</div>"
|
|
@@ -29,6 +29,16 @@ columns need more room.
|
|
|
29
29
|
| `--single-checkbox-margin-block-start` | `1.2rem` | Space between the legend/description and the checkbox |
|
|
30
30
|
| `--single-checkbox-column-min-width` | `100px` | Grid column minimum width with `options-layout="equal-widths"` |
|
|
31
31
|
|
|
32
|
+
> **Changed 2026-10-16**: the equal-width column minimum is capped at `min(100%, ...)` so one long
|
|
33
|
+
> label can no longer push the grid wider than its container.
|
|
34
|
+
|
|
35
|
+
### Root fieldset
|
|
36
|
+
|
|
37
|
+
`.multiple-checkboxes-fieldset` and `.single-checkbox-fieldset` set `min-inline-size: 0` and
|
|
38
|
+
`overflow-wrap: anywhere` (added 2026-10-16). The second inherits into the legend, description,
|
|
39
|
+
option labels and error, so long unbroken words and URLs wrap instead of overflowing. Neither is a
|
|
40
|
+
token; override `overflow-wrap` on your passthrough class if you need the old behaviour.
|
|
41
|
+
|
|
32
42
|
---
|
|
33
43
|
|
|
34
44
|
## State hooks
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
</template>
|
|
24
24
|
</InputDescription>
|
|
25
25
|
<div ref="itemsContainer" class="multiple-checkboxes-items" :data-options-layout="optionsLayout">
|
|
26
|
-
<template v-for="item in fieldData.data" :key="item.id">
|
|
26
|
+
<template v-for="(item, index) in fieldData.data" :key="item.id">
|
|
27
27
|
<InputCheckboxRadioButton
|
|
28
28
|
v-if="isButton"
|
|
29
|
-
:id="`${
|
|
29
|
+
:id="`${optionIdPrefix}-${index}`"
|
|
30
30
|
v-model="modelValue"
|
|
31
31
|
type="checkbox"
|
|
32
32
|
:name
|
|
@@ -119,7 +119,14 @@ const slots = useSlots();
|
|
|
119
119
|
const modelValue = defineModel<(string | number | boolean)[] | string | number | boolean | undefined>({ required: true });
|
|
120
120
|
const fieldData = defineModel<IFormMultipleOptions>("fieldData", { required: true });
|
|
121
121
|
|
|
122
|
-
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
122
|
+
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
123
|
+
|
|
124
|
+
watch(
|
|
125
|
+
() => props.styleClassPassthrough,
|
|
126
|
+
() => {
|
|
127
|
+
resetElementClasses(props.styleClassPassthrough);
|
|
128
|
+
}
|
|
129
|
+
);
|
|
123
130
|
|
|
124
131
|
const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
|
|
125
132
|
props.name,
|
|
@@ -127,6 +134,8 @@ const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
|
|
|
127
134
|
slots
|
|
128
135
|
);
|
|
129
136
|
|
|
137
|
+
const optionIdPrefix = useId();
|
|
138
|
+
|
|
130
139
|
const { maxChildWidth, updateMaxChildWidth } = useMaxChildWidth(
|
|
131
140
|
".input-checkbox-radio-field-label, .input-checkbox-radio-field",
|
|
132
141
|
"100px"
|
|
@@ -146,6 +155,11 @@ watch(
|
|
|
146
155
|
|
|
147
156
|
<style lang="css">
|
|
148
157
|
@layer components {
|
|
158
|
+
.multiple-checkboxes-fieldset {
|
|
159
|
+
min-inline-size: 0;
|
|
160
|
+
overflow-wrap: anywhere;
|
|
161
|
+
}
|
|
162
|
+
|
|
149
163
|
.multiple-checkboxes-items {
|
|
150
164
|
display: flex;
|
|
151
165
|
gap: var(--multiple-checkboxes-gap, 1.2rem);
|
|
@@ -165,7 +179,7 @@ watch(
|
|
|
165
179
|
grid-template-columns: repeat(
|
|
166
180
|
auto-fit,
|
|
167
181
|
minmax(
|
|
168
|
-
calc(var(--input-checked-icon-gap) + (2 * var(--input-checkbox-label-padding-inline)) + v-bind(maxChildWidth)),
|
|
182
|
+
min(100%, calc(var(--input-checked-icon-gap) + (2 * var(--input-checkbox-label-padding-inline)) + v-bind(maxChildWidth))),
|
|
169
183
|
1fr
|
|
170
184
|
)
|
|
171
185
|
);
|
|
@@ -84,7 +84,14 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
84
84
|
|
|
85
85
|
const slots = useSlots();
|
|
86
86
|
const modelValue = defineModel<(string | number | boolean)[] | string | number | boolean | undefined>({ required: true });
|
|
87
|
-
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
87
|
+
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
88
|
+
|
|
89
|
+
watch(
|
|
90
|
+
() => props.styleClassPassthrough,
|
|
91
|
+
() => {
|
|
92
|
+
resetElementClasses(props.styleClassPassthrough);
|
|
93
|
+
}
|
|
94
|
+
);
|
|
88
95
|
|
|
89
96
|
const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
|
|
90
97
|
props.name,
|
|
@@ -95,6 +102,11 @@ const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
|
|
|
95
102
|
|
|
96
103
|
<style lang="css">
|
|
97
104
|
@layer components {
|
|
105
|
+
.single-checkbox-fieldset {
|
|
106
|
+
min-inline-size: 0;
|
|
107
|
+
overflow-wrap: anywhere;
|
|
108
|
+
}
|
|
109
|
+
|
|
98
110
|
.single-checkbox-items {
|
|
99
111
|
display: flex;
|
|
100
112
|
gap: var(--single-checkbox-gap, 1.2rem);
|
|
@@ -111,7 +123,7 @@ const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
|
|
|
111
123
|
|
|
112
124
|
&[data-options-layout="equal-widths"] {
|
|
113
125
|
display: grid;
|
|
114
|
-
grid-template-columns: repeat(auto-fit, minmax(var(--single-checkbox-column-min-width, 100px), 1fr));
|
|
126
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--single-checkbox-column-min-width, 100px)), 1fr));
|
|
115
127
|
}
|
|
116
128
|
}
|
|
117
129
|
}
|