srcdev-nuxt-components 9.6.18 → 9.6.20
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/css-relative-colour-calc.md +15 -0
- package/.claude/rules/consumer/ids-from-useid.md +3 -2
- package/.claude/rules/consumer/vue-dynamic-arg-no-spaces.md +16 -0
- package/.claude/skills/component-compliance-checklist.md +11 -1
- package/.claude/skills/component-dynamic-slots.md +2 -2
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/accordion-chat.md +99 -0
- package/.claude/skills/components/accordion-group.md +98 -0
- package/.claude/skills/components/action-menu.md +11 -5
- package/.claude/skills/components/auto-grid.md +3 -1
- package/.claude/skills/components/capture-qr-code.md +9 -5
- package/.claude/skills/components/carousel-flip.md +41 -30
- package/.claude/skills/components/column-flow-grid.md +2 -0
- package/.claude/skills/components/dashboard-quad-grid.md +7 -2
- package/.claude/skills/components/dashboard-stats-grid.md +4 -0
- package/.claude/skills/components/decode-qr-code.md +7 -2
- package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
- package/.claude/skills/components/deep-expanding-menu.md +22 -21
- package/.claude/skills/components/expanding-panel-classic.md +9 -7
- package/.claude/skills/components/expanding-panel.md +9 -8
- package/.claude/skills/components/grid-stack.md +2 -0
- package/.claude/skills/components/layout-grid-by-cols.md +2 -0
- package/.claude/skills/components/masonry-grid.md +9 -1
- package/.claude/skills/components/page-row.md +3 -0
- package/.claude/skills/components/samaritan-prompt.md +24 -8
- package/.claude/skills/components/site-header.md +16 -2
- package/.claude/skills/components/sparkle-effect.md +79 -0
- package/.claude/skills/index.md +16 -14
- package/.claude/skills/storybook-add-story.md +33 -0
- package/.claude/skills/testing-add-unit-test.md +26 -0
- package/.claude/skills/using-component-skills.md +1 -1
- package/.vscode/srcdev-component-accordion-chat.code-snippets +42 -0
- package/.vscode/srcdev-component-accordion-group.code-snippets +38 -0
- package/.vscode/srcdev-component-action-menu.code-snippets +3 -3
- package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
- package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
- package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
- package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
- package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
- package/.vscode/srcdev-component-site-header.code-snippets +13 -1
- package/.vscode/srcdev-component-sparkle-effect.code-snippets +33 -0
- package/app/components/01.atoms/animations/sparkle-effect/CONSUMER-STYLING.md +75 -0
- package/app/components/01.atoms/animations/sparkle-effect/SparkleEffect.vue +213 -0
- package/app/components/01.atoms/animations/sparkle-effect/stories/SparkleEffect.stories.ts +303 -0
- package/app/components/01.atoms/animations/sparkle-effect/tests/SparkleEffect.spec.ts +150 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +1 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
- package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
- package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
- package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
- package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
- package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
- package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
- package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
- package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
- package/app/components/01.atoms/page-row/PageRow.vue +16 -15
- package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
- package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
- package/app/components/02.molecules/action-menu/ActionMenu.vue +6 -2
- package/app/components/02.molecules/action-menu/ActionMenuItem.vue +6 -3
- package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +11 -4
- package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +130 -18
- package/app/components/02.molecules/expandable/accordion-chat/AccordionChat.vue +227 -0
- package/app/components/02.molecules/expandable/accordion-chat/CONSUMER-STYLING.md +150 -0
- package/app/components/02.molecules/expandable/accordion-chat/stories/AccordionChat.stories.ts +347 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/AccordionChat.spec.ts +205 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/__snapshots__/AccordionChat.spec.ts.snap +40 -0
- package/app/components/02.molecules/expandable/{accordian/AccordianCore.vue → accordion-group/AccordionGroup.vue} +32 -20
- package/app/components/02.molecules/expandable/accordion-group/CONSUMER-STYLING.md +66 -0
- package/app/components/02.molecules/expandable/accordion-group/stories/AccordionGroup.stories.ts +238 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/AccordionGroup.spec.ts +172 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/__snapshots__/AccordionGroup.spec.ts.snap +45 -0
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +11 -6
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +82 -1
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +8 -8
- package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +10 -5
- package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +83 -1
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
- package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
- package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
- package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
- package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
- package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
- package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
- package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
- package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
- package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
- package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
- package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
- package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
- package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +2 -2
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +2 -2
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
- package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
- package/app/types/components/qr-code.d.ts +12 -0
- package/app/utils/graphemes.ts +8 -0
- package/package.json +1 -1
- package/.claude/skills/components/accordian-core.md +0 -180
- package/.vscode/srcdev-component-accordian-core.code-snippets +0 -74
- package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
- package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
- package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
- package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
- package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
- package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
- package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +0 -100
- package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +0 -272
- package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +0 -257
- package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -95
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
|
@@ -1,24 +1,37 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import AutoGrid from "../AutoGrid.vue";
|
|
4
4
|
|
|
5
|
+
const mounted: Awaited<ReturnType<typeof mountSuspended>>[] = [];
|
|
6
|
+
|
|
7
|
+
const mountComponent = async (options: Parameters<typeof mountSuspended<typeof AutoGrid>>[1] = {}) => {
|
|
8
|
+
const wrapper = await mountSuspended(AutoGrid, options);
|
|
9
|
+
mounted.push(wrapper);
|
|
10
|
+
return wrapper;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
5
18
|
describe("AutoGrid", () => {
|
|
6
19
|
// ─── Mount ───────────────────────────────────────────────────────────────
|
|
7
20
|
|
|
8
21
|
it("mounts without error", async () => {
|
|
9
|
-
const wrapper = await
|
|
22
|
+
const wrapper = await mountComponent();
|
|
10
23
|
expect(wrapper.vm).toBeTruthy();
|
|
11
24
|
});
|
|
12
25
|
|
|
13
26
|
// ─── Snapshots ───────────────────────────────────────────────────────────
|
|
14
27
|
|
|
15
28
|
it("renders correct HTML structure (default props)", async () => {
|
|
16
|
-
const wrapper = await
|
|
29
|
+
const wrapper = await mountComponent();
|
|
17
30
|
expect(wrapper.html()).toMatchSnapshot();
|
|
18
31
|
});
|
|
19
32
|
|
|
20
33
|
it("renders correct HTML structure (all props and slots set)", async () => {
|
|
21
|
-
const wrapper = await
|
|
34
|
+
const wrapper = await mountComponent({
|
|
22
35
|
props: {
|
|
23
36
|
tag: "section",
|
|
24
37
|
isResponsive: true,
|
|
@@ -36,29 +49,29 @@ describe("AutoGrid", () => {
|
|
|
36
49
|
// ─── Root element ────────────────────────────────────────────────────────
|
|
37
50
|
|
|
38
51
|
it("renders a <div> as the root element by default", async () => {
|
|
39
|
-
const wrapper = await
|
|
52
|
+
const wrapper = await mountComponent();
|
|
40
53
|
expect(wrapper.element.tagName).toBe("DIV");
|
|
41
54
|
});
|
|
42
55
|
|
|
43
56
|
it("always has the auto-grid class", async () => {
|
|
44
|
-
const wrapper = await
|
|
57
|
+
const wrapper = await mountComponent();
|
|
45
58
|
expect(wrapper.classes()).toContain("auto-grid");
|
|
46
59
|
});
|
|
47
60
|
|
|
48
61
|
// ─── Tag prop ────────────────────────────────────────────────────────────
|
|
49
62
|
|
|
50
63
|
it("renders a <section> when tag is section", async () => {
|
|
51
|
-
const wrapper = await
|
|
64
|
+
const wrapper = await mountComponent({ props: { tag: "section" } });
|
|
52
65
|
expect(wrapper.element.tagName).toBe("SECTION");
|
|
53
66
|
});
|
|
54
67
|
|
|
55
68
|
it("renders an <article> when tag is article", async () => {
|
|
56
|
-
const wrapper = await
|
|
69
|
+
const wrapper = await mountComponent({ props: { tag: "article" } });
|
|
57
70
|
expect(wrapper.element.tagName).toBe("ARTICLE");
|
|
58
71
|
});
|
|
59
72
|
|
|
60
73
|
it("renders a <main> when tag is main", async () => {
|
|
61
|
-
const wrapper = await
|
|
74
|
+
const wrapper = await mountComponent({ props: { tag: "main" } });
|
|
62
75
|
expect(wrapper.element.tagName).toBe("MAIN");
|
|
63
76
|
});
|
|
64
77
|
|
|
@@ -71,13 +84,13 @@ describe("AutoGrid", () => {
|
|
|
71
84
|
it.each(["div", "section", "article", "main"] as const)(
|
|
72
85
|
"does not set aria-labelledby when tag is %s",
|
|
73
86
|
async (tag) => {
|
|
74
|
-
const wrapper = await
|
|
87
|
+
const wrapper = await mountComponent({ props: { tag } });
|
|
75
88
|
expect(wrapper.attributes("aria-labelledby")).toBeUndefined();
|
|
76
89
|
}
|
|
77
90
|
);
|
|
78
91
|
|
|
79
92
|
it("passes a consumer aria-label through to the root", async () => {
|
|
80
|
-
const wrapper = await
|
|
93
|
+
const wrapper = await mountComponent({
|
|
81
94
|
props: { tag: "section" },
|
|
82
95
|
attrs: { "aria-label": "Practice stats" },
|
|
83
96
|
});
|
|
@@ -86,27 +99,27 @@ describe("AutoGrid", () => {
|
|
|
86
99
|
|
|
87
100
|
// ─── isResponsive ────────────────────────────────────────────────────────
|
|
88
101
|
|
|
89
|
-
it("does not have
|
|
90
|
-
const wrapper = await
|
|
91
|
-
expect(wrapper.
|
|
102
|
+
it("does not have data-responsive by default", async () => {
|
|
103
|
+
const wrapper = await mountComponent();
|
|
104
|
+
expect(wrapper.attributes("data-responsive")).toBeUndefined();
|
|
92
105
|
});
|
|
93
106
|
|
|
94
|
-
it("adds
|
|
95
|
-
const wrapper = await
|
|
96
|
-
expect(wrapper.
|
|
107
|
+
it("adds data-responsive when isResponsive is true", async () => {
|
|
108
|
+
const wrapper = await mountComponent({ props: { isResponsive: true } });
|
|
109
|
+
expect(wrapper.attributes("data-responsive")).toBe("");
|
|
97
110
|
});
|
|
98
111
|
|
|
99
|
-
it("removes
|
|
100
|
-
const wrapper = await
|
|
101
|
-
expect(wrapper.
|
|
112
|
+
it("removes data-responsive when isResponsive changes to false", async () => {
|
|
113
|
+
const wrapper = await mountComponent({ props: { isResponsive: true } });
|
|
114
|
+
expect(wrapper.attributes("data-responsive")).toBe("");
|
|
102
115
|
await wrapper.setProps({ isResponsive: false });
|
|
103
|
-
expect(wrapper.
|
|
116
|
+
expect(wrapper.attributes("data-responsive")).toBeUndefined();
|
|
104
117
|
});
|
|
105
118
|
|
|
106
119
|
// ─── Slots ───────────────────────────────────────────────────────────────
|
|
107
120
|
|
|
108
121
|
it("renders a named slot", async () => {
|
|
109
|
-
const wrapper = await
|
|
122
|
+
const wrapper = await mountComponent({
|
|
110
123
|
slots: { "item-1": "<div class='card'>Card 1</div>" },
|
|
111
124
|
});
|
|
112
125
|
expect(wrapper.find(".card").exists()).toBe(true);
|
|
@@ -114,7 +127,7 @@ describe("AutoGrid", () => {
|
|
|
114
127
|
});
|
|
115
128
|
|
|
116
129
|
it("renders multiple named slots", async () => {
|
|
117
|
-
const wrapper = await
|
|
130
|
+
const wrapper = await mountComponent({
|
|
118
131
|
slots: {
|
|
119
132
|
"item-1": "<div class='card-1'>Card 1</div>",
|
|
120
133
|
"item-2": "<div class='card-2'>Card 2</div>",
|
|
@@ -127,21 +140,21 @@ describe("AutoGrid", () => {
|
|
|
127
140
|
});
|
|
128
141
|
|
|
129
142
|
it("renders no slot content when no slots are provided", async () => {
|
|
130
|
-
const wrapper = await
|
|
143
|
+
const wrapper = await mountComponent();
|
|
131
144
|
expect(wrapper.text()).toBe("");
|
|
132
145
|
});
|
|
133
146
|
|
|
134
147
|
// ─── styleClassPassthrough ───────────────────────────────────────────────
|
|
135
148
|
|
|
136
149
|
it("applies a single styleClassPassthrough string to the root", async () => {
|
|
137
|
-
const wrapper = await
|
|
150
|
+
const wrapper = await mountComponent({
|
|
138
151
|
props: { styleClassPassthrough: "my-grid" },
|
|
139
152
|
});
|
|
140
153
|
expect(wrapper.classes()).toContain("my-grid");
|
|
141
154
|
});
|
|
142
155
|
|
|
143
156
|
it("applies multiple styleClassPassthrough classes from an array", async () => {
|
|
144
|
-
const wrapper = await
|
|
157
|
+
const wrapper = await mountComponent({
|
|
145
158
|
props: { styleClassPassthrough: ["my-grid", "mbe-32"] },
|
|
146
159
|
});
|
|
147
160
|
expect(wrapper.classes()).toContain("my-grid");
|
|
@@ -149,7 +162,7 @@ describe("AutoGrid", () => {
|
|
|
149
162
|
});
|
|
150
163
|
|
|
151
164
|
it("updates classes when styleClassPassthrough prop changes", async () => {
|
|
152
|
-
const wrapper = await
|
|
165
|
+
const wrapper = await mountComponent({
|
|
153
166
|
props: { styleClassPassthrough: ["original"] },
|
|
154
167
|
});
|
|
155
168
|
expect(wrapper.classes()).toContain("original");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
3
|
exports[`AutoGrid > renders correct HTML structure (all props and slots set) 1`] = `
|
|
4
|
-
"<section class="auto-grid custom-class
|
|
4
|
+
"<section class="auto-grid custom-class" data-responsive="">
|
|
5
5
|
<div>Item 1</div>
|
|
6
6
|
<div>Item 2</div>
|
|
7
7
|
<div>Item 3</div>
|
|
@@ -24,6 +24,15 @@ part of the public token API. See `.claude/skills/components/column-flow-grid.md
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
+
## State hooks
|
|
28
|
+
|
|
29
|
+
No state attributes. Element classes: `.column-flow-grid-wrapper` (root, the multi-column box) and
|
|
30
|
+
`.column-flow-grid-item` (each slot, `break-inside: avoid`, wraps long words since 2026-10-21).
|
|
31
|
+
|
|
32
|
+
Private (not public API): `--_column-flow-grid-gap` and `--_column-flow-grid-item-min-width`, set
|
|
33
|
+
inline from the `gap`/`unit` and `itemMinWidth` props. The second was `--_item-min-width` until
|
|
34
|
+
2026-10-21, a generic name that leaked into slotted components.
|
|
35
|
+
|
|
27
36
|
## Local overrides
|
|
28
37
|
|
|
29
38
|
Set the tokens above on an element you own (a page or section class, or a class added with
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
:is="tag"
|
|
4
4
|
class="column-flow-grid-wrapper"
|
|
5
5
|
:class="[elementClasses]"
|
|
6
|
-
:style="`--_column-flow-grid-gap: ${gap}${unit}; --
|
|
6
|
+
:style="`--_column-flow-grid-gap: ${gap}${unit}; --_column-flow-grid-item-min-width: ${itemMinWidth}px`"
|
|
7
7
|
>
|
|
8
8
|
<div v-for="(_, name) in $slots" :key="name" class="column-flow-grid-item">
|
|
9
9
|
<slot :name="name"></slot>
|
|
@@ -41,11 +41,12 @@ watch(
|
|
|
41
41
|
<style lang="css">
|
|
42
42
|
@layer components {
|
|
43
43
|
.column-flow-grid-wrapper {
|
|
44
|
-
columns: auto var(--
|
|
44
|
+
columns: auto var(--_column-flow-grid-item-min-width);
|
|
45
45
|
column-gap: var(--_column-flow-grid-gap);
|
|
46
46
|
|
|
47
47
|
.column-flow-grid-item {
|
|
48
48
|
break-inside: avoid;
|
|
49
|
+
overflow-wrap: anywhere;
|
|
49
50
|
outline: 0.1rem solid var(--column-flow-grid-item-border-colour, var(--theme-border));
|
|
50
51
|
padding: var(--column-flow-grid-item-padding, 1.2rem);
|
|
51
52
|
margin-block-end: var(--_column-flow-grid-gap);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
1
2
|
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
3
|
+
import type { MediaCanvas } from "~/types/components";
|
|
4
|
+
import CanvasSwitcher from "../../../canvas-switcher/CanvasSwitcher.vue";
|
|
2
5
|
import ColumnFlowGrid from "../ColumnFlowGrid.vue";
|
|
3
6
|
|
|
4
7
|
interface ColumnFlowGridArgs {
|
|
@@ -176,3 +179,46 @@ export const TwoItems: StoryFn<ColumnFlowGridArgs> = (args) => ({
|
|
|
176
179
|
`,
|
|
177
180
|
});
|
|
178
181
|
TwoItems.storyName = "Two Items (no count prop needed)";
|
|
182
|
+
|
|
183
|
+
export const StressTest: StoryFn = () => ({
|
|
184
|
+
components: { ColumnFlowGrid },
|
|
185
|
+
template: `
|
|
186
|
+
<ColumnFlowGrid :item-min-width="240">
|
|
187
|
+
<template #item-1><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><h3 style="margin: 0;">Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspaces</h3><p style="margin: 0;">https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own</p></div></template>
|
|
188
|
+
<template #item-2><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">Ein sehr langer Beschreibungstext aus Northtown, der auf schmalen Bildschirmen mehrfach umbrechen muss, ohne das Raster zu sprengen oder die Nachbarspalten zusammenzudrücken.</p></div></template>
|
|
189
|
+
<template #item-3><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><img src="/images/banners/banner-1.webp" width="1600" height="400" alt="" style="display: block; max-inline-size: 100%; block-size: auto;"></div></template>
|
|
190
|
+
<template #item-4><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">🙂 👍🏽 👨👩👧 <b>Not bold</b></p></div></template>
|
|
191
|
+
<template #item-5><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;" dir="rtl">مرحبا بالعالم، نص طويل من اليمين إلى اليسار</p></div></template>
|
|
192
|
+
<template #item-6><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"></div></template>
|
|
193
|
+
<template #item-7><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">x</p></div></template>
|
|
194
|
+
</ColumnFlowGrid>
|
|
195
|
+
`,
|
|
196
|
+
});
|
|
197
|
+
StressTest.storyName = "Stress Test (Worst-Case Data)";
|
|
198
|
+
StressTest.decorators = [
|
|
199
|
+
(story, context) => ({
|
|
200
|
+
components: { story, CanvasSwitcher },
|
|
201
|
+
setup() {
|
|
202
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "mobileCanvas");
|
|
203
|
+
return { canvasName };
|
|
204
|
+
},
|
|
205
|
+
template: `
|
|
206
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
207
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
208
|
+
</div>
|
|
209
|
+
<div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
|
|
210
|
+
<story />
|
|
211
|
+
</div>
|
|
212
|
+
`,
|
|
213
|
+
}),
|
|
214
|
+
];
|
|
215
|
+
StressTest.parameters = {
|
|
216
|
+
layout: "fullscreen",
|
|
217
|
+
docs: {
|
|
218
|
+
story: { inline: false, height: "720px" },
|
|
219
|
+
description: {
|
|
220
|
+
story:
|
|
221
|
+
"Oversized and hostile slot content at every `CanvasSwitcher` width. Check that: the unbroken heading and URL wrap inside their cell instead of widening it; the German paragraph wraps; the 1600px image scales down to its cell; emoji, `<b>` and right-to-left text render as text; the empty and one-character cells keep their place; no neighbouring cell is squeezed to nothing and nothing scrolls sideways. Items flow down each column, so reading order runs top to bottom, then across.",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
};
|
|
@@ -1,27 +1,40 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import ColumnFlowGrid from "../ColumnFlowGrid.vue";
|
|
4
4
|
|
|
5
|
+
const mounted: Awaited<ReturnType<typeof mountSuspended>>[] = [];
|
|
6
|
+
|
|
7
|
+
const mountComponent = async (options: Parameters<typeof mountSuspended<typeof ColumnFlowGrid>>[1] = {}) => {
|
|
8
|
+
const wrapper = await mountSuspended(ColumnFlowGrid, options);
|
|
9
|
+
mounted.push(wrapper);
|
|
10
|
+
return wrapper;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
5
18
|
describe("ColumnFlowGrid", () => {
|
|
6
19
|
it("mounts without error", async () => {
|
|
7
|
-
const wrapper = await
|
|
20
|
+
const wrapper = await mountComponent();
|
|
8
21
|
expect(wrapper.vm).toBeTruthy();
|
|
9
22
|
});
|
|
10
23
|
|
|
11
24
|
it("renders as div by default", async () => {
|
|
12
|
-
const wrapper = await
|
|
25
|
+
const wrapper = await mountComponent();
|
|
13
26
|
expect(wrapper.element.tagName).toBe("DIV");
|
|
14
27
|
});
|
|
15
28
|
|
|
16
29
|
it("renders a different tag when provided", async () => {
|
|
17
|
-
const wrapper = await
|
|
30
|
+
const wrapper = await mountComponent({
|
|
18
31
|
props: { tag: "section" },
|
|
19
32
|
});
|
|
20
33
|
expect(wrapper.element.tagName).toBe("SECTION");
|
|
21
34
|
});
|
|
22
35
|
|
|
23
36
|
it("renders slot content", async () => {
|
|
24
|
-
const wrapper = await
|
|
37
|
+
const wrapper = await mountComponent({
|
|
25
38
|
slots: {
|
|
26
39
|
"item-1": "<div>Item 1</div>",
|
|
27
40
|
},
|
|
@@ -30,7 +43,7 @@ describe("ColumnFlowGrid", () => {
|
|
|
30
43
|
});
|
|
31
44
|
|
|
32
45
|
it("renders each provided slot as its own column-flow-grid-item", async () => {
|
|
33
|
-
const wrapper = await
|
|
46
|
+
const wrapper = await mountComponent({
|
|
34
47
|
slots: {
|
|
35
48
|
"item-1": "<div>Item 1</div>",
|
|
36
49
|
"item-2": "<div>Item 2</div>",
|
|
@@ -45,28 +58,28 @@ describe("ColumnFlowGrid", () => {
|
|
|
45
58
|
});
|
|
46
59
|
|
|
47
60
|
it("renders no items when no slots are provided", async () => {
|
|
48
|
-
const wrapper = await
|
|
61
|
+
const wrapper = await mountComponent();
|
|
49
62
|
expect(wrapper.findAll(".column-flow-grid-item")).toHaveLength(0);
|
|
50
63
|
});
|
|
51
64
|
|
|
52
65
|
it("applies itemMinWidth and gap as inline CSS custom properties", async () => {
|
|
53
|
-
const wrapper = await
|
|
66
|
+
const wrapper = await mountComponent({
|
|
54
67
|
props: { itemMinWidth: 250, gap: 2, unit: "em" },
|
|
55
68
|
});
|
|
56
69
|
const style = (wrapper.element as HTMLElement).style;
|
|
57
|
-
expect(style.getPropertyValue("--
|
|
70
|
+
expect(style.getPropertyValue("--_column-flow-grid-item-min-width")).toBe("250px");
|
|
58
71
|
expect(style.getPropertyValue("--_column-flow-grid-gap")).toBe("2em");
|
|
59
72
|
});
|
|
60
73
|
|
|
61
74
|
it("applies styleClassPassthrough", async () => {
|
|
62
|
-
const wrapper = await
|
|
75
|
+
const wrapper = await mountComponent({
|
|
63
76
|
props: { styleClassPassthrough: "custom-class" },
|
|
64
77
|
});
|
|
65
78
|
expect(wrapper.classes()).toContain("custom-class");
|
|
66
79
|
});
|
|
67
80
|
|
|
68
81
|
it("applies multiple styleClassPassthrough classes", async () => {
|
|
69
|
-
const wrapper = await
|
|
82
|
+
const wrapper = await mountComponent({
|
|
70
83
|
props: { styleClassPassthrough: ["class-a", "class-b"] },
|
|
71
84
|
});
|
|
72
85
|
expect(wrapper.classes()).toContain("class-a");
|
|
@@ -31,6 +31,9 @@ There are no `data-*` attributes or state classes.
|
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
|
+
The stack's single column is `minmax(0, 1fr)` and each layer has `min-inline-size: 0` (since
|
|
35
|
+
2026-10-21), so a layer with an unbroken string can't widen the stack past its container.
|
|
36
|
+
|
|
34
37
|
## Local overrides
|
|
35
38
|
|
|
36
39
|
Set the tokens above on an element you own (a page or section class, or a class added with
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
1
2
|
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
3
|
+
import type { MediaCanvas } from "~/types/components";
|
|
4
|
+
import CanvasSwitcher from "../../../canvas-switcher/CanvasSwitcher.vue";
|
|
2
5
|
import GridStackComponent from "../GridStack.vue";
|
|
3
6
|
import BannerVideoComponent from "../../../banner-video/BannerVideo.vue";
|
|
4
7
|
|
|
@@ -160,3 +163,41 @@ WithVideoOverlay.parameters = {
|
|
|
160
163
|
},
|
|
161
164
|
},
|
|
162
165
|
};
|
|
166
|
+
|
|
167
|
+
export const StressTest: StoryFn = () => ({
|
|
168
|
+
components: { GridStackComponent },
|
|
169
|
+
template: `
|
|
170
|
+
<GridStackComponent>
|
|
171
|
+
<template #background><img src="/images/banners/banner-1.webp" width="1600" height="400" alt="" style="display: block; max-inline-size: 100%; block-size: auto;"></template>
|
|
172
|
+
<template #overlay><div style="align-self: end; padding: 1.2rem; background: color-mix(in oklab, var(--theme-surface-subtle) 85%, transparent);"><h3 style="margin: 0;">Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspaces</h3><p style="margin: 0;">https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own</p><p style="margin: 0;">Ein sehr langer Beschreibungstext aus Northtown, der auf schmalen Bildschirmen mehrfach umbrechen muss, ohne das Raster zu sprengen oder die Nachbarspalten zusammenzudrücken.</p><p style="margin: 0;" dir="rtl">مرحبا بالعالم، نص طويل من اليمين إلى اليسار</p></div></template>
|
|
173
|
+
</GridStackComponent>
|
|
174
|
+
`,
|
|
175
|
+
});
|
|
176
|
+
StressTest.storyName = "Stress Test (Worst-Case Data)";
|
|
177
|
+
StressTest.decorators = [
|
|
178
|
+
(story, context) => ({
|
|
179
|
+
components: { story, CanvasSwitcher },
|
|
180
|
+
setup() {
|
|
181
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "mobileCanvas");
|
|
182
|
+
return { canvasName };
|
|
183
|
+
},
|
|
184
|
+
template: `
|
|
185
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
186
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
187
|
+
</div>
|
|
188
|
+
<div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
|
|
189
|
+
<story />
|
|
190
|
+
</div>
|
|
191
|
+
`,
|
|
192
|
+
}),
|
|
193
|
+
];
|
|
194
|
+
StressTest.parameters = {
|
|
195
|
+
layout: "fullscreen",
|
|
196
|
+
docs: {
|
|
197
|
+
story: { inline: false, height: "720px" },
|
|
198
|
+
description: {
|
|
199
|
+
story:
|
|
200
|
+
"Oversized and hostile slot content at every `CanvasSwitcher` width. Check that: the unbroken heading and URL wrap inside their cell instead of widening it; the German paragraph wraps; the 1600px image scales down to its cell; emoji, `<b>` and right-to-left text render as text; the empty and one-character cells keep their place; no neighbouring cell is squeezed to nothing and nothing scrolls sideways. Here the stress is one layer whose text is far wider than the image layer beneath it: both stay inside the stack.",
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
};
|
|
@@ -1,27 +1,40 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import GridStack from "../GridStack.vue";
|
|
4
4
|
|
|
5
|
+
const mounted: Awaited<ReturnType<typeof mountSuspended>>[] = [];
|
|
6
|
+
|
|
7
|
+
const mountComponent = async (options: Parameters<typeof mountSuspended<typeof GridStack>>[1] = {}) => {
|
|
8
|
+
const wrapper = await mountSuspended(GridStack, options);
|
|
9
|
+
mounted.push(wrapper);
|
|
10
|
+
return wrapper;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
5
18
|
describe("GridStack", () => {
|
|
6
19
|
it("mounts without error", async () => {
|
|
7
|
-
const wrapper = await
|
|
20
|
+
const wrapper = await mountComponent();
|
|
8
21
|
expect(wrapper.vm).toBeTruthy();
|
|
9
22
|
});
|
|
10
23
|
|
|
11
24
|
it("renders as div by default", async () => {
|
|
12
|
-
const wrapper = await
|
|
25
|
+
const wrapper = await mountComponent();
|
|
13
26
|
expect(wrapper.element.tagName).toBe("DIV");
|
|
14
27
|
});
|
|
15
28
|
|
|
16
29
|
it("renders a different tag when provided", async () => {
|
|
17
|
-
const wrapper = await
|
|
30
|
+
const wrapper = await mountComponent({
|
|
18
31
|
props: { tag: "section" },
|
|
19
32
|
});
|
|
20
33
|
expect(wrapper.element.tagName).toBe("SECTION");
|
|
21
34
|
});
|
|
22
35
|
|
|
23
36
|
it("renders slot content", async () => {
|
|
24
|
-
const wrapper = await
|
|
37
|
+
const wrapper = await mountComponent({
|
|
25
38
|
slots: {
|
|
26
39
|
"layer-1": "<div>Layer 1</div>",
|
|
27
40
|
},
|
|
@@ -30,7 +43,7 @@ describe("GridStack", () => {
|
|
|
30
43
|
});
|
|
31
44
|
|
|
32
45
|
it("renders multiple slots as stacked layers", async () => {
|
|
33
|
-
const wrapper = await
|
|
46
|
+
const wrapper = await mountComponent({
|
|
34
47
|
slots: {
|
|
35
48
|
"layer-1": "<div>Layer 1</div>",
|
|
36
49
|
"layer-2": "<div>Layer 2</div>",
|
|
@@ -45,19 +58,19 @@ describe("GridStack", () => {
|
|
|
45
58
|
});
|
|
46
59
|
|
|
47
60
|
it("renders no layers when no slots are provided", async () => {
|
|
48
|
-
const wrapper = await
|
|
61
|
+
const wrapper = await mountComponent();
|
|
49
62
|
expect(wrapper.findAll(".grid-stack__layer")).toHaveLength(0);
|
|
50
63
|
});
|
|
51
64
|
|
|
52
65
|
it("applies styleClassPassthrough", async () => {
|
|
53
|
-
const wrapper = await
|
|
66
|
+
const wrapper = await mountComponent({
|
|
54
67
|
props: { styleClassPassthrough: "custom-class" },
|
|
55
68
|
});
|
|
56
69
|
expect(wrapper.classes()).toContain("custom-class");
|
|
57
70
|
});
|
|
58
71
|
|
|
59
72
|
it("applies multiple styleClassPassthrough classes", async () => {
|
|
60
|
-
const wrapper = await
|
|
73
|
+
const wrapper = await mountComponent({
|
|
61
74
|
props: { styleClassPassthrough: ["class-a", "class-b"] },
|
|
62
75
|
});
|
|
63
76
|
expect(wrapper.classes()).toContain("class-a");
|
|
@@ -65,7 +78,7 @@ describe("GridStack", () => {
|
|
|
65
78
|
});
|
|
66
79
|
|
|
67
80
|
it("updates classes when styleClassPassthrough prop changes", async () => {
|
|
68
|
-
const wrapper = await
|
|
81
|
+
const wrapper = await mountComponent({
|
|
69
82
|
props: { styleClassPassthrough: ["original"] },
|
|
70
83
|
});
|
|
71
84
|
await wrapper.setProps({ styleClassPassthrough: ["updated"] });
|
|
@@ -74,12 +87,12 @@ describe("GridStack", () => {
|
|
|
74
87
|
});
|
|
75
88
|
|
|
76
89
|
it("always has the grid-stack class", async () => {
|
|
77
|
-
const wrapper = await
|
|
90
|
+
const wrapper = await mountComponent();
|
|
78
91
|
expect(wrapper.classes()).toContain("grid-stack");
|
|
79
92
|
});
|
|
80
93
|
|
|
81
94
|
it("passes a consumer aria-label through to the root", async () => {
|
|
82
|
-
const wrapper = await
|
|
95
|
+
const wrapper = await mountComponent({
|
|
83
96
|
props: { tag: "section" },
|
|
84
97
|
attrs: { "aria-label": "Hero" },
|
|
85
98
|
});
|
|
@@ -88,7 +101,7 @@ describe("GridStack", () => {
|
|
|
88
101
|
});
|
|
89
102
|
|
|
90
103
|
it("renders the base layer first so later slots paint on top", async () => {
|
|
91
|
-
const wrapper = await
|
|
104
|
+
const wrapper = await mountComponent({
|
|
92
105
|
slots: {
|
|
93
106
|
"layer-1": "<div class='base'>Base</div>",
|
|
94
107
|
"layer-2": "<div class='overlay'>Overlay</div>",
|
|
@@ -26,6 +26,21 @@ computed in JS from measured pixel values. See `.claude/skills/components/masonr
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
+
## State hooks
|
|
30
|
+
|
|
31
|
+
| Hook | Meaning |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `.masonry-grid-wrapper[data-multiple-cols]` | More than one column fits, so tiles are absolutely positioned by script |
|
|
34
|
+
|
|
35
|
+
Element classes: `.masonry-grid` (root), `.masonry-grid-wrapper`, `.masonry-grid-item`.
|
|
36
|
+
|
|
37
|
+
Private (not public API), set inline by script: `--_masonry-grid-item-position`,
|
|
38
|
+
`--_masonry-grid-item-top`, `--_masonry-grid-item-left` and `--_masonry-grid-item-width` on each tile and `--_masonry-grid-height` on the wrapper.
|
|
39
|
+
|
|
40
|
+
> Changed 2026-10-21: the state was the class `.multiple-cols`, and the private tokens were
|
|
41
|
+
> `--_position`, `--_position-top`, `--_position-left`, `--_element-width` and `--_wrapper-height`:
|
|
42
|
+
> generic names that inherited into components placed inside a tile.
|
|
43
|
+
|
|
29
44
|
## Local overrides
|
|
30
45
|
|
|
31
46
|
Set the tokens above on an element you own (a page or section class, or a class added with
|