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.
Files changed (187) hide show
  1. package/.claude/rules/consumer/css-relative-colour-calc.md +15 -0
  2. package/.claude/rules/consumer/ids-from-useid.md +3 -2
  3. package/.claude/rules/consumer/vue-dynamic-arg-no-spaces.md +16 -0
  4. package/.claude/skills/component-compliance-checklist.md +11 -1
  5. package/.claude/skills/component-dynamic-slots.md +2 -2
  6. package/.claude/skills/component-naming.md +1 -1
  7. package/.claude/skills/components/accordion-chat.md +99 -0
  8. package/.claude/skills/components/accordion-group.md +98 -0
  9. package/.claude/skills/components/action-menu.md +11 -5
  10. package/.claude/skills/components/auto-grid.md +3 -1
  11. package/.claude/skills/components/capture-qr-code.md +9 -5
  12. package/.claude/skills/components/carousel-flip.md +41 -30
  13. package/.claude/skills/components/column-flow-grid.md +2 -0
  14. package/.claude/skills/components/dashboard-quad-grid.md +7 -2
  15. package/.claude/skills/components/dashboard-stats-grid.md +4 -0
  16. package/.claude/skills/components/decode-qr-code.md +7 -2
  17. package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
  18. package/.claude/skills/components/deep-expanding-menu.md +22 -21
  19. package/.claude/skills/components/expanding-panel-classic.md +9 -7
  20. package/.claude/skills/components/expanding-panel.md +9 -8
  21. package/.claude/skills/components/grid-stack.md +2 -0
  22. package/.claude/skills/components/layout-grid-by-cols.md +2 -0
  23. package/.claude/skills/components/masonry-grid.md +9 -1
  24. package/.claude/skills/components/page-row.md +3 -0
  25. package/.claude/skills/components/samaritan-prompt.md +24 -8
  26. package/.claude/skills/components/site-header.md +16 -2
  27. package/.claude/skills/components/sparkle-effect.md +79 -0
  28. package/.claude/skills/index.md +16 -14
  29. package/.claude/skills/storybook-add-story.md +33 -0
  30. package/.claude/skills/testing-add-unit-test.md +26 -0
  31. package/.claude/skills/using-component-skills.md +1 -1
  32. package/.vscode/srcdev-component-accordion-chat.code-snippets +42 -0
  33. package/.vscode/srcdev-component-accordion-group.code-snippets +38 -0
  34. package/.vscode/srcdev-component-action-menu.code-snippets +3 -3
  35. package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
  36. package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
  37. package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
  38. package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
  39. package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
  40. package/.vscode/srcdev-component-site-header.code-snippets +13 -1
  41. package/.vscode/srcdev-component-sparkle-effect.code-snippets +33 -0
  42. package/app/components/01.atoms/animations/sparkle-effect/CONSUMER-STYLING.md +75 -0
  43. package/app/components/01.atoms/animations/sparkle-effect/SparkleEffect.vue +213 -0
  44. package/app/components/01.atoms/animations/sparkle-effect/stories/SparkleEffect.stories.ts +303 -0
  45. package/app/components/01.atoms/animations/sparkle-effect/tests/SparkleEffect.spec.ts +150 -0
  46. package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +1 -1
  47. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
  48. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
  49. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
  50. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
  51. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
  52. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
  53. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
  54. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
  55. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
  56. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
  57. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
  58. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
  59. package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
  60. package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
  61. package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
  62. package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
  63. package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
  64. package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
  65. package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
  66. package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
  67. package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
  68. package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
  69. package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
  70. package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
  71. package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
  72. package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
  73. package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
  74. package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
  75. package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
  76. package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
  77. package/app/components/01.atoms/page-row/PageRow.vue +16 -15
  78. package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
  79. package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
  80. package/app/components/02.molecules/action-menu/ActionMenu.vue +6 -2
  81. package/app/components/02.molecules/action-menu/ActionMenuItem.vue +6 -3
  82. package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +11 -4
  83. package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +130 -18
  84. package/app/components/02.molecules/expandable/accordion-chat/AccordionChat.vue +227 -0
  85. package/app/components/02.molecules/expandable/accordion-chat/CONSUMER-STYLING.md +150 -0
  86. package/app/components/02.molecules/expandable/accordion-chat/stories/AccordionChat.stories.ts +347 -0
  87. package/app/components/02.molecules/expandable/accordion-chat/tests/AccordionChat.spec.ts +205 -0
  88. package/app/components/02.molecules/expandable/accordion-chat/tests/__snapshots__/AccordionChat.spec.ts.snap +40 -0
  89. package/app/components/02.molecules/expandable/{accordian/AccordianCore.vue → accordion-group/AccordionGroup.vue} +32 -20
  90. package/app/components/02.molecules/expandable/accordion-group/CONSUMER-STYLING.md +66 -0
  91. package/app/components/02.molecules/expandable/accordion-group/stories/AccordionGroup.stories.ts +238 -0
  92. package/app/components/02.molecules/expandable/accordion-group/tests/AccordionGroup.spec.ts +172 -0
  93. package/app/components/02.molecules/expandable/accordion-group/tests/__snapshots__/AccordionGroup.spec.ts.snap +45 -0
  94. package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +1 -0
  95. package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +11 -6
  96. package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +82 -1
  97. package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +24 -30
  98. package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +8 -8
  99. package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +1 -0
  100. package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +10 -5
  101. package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +83 -1
  102. package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +24 -30
  103. package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +8 -8
  104. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
  105. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
  106. package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
  107. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
  108. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
  109. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
  110. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
  111. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
  112. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
  113. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
  114. package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
  115. package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
  116. package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
  117. package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
  118. package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
  119. package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
  120. package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
  121. package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
  122. package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
  123. package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
  124. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
  125. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
  126. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
  127. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
  128. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
  129. package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
  130. package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
  131. package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
  132. package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +2 -2
  133. package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +2 -2
  134. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
  135. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
  136. package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
  137. package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
  138. package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
  139. package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
  140. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
  141. package/app/types/components/qr-code.d.ts +12 -0
  142. package/app/utils/graphemes.ts +8 -0
  143. package/package.json +1 -1
  144. package/.claude/skills/components/accordian-core.md +0 -180
  145. package/.vscode/srcdev-component-accordian-core.code-snippets +0 -74
  146. package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
  147. package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
  148. package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
  149. package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
  150. package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
  151. package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
  152. package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
  153. package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
  154. package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
  155. package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
  156. package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
  157. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
  158. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
  159. package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
  160. package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
  161. package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
  162. package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
  163. package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
  164. package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
  165. package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  166. package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +0 -100
  167. package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +0 -272
  168. package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +0 -257
  169. package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -95
  170. package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
  171. package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
  172. package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
  173. package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
  174. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
  175. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
  176. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
  177. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
  178. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
  179. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
  180. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
  181. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
  182. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
  183. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
  184. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
  185. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
  186. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
  187. package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
