srcdev-nuxt-components 9.6.19 → 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 (135) hide show
  1. package/.claude/rules/consumer/ids-from-useid.md +3 -2
  2. package/.claude/skills/component-compliance-checklist.md +5 -0
  3. package/.claude/skills/components/auto-grid.md +3 -1
  4. package/.claude/skills/components/capture-qr-code.md +9 -5
  5. package/.claude/skills/components/carousel-flip.md +41 -30
  6. package/.claude/skills/components/column-flow-grid.md +2 -0
  7. package/.claude/skills/components/dashboard-quad-grid.md +7 -2
  8. package/.claude/skills/components/dashboard-stats-grid.md +4 -0
  9. package/.claude/skills/components/decode-qr-code.md +7 -2
  10. package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
  11. package/.claude/skills/components/deep-expanding-menu.md +22 -21
  12. package/.claude/skills/components/grid-stack.md +2 -0
  13. package/.claude/skills/components/layout-grid-by-cols.md +2 -0
  14. package/.claude/skills/components/masonry-grid.md +9 -1
  15. package/.claude/skills/components/page-row.md +3 -0
  16. package/.claude/skills/components/samaritan-prompt.md +24 -8
  17. package/.claude/skills/components/site-header.md +16 -2
  18. package/.claude/skills/index.md +12 -12
  19. package/.claude/skills/storybook-add-story.md +25 -0
  20. package/.claude/skills/testing-add-unit-test.md +26 -0
  21. package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
  22. package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
  23. package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
  24. package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
  25. package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
  26. package/.vscode/srcdev-component-site-header.code-snippets +13 -1
  27. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
  28. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
  29. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
  30. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
  31. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
  32. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
  33. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
  34. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
  35. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
  36. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
  37. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
  38. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
  39. package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
  40. package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
  41. package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
  42. package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
  43. package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
  44. package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
  45. package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
  46. package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
  47. package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
  48. package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
  49. package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
  50. package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
  51. package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
  52. package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
  53. package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
  54. package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
  55. package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
  56. package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
  57. package/app/components/01.atoms/page-row/PageRow.vue +16 -15
  58. package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
  59. package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
  60. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
  61. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
  62. package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
  63. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
  64. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
  65. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
  66. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
  67. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
  68. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
  69. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
  70. package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
  71. package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
  72. package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
  73. package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
  74. package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
  75. package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
  76. package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
  77. package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
  78. package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
  79. package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
  80. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
  81. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
  82. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
  83. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
  84. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
  85. package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
  86. package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
  87. package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
  88. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
  89. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
  90. package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
  91. package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
  92. package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
  93. package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
  94. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
  95. package/app/types/components/qr-code.d.ts +12 -0
  96. package/app/utils/graphemes.ts +8 -0
  97. package/package.json +1 -1
  98. package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
  99. package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
  100. package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
  101. package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
  102. package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
  103. package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
  104. package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
  105. package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
  106. package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
  107. package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
  108. package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
  109. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
  110. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
  111. package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
  112. package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
  113. package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
  114. package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
  115. package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
  116. package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
  117. package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  118. package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
  119. package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
  120. package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
  121. package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
  122. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
  123. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
  124. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
  125. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
  126. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
  127. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
  128. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
  129. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
  130. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
  131. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
  132. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
  133. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
  134. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
  135. package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
@@ -1,6 +1,8 @@
1
1
  import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
- import { computed } from "vue";
2
+ import { computed, ref } from "vue";
3
3
  import StorybookComponent from "../CarouselFlip.vue";
4
+ import CanvasSwitcher from "../../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
5
+ import type { MediaCanvas } from "~/types/components";
4
6
 
5
7
  // Define carousel item interface
