srcdev-nuxt-components 9.4.9 → 9.4.11
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/commands/migrate-component.md +38 -6
- package/.claude/component-ledger/audit.json +1 -1
- package/.claude/component-ledger/build.mjs +26 -0
- package/.claude/component-ledger/output.html +8 -3
- package/.claude/component-ledger/template.html +7 -2
- package/.claude/skills/component-dynamic-slots.md +1 -1
- package/.claude/skills/components/alert-mask-core.md +84 -0
- package/.claude/skills/components/canvas-switcher.md +1 -1
- package/.claude/skills/components/carousel-flip.md +112 -168
- package/.claude/skills/components/container-glow.md +123 -0
- package/.claude/skills/components/dashboard-quad-grid.md +98 -0
- package/.claude/skills/components/dashboard-stats-grid.md +103 -0
- package/.claude/skills/components/display-theme-switch.md +60 -0
- package/.claude/skills/components/glowing-border.md +66 -0
- package/.claude/skills/components/input-number-core.md +214 -0
- package/.claude/skills/components/input-range-core.md +142 -0
- package/.claude/skills/components/input-select-core.md +151 -0
- package/.claude/skills/components/input-text-core.md +16 -0
- package/.claude/skills/components/input-textarea-core.md +154 -0
- package/.claude/skills/components/section-parallax.md +32 -10
- package/.claude/skills/components/select-menu.md +195 -0
- package/.claude/skills/components/toggle-switch-core.md +150 -0
- package/.claude/skills/components/ui-block-decorated.md +56 -0
- package/.claude/skills/components/wipe-away-vertical.md +98 -0
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/index.md +15 -1
- package/.vscode/css-custom-data.json +38 -0
- package/.vscode/settings.json +3 -0
- package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
- package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
- package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
- package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
- package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
- package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
- package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
- package/.vscode/srcdev-component-input-number.code-snippets +65 -0
- package/.vscode/srcdev-component-input-range.code-snippets +85 -0
- package/.vscode/srcdev-component-input-select.code-snippets +42 -0
- package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
- package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
- package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
- package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -0
- package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
- package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
- package/README.md +18 -37
- package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
- package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
- package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
- package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
- package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
- package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
- package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
- package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
- package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
- package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
- package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
- package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
- package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
- package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
- package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
- package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
- package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
- package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
- package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
- package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
- package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
- package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
- package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +3 -3
- package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
- package/app/components/01.atoms/card/CardCore.vue +4 -4
- package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
- package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
- package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
- package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
- package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
- package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
- package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +8 -8
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
- package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
- package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
- package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
- package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
- package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
- package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
- package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
- package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
- package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
- package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
- package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
- package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
- package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -0
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +114 -29
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
- package/app/components/05.forms/form-field/FormField.vue +1 -1
- package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +2 -7
- package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
- package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
- package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
- package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
- package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
- package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
- package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
- package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
- package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
- package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
- package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
- package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
- package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
- package/app/components/05.forms/input-select/InputSelectCore.vue +23 -30
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
- package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
- package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
- package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
- package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
- package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
- package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +220 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +20 -6
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +12 -10
- package/app/types/components/container-glow.d.ts +8 -0
- package/app/types/components/index.ts +2 -1
- package/app/types/components/select-menu.d.ts +5 -0
- package/package.json +3 -2
- package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
- package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
- package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
- package/app/components/carousels/CarouselBasic.vue +0 -317
- package/app/components/carousels/CarouselInfinite.vue +0 -358
- package/app/components/container-glow/ContainerGlowCore.vue +0 -296
- package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
- package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
- package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
- package/app/components/glowing-border/GlowingBorder.vue +0 -142
- package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
- package/app/components/layout-grids/LayoutGridB.vue +0 -134
- package/app/components/test-storybook/TestStorybook.vue +0 -49
- package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
- package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
- package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
- package/app/types/components/carousel-basic.d.ts +0 -19
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
|
+
import { nextTick } from "vue";
|
|
4
|
+
import SelectMenu from "../SelectMenu.vue";
|
|
5
|
+
import type { SelectMenuOption } from "~/types/components/select-menu";
|
|
6
|
+
|
|
7
|
+
// --- Types ---
|
|
8
|
+
interface SelectMenuInstance {
|
|
9
|
+
menuId: string;
|
|
10
|
+
anchorName: string;
|
|
11
|
+
selectOption: (option: SelectMenuOption) => void;
|
|
12
|
+
handleToggle: (event: Event) => void;
|
|
13
|
+
handleKeydown: (event: KeyboardEvent) => void;
|
|
14
|
+
getMenuItems: () => HTMLElement[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const options: SelectMenuOption[] = [
|
|
18
|
+
{ value: "en", label: "English", icon: "flag:gb-4x3" },
|
|
19
|
+
{ value: "fr", label: "Français", icon: "flag:fr-4x3" },
|
|
20
|
+
{ value: "de", label: "Deutsch", icon: "flag:de-4x3" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// --- Helpers ---
|
|
24
|
+
const createWrapper = async (props: Record<string, unknown> = {}) => {
|
|
25
|
+
return mountSuspended(SelectMenu, {
|
|
26
|
+
props: { options, label: "Language", ...props },
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("SelectMenu", () => {
|
|
31
|
+
let wrapper: Awaited<ReturnType<typeof createWrapper>>;
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
Object.defineProperty(HTMLElement.prototype, "hidePopover", {
|
|
35
|
+
value: vi.fn(),
|
|
36
|
+
writable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(HTMLElement.prototype, "showPopover", {
|
|
40
|
+
value: vi.fn(),
|
|
41
|
+
writable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
wrapper?.unmount();
|
|
48
|
+
vi.restoreAllMocks();
|
|
49
|
+
delete (HTMLElement.prototype as unknown as Record<string, unknown>)["hidePopover"];
|
|
50
|
+
delete (HTMLElement.prototype as unknown as Record<string, unknown>)["showPopover"];
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// -------------------------
|
|
54
|
+
// Snapshots
|
|
55
|
+
// -------------------------
|
|
56
|
+
describe("Snapshots", () => {
|
|
57
|
+
it("default", async () => {
|
|
58
|
+
wrapper = await createWrapper();
|
|
59
|
+
expect(wrapper.html()).toMatchSnapshot();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("with a selected value", async () => {
|
|
63
|
+
wrapper = await createWrapper({ modelValue: "fr" });
|
|
64
|
+
expect(wrapper.html()).toMatchSnapshot();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("icon-only trigger", async () => {
|
|
68
|
+
wrapper = await createWrapper({ modelValue: "en", showLabel: false, showChevron: false });
|
|
69
|
+
expect(wrapper.html()).toMatchSnapshot();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// -------------------------
|
|
74
|
+
// Trigger button
|
|
75
|
+
// -------------------------
|
|
76
|
+
describe("Trigger button", () => {
|
|
77
|
+
it("renders a trigger button", async () => {
|
|
78
|
+
wrapper = await createWrapper();
|
|
79
|
+
expect(wrapper.find(".select-menu-trigger").exists()).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("trigger button has type='button'", async () => {
|
|
83
|
+
wrapper = await createWrapper();
|
|
84
|
+
expect(wrapper.find(".select-menu-trigger").attributes("type")).toBe("button");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("trigger button has aria-haspopup='listbox'", async () => {
|
|
88
|
+
wrapper = await createWrapper();
|
|
89
|
+
expect(wrapper.find(".select-menu-trigger").attributes("aria-haspopup")).toBe("listbox");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("uses label prop as aria-label", async () => {
|
|
93
|
+
wrapper = await createWrapper({ label: "Filter by service" });
|
|
94
|
+
expect(wrapper.find(".select-menu-trigger").attributes("aria-label")).toBe("Filter by service");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("shows label text as the placeholder/category tag when nothing is selected", async () => {
|
|
98
|
+
wrapper = await createWrapper({ label: "Choose a service" });
|
|
99
|
+
expect(wrapper.find(".select-menu-trigger-label").text()).toBe("Choose a service");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("prefers an explicit placeholder over the label when nothing is selected", async () => {
|
|
103
|
+
wrapper = await createWrapper({ label: "Language", placeholder: "Select a language" });
|
|
104
|
+
expect(wrapper.find(".select-menu-trigger-label").text()).toBe("Select a language");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("shows the selected option's label once a value is set", async () => {
|
|
108
|
+
wrapper = await createWrapper({ modelValue: "fr" });
|
|
109
|
+
expect(wrapper.find(".select-menu-trigger-label").text()).toBe("Français");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("shows the selected option's icon in the trigger by default", async () => {
|
|
113
|
+
wrapper = await createWrapper({ modelValue: "en" });
|
|
114
|
+
expect(wrapper.find(".select-menu-trigger-icon").exists()).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("hides the trigger icon when showIcon is false", async () => {
|
|
118
|
+
wrapper = await createWrapper({ modelValue: "en", showIcon: false });
|
|
119
|
+
expect(wrapper.find(".select-menu-trigger-icon").exists()).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("hides the trigger label when showLabel is false (icon-only mode)", async () => {
|
|
123
|
+
wrapper = await createWrapper({ modelValue: "en", showLabel: false });
|
|
124
|
+
expect(wrapper.find(".select-menu-trigger-label").exists()).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("hides the chevron when showChevron is false", async () => {
|
|
128
|
+
wrapper = await createWrapper({ showChevron: false });
|
|
129
|
+
expect(wrapper.find(".select-menu-trigger-chevron").exists()).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("popovertarget matches the popover id", async () => {
|
|
133
|
+
wrapper = await createWrapper();
|
|
134
|
+
const vm = wrapper.vm as unknown as SelectMenuInstance;
|
|
135
|
+
expect(wrapper.find(".select-menu-trigger").attributes("popovertarget")).toBe(vm.menuId);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// -------------------------
|
|
140
|
+
// Popover / listbox
|
|
141
|
+
// -------------------------
|
|
142
|
+
describe("Popover listbox", () => {
|
|
143
|
+
it("renders one option per entry in options", async () => {
|
|
144
|
+
wrapper = await createWrapper();
|
|
145
|
+
expect(wrapper.findAll(".select-menu-list-item")).toHaveLength(3);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("list has role='listbox'", async () => {
|
|
149
|
+
wrapper = await createWrapper();
|
|
150
|
+
expect(wrapper.find(".select-menu-list").attributes("role")).toBe("listbox");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("each item has role='option'", async () => {
|
|
154
|
+
wrapper = await createWrapper();
|
|
155
|
+
wrapper.findAll(".select-menu-list-item").forEach((item) => {
|
|
156
|
+
expect(item.attributes("role")).toBe("option");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("marks the selected option with aria-selected='true'", async () => {
|
|
161
|
+
wrapper = await createWrapper({ modelValue: "de" });
|
|
162
|
+
const items = wrapper.findAll(".select-menu-list-item");
|
|
163
|
+
expect(items[0]!.attributes("aria-selected")).toBe("false");
|
|
164
|
+
expect(items[2]!.attributes("aria-selected")).toBe("true");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("renders a checkmark icon only next to the selected option", async () => {
|
|
168
|
+
wrapper = await createWrapper({ modelValue: "de" });
|
|
169
|
+
const items = wrapper.findAll(".select-menu-list-item");
|
|
170
|
+
expect(items[0]!.find(".select-menu-item-check-icon").exists()).toBe(false);
|
|
171
|
+
expect(items[2]!.find(".select-menu-item-check-icon").exists()).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// -------------------------
|
|
176
|
+
// Selecting an option
|
|
177
|
+
// -------------------------
|
|
178
|
+
describe("selectOption", () => {
|
|
179
|
+
it("updates v-model when an option is clicked", async () => {
|
|
180
|
+
wrapper = await createWrapper({ modelValue: "en" });
|
|
181
|
+
await wrapper.findAll(".select-menu-list-item")[1]!.trigger("click");
|
|
182
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual(["fr"]);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("closes the popover when an option is selected", async () => {
|
|
186
|
+
wrapper = await createWrapper();
|
|
187
|
+
const popoverEl = wrapper.find(".select-menu-popover").element as HTMLElement;
|
|
188
|
+
const hidePopoverSpy = vi.spyOn(popoverEl, "hidePopover");
|
|
189
|
+
await wrapper.findAll(".select-menu-list-item")[0]!.trigger("click");
|
|
190
|
+
expect(hidePopoverSpy).toHaveBeenCalledOnce();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("returns focus to the trigger after selecting", async () => {
|
|
194
|
+
wrapper = await createWrapper();
|
|
195
|
+
const triggerEl = wrapper.find(".select-menu-trigger").element as HTMLElement;
|
|
196
|
+
const focusSpy = vi.spyOn(triggerEl, "focus");
|
|
197
|
+
await wrapper.findAll(".select-menu-list-item")[0]!.trigger("click");
|
|
198
|
+
expect(focusSpy).toHaveBeenCalledOnce();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// -------------------------
|
|
203
|
+
// handleToggle — focus management
|
|
204
|
+
// -------------------------
|
|
205
|
+
describe("handleToggle", () => {
|
|
206
|
+
it("focuses the first option when opened with nothing selected", async () => {
|
|
207
|
+
wrapper = await createWrapper();
|
|
208
|
+
const vm = wrapper.vm as unknown as SelectMenuInstance;
|
|
209
|
+
const items = vm.getMenuItems();
|
|
210
|
+
const focusSpy = vi.spyOn(items[0]!, "focus");
|
|
211
|
+
|
|
212
|
+
vm.handleToggle(Object.assign(new Event("toggle"), { newState: "open" }));
|
|
213
|
+
await nextTick();
|
|
214
|
+
expect(focusSpy).toHaveBeenCalledOnce();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("focuses the currently selected option when opened", async () => {
|
|
218
|
+
wrapper = await createWrapper({ modelValue: "de" });
|
|
219
|
+
const vm = wrapper.vm as unknown as SelectMenuInstance;
|
|
220
|
+
const items = vm.getMenuItems();
|
|
221
|
+
const focusSpy = vi.spyOn(items[2]!, "focus");
|
|
222
|
+
|
|
223
|
+
vm.handleToggle(Object.assign(new Event("toggle"), { newState: "open" }));
|
|
224
|
+
await nextTick();
|
|
225
|
+
expect(focusSpy).toHaveBeenCalledOnce();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("sets aria-expanded on the trigger when open", async () => {
|
|
229
|
+
wrapper = await createWrapper();
|
|
230
|
+
const vm = wrapper.vm as unknown as SelectMenuInstance;
|
|
231
|
+
vm.handleToggle(Object.assign(new Event("toggle"), { newState: "open" }));
|
|
232
|
+
await nextTick();
|
|
233
|
+
expect(wrapper.find(".select-menu-trigger").attributes("aria-expanded")).toBe("true");
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// -------------------------
|
|
238
|
+
// Keyboard navigation
|
|
239
|
+
// -------------------------
|
|
240
|
+
describe("Keyboard navigation (handleKeydown)", () => {
|
|
241
|
+
const mountWithFocusSpies = async () => {
|
|
242
|
+
const w = await createWrapper();
|
|
243
|
+
const vm = w.vm as unknown as SelectMenuInstance;
|
|
244
|
+
const items = vm.getMenuItems();
|
|
245
|
+
const focusSpies = items.map((el) => vi.spyOn(el, "focus"));
|
|
246
|
+
|
|
247
|
+
const fire = (key: string) =>
|
|
248
|
+
vm.handleKeydown(
|
|
249
|
+
Object.assign(new KeyboardEvent("keydown", { key, bubbles: true }), { preventDefault: vi.fn() })
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const setActiveItem = (index: number) =>
|
|
253
|
+
vi.spyOn(document, "activeElement", "get").mockReturnValue(items[index]!);
|
|
254
|
+
|
|
255
|
+
return { w, vm, items, focusSpies, fire, setActiveItem };
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
it("ArrowDown moves focus to the next option", async () => {
|
|
259
|
+
const { w, focusSpies, fire, setActiveItem } = await mountWithFocusSpies();
|
|
260
|
+
const getter = setActiveItem(0);
|
|
261
|
+
fire("ArrowDown");
|
|
262
|
+
expect(focusSpies[1]).toHaveBeenCalledOnce();
|
|
263
|
+
getter.mockRestore();
|
|
264
|
+
w.unmount();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("ArrowDown wraps from last to first option", async () => {
|
|
268
|
+
const { w, focusSpies, fire, setActiveItem } = await mountWithFocusSpies();
|
|
269
|
+
const getter = setActiveItem(2);
|
|
270
|
+
fire("ArrowDown");
|
|
271
|
+
expect(focusSpies[0]).toHaveBeenCalledOnce();
|
|
272
|
+
getter.mockRestore();
|
|
273
|
+
w.unmount();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("ArrowUp wraps from first to last option", async () => {
|
|
277
|
+
const { w, focusSpies, fire, setActiveItem } = await mountWithFocusSpies();
|
|
278
|
+
const getter = setActiveItem(0);
|
|
279
|
+
fire("ArrowUp");
|
|
280
|
+
expect(focusSpies[2]).toHaveBeenCalledOnce();
|
|
281
|
+
getter.mockRestore();
|
|
282
|
+
w.unmount();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("Home focuses the first option", async () => {
|
|
286
|
+
const { w, focusSpies, fire, setActiveItem } = await mountWithFocusSpies();
|
|
287
|
+
const getter = setActiveItem(2);
|
|
288
|
+
fire("Home");
|
|
289
|
+
expect(focusSpies[0]).toHaveBeenCalledOnce();
|
|
290
|
+
getter.mockRestore();
|
|
291
|
+
w.unmount();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("End focuses the last option", async () => {
|
|
295
|
+
const { w, focusSpies, fire, setActiveItem } = await mountWithFocusSpies();
|
|
296
|
+
const getter = setActiveItem(0);
|
|
297
|
+
fire("End");
|
|
298
|
+
expect(focusSpies[2]).toHaveBeenCalledOnce();
|
|
299
|
+
getter.mockRestore();
|
|
300
|
+
w.unmount();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("Enter selects the focused option", async () => {
|
|
304
|
+
const { w, vm, setActiveItem } = await mountWithFocusSpies();
|
|
305
|
+
const getter = setActiveItem(1);
|
|
306
|
+
const popoverEl = w.find(".select-menu-popover").element as HTMLElement;
|
|
307
|
+
const hidePopoverSpy = vi.spyOn(popoverEl, "hidePopover");
|
|
308
|
+
vm.handleKeydown(Object.assign(new KeyboardEvent("keydown", { key: "Enter" }), { preventDefault: vi.fn() }));
|
|
309
|
+
expect(hidePopoverSpy).toHaveBeenCalledOnce();
|
|
310
|
+
expect(w.emitted("update:modelValue")?.[0]).toEqual(["fr"]);
|
|
311
|
+
getter.mockRestore();
|
|
312
|
+
w.unmount();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("Space selects the focused option", async () => {
|
|
316
|
+
const { w, vm, setActiveItem } = await mountWithFocusSpies();
|
|
317
|
+
const getter = setActiveItem(2);
|
|
318
|
+
vm.handleKeydown(Object.assign(new KeyboardEvent("keydown", { key: " " }), { preventDefault: vi.fn() }));
|
|
319
|
+
expect(w.emitted("update:modelValue")?.[0]).toEqual(["de"]);
|
|
320
|
+
getter.mockRestore();
|
|
321
|
+
w.unmount();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("Tab closes the popover without moving focus to the trigger", async () => {
|
|
325
|
+
const { w, fire } = await mountWithFocusSpies();
|
|
326
|
+
const popoverEl = w.find(".select-menu-popover").element as HTMLElement;
|
|
327
|
+
const hidePopoverSpy = vi.spyOn(popoverEl, "hidePopover");
|
|
328
|
+
const triggerEl = w.find(".select-menu-trigger").element as HTMLElement;
|
|
329
|
+
const focusSpy = vi.spyOn(triggerEl, "focus");
|
|
330
|
+
|
|
331
|
+
fire("Tab");
|
|
332
|
+
|
|
333
|
+
expect(hidePopoverSpy).toHaveBeenCalledOnce();
|
|
334
|
+
expect(focusSpy).not.toHaveBeenCalled();
|
|
335
|
+
w.unmount();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("does nothing when there are no options", async () => {
|
|
339
|
+
const w = await mountSuspended(SelectMenu, { props: { options: [], label: "Empty" } });
|
|
340
|
+
const vm = w.vm as unknown as SelectMenuInstance;
|
|
341
|
+
expect(() => vm.handleKeydown(new KeyboardEvent("keydown", { key: "ArrowDown" }))).not.toThrow();
|
|
342
|
+
w.unmount();
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// -------------------------
|
|
347
|
+
// Multiple selection
|
|
348
|
+
// -------------------------
|
|
349
|
+
describe("Multiple selection (multiple=true)", () => {
|
|
350
|
+
it("list has aria-multiselectable='true'", async () => {
|
|
351
|
+
wrapper = await createWrapper({ multiple: true });
|
|
352
|
+
expect(wrapper.find(".select-menu-list").attributes("aria-multiselectable")).toBe("true");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("omits aria-multiselectable in single-select mode", async () => {
|
|
356
|
+
wrapper = await createWrapper();
|
|
357
|
+
expect(wrapper.find(".select-menu-list").attributes("aria-multiselectable")).toBeUndefined();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("renders a checkbox icon for every option, checked or unchecked", async () => {
|
|
361
|
+
wrapper = await createWrapper({ multiple: true, modelValue: ["fr"] });
|
|
362
|
+
const items = wrapper.findAll(".select-menu-list-item");
|
|
363
|
+
expect(items[0]!.find(".select-menu-item-check-icon").exists()).toBe(true);
|
|
364
|
+
expect(items[1]!.find(".select-menu-item-check-icon").exists()).toBe(true);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("adds a value to the array on first click", async () => {
|
|
368
|
+
wrapper = await createWrapper({ multiple: true, modelValue: [] });
|
|
369
|
+
await wrapper.findAll(".select-menu-list-item")[0]!.trigger("click");
|
|
370
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([["en"]]);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("removes a value from the array when clicked again", async () => {
|
|
374
|
+
wrapper = await createWrapper({ multiple: true, modelValue: ["en", "fr"] });
|
|
375
|
+
await wrapper.findAll(".select-menu-list-item")[0]!.trigger("click");
|
|
376
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([["fr"]]);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("does not close the popover when an option is toggled", async () => {
|
|
380
|
+
wrapper = await createWrapper({ multiple: true, modelValue: [] });
|
|
381
|
+
const popoverEl = wrapper.find(".select-menu-popover").element as HTMLElement;
|
|
382
|
+
const hidePopoverSpy = vi.spyOn(popoverEl, "hidePopover");
|
|
383
|
+
await wrapper.findAll(".select-menu-list-item")[0]!.trigger("click");
|
|
384
|
+
expect(hidePopoverSpy).not.toHaveBeenCalled();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("marks each selected option with aria-selected='true'", async () => {
|
|
388
|
+
wrapper = await createWrapper({ multiple: true, modelValue: ["en", "de"] });
|
|
389
|
+
const items = wrapper.findAll(".select-menu-list-item");
|
|
390
|
+
expect(items[0]!.attributes("aria-selected")).toBe("true");
|
|
391
|
+
expect(items[1]!.attributes("aria-selected")).toBe("false");
|
|
392
|
+
expect(items[2]!.attributes("aria-selected")).toBe("true");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("keeps the static label as the trigger text once options are selected", async () => {
|
|
396
|
+
wrapper = await createWrapper({ multiple: true, modelValue: ["en", "de"], label: "Services required" });
|
|
397
|
+
expect(wrapper.find(".select-menu-trigger-label").text()).toBe("Services required");
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("shows the label/placeholder as the trigger text when nothing is selected", async () => {
|
|
401
|
+
wrapper = await createWrapper({ multiple: true, modelValue: [], label: "Services required" });
|
|
402
|
+
expect(wrapper.find(".select-menu-trigger-label").text()).toBe("Services required");
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("prefers placeholder over label as the static trigger text, regardless of selection", async () => {
|
|
406
|
+
wrapper = await createWrapper({
|
|
407
|
+
multiple: true,
|
|
408
|
+
modelValue: ["en"],
|
|
409
|
+
label: "Language",
|
|
410
|
+
placeholder: "Select languages",
|
|
411
|
+
});
|
|
412
|
+
expect(wrapper.find(".select-menu-trigger-label").text()).toBe("Select languages");
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("shows a comma-joined selection list in the trigger when showSelectionInTrigger is true", async () => {
|
|
416
|
+
wrapper = await createWrapper({
|
|
417
|
+
multiple: true,
|
|
418
|
+
modelValue: ["en", "de"],
|
|
419
|
+
label: "Services required",
|
|
420
|
+
showSelectionInTrigger: true,
|
|
421
|
+
});
|
|
422
|
+
expect(wrapper.find(".select-menu-trigger-label").text()).toBe("English, Deutsch");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("falls back to label/placeholder when showSelectionInTrigger is true but nothing is selected", async () => {
|
|
426
|
+
wrapper = await createWrapper({
|
|
427
|
+
multiple: true,
|
|
428
|
+
modelValue: [],
|
|
429
|
+
label: "Services required",
|
|
430
|
+
showSelectionInTrigger: true,
|
|
431
|
+
});
|
|
432
|
+
expect(wrapper.find(".select-menu-trigger-label").text()).toBe("Services required");
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("does not render a trigger icon in multiple mode", async () => {
|
|
436
|
+
wrapper = await createWrapper({ multiple: true, modelValue: ["en"] });
|
|
437
|
+
expect(wrapper.find(".select-menu-trigger-icon").exists()).toBe(false);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("Enter toggles the focused option without closing the popover", async () => {
|
|
441
|
+
wrapper = await createWrapper({ multiple: true, modelValue: [] });
|
|
442
|
+
const vm = wrapper.vm as unknown as SelectMenuInstance;
|
|
443
|
+
const items = vm.getMenuItems();
|
|
444
|
+
const getter = vi.spyOn(document, "activeElement", "get").mockReturnValue(items[1]!);
|
|
445
|
+
const popoverEl = wrapper.find(".select-menu-popover").element as HTMLElement;
|
|
446
|
+
const hidePopoverSpy = vi.spyOn(popoverEl, "hidePopover");
|
|
447
|
+
|
|
448
|
+
vm.handleKeydown(Object.assign(new KeyboardEvent("keydown", { key: "Enter" }), { preventDefault: vi.fn() }));
|
|
449
|
+
|
|
450
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([["fr"]]);
|
|
451
|
+
expect(hidePopoverSpy).not.toHaveBeenCalled();
|
|
452
|
+
getter.mockRestore();
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// -------------------------
|
|
457
|
+
// styleClassPassthrough
|
|
458
|
+
// -------------------------
|
|
459
|
+
describe("styleClassPassthrough", () => {
|
|
460
|
+
it("applies a string class to the root element", async () => {
|
|
461
|
+
wrapper = await createWrapper({ styleClassPassthrough: "custom-select" });
|
|
462
|
+
expect(wrapper.classes()).toContain("custom-select");
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it("applies an array of classes to the root element", async () => {
|
|
466
|
+
wrapper = await createWrapper({ styleClassPassthrough: ["select-a", "select-b"] });
|
|
467
|
+
expect(wrapper.classes()).toContain("select-a");
|
|
468
|
+
expect(wrapper.classes()).toContain("select-b");
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("root element always has the 'select-menu' class", async () => {
|
|
472
|
+
wrapper = await createWrapper({ styleClassPassthrough: "extra-class" });
|
|
473
|
+
expect(wrapper.classes()).toContain("select-menu");
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`SelectMenu > Snapshots > default 1`] = `
|
|
4
|
+
"<div class="select-menu" style="--_anchor-name: --select-menu-anchor-v-0-0;"><button popovertarget="select-menu-v-0-0" popovertargetaction="toggle" type="button" class="select-menu-trigger" aria-label="Language" aria-haspopup="listbox" aria-expanded="false">
|
|
5
|
+
<!--v-if--><span class="select-menu-trigger-label">Language</span><span class="iconify i-lucide:chevron-down select-menu-trigger-chevron" aria-hidden="true"></span>
|
|
6
|
+
</button>
|
|
7
|
+
<div id="select-menu-v-0-0" popover="" class="select-menu-popover">
|
|
8
|
+
<ul class="select-menu-list" role="listbox" aria-label="Language">
|
|
9
|
+
<li class="select-menu-list-item" role="option" tabindex="-1" aria-selected="false"><span class="select-menu-item-check" aria-hidden="true"><!--v-if--></span><span class="iconify i-flag:gb-4x3 select-menu-item-icon" aria-hidden="true"></span><span class="select-menu-item-label">English</span></li>
|
|
10
|
+
<li class="select-menu-list-item" role="option" tabindex="-1" aria-selected="false"><span class="select-menu-item-check" aria-hidden="true"><!--v-if--></span><span class="iconify i-flag:fr-4x3 select-menu-item-icon" aria-hidden="true"></span><span class="select-menu-item-label">Français</span></li>
|
|
11
|
+
<li class="select-menu-list-item" role="option" tabindex="-1" aria-selected="false"><span class="select-menu-item-check" aria-hidden="true"><!--v-if--></span><span class="iconify i-flag:de-4x3 select-menu-item-icon" aria-hidden="true"></span><span class="select-menu-item-label">Deutsch</span></li>
|
|
12
|
+
</ul>
|
|
13
|
+
</div>
|
|
14
|
+
</div>"
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
exports[`SelectMenu > Snapshots > icon-only trigger 1`] = `
|
|
18
|
+
"<div class="select-menu" style="--_anchor-name: --select-menu-anchor-v-0-0;"><button popovertarget="select-menu-v-0-0" popovertargetaction="toggle" type="button" class="select-menu-trigger" aria-label="Language" aria-haspopup="listbox" aria-expanded="false"><span class="iconify i-flag:gb-4x3 select-menu-trigger-icon" aria-hidden="true"></span>
|
|
19
|
+
<!--v-if-->
|
|
20
|
+
<!--v-if-->
|
|
21
|
+
</button>
|
|
22
|
+
<div id="select-menu-v-0-0" popover="" class="select-menu-popover">
|
|
23
|
+
<ul class="select-menu-list" role="listbox" aria-label="Language">
|
|
24
|
+
<li class="select-menu-list-item" role="option" tabindex="-1" aria-selected="true"><span class="select-menu-item-check" aria-hidden="true"><span class="iconify i-lucide:check select-menu-item-check-icon" aria-hidden="true"></span></span><span class="iconify i-flag:gb-4x3 select-menu-item-icon" aria-hidden="true"></span><span class="select-menu-item-label">English</span></li>
|
|
25
|
+
<li class="select-menu-list-item" role="option" tabindex="-1" aria-selected="false"><span class="select-menu-item-check" aria-hidden="true"><!--v-if--></span><span class="iconify i-flag:fr-4x3 select-menu-item-icon" aria-hidden="true"></span><span class="select-menu-item-label">Français</span></li>
|
|
26
|
+
<li class="select-menu-list-item" role="option" tabindex="-1" aria-selected="false"><span class="select-menu-item-check" aria-hidden="true"><!--v-if--></span><span class="iconify i-flag:de-4x3 select-menu-item-icon" aria-hidden="true"></span><span class="select-menu-item-label">Deutsch</span></li>
|
|
27
|
+
</ul>
|
|
28
|
+
</div>
|
|
29
|
+
</div>"
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
exports[`SelectMenu > Snapshots > with a selected value 1`] = `
|
|
33
|
+
"<div class="select-menu" style="--_anchor-name: --select-menu-anchor-v-0-0;"><button popovertarget="select-menu-v-0-0" popovertargetaction="toggle" type="button" class="select-menu-trigger" aria-label="Language" aria-haspopup="listbox" aria-expanded="false"><span class="iconify i-flag:fr-4x3 select-menu-trigger-icon" aria-hidden="true"></span><span class="select-menu-trigger-label">Français</span><span class="iconify i-lucide:chevron-down select-menu-trigger-chevron" aria-hidden="true"></span></button>
|
|
34
|
+
<div id="select-menu-v-0-0" popover="" class="select-menu-popover">
|
|
35
|
+
<ul class="select-menu-list" role="listbox" aria-label="Language">
|
|
36
|
+
<li class="select-menu-list-item" role="option" tabindex="-1" aria-selected="false"><span class="select-menu-item-check" aria-hidden="true"><!--v-if--></span><span class="iconify i-flag:gb-4x3 select-menu-item-icon" aria-hidden="true"></span><span class="select-menu-item-label">English</span></li>
|
|
37
|
+
<li class="select-menu-list-item" role="option" tabindex="-1" aria-selected="true"><span class="select-menu-item-check" aria-hidden="true"><span class="iconify i-lucide:check select-menu-item-check-icon" aria-hidden="true"></span></span><span class="iconify i-flag:fr-4x3 select-menu-item-icon" aria-hidden="true"></span><span class="select-menu-item-label">Français</span></li>
|
|
38
|
+
<li class="select-menu-list-item" role="option" tabindex="-1" aria-selected="false"><span class="select-menu-item-check" aria-hidden="true"><!--v-if--></span><span class="iconify i-flag:de-4x3 select-menu-item-icon" aria-hidden="true"></span><span class="select-menu-item-label">Deutsch</span></li>
|
|
39
|
+
</ul>
|
|
40
|
+
</div>
|
|
41
|
+
</div>"
|
|
42
|
+
`;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# CarouselFlip — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## Public token API
|
|
4
|
+
|
|
5
|
+
| Token | Default | Controls |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `--carousel-flip-gap` | `1rem` | Gap between items inside the item track |
|
|
8
|
+
| `--carousel-flip-layout-gap` | `1rem` | Grid gap between the prev/carousel/next/controls areas |
|
|
9
|
+
| `--carousel-flip-display-max-width` | `80rem` | Max width of the item track and the controls bar |
|
|
10
|
+
| `--carousel-flip-item-max-width` | `80rem` | Max width of a single item, before subtracting the edge-preview width |
|
|
11
|
+
| `--carousel-flip-edge-preview-width` | `4rem` | How much of the neighbouring items peek in at each edge |
|
|
12
|
+
| `--carousel-flip-marker-gap` | `1rem` | Gap between marker (dot) buttons |
|
|
13
|
+
| `--carousel-flip-marker-active-colour` | `var(--slate-10)` | Active marker background colour |
|
|
14
|
+
| `--carousel-flip-button-background-colour` | `white` | Prev/next button background colour |
|
|
15
|
+
| `--carousel-flip-button-border-width` | `0.1rem` | Prev/next button border width |
|
|
16
|
+
| `--carousel-flip-button-border-colour` | `hsl(0, 29%, 3%)` | Prev/next button border colour |
|
|
17
|
+
| `--carousel-flip-button-border-radius` | `100vw` | Prev/next button corner radius |
|
|
18
|
+
| `--carousel-flip-button-padding` | `0.8rem` | Prev/next button padding |
|
|
19
|
+
| `--carousel-flip-button-icon-size` | `2.4rem` | Prev/next button icon glyph size |
|
|
20
|
+
| `--carousel-flip-buttons-gap` | `1rem` | Gap between prev/next buttons in the `controls-grouped-right` layout |
|
|
21
|
+
| `--carousel-flip-overlay-controls-offset` | `1rem` | Distance of the controls bar from the bottom edge in the `overlay` layout |
|
|
22
|
+
| `--carousel-flip-focus-outline-width` | `0.2rem` | Marker button focus-visible outline width |
|
|
23
|
+
| `--carousel-flip-focus-outline-colour` | `var(--theme-ring)` | Marker button focus-visible outline colour |
|
|
24
|
+
| `--carousel-flip-focus-outline-offset` | `0.2rem` | Marker button focus-visible outline offset |
|
|
25
|
+
|
|
26
|
+
```css
|
|
27
|
+
.my-page {
|
|
28
|
+
--carousel-flip-button-background-colour: var(--theme-surface);
|
|
29
|
+
--carousel-flip-marker-active-colour: var(--brand-primary);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or scope to a single instance via `styleClassPassthrough`:
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<CarouselFlip style-class-passthrough="hero-carousel">...</CarouselFlip>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Notes
|
|
40
|
+
|
|
41
|
+
- `--carousel-flip-display-max-width`/`--carousel-flip-item-max-width` previously had no declared
|
|
42
|
+
value anywhere in the component (referenced via `var()` with no fallback) — this meant the
|
|
43
|
+
edge-preview offset and max-width caps silently never applied in production. Fixed 2026-09-21;
|
|
44
|
+
see the component's skill doc for detail.
|
|
45
|
+
- `useFlipAnimation`/`useSpringEffect`/`transitionSpeed` control animation behaviour via props,
|
|
46
|
+
not tokens — use the props rather than CSS overrides for those.
|