@@ -1,6 +1,8 @@
1
+ import { computed, ref } from "vue";
1
2
  import DeepExpandingMenu from "../DeepExpandingMenu.vue";
3
+ import CanvasSwitcher from "../../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
2
4
  import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
- import type { ResponsiveHeaderNavItem } from "~/types/components";
5
+ import type { MediaCanvas, ResponsiveHeaderNavItem } from "~/types/components";
4
6
 
5
7
  const navLinks: ResponsiveHeaderNavItem[] = [
6
8
  { name: "Home", path: "/" },
@@ -16,63 +18,181 @@ const navLinks: ResponsiveHeaderNavItem[] = [
16
18
  { name: "Contact", path: "/contact" },
17
19
  ];
18
20
 
19
- const meta: Meta<typeof DeepExpandingMenu> = {
21
+ const stressNavLinks: ResponsiveHeaderNavItem[] = [
22
+ { name: "H", path: "/" },
23
+ {
24
+ name: "Dienstleistungsangebotsübersichtsseite",
25
+ childLinksTitle:
26
+ "Unsere vollständige Übersicht aller Dienstleistungen, Behandlungen und Pflegeangebote für Haut und Haar in Northtown",
27
+ childLinks: [
28
+ { name: "Haarschnitt", path: "/services/haircuts" },
29
+ { name: "https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own", path: "/long-url" },
30
+ { name: "🎨 Colouring", path: "/services/colouring" },
31
+ { name: "Styling", path: "/services/styling-1" },
32
+ { name: "Styling", path: "/services/styling-2" },
33
+ { name: "<b>Not bold</b>", path: "/services/html" },
34
+ { name: "قص الشعر", path: "/services/rtl" },
35
+ ...Array.from({ length: 24 }, (_, i) => ({ name: `Treatment ${i + 1}`, path: `/services/treatment-${i + 1}` })),
36
+ ],
37
+ },
38
+ { name: "Gruppe ohne Titel", childLinks: [{ name: "Only link", path: "/only" }] },
39
+ { name: "Empty group", childLinksTitle: "Nothing here yet", childLinks: [] },
40
+ { name: "Supercalifragilisticexpialidociousandthensomemoretext", path: "/unbroken" },
41
+ { name: "🙂", path: "/emoji" },
42
+ { name: "Contact", path: "/contact" },
43
+ { name: "Contact", path: "/contact" },
44
+ {
45
+ name: "About us and our story",
46
+ childLinksTitle: "About Alex Morgan",
47
+ childLinks: [
48
+ { name: "Team", path: "/about/team" },
49
+ { name: "History", path: "/about/history" },
50
+ ],
51
+ },
52
+ ];
53
+
54
+ type LineClamp = "none" | "1" | "2" | "3";
55
+
56
+ // Story-only arg: drives a CSS token, not a DeepExpandingMenu prop. See the argTypes entry.
57
+ type StoryArgs = InstanceType<typeof DeepExpandingMenu>["$props"] & {
58
+ headingLineClamp: LineClamp;
59
+ };
60
+
61
+ const meta: Meta<StoryArgs> = {
20
62
  title: "Molecules/DeepExpandingMenu",
21
63
  component: DeepExpandingMenu,
22
64
  argTypes: {
65
+ headingLineClamp: {
66
+ control: "select",
67
+ options: ["none", "1", "2", "3"] satisfies LineClamp[],
68
+ description:
69
+ "**Story control, not a prop.** Sets the `--deep-expanding-menu-panel-heading-line-clamp` CSS token on a wrapper so you can try it here. " +
70
+ "To use it in an app, set the token in your own CSS, e.g. `.site-nav { --deep-expanding-menu-panel-heading-line-clamp: 1; }`. " +
71
+ "`1` is single-line ellipsis, `none` (the default) shows everything.",
72
+ table: { category: "CSS tokens (story only, set in your CSS)", defaultValue: { summary: "none" } },
73
+ },
23
74
  tag: {
24
75
  control: { type: "select" },
25
- options: ["div", "section", "nav", "ul", "ol"],
76
+ options: ["div", "section", "nav"],
26
77
  description: "Root element tag",
78
+ table: { category: "Basic", defaultValue: { summary: "nav" } },
79
+ },
80
+ ariaLabel: {
81
+ control: "text",
82
+ description: "Accessible name for the root. Set it when the page has more than one `<nav>`",
27
83
  table: { category: "Basic" },
28
84
  },
29
- navLinks: { table: { disable: true } },
85
+ navLinks: {
86
+ control: "object",
87
+ description: "Top-level items: `path` renders a link, `childLinks` renders a toggle and panel",
88
+ table: { category: "Content" },
89
+ },
30
90
  styleClassPassthrough: { table: { disable: true } },
31
91
  },
32
92
  args: {
33
93
  tag: "nav",
34
94
  navLinks,
95
+ headingLineClamp: "none",
35
96
  },
97
+ decorators: [
98
+ (story, context) => ({
99
+ components: { story, CanvasSwitcher },
100
+ setup() {
101
+ const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "fullWidthCanvas");
102
+ return { canvasName };
103
+ },
104
+ template: `
105
+ <div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
106
+ <CanvasSwitcher v-model:canvas-name="canvasName" />
107
+ </div>
108
+ <div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
109
+ <story />
110
+ </div>
111
+ `,
112
+ }),
113
+ ],
36
114
  parameters: {
115
+ layout: "fullscreen",
37
116
  docs: {
38
117
  description: {
39
118
  component:
40
- "Modern implementation of a top-level nav with expanding child-link panels, using CSS anchor-positioning and the Popover API. See `DeepExpandingMenuClassic` for a `<details>`-based fallback with wider browser support.",
119
+ "Top-level nav row where each item is a direct link (`path`) or a toggle that opens a panel of child links (`childLinks`, with an optional `childLinksTitle` heading). " +
120
+ "Built on CSS anchor positioning and the Popover API, with a JS fallback for Safari 16-18. Props: `navLinks`, `tag`, `ariaLabel`. " +
121
+ "See `DeepExpandingMenuClassic` for the `<details>`-based alternative.",
41
122
  },
42
123
  },
43
124
  },
44
125
  };
45
126
 
46
127
  export default meta;
47
- type Story = StoryObj<typeof DeepExpandingMenu>;
128
+ type Story = StoryObj<StoryArgs>;
129
+
130
+ function useStorySetup(args: StoryArgs) {
131
+ const componentArgs = computed(() => {
132
+ const { headingLineClamp: _headingLineClamp, ...rest } = args;
133
+ return rest;
134
+ });
135
+ const tokenStyles = computed(() => ({
136
+ "--deep-expanding-menu-panel-heading-line-clamp": args.headingLineClamp,
137
+ }));
138
+ return { componentArgs, tokenStyles };
139
+ }
140
+
141
+ const render = (args: StoryArgs) => ({
142
+ components: { DeepExpandingMenu },
143
+ setup() {
144
+ return useStorySetup(args);
145
+ },
146
+ template: `<div :style="tokenStyles"><DeepExpandingMenu v-bind="componentArgs" /></div>`,
147
+ });
48
148
 
49
149
  export const Default: Story = {
50
- render: (args) => ({
51
- components: { DeepExpandingMenu },
52
- setup() {
53
- return { args };
54
- },
55
- template: `<DeepExpandingMenu v-bind="args" style="padding: 4rem;" />`,
56
- }),
150
+ render,
57
151
  };
58
152
 
59
153
  export const CustomColours: Story = {
154
+ name: "Custom Colours",
60
155
  render: (args) => ({
61
156
  components: { DeepExpandingMenu },
62
157
  setup() {
63
- return { args };
158
+ return useStorySetup(args);
64
159
  },
65
160
  template: `
66
- <DeepExpandingMenu
67
- v-bind="args"
68
- style="padding: 4rem; --deep-expanding-menu-panel-background-colour: #1a1a1a; --deep-expanding-menu-panel-border-colour: transparent; --deep-expanding-menu-group-link-colour: white; --deep-expanding-menu-panel-heading-colour: #aaaaaa;"
69
- />
161
+ <div
162
+ :style="tokenStyles"
163
+ style="--deep-expanding-menu-panel-background-colour: #1a1a1a; --deep-expanding-menu-panel-border-colour: transparent; --deep-expanding-menu-group-link-colour: white; --deep-expanding-menu-group-link-border-colour-hover: white; --deep-expanding-menu-panel-heading-colour: #aaaaaa;"
164
+ >
165
+ <DeepExpandingMenu v-bind="componentArgs" />
166
+ </div>
70
167
  `,
71
168
  }),
72
169
  parameters: {
73
170
  docs: {
74
171
  description: {
75
- story: "Overriding the public tokens via inline `--deep-expanding-menu-*` custom properties.",
172
+ story: "Overriding the public `--deep-expanding-menu-*` tokens on a wrapper element.",
173
+ },
174
+ },
175
+ },
176
+ };
177
+
178
+ export const StressTest: Story = {
179
+ name: "Stress Test (Worst-Case Data)",
180
+ args: {
181
+ navLinks: stressNavLinks,
182
+ },
183
+ render,
184
+ parameters: {
185
+ initialCanvas: "mobileCanvas",
186
+ docs: {
187
+ story: { inline: false, height: "720px" },
188
+ description: {
189
+ story:
190
+ "Hostile nav data at every `CanvasSwitcher` width. Check that: the top-level row wraps instead of overflowing; " +
191
+ "the long German label and the unbroken word break inside the canvas; the caret stays beside a wrapped toggle label; " +
192
+ "the first panel's long heading wraps (try the story-only `headingLineClamp` CSS token control), its unbroken URL link stays inside the panel, " +
193
+ "`<b>Not bold</b>` renders as text, the right-to-left link reads correctly, and its 31 links scroll inside the panel; " +
194
+ "duplicate `Styling` and `Contact` items both render; a group without a title shows its list only; " +
195
+ "an empty group opens with its heading and no list; the last group's panel flips to stay on screen near the right edge.",
76
196
  },
77
197
  },
78
198
  },
@@ -17,49 +17,62 @@ const defaultNavLinks: ResponsiveHeaderNavItem[] = [
17
17
  { name: "Contact", path: "/contact" },
18
18
  ];
19
19
 
20
+ const mounted: Awaited<ReturnType<typeof mountSuspended>>[] = [];
21
+
22
+ const mountMenu = async (options: { props?: Record<string, unknown>; attachTo?: HTMLElement } = {}) => {
23
+ const wrapper = await mountSuspended(DeepExpandingMenu, options);
24
+ mounted.push(wrapper);
25
+ return wrapper;
26
+ };
27
+
28
+ afterEach(() => {
29
+ mounted.splice(0).forEach((wrapper) => wrapper.unmount());
30
+ vi.restoreAllMocks();
31
+ });
32
+
20
33
  describe("DeepExpandingMenu", () => {
21
34
  it("mounts without error", async () => {
22
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: defaultNavLinks } });
35
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
23
36
  expect(wrapper.vm).toBeTruthy();
24
37
  });
25
38
 
26
39
  it("renders correct HTML structure", async () => {
27
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: defaultNavLinks } });
40
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
28
41
  expect(wrapper.html()).toMatchSnapshot();