6
8
  interface CarouselItem {
@@ -16,7 +18,7 @@ interface CarouselFlipStoryArgs {
16
18
  transitionSpeed: number;
17
19
  useFlipAnimation: boolean;
18
20
  useSpringEffect: boolean;
19
- buttonLayout: "sides" | "controls-flanking" | "controls-grouped-right" | "overlay";
21
+ buttonLayout: "sides" | "controls-flanking" | "controls-grouped-right" | "overlay" | "track-edges";
20
22
  showControls: boolean;
21
23
  styleClassPassthrough: string[];
22
24
  ariaLabel: string;
@@ -116,7 +118,7 @@ export default {
116
118
  },
117
119
  buttonLayout: {
118
120
  control: { type: "select" },
119
- options: ["sides", "controls-flanking", "controls-grouped-right", "overlay"],
121
+ options: ["sides", "controls-flanking", "controls-grouped-right", "overlay", "track-edges"],
120
122
  description: "Placement of prev/next buttons relative to the carousel and controls bar",
121
123
  table: {
122
124
  category: "Layout",
@@ -195,6 +197,16 @@ export default {
195
197
  },
196
198
  },
197
199
  },
200
+ parameters: {
201
+ docs: {
202
+ description: {
203
+ component:
204
+ "Carousel that reorders its items with a FLIP animation. Each id in `carouselDataIds` names a slot holding that item's content. " +
205
+ "Props: `carouselDataIds`, `buttonLayout`, `showControls`, `allowCarouselOverflow`, `useFlipAnimation`, `useSpringEffect`, `transitionSpeed`, and label props for every control. " +
206
+ "Slots: one per id, plus `prev-icon` and `next-icon`. Arrow keys move between items when the track or a marker has focus; swipe works on touch.",
207
+ },
208
+ },
209
+ },
198
210
  args: {
199
211
  carouselDataIds: [1, 2, 3, 4, 5],
200
212
  allowCarouselOverflow: true,
@@ -249,6 +261,11 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
249
261
  --carousel-flip-edge-preview-width: 12px;
250
262
  }
251
263
 
264
+ .button-offset-demo.carousel-flip {
265
+ --carousel-flip-button-inline-offset: 2.4rem;
266
+ --carousel-flip-button-align: end;
267
+ }
268
+
252
269
  @media (width >= 768px) {
253
270
  .carousel-flip-demo.carousel-flip {
254
271
  --carousel-flip-gap: 24px;
@@ -256,7 +273,7 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
256
273
  }
257
274
  }
258
275
 
259
- .carousel-flip-demo.carousel-flip .item-container {
276
+ .carousel-flip-demo.carousel-flip .carousel-flip-track {
260
277
  max-inline-size: 800px;
261
278
  margin-inline: auto;
262
279
  outline: 1px solid var(--slate-08, #666);
@@ -265,18 +282,18 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
265
282
  }
266
283
 
267
284
  @media (width >= 768px) {
268
- .carousel-flip-demo.carousel-flip .item-container {
285
+ .carousel-flip-demo.carousel-flip .carousel-flip-track {
269
286
  padding-block: 12px;
270
287
  padding-inline: 12px;
271
288
  }
272
289
  }
273
290
 
274
- .carousel-flip-demo.carousel-flip .item {
291
+ .carousel-flip-demo.carousel-flip .carousel-flip-item {
275
292
  background-color: var(--slate-05, #e5e5e5);
276
293
  contain: layout style paint;
277
294
  }
278
295
 
279
- .carousel-flip-demo.carousel-flip .item:nth-child(odd) {
296
+ .carousel-flip-demo.carousel-flip .carousel-flip-item:nth-child(odd) {
280
297
  background-color: var(--slate-06, #999);
281
298
  }
282
299
 
@@ -294,32 +311,32 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
294
311
  outline: 1px solid #00000090;
295
312
  }
296
313
 
297
- .carousel-flip-demo.carousel-flip .controls-container {
314
+ .carousel-flip-demo.carousel-flip .carousel-flip-controls {
298
315
  gap: 20px;
299
316
  max-inline-size: 800px;
300
317
  }
301
318
 
302
- .carousel-flip-demo.carousel-flip .markers-container {
319
+ .carousel-flip-demo.carousel-flip .carousel-flip-markers {
303
320
  --marker-height: 12px;
304
321
  --marker-width: 12px;
305
322
  --marker-border-radius: 100vw;
306
323
  }
307
324
 
308
325
  @media (width >= 768px) {
309
- .carousel-flip-demo.carousel-flip .markers-container {
326
+ .carousel-flip-demo.carousel-flip .carousel-flip-markers {
310
327
  --marker-height: 22px;
311
328
  --marker-width: 22px;
312
329
  }
313
330
  }
314
331
 
315
- .carousel-flip-demo.carousel-flip .markers-item {
332
+ .carousel-flip-demo.carousel-flip .carousel-flip-markers-item {
316
333
  width: var(--marker-width);
317
334
  height: var(--marker-height);
318
335
  border-radius: var(--marker-border-radius);
319
336
  line-height: var(--marker-height);
320
337
  }
321
338
 
322
- .carousel-flip-demo.carousel-flip .btn-marker {
339
+ .carousel-flip-demo.carousel-flip .carousel-flip-marker {
323
340
  width: var(--marker-width);
324
341
  height: var(--marker-height);
325
342
  border-radius: var(--marker-border-radius);
@@ -330,13 +347,13 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
330
347
  outline: 1px solid transparent;
331
348
  }
332
349
 
333
- .carousel-flip-demo.carousel-flip .btn-marker.active {
350
+ .carousel-flip-demo.carousel-flip .carousel-flip-marker[data-active] {
334
351
  background-color: var(--slate-012, #000);
335
352
  will-change: background-color;
336
353
  }
337
354
 
338
- .carousel-flip-demo.carousel-flip .btn-marker:hover,
339
- .carousel-flip-demo.carousel-flip .btn-marker:focus-visible {
355
+ .carousel-flip-demo.carousel-flip .carousel-flip-marker:hover,
356
+ .carousel-flip-demo.carousel-flip .carousel-flip-marker:focus-visible {
340
357
  outline-color: #000;
341
358
  will-change: background-color, outline-color;
342
359
  }
@@ -442,6 +459,41 @@ NoOverflow.args = {
442
459
  styleClassPassthrough: ["carousel-flip-demo", "no-overflow"],
443
460
  };
444
461
 
462
+ export const TrackEdges = Template.bind({});
463
+ TrackEdges.storyName = "Button Layout: Track Edges";
464
+ TrackEdges.args = {
465
+ carouselDataIds: [1, 2, 3, 4, 5],
466
+ allowCarouselOverflow: true,
467
+ buttonLayout: "track-edges",
468
+ };
469
+ TrackEdges.parameters = {
470
+ docs: {
471
+ description: {
472
+ story:
473
+ "`buttonLayout=\"track-edges\"` overlays the buttons on the ends of the visible track, inset past the edge previews (`--carousel-flip-button-edge-inset`, default the preview width plus `0.8rem`). " +
474
+ "They stay on the active frame even with `allowCarouselOverflow` on, where `sides` would leave them over the neighbouring items.",
475
+ },
476
+ },
477
+ };
478
+
479
+ export const ButtonPlacementTokens = Template.bind({});
480
+ ButtonPlacementTokens.storyName = "Button Placement Tokens";
481
+ ButtonPlacementTokens.args = {
482
+ carouselDataIds: [1, 2, 3, 4, 5],
483
+ allowCarouselOverflow: true,
484
+ buttonLayout: "sides",
485
+ styleClassPassthrough: ["carousel-flip-demo", "button-offset-demo"],
486
+ };
487
+ ButtonPlacementTokens.parameters = {
488
+ docs: {
489
+ description: {
490
+ story:
491
+ "The `sides` layout with `--carousel-flip-button-inline-offset: 2.4rem` (pushes each button outward; negative pulls it inward) and `--carousel-flip-button-align: end` (bottom of the row). " +
492
+ "To reach the viewport edges from a centred carousel of known width, set the offset to `calc((100vw - <that width>) / 2 - 1.6rem)` (a percentage won't work: on `translate` it resolves against the button itself). `vw` includes the scrollbar, hence the margin.",
493
+ },
494
+ },
495
+ };
496
+
445
497
  export const NoControls = Template.bind({});
446
498
  NoControls.args = {
447
499
  carouselDataIds: [1, 2, 3, 4, 5],
@@ -510,7 +562,7 @@ const RichContentTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
510
562
  }
511
563
  }
512
564
 
513
- .carousel-flip-demo.carousel-flip .item-container {
565
+ .carousel-flip-demo.carousel-flip .carousel-flip-track {
514
566
  max-inline-size: 800px;
515
567
  margin-inline: auto;
516
568
  outline: 1px solid var(--slate-08, #666);
@@ -519,18 +571,18 @@ const RichContentTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
519
571
  }
520
572
 
521
573
  @media (width >= 768px) {
522
- .carousel-flip-demo.carousel-flip .item-container {
574
+ .carousel-flip-demo.carousel-flip .carousel-flip-track {
523
575
  padding-block: 12px;
524
576
  padding-inline: 12px;
525
577
  }
526
578
  }
527
579
 
528
- .carousel-flip-demo.carousel-flip .item {
580
+ .carousel-flip-demo.carousel-flip .carousel-flip-item {
529
581
  background-color: var(--slate-05, #e5e5e5);
530
582
  contain: layout style paint;
531
583
  }
532
584
 
533
- .carousel-flip-demo.carousel-flip .item:nth-child(odd) {
585
+ .carousel-flip-demo.carousel-flip .carousel-flip-item:nth-child(odd) {
534
586
  background-color: var(--slate-06, #999);
535
587
  }
536
588
  `;
@@ -624,7 +676,7 @@ const RichContentTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
624
676
  border-radius: 50%;
625
677
  transform: rotate(45deg);
626
678
  "
627
- />
679
+ ></div>
628
680
  </div>
629
681
  </template>
630
682
  </StorybookComponent>
@@ -682,13 +734,13 @@ const AnimationShowcaseTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
682
734
  Testing different animation combinations and timing
683
735
  </p>
684
736
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; margin-bottom: 20px;">
685
- <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333; background: args.useFlipAnimation ? '#e7f5e7' : 'transparent';">
737
+ <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333;" :style="{ background: args.useFlipAnimation ? '#e7f5e7' : 'transparent' }">
686
738
  Flip: {{ args.useFlipAnimation ? '✓' : '✗' }}
687
739
  </div>
688
- <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333; background: args.useSpringEffect ? '#e7f5e7' : 'transparent';">
740
+ <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333;" :style="{ background: args.useSpringEffect ? '#e7f5e7' : 'transparent' }">
689
741
  Spring: {{ args.useSpringEffect ? '✓' : '✗' }}
690
742
  </div>
691
- <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333; background: args.allowCarouselOverflow ? '#e7f5e7' : 'transparent';">
743
+ <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333;" :style="{ background: args.allowCarouselOverflow ? '#e7f5e7' : 'transparent' }">
692
744
  Overflow: {{ args.allowCarouselOverflow ? '✓' : '✗' }}
693
745
  </div>
694
746
  <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333;">
@@ -755,3 +807,81 @@ AnimationShowcase.args = {
755
807
  allowCarouselOverflow: true,
756
808
  styleClassPassthrough: ["carousel-flip-demo", "animation-showcase"],
757
809
  };
810
+
811
+ const stressItems = [
812
+ { id: "short", title: "x", body: "" },
813
+ {
814
+ id: "unbroken",
815
+ title: "Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspaces",
816
+ body: "https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own",
817
+ },
818
+ {
819
+ id: "german",
820
+ title: "Unsere vollständige Übersicht aller Behandlungen in Northtown",
821
+ body: "Ein sehr langer Beschreibungstext, der auf schmalen Bildschirmen mehrfach umbrechen muss, ohne das Karussell zu sprengen.",
822
+ },
823
+ { id: "emoji", title: "🙂 👍🏽 👨‍👩‍👧", body: "Emoji only" },
824
+ { id: "html", title: "<b>Not bold</b>", body: "<script>alert(1)</script>" },
825
+ { id: "rtl", title: "مرحبا بالعالم", body: "نص طويل من اليمين إلى اليسار" },
826
+ ...Array.from({ length: 24 }, (_, i) => ({ id: `extra-${i + 1}`, title: `Item ${i + 7}`, body: "" })),
827
+ ];
828
+
829
+ export const StressTest: StoryFn<CarouselFlipStoryArgs> = (args) => ({
830
+ components: { StorybookComponent, CanvasSwitcher },
831
+ setup() {
832
+ const canvasName = ref<MediaCanvas>("mobileCanvas");
833
+ const items = computed(() => stressItems.filter((item) => args.carouselDataIds.includes(item.id)));
834
+ return { args, canvasName, items };
835
+ },
836
+ template: `
837
+ <div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
838
+ <CanvasSwitcher v-model:canvas-name="canvasName" />
839
+ </div>
840
+ <div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
841
+ <StorybookComponent
842
+ :carousel-data-ids="args.carouselDataIds"
843
+ :allow-carousel-overflow="args.allowCarouselOverflow"
844
+ :transition-speed="args.transitionSpeed"
845
+ :use-flip-animation="args.useFlipAnimation"
846
+ :use-spring-effect="args.useSpringEffect"
847
+ :button-layout="args.buttonLayout"
848
+ :show-controls="args.showControls"
849
+ :previous-label="args.previousLabel"
850
+ :next-label="args.nextLabel"
851
+ :jump-to-item-label="args.jumpToItemLabel"
852
+ >
853
+ <template v-for="(item, index) in items" #[item.id] :key="index">
854
+ <div style="display: grid; gap: 0.8rem; inline-size: 100%; padding: 1.6rem; aspect-ratio: 4 / 3; background: var(--theme-surface-subtle); outline: 1px solid currentColor;">
855
+ <h3 style="margin: 0;">{{ item.title }}</h3>
856
+ <p style="margin: 0;">{{ item.body }}</p>
857
+ </div>
858
+ </template>
859
+ </StorybookComponent>
860
+ </div>
861
+ `,
862
+ });
863
+ StressTest.storyName = "Stress Test (Worst-Case Data)";
864
+ StressTest.args = {
865
+ carouselDataIds: stressItems.map((item) => item.id),
866
+ allowCarouselOverflow: false,
867
+ transitionSpeed: 400,
868
+ useFlipAnimation: true,
869
+ buttonLayout: "controls-flanking",
870
+ previousLabel: "Zum vorherigen Element im Karussell wechseln",
871
+ nextLabel: "Zum nächsten Element im Karussell wechseln",
872
+ jumpToItemLabel: "Springe zu Element",
873
+ styleClassPassthrough: [],
874
+ };
875
+ StressTest.parameters = {
876
+ layout: "fullscreen",
877
+ docs: {
878
+ story: { inline: false, height: "720px" },
879
+ description: {
880
+ story:
881
+ "30 items with hostile content at every `CanvasSwitcher` width, in the `controls-flanking` layout. Check that: the 30 markers wrap onto several rows instead of overflowing; " +
882
+ "the unbroken title and URL wrap inside their item; the German text wraps; emoji, `<b>` and `<script>` render as text; the right-to-left item reads correctly; prev/next still cycle through all 30. " +
883
+ "In Controls, set `carouselDataIds` to `[]`: the carousel renders empty without freezing the tab (until 2026-10-21 an empty list hung it in an infinite loop). " +
884
+ "Set it to one id: the markers and prev/next buttons disappear, since there is nothing to move to.",
885
+ },
886
+ },
887
+ };