29
42
  });
30
43
 
31
44
  it("renders a <nav> by default", async () => {
32
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: defaultNavLinks } });
45
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
33
46
  expect(wrapper.element.tagName.toLowerCase()).toBe("nav");
34
47
  });
35
48
 
36
49
  it("renders the given tag", async () => {
37
- const wrapper = await mountSuspended(DeepExpandingMenu, {
50
+ const wrapper = await mountMenu({
38
51
  props: { navLinks: defaultNavLinks, tag: "div" },
39
52
  });
40
53
  expect(wrapper.element.tagName.toLowerCase()).toBe("div");
41
54
  });
42
55
 
43
56
  it("renders a direct link for items with a path", async () => {
44
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: defaultNavLinks } });
45
- const links = wrapper.findAll(".navigation-link");
57
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
58
+ const links = wrapper.findAll(".deep-expanding-menu-link");
46
59
  expect(links.map((l) => l.text())).toEqual(["Home", "Contact"]);
47
60
  });
48
61
 
49
62
  it("renders a toggle group for items with childLinks", async () => {
50
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: defaultNavLinks } });
51
- expect(wrapper.find(".navigation-group-toggle").text()).toContain("Services");
63
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
64
+ expect(wrapper.find(".deep-expanding-menu-group-toggle").text()).toContain("Services");
52
65
  });
53
66
 
54
67
  it("renders each child link inside the group panel", async () => {
55
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: defaultNavLinks } });
56
- const childLinks = wrapper.findAll(".navigation-group-link");
68
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
69
+ const childLinks = wrapper.findAll(".deep-expanding-menu-group-link");
57
70
  expect(childLinks.map((l) => l.text())).toEqual(["Haircuts", "Colouring"]);
58
71
  });
59
72
 
60
73
  it("renders the childLinksTitle heading", async () => {
61
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: defaultNavLinks } });
62
- expect(wrapper.find(".navigation-group-panel h4").text()).toBe("Our services");
74
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
75
+ expect(wrapper.find(".deep-expanding-menu-group-heading").text()).toBe("Our services");
63
76
  });
64
77
 
65
78
  it("gives each group toggle a unique popovertarget", async () => {
@@ -67,21 +80,21 @@ describe("DeepExpandingMenu", () => {
67
80
  { name: "A", childLinksTitle: "A", childLinks: [{ name: "A1", path: "/a1" }] },
68
81
  { name: "B", childLinksTitle: "B", childLinks: [{ name: "B1", path: "/b1" }] },
69
82
  ];
70
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks } });
71
- const toggles = wrapper.findAll(".navigation-group-toggle");
83
+ const wrapper = await mountMenu({ props: { navLinks } });
84
+ const toggles = wrapper.findAll(".deep-expanding-menu-group-toggle");
72
85
  const targets = toggles.map((t) => t.attributes("popovertarget"));
73
86
  expect(new Set(targets).size).toBe(2);
74
87
  });
75
88
 
76
89
  it("applies styleClassPassthrough classes", async () => {
77
- const wrapper = await mountSuspended(DeepExpandingMenu, {
90
+ const wrapper = await mountMenu({
78
91
  props: { navLinks: defaultNavLinks, styleClassPassthrough: ["custom-class"] },
79
92
  });
80
93
  expect(wrapper.classes()).toContain("custom-class");
81
94
  });
82
95
 
83
96
  it("resets classes when styleClassPassthrough prop changes", async () => {
84
- const wrapper = await mountSuspended(DeepExpandingMenu, {
97
+ const wrapper = await mountMenu({
85
98
  props: { navLinks: defaultNavLinks, styleClassPassthrough: ["initial-class"] },
86
99
  });
87
100
  expect(wrapper.classes()).toContain("initial-class");
@@ -91,9 +104,60 @@ describe("DeepExpandingMenu", () => {
91
104
  expect(wrapper.classes()).toContain("updated-class");
92
105
  });
93
106
 
107
+ it("labels the root when ariaLabel is set, and leaves it unlabelled otherwise", async () => {
108
+ const unlabelled = await mountMenu({ props: { navLinks: defaultNavLinks } });
109
+ expect(unlabelled.attributes("aria-label")).toBeUndefined();
110
+
111
+ const labelled = await mountMenu({
112
+ props: { navLinks: defaultNavLinks, ariaLabel: "Main" },
113
+ });
114
+ expect(labelled.attributes("aria-label")).toBe("Main");
115
+ });
116
+
117
+ it("gives the child link list a list role", async () => {
118
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
119
+ expect(wrapper.find(".deep-expanding-menu-group-list").attributes("role")).toBe("list");
120
+ });
121
+
122
+ it("omits the heading when childLinksTitle is missing or empty", async () => {
123
+ const navLinks: ResponsiveHeaderNavItem[] = [
124
+ { name: "A", childLinks: [{ name: "A1", path: "/a1" }] },
125
+ { name: "B", childLinksTitle: "", childLinks: [{ name: "B1", path: "/b1" }] },
126
+ ];
127
+ const wrapper = await mountMenu({ props: { navLinks } });
128
+ expect(wrapper.find(".deep-expanding-menu-group-heading").exists()).toBe(false);
129
+ });
130
+
131
+ it("omits the list when a group has no child links", async () => {
132
+ const navLinks: ResponsiveHeaderNavItem[] = [{ name: "Empty", childLinksTitle: "Empty", childLinks: [] }];
133
+ const wrapper = await mountMenu({ props: { navLinks } });
134
+ expect(wrapper.find(".deep-expanding-menu-group-list").exists()).toBe(false);
135
+ });
136
+
137
+ it("keeps child links with duplicate names in step when the list is reordered", async () => {
138
+ const childLinks = [
139
+ { name: "Same", path: "/one" },
140
+ { name: "Same", path: "/two" },
141
+ { name: "Other", path: "/three" },
142
+ ];
143
+ const wrapper = await mountMenu({
144
+ props: { navLinks: [{ name: "Group", childLinksTitle: "Group", childLinks }] },
145
+ });
146
+ await wrapper.setProps({
147
+ navLinks: [{ name: "Group", childLinksTitle: "Group", childLinks: [childLinks[2]!, childLinks[0]!] }],
148
+ });
149
+ const links = wrapper.findAll(".deep-expanding-menu-group-link");
150
+ expect(links.map((l) => l.attributes("href"))).toEqual(["/three", "/one"]);
151
+ });
152
+
153
+ it("hides the caret icon from assistive technology", async () => {
154
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
155
+ expect(wrapper.find(".deep-expanding-menu-group-icon").attributes("aria-hidden")).toBe("true");
156
+ });
157
+
94
158
  it("doesn't give the link panels a menu role", async () => {
95
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: defaultNavLinks } });
96
- expect(wrapper.find(".navigation-group-panel").attributes("role")).toBeUndefined();
159
+ const wrapper = await mountMenu({ props: { navLinks: defaultNavLinks } });
160
+ expect(wrapper.find(".deep-expanding-menu-group-panel").attributes("role")).toBeUndefined();
97
161
  });
98
162
 
99
163
  describe("open state", () => {
@@ -116,9 +180,9 @@ describe("DeepExpandingMenu", () => {
116
180
  });
117
181
 
118
182
  it("tracks the open group from toggle events, whichever order they arrive in", async () => {
119
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: twoGroups } });
120
- const [first, second] = wrapper.findAll(".navigation-group-panel");
121
- const toggles = wrapper.findAll(".navigation-group-toggle");
183
+ const wrapper = await mountMenu({ props: { navLinks: twoGroups } });
184
+ const [first, second] = wrapper.findAll(".deep-expanding-menu-group-panel");
185
+ const toggles = wrapper.findAll(".deep-expanding-menu-group-toggle");
122
186
 
123
187
  first!.element.dispatchEvent(toggleEvent("open"));
124
188
  await nextTick();
@@ -142,9 +206,9 @@ describe("DeepExpandingMenu", () => {
142
206
  });
143
207
 
144
208
  it("opens, switches and closes groups from their toggles", async () => {
145
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: twoGroups } });
146
- const toggles = wrapper.findAll(".navigation-group-toggle");
147
- const panels = wrapper.findAll(".navigation-group-panel");
209
+ const wrapper = await mountMenu({ props: { navLinks: twoGroups } });
210
+ const toggles = wrapper.findAll(".deep-expanding-menu-group-toggle");
211
+ const panels = wrapper.findAll(".deep-expanding-menu-group-panel");
148
212
 
149
213
  await toggles[0]!.trigger("click");
150
214
  expect(panels[0]!.classes()).toContain("deep-expanding-menu-panel-open");
@@ -160,14 +224,13 @@ describe("DeepExpandingMenu", () => {
160
224
  });
161
225
 
162
226
  it("closes the open group on Escape", async () => {
163
- const wrapper = await mountSuspended(DeepExpandingMenu, { props: { navLinks: twoGroups }, attachTo: document.body });
164
- await wrapper.findAll(".navigation-group-toggle")[0]!.trigger("click");
227
+ const wrapper = await mountMenu({ props: { navLinks: twoGroups }, attachTo: document.body });
228
+ await wrapper.findAll(".deep-expanding-menu-group-toggle")[0]!.trigger("click");
165
229
  await nextTick();
166
230
 
167
231
  document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
168
232
  await nextTick();
169
233
  expect(wrapper.find(".deep-expanding-menu-panel-open").exists()).toBe(false);
170
- wrapper.unmount();
171
234
  });
172
235
  });
173
236
  });
@@ -2,16 +2,16 @@
2
2
 
3
3
  exports[`DeepExpandingMenu > renders correct HTML structure 1`] = `
4
4
  "<nav class="deep-expanding-menu">
5
- <div class="inner"><a href="/" class="navigation-link">Home</a>
6
- <div class="navigation-group" style="--_anchor-name: --anchor-v-0-0-1;"><button type="button" popovertarget="popovertarget-v-0-0-1" class="navigation-group-toggle" aria-expanded="false"><span>Services</span><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></button>
7
- <div id="popovertarget-v-0-0-1" class="navigation-group-panel" popover="">
8
- <h4 class="page-heading-4 mb-6">Our services</h4>
9
- <ul class="navigation-group-list">
10
- <li class="navigation-group-item"><a href="/services/haircuts" class="navigation-group-link">Haircuts</a></li>
11
- <li class="navigation-group-item"><a href="/services/colouring" class="navigation-group-link">Colouring</a></li>
5
+ <div class="deep-expanding-menu-inner"><a href="/" class="deep-expanding-menu-link">Home</a>
6
+ <div class="deep-expanding-menu-group" style="--_anchor-name: --anchor-v-0-0-1;"><button type="button" popovertarget="popovertarget-v-0-0-1" class="deep-expanding-menu-group-toggle" aria-expanded="false"><span class="deep-expanding-menu-group-label">Services</span><span class="iconify i-bi:caret-down-fill deep-expanding-menu-group-icon" aria-hidden="true"></span></button>
7
+ <div id="popovertarget-v-0-0-1" class="deep-expanding-menu-group-panel" popover="">
8
+ <h4 class="deep-expanding-menu-group-heading page-heading-4 mb-6">Our services</h4>
9
+ <ul class="deep-expanding-menu-group-list" role="list">
10
+ <li class="deep-expanding-menu-group-item"><a href="/services/haircuts" class="deep-expanding-menu-group-link">Haircuts</a></li>
11
+ <li class="deep-expanding-menu-group-item"><a href="/services/colouring" class="deep-expanding-menu-group-link">Colouring</a></li>
12
12
  </ul>
13
13
  </div>
14
- </div><a href="/contact" class="navigation-link">Contact</a>
14
+ </div><a href="/contact" class="deep-expanding-menu-link">Contact</a>
15
15
  </div>
16
16
  </nav>"
17
17
  `;
@@ -2,44 +2,72 @@
2
2
 
3
3
  ## Public token API
4
4
 
5
+ > Changed 2026-10-21: colour defaults now come from `--theme-*` tokens (were `--blue-10`, `white`,
6
+ > `black`, `--slate-10`), `--deep-expanding-menu-classic-panel-offset-top` defaults to
7
+ > `calc(100% + 1rem)` (was `4rem`, which overlapped a wrapped toggle label), the panel widths switch
8
+ > on the menu's own width instead of the viewport, and `--deep-expanding-menu-classic-gap` was
9
+ > removed (the root grid has one area, so it never did anything).
10
+
11
+ ### Top-level row
12
+
5
13
  | Token | Default | Controls |
6
14
  |---|---|---|
7
- | `--deep-expanding-menu-classic-gap` | `1.2rem` | Gap on the root grid |
8
- | `--deep-expanding-menu-classic-item-gap` | `2.4rem` | Gap between top-level items |
9
- | `--deep-expanding-menu-classic-link-border-width` | `0.2rem` | Bottom border width on links and group toggles |
15
+ | `--deep-expanding-menu-classic-item-gap` | `2.4rem` | Gap between top-level items (row and column, the row wraps) |
16
+ | `--deep-expanding-menu-classic-link-border-width` | `0.2rem` | Bottom border width on links, group toggles and child links |
10
17
  | `--deep-expanding-menu-classic-link-padding-block` | `0.8rem` | Vertical padding on links and group toggles |
11
- | `--deep-expanding-menu-classic-link-border-colour-hover` | `var(--blue-10)` | Bottom border colour on hover/focus |
18
+ | `--deep-expanding-menu-classic-link-border-colour-hover` | `var(--theme-border)` | Bottom border colour on hover/focus |
19
+ | `--deep-expanding-menu-classic-focus-outline-width` | `0.2rem` | Keyboard focus ring width on links and toggles |
20
+ | `--deep-expanding-menu-classic-focus-outline-colour` | `var(--theme-ring, currentcolor)` | Keyboard focus ring colour |
12
21
  | `--deep-expanding-menu-classic-icon-size` | `1.2rem` | Caret icon size |
13
- | `--deep-expanding-menu-classic-panel-offset-top` | `4rem` | Panel offset from the toggle |
14
- | `--deep-expanding-menu-classic-panel-width` | `20rem` | Panel width below the `768px` breakpoint |
15
- | `--deep-expanding-menu-classic-panel-width-tablet` | `40rem` | Panel width from `768px` |
16
- | `--deep-expanding-menu-classic-panel-width-desktop` | `60rem` | Panel width from `1024px` |
17
- | `--deep-expanding-menu-classic-panel-background-colour` | `white` | Panel background |
22
+
23
+ ### Panel
24
+
25
+ | Token | Default | Controls |
26
+ |---|---|---|
27
+ | `--deep-expanding-menu-classic-panel-offset-top` | `calc(100% + 1rem)` | Panel offset from the top of its group |
28
+ | `--deep-expanding-menu-classic-panel-width` | `20rem` | Panel width while the menu is under `768px` wide |
29
+ | `--deep-expanding-menu-classic-panel-width-tablet` | `40rem` | Panel width once the menu is `768px` wide |
30
+ | `--deep-expanding-menu-classic-panel-width-desktop` | `60rem` | Panel width once the menu is `1024px` wide |
31
+ | `--deep-expanding-menu-classic-panel-text-colour` | `var(--theme-text)` | Panel text colour |
32
+ | `--deep-expanding-menu-classic-panel-background-colour` | `var(--theme-surface-subtle)` | Panel background |
18
33
  | `--deep-expanding-menu-classic-panel-border-width` | `0.1rem` | Panel border width |
19
- | `--deep-expanding-menu-classic-panel-border-colour` | `black` | Panel border colour |
34
+ | `--deep-expanding-menu-classic-panel-border-colour` | `var(--theme-border)` | Panel border colour |
20
35
  | `--deep-expanding-menu-classic-panel-border-radius` | `1.2rem` | Panel corner radius |
21
36
  | `--deep-expanding-menu-classic-panel-shadow` | `0 0 1rem rgba(0, 0, 0, 0.1)` | Panel box-shadow |
22
37
  | `--deep-expanding-menu-classic-panel-padding` | `1.2rem` | Panel padding |
23
- | `--deep-expanding-menu-classic-panel-heading-colour` | `var(--slate-10)` | `childLinksTitle` heading colour inside the panel |
24
- | `--deep-expanding-menu-classic-panel-list-gap` | `1.2rem` | Gap between child link grid items |
25
- | `--deep-expanding-menu-classic-group-link-colour` | `var(--slate-10)` | Child link text colour |
26
- | `--deep-expanding-menu-classic-group-link-border-colour-hover` | `var(--slate-10)` | Child link bottom border colour on hover/focus |
38
+ | `--deep-expanding-menu-classic-panel-heading-colour` | `var(--theme-text)` | `childLinksTitle` heading colour |
39
+ | `--deep-expanding-menu-classic-panel-heading-line-clamp` | `none` | Lines of `childLinksTitle` before an ellipsis. `1` is single-line ellipsis, `none` shows everything |
40
+ | `--deep-expanding-menu-classic-panel-list-gap` | `1.2rem` | Gap between the panel's heading and list, and between child link grid items |
41
+ | `--deep-expanding-menu-classic-group-link-colour` | `var(--theme-text)` | Child link text colour |
42
+ | `--deep-expanding-menu-classic-group-link-border-colour-hover` | `var(--theme-text)` | Child link bottom border colour on hover/focus |
43
+
44
+ Each panel width is capped at the menu's own width (`min(<token>, 100cqi)`). Private tokens (not
45
+ public API): `--_icon-transform` (caret flip).
46
+
47
+ ## State hooks
27
48
 
28
- ```css
29
- .my-page {
30
- --deep-expanding-menu-classic-panel-background-colour: #1a1a1a;
31
- --deep-expanding-menu-classic-panel-border-colour: transparent;
32
- --deep-expanding-menu-classic-group-link-colour: white;
33
- }
34
- ```
49
+ | Hook | Meaning |
50
+ |---|---|
51
+ | `.deep-expanding-menu-classic-group[open]` | Group open (flips the caret) |
35
52
 
36
- Or scope to a single instance via `styleClassPassthrough`:
53
+ Inner elements: `.deep-expanding-menu-classic-inner` (the flex row),
54
+ `.deep-expanding-menu-classic-link` (direct link), `.deep-expanding-menu-classic-group` (the
55
+ `<details>`), `.deep-expanding-menu-classic-group-toggle` (the `<summary>`),
56
+ `.deep-expanding-menu-classic-group-label`, `.deep-expanding-menu-classic-group-icon`,
57
+ `.deep-expanding-menu-classic-group-panel`, `.deep-expanding-menu-classic-group-heading`,
58
+ `.deep-expanding-menu-classic-group-list`, `.deep-expanding-menu-classic-group-item`,
59
+ `.deep-expanding-menu-classic-group-link`.
37
60
 
38
- ```vue
39
- <DeepExpandingMenuClassic style-class-passthrough="main-nav">...</DeepExpandingMenuClassic>
40
- ```
61
+ > Changed 2026-10-21: these were `.inner`, `.navigation-link`, `.navigation-group`,
62
+ > `.navigation-group-toggle`, `.icon`, `.navigation-group-panel`, `.navigation-group-list`,
63
+ > `.navigation-group-item` and `.navigation-group-link`. The heading had no class of its own.
41
64
 
42
- ---
65
+ ## Sizing model
66
+
67
+ The root is an inline-size container named `deep-expanding-menu-classic`, so it can't size itself
68
+ from its content: give it a width from its parent (it fills a block or grid cell by default). A
69
+ panel opens from its toggle's left edge, so a group near the menu's right edge can still overrun it
70
+ by up to the panel's width; `DeepExpandingMenu` flips its panel instead.
43
71
 
44
72
  ## Local overrides
45
73
 
@@ -49,3 +77,7 @@ Set the tokens above on an element you own (a page or section class, or a class
49
77
  `<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
50
78
  `:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
51
79
 
80
+ ## Class passthrough
81
+
82
+ `style-class-passthrough` adds classes to the root element (the `<nav>` by default). It's the
83
+ usual place to scope token overrides to one instance.