srcdev-nuxt-components 9.6.18 → 9.6.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/consumer/css-relative-colour-calc.md +15 -0
- package/.claude/rules/consumer/ids-from-useid.md +3 -2
- package/.claude/rules/consumer/vue-dynamic-arg-no-spaces.md +16 -0
- package/.claude/skills/component-compliance-checklist.md +11 -1
- package/.claude/skills/component-dynamic-slots.md +2 -2
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/accordion-chat.md +99 -0
- package/.claude/skills/components/accordion-group.md +98 -0
- package/.claude/skills/components/action-menu.md +11 -5
- package/.claude/skills/components/auto-grid.md +3 -1
- package/.claude/skills/components/capture-qr-code.md +9 -5
- package/.claude/skills/components/carousel-flip.md +41 -30
- package/.claude/skills/components/column-flow-grid.md +2 -0
- package/.claude/skills/components/dashboard-quad-grid.md +7 -2
- package/.claude/skills/components/dashboard-stats-grid.md +4 -0
- package/.claude/skills/components/decode-qr-code.md +7 -2
- package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
- package/.claude/skills/components/deep-expanding-menu.md +22 -21
- package/.claude/skills/components/expanding-panel-classic.md +9 -7
- package/.claude/skills/components/expanding-panel.md +9 -8
- package/.claude/skills/components/grid-stack.md +2 -0
- package/.claude/skills/components/layout-grid-by-cols.md +2 -0
- package/.claude/skills/components/masonry-grid.md +9 -1
- package/.claude/skills/components/page-row.md +3 -0
- package/.claude/skills/components/samaritan-prompt.md +24 -8
- package/.claude/skills/components/site-header.md +16 -2
- package/.claude/skills/components/sparkle-effect.md +79 -0
- package/.claude/skills/index.md +16 -14
- package/.claude/skills/storybook-add-story.md +33 -0
- package/.claude/skills/testing-add-unit-test.md +26 -0
- package/.claude/skills/using-component-skills.md +1 -1
- package/.vscode/srcdev-component-accordion-chat.code-snippets +42 -0
- package/.vscode/srcdev-component-accordion-group.code-snippets +38 -0
- package/.vscode/srcdev-component-action-menu.code-snippets +3 -3
- package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
- package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
- package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
- package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
- package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
- package/.vscode/srcdev-component-site-header.code-snippets +13 -1
- package/.vscode/srcdev-component-sparkle-effect.code-snippets +33 -0
- package/app/components/01.atoms/animations/sparkle-effect/CONSUMER-STYLING.md +75 -0
- package/app/components/01.atoms/animations/sparkle-effect/SparkleEffect.vue +213 -0
- package/app/components/01.atoms/animations/sparkle-effect/stories/SparkleEffect.stories.ts +303 -0
- package/app/components/01.atoms/animations/sparkle-effect/tests/SparkleEffect.spec.ts +150 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +1 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
- package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
- package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
- package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
- package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
- package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
- package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
- package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
- package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
- package/app/components/01.atoms/page-row/PageRow.vue +16 -15
- package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
- package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
- package/app/components/02.molecules/action-menu/ActionMenu.vue +6 -2
- package/app/components/02.molecules/action-menu/ActionMenuItem.vue +6 -3
- package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +11 -4
- package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +130 -18
- package/app/components/02.molecules/expandable/accordion-chat/AccordionChat.vue +227 -0
- package/app/components/02.molecules/expandable/accordion-chat/CONSUMER-STYLING.md +150 -0
- package/app/components/02.molecules/expandable/accordion-chat/stories/AccordionChat.stories.ts +347 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/AccordionChat.spec.ts +205 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/__snapshots__/AccordionChat.spec.ts.snap +40 -0
- package/app/components/02.molecules/expandable/{accordian/AccordianCore.vue → accordion-group/AccordionGroup.vue} +32 -20
- package/app/components/02.molecules/expandable/accordion-group/CONSUMER-STYLING.md +66 -0
- package/app/components/02.molecules/expandable/accordion-group/stories/AccordionGroup.stories.ts +238 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/AccordionGroup.spec.ts +172 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/__snapshots__/AccordionGroup.spec.ts.snap +45 -0
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +11 -6
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +82 -1
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +8 -8
- package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +10 -5
- package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +83 -1
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
- package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
- package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
- package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
- package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
- package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
- package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
- package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
- package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
- package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
- package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
- package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
- package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
- package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +2 -2
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +2 -2
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
- package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
- package/app/types/components/qr-code.d.ts +12 -0
- package/app/utils/graphemes.ts +8 -0
- package/package.json +1 -1
- package/.claude/skills/components/accordian-core.md +0 -180
- package/.vscode/srcdev-component-accordian-core.code-snippets +0 -74
- package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
- package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
- package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
- package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
- package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
- package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
- package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +0 -100
- package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +0 -272
- package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +0 -257
- package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -95
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Public token API
|
|
4
4
|
|
|
5
|
+
> Changed 2026-10-21: colour defaults come from `--theme-*` tokens (were `white`,
|
|
6
|
+
> `hsl(0, 29%, 3%)` and `--slate-10`), `--carousel-flip-button-icon-colour` is new, and the
|
|
7
|
+
> `--carousel-flip-focus-outline-*` tokens now style the prev/next buttons as well as the markers.
|
|
8
|
+
|
|
9
|
+
### Layout
|
|
10
|
+
|
|
5
11
|
| Token | Default | Controls |
|
|
6
12
|
|---|---|---|
|
|
7
13
|
| `--carousel-flip-gap` | `1rem` | Gap between items inside the item track |
|
|
@@ -9,34 +15,80 @@
|
|
|
9
15
|
| `--carousel-flip-display-max-width` | `80rem` | Max width of the item track and the controls bar |
|
|
10
16
|
| `--carousel-flip-item-max-width` | `80rem` | Max width of a single item, before subtracting the edge-preview width |
|
|
11
17
|
| `--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
18
|
| `--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
19
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
### Markers
|
|
21
|
+
|
|
22
|
+
| Token | Default | Controls |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `--carousel-flip-marker-gap` | `1rem` | Gap between marker (dot) buttons, in both directions (the markers wrap) |
|
|
25
|
+
| `--carousel-flip-marker-active-colour` | `var(--theme-surface)` | Active marker background colour |
|
|
26
|
+
|
|
27
|
+
The markers otherwise keep the browser's button styling: size and shape them on
|
|
28
|
+
`.carousel-flip-marker` (see the story's `carousel-flip-demo` styles for an example).
|
|
29
|
+
|
|
30
|
+
### Prev/next buttons
|
|
31
|
+
|
|
32
|
+
| Token | Default | Controls |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `--carousel-flip-button-icon-colour` | `var(--theme-text)` | Icon colour |
|
|
35
|
+
| `--carousel-flip-button-background-colour` | `var(--theme-surface-subtle)` | Background colour |
|
|
36
|
+
| `--carousel-flip-button-border-width` | `0.1rem` | Border width |
|
|
37
|
+
| `--carousel-flip-button-border-colour` | `var(--theme-border)` | Border colour |
|
|
38
|
+
| `--carousel-flip-button-border-radius` | `100vw` | Corner radius |
|
|
39
|
+
| `--carousel-flip-button-padding` | `0.8rem` | Padding |
|
|
40
|
+
| `--carousel-flip-button-icon-size` | `2.4rem` | Icon glyph size |
|
|
41
|
+
| `--carousel-flip-buttons-gap` | `1rem` | Gap between the buttons in the `controls-grouped-right` layout |
|
|
42
|
+
| `--carousel-flip-button-align` | `center` | Vertical alignment of each button in its row: `start`, `center` or `end` (every layout except `controls-grouped-right`, where the buttons sit in the controls row) |
|
|
43
|
+
| `--carousel-flip-button-inline-offset` | `0px` | `sides` and `controls-flanking` only: pushes each button outward from its default spot (negative pulls it inward over the track). Mirrors in right-to-left |
|
|
44
|
+
| `--carousel-flip-button-edge-inset` | `calc(var(--carousel-flip-edge-preview-width, 4rem) + 0.8rem)` | `track-edges` only: distance of each button from the visible track's end, so the default clears the edge preview |
|
|
45
|
+
|
|
46
|
+
The buttons are `InputButton` with `variant="inline"`, which adds no colours of its own; these
|
|
47
|
+
tokens style them.
|
|
32
48
|
|
|
33
|
-
|
|
49
|
+
**Placement.** In `sides` the buttons sit at the outer edges of the component's own box, so with
|
|
50
|
+
`allowCarouselOverflow` on they can land over the neighbouring items that peek out past it. Use
|
|
51
|
+
`buttonLayout="track-edges"` to keep them on the active frame, or the offset token to move them.
|
|
52
|
+
To reach the viewport edges from a centred carousel of known width, set
|
|
53
|
+
`--carousel-flip-button-inline-offset: calc((100vw - <that width>) / 2 - 1.6rem)`. A percentage
|
|
54
|
+
doesn't work there (on `translate` it resolves against the button), `vw` includes the scrollbar,
|
|
55
|
+
and an offset past the viewport can cause horizontal scrolling.
|
|
34
56
|
|
|
35
|
-
|
|
36
|
-
<CarouselFlip style-class-passthrough="hero-carousel">...</CarouselFlip>
|
|
37
|
-
```
|
|
57
|
+
> Added 2026-10-21: the three placement tokens and the `track-edges` layout.
|
|
38
58
|
|
|
39
|
-
|
|
59
|
+
### Focus
|
|
60
|
+
|
|
61
|
+
| Token | Default | Controls |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `--carousel-flip-focus-outline-width` | `0.2rem` | `:focus-visible` outline width on markers and prev/next buttons |
|
|
64
|
+
| `--carousel-flip-focus-outline-colour` | `var(--theme-ring)` | `:focus-visible` outline colour |
|
|
65
|
+
| `--carousel-flip-focus-outline-offset` | `0.2rem` | `:focus-visible` outline offset |
|
|
66
|
+
|
|
67
|
+
Private (not public API): `--_carousel-item-track-gap`, read from `--carousel-flip-gap`, and
|
|
68
|
+
`--_carousel-flip-button-direction` (`1`, or `-1` under `:dir(rtl)`).
|
|
69
|
+
|
|
70
|
+
## State hooks
|
|
71
|
+
|
|
72
|
+
| Hook | Meaning |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `.carousel-flip[data-button-layout="…"]` | The `buttonLayout` prop |
|
|
75
|
+
| `.carousel-flip[data-mounted]` | Setup finished; the root fades from `opacity: 0` to `1` |
|
|
76
|
+
| `.carousel-flip-track[data-allow-overflow]` | `allowCarouselOverflow` is on |
|
|
77
|
+
| `.carousel-flip-item[aria-current="true"]` | The active item |
|
|
78
|
+
| `.carousel-flip-item[data-loaded]` | The user has navigated at least once; items get their CSS transition |
|
|
79
|
+
| `.carousel-flip-marker[data-active]` | The active marker (also `aria-current="true"`) |
|
|
80
|
+
|
|
81
|
+
Inner elements: `.carousel-flip-track`, `.carousel-flip-item`, `.carousel-flip-controls`,
|
|
82
|
+
`.carousel-flip-markers` (the `<ul>`), `.carousel-flip-markers-item`, `.carousel-flip-marker`,
|
|
83
|
+
`.carousel-flip-buttons`, `.carousel-flip-button` (with `.carousel-flip-button-prev` /
|
|
84
|
+
`.carousel-flip-button-next`), `.carousel-flip-button-icon`.
|
|
85
|
+
|
|
86
|
+
> Changed 2026-10-21: these were `.item-container`, `.item`, `.controls-container`,
|
|
87
|
+
> `.markers-container`/`.markers-list`, `.markers-item`, `.btn-marker`, `.buttons-container`,
|
|
88
|
+
> `.btn-action`/`.btn-prev`/`.btn-next` and `.arrows-icon`, with state classes `.mounted`,
|
|
89
|
+
> `.allow-overflow`, `.loaded` and `.active`. The markers lost their extra wrapper div.
|
|
90
|
+
|
|
91
|
+
The controls bar and the prev/next buttons only render when there are at least two items.
|
|
40
92
|
|
|
41
93
|
## Local overrides
|
|
42
94
|
|
|
@@ -46,14 +98,15 @@ Set the tokens above on an element you own (a page or section class, or a class
|
|
|
46
98
|
`<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
|
|
47
99
|
`:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
|
|
48
100
|
|
|
49
|
-
|
|
101
|
+
## Class passthrough
|
|
102
|
+
|
|
103
|
+
`style-class-passthrough` adds classes to the root `<section>`. Reactive: changing the prop
|
|
104
|
+
replaces the previous classes (until 2026-10-21 later changes were ignored).
|
|
50
105
|
|
|
51
106
|
## Notes
|
|
52
107
|
|
|
53
108
|
- `--carousel-flip-display-max-width`/`--carousel-flip-item-max-width` previously had no declared
|
|
54
|
-
value anywhere in the component (referenced via `var()` with no fallback)
|
|
55
|
-
|
|
56
|
-
see the component's skill doc for detail.
|
|
109
|
+
value anywhere in the component (referenced via `var()` with no fallback), so the edge-preview
|
|
110
|
+
offset and max-width caps never applied. Fixed 2026-09-21; see the component's skill doc.
|
|
57
111
|
- `useFlipAnimation`/`useSpringEffect`/`transitionSpeed` control animation behaviour via props,
|
|
58
|
-
not tokens
|
|
59
|
-
|
|
112
|
+
not tokens.
|
|
@@ -4,65 +4,79 @@
|
|
|
4
4
|
class="carousel-flip"
|
|
5
5
|
:class="[elementClasses]"
|
|
6
6
|
:data-button-layout="buttonLayout"
|
|
7
|
-
|
|
7
|
+
:data-mounted="isMounted ? '' : undefined"
|
|
8
8
|
:aria-label="ariaLabel"
|
|
9
9
|
>
|
|
10
10
|
<div aria-live="polite" aria-atomic="true" class="sr-only">
|
|
11
|
-
{{ props.itemPositionAnnouncement(currentActiveIndex + 1, itemCount) }}
|
|
11
|
+
{{ itemCount > 0 ? props.itemPositionAnnouncement(currentActiveIndex + 1, itemCount) : "" }}
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
14
|
<div
|
|
15
15
|
ref="carouselContainerRef"
|
|
16
16
|
tabindex="0"
|
|
17
|
-
class="
|
|
18
|
-
:
|
|
17
|
+
class="carousel-flip-track"
|
|
18
|
+
:data-allow-overflow="allowCarouselOverflow ? '' : undefined"
|
|
19
19
|
role="group"
|
|
20
20
|
:aria-label="itemsAriaLabel"
|
|
21
21
|
>
|
|
22
22
|
<div
|
|
23
23
|
v-for="(item, index) in carouselDataIds"
|
|
24
|
-
:key="index"
|
|
24
|
+
:key="`${index}-${item}`"
|
|
25
25
|
ref="carouselItems"
|
|
26
|
-
class="item"
|
|
27
|
-
:
|
|
26
|
+
class="carousel-flip-item"
|
|
27
|
+
:data-loaded="carouselInitComplete && userHasInteracted ? '' : undefined"
|
|
28
28
|
:data-id="item"
|
|
29
|
-
:aria-current="currentActiveIndex === index ? 'true' :
|
|
29
|
+
:aria-current="currentActiveIndex === index ? 'true' : undefined"
|
|
30
30
|
>
|
|
31
31
|
<slot :name="item"></slot>
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
34
|
|
|
35
|
-
<div v-if="showControls" ref="controlsContainerRef"
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
</div>
|
|
35
|
+
<div v-if="showControls && itemCount > 1" ref="controlsContainerRef" class="carousel-flip-controls">
|
|
36
|
+
<ul class="carousel-flip-markers" role="list">
|
|
37
|
+
<li v-for="index in itemCount" :key="index" class="carousel-flip-markers-item">
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
class="carousel-flip-marker"
|
|
41
|
+
:data-active="displayActiveIndex === index - 1 ? '' : undefined"
|
|
42
|
+
:aria-current="displayActiveIndex === index - 1 ? 'true' : undefined"
|
|
43
|
+
:aria-label="`${jumpToItemLabel} ${index}`"
|
|
44
|
+
@click.prevent="jumpToFrame(index - 1)"
|
|
45
|
+
></button>
|
|
46
|
+
</li>
|
|
47
|
+
</ul>
|
|
49
48
|
</div>
|
|
50
49
|
|
|
51
|
-
<div class="buttons
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
<div v-if="itemCount > 1" class="carousel-flip-buttons">
|
|
51
|
+
<InputButton
|
|
52
|
+
variant="inline"
|
|
53
|
+
class="carousel-flip-button carousel-flip-button-prev"
|
|
54
|
+
:button-text="previousLabel"
|
|
55
|
+
@click.prevent="actionPrevious()"
|
|
56
|
+
>
|
|
57
|
+
<template #iconOnly>
|
|
58
|
+
<slot name="prev-icon">
|
|
59
|
+
<Icon :name="prevIcon" class="carousel-flip-button-icon" />
|
|
60
|
+
</slot>
|
|
61
|
+
</template>
|
|
62
|
+
</InputButton>
|
|
63
|
+
<InputButton
|
|
64
|
+
variant="inline"
|
|
65
|
+
class="carousel-flip-button carousel-flip-button-next"
|
|
66
|
+
:button-text="nextLabel"
|
|
67
|
+
@click.prevent="actionNext()"
|
|
68
|
+
>
|
|
69
|
+
<template #iconOnly>
|
|
70
|
+
<slot name="next-icon">
|
|
71
|
+
<Icon :name="nextIcon" class="carousel-flip-button-icon" />
|
|
72
|
+
</slot>
|
|
73
|
+
</template>
|
|
74
|
+
</InputButton>
|
|
62
75
|
</div>
|
|
63
76
|
</section>
|
|
64
77
|
</template>
|
|
65
78
|
|
|
79
|
+
|
|
66
80
|
<script setup lang="ts">
|
|
67
81
|
import { useEventListener, useResizeObserver, useSwipe } from "@vueuse/core";
|
|
68
82
|
|
|
@@ -73,7 +87,7 @@ interface Props {
|
|
|
73
87
|
allowCarouselOverflow?: boolean;
|
|
74
88
|
useFlipAnimation?: boolean;
|
|
75
89
|
useSpringEffect?: boolean;
|
|
76
|
-
buttonLayout?: "sides" | "controls-flanking" | "controls-grouped-right" | "overlay";
|
|
90
|
+
buttonLayout?: "sides" | "controls-flanking" | "controls-grouped-right" | "overlay" | "track-edges";
|
|
77
91
|
showControls?: boolean;
|
|
78
92
|
ariaLabel?: string;
|
|
79
93
|
itemsAriaLabel?: string;
|
|
@@ -104,8 +118,12 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
104
118
|
nextIcon: "ic:outline-keyboard-arrow-right",
|
|
105
119
|
});
|
|
106
120
|
|
|
121
|
+
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
watch(
|
|
124
|
+
() => props.styleClassPassthrough,
|
|
125
|
+
() => resetElementClasses(props.styleClassPassthrough)
|
|
126
|
+
);
|
|
109
127
|
|
|
110
128
|
const carouselWrapperRef = ref<HTMLDivElement | null>(null);
|
|
111
129
|
const carouselContainerRef = ref<HTMLDivElement | null>(null);
|
|
@@ -113,14 +131,15 @@ const carouselItemsRef = useTemplateRef<HTMLDivElement[]>("carouselItems");
|
|
|
113
131
|
const controlsContainerRef = ref<HTMLDivElement | null>(null);
|
|
114
132
|
const carouselInitComplete = ref(false);
|
|
115
133
|
const userHasInteracted = ref(false);
|
|
134
|
+
const isMounted = ref(false);
|
|
116
135
|
|
|
117
136
|
const initialItemOffset = computed(() => {
|
|
118
137
|
return props.useFlipAnimation ? 1 : 2;
|
|
119
138
|
});
|
|
120
139
|
|
|
121
140
|
const currentIndex = ref(0);
|
|
122
|
-
const itemCount =
|
|
123
|
-
const transitionSpeedStr = props.transitionSpeed
|
|
141
|
+
const itemCount = computed(() => props.carouselDataIds.length);
|
|
142
|
+
const transitionSpeedStr = computed(() => `${props.transitionSpeed}ms`);
|
|
124
143
|
|
|
125
144
|
const itemWidth = ref(0);
|
|
126
145
|
const itemWidthOffsetStr = computed(() => {
|
|
@@ -176,7 +195,7 @@ function analyzeOffsets(offsets: number[]) {
|
|
|
176
195
|
}
|
|
177
196
|
|
|
178
197
|
const reorderItems = (skipAnimation: boolean = false) => {
|
|
179
|
-
if (!carouselItemsRef?.value) return;
|
|
198
|
+
if (!carouselItemsRef?.value || itemCount.value < 1) return;
|
|
180
199
|
|
|
181
200
|
// Capture positions before reordering (only if we're going to animate)
|
|
182
201
|
const beforeRects = skipAnimation ? [] : carouselItemsRef.value.map((item) => item.getBoundingClientRect());
|
|
@@ -274,18 +293,18 @@ const reorderItems = (skipAnimation: boolean = false) => {
|
|
|
274
293
|
if (shouldTransition) {
|
|
275
294
|
if (props.allowCarouselOverflow) {
|
|
276
295
|
if (props.useFlipAnimation) {
|
|
277
|
-
transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`;
|
|
296
|
+
transitionProperties = `transform ${transitionSpeedStr.value} ${timingFunction}`;
|
|
278
297
|
} else {
|
|
279
298
|
if (leftValues.minorityIndex !== index) {
|
|
280
|
-
transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`;
|
|
299
|
+
transitionProperties = `transform ${transitionSpeedStr.value} ${timingFunction}`;
|
|
281
300
|
}
|
|
282
301
|
}
|
|
283
302
|
} else {
|
|
284
303
|
if (props.useFlipAnimation) {
|
|
285
|
-
transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`;
|
|
304
|
+
transitionProperties = `transform ${transitionSpeedStr.value} ${timingFunction}`;
|
|
286
305
|
} else {
|
|
287
306
|
if (leftValues.minorityIndex !== index) {
|
|
288
|
-
transitionProperties = `transform ${transitionSpeedStr} ${timingFunction}`;
|
|
307
|
+
transitionProperties = `transform ${transitionSpeedStr.value} ${timingFunction}`;
|
|
289
308
|
}
|
|
290
309
|
}
|
|
291
310
|
}
|
|
@@ -315,7 +334,7 @@ const reorderItems = (skipAnimation: boolean = false) => {
|
|
|
315
334
|
};
|
|
316
335
|
|
|
317
336
|
const actionPrevious = () => {
|
|
318
|
-
if (!carouselInitComplete.value || !carouselItemsRef?.value) return;
|
|
337
|
+
if (!carouselInitComplete.value || !carouselItemsRef?.value || itemCount.value < 2) return;
|
|
319
338
|
|
|
320
339
|
userHasInteracted.value = true;
|
|
321
340
|
|
|
@@ -330,7 +349,7 @@ const actionPrevious = () => {
|
|
|
330
349
|
};
|
|
331
350
|
|
|
332
351
|
const actionNext = () => {
|
|
333
|
-
if (!carouselInitComplete.value || !carouselItemsRef?.value) return;
|
|
352
|
+
if (!carouselInitComplete.value || !carouselItemsRef?.value || itemCount.value < 2) return;
|
|
334
353
|
|
|
335
354
|
userHasInteracted.value = true;
|
|
336
355
|
|
|
@@ -381,11 +400,8 @@ const initialSetup = () => {
|
|
|
381
400
|
carouselInitComplete.value = true;
|
|
382
401
|
checkAndMoveLastItem();
|
|
383
402
|
|
|
384
|
-
// Add mounted class to trigger opacity transition after setup is complete
|
|
385
403
|
nextTick(() => {
|
|
386
|
-
|
|
387
|
-
carouselWrapperRef.value.classList.add("mounted");
|
|
388
|
-
}
|
|
404
|
+
isMounted.value = true;
|
|
389
405
|
});
|
|
390
406
|
};
|
|
391
407
|
|
|
@@ -416,6 +432,14 @@ useEventListener(controlsContainerRef, "keydown", (event: KeyboardEvent) => {
|
|
|
416
432
|
}
|
|
417
433
|
});
|
|
418
434
|
|
|
435
|
+
watch(
|
|
436
|
+
() => props.carouselDataIds.join("\u0000"),
|
|
437
|
+
() => {
|
|
438
|
+
if (currentActiveIndex.value >= itemCount.value) currentActiveIndex.value = 0;
|
|
439
|
+
nextTick(initialSetup);
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
|
|
419
443
|
useResizeObserver(carouselWrapperRef, async () => {
|
|
420
444
|
initialSetup();
|
|
421
445
|
});
|
|
@@ -429,9 +453,10 @@ onMounted(() => {
|
|
|
429
453
|
@layer components {
|
|
430
454
|
.carousel-flip {
|
|
431
455
|
--_carousel-item-track-gap: var(--carousel-flip-gap, 1rem);
|
|
456
|
+
--_carousel-flip-button-direction: 1;
|
|
432
457
|
|
|
433
458
|
display: grid;
|
|
434
|
-
grid-template-columns: auto 1fr auto;
|
|
459
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
435
460
|
grid-template-rows: 1fr auto;
|
|
436
461
|
grid-template-areas:
|
|
437
462
|
"prev carousel next"
|
|
@@ -439,41 +464,36 @@ onMounted(() => {
|
|
|
439
464
|
gap: var(--carousel-flip-layout-gap, 1rem);
|
|
440
465
|
opacity: 0;
|
|
441
466
|
|
|
442
|
-
|
|
467
|
+
&[data-mounted] {
|
|
443
468
|
opacity: 1;
|
|
444
469
|
}
|
|
445
470
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
width: 1px;
|
|
449
|
-
height: 1px;
|
|
450
|
-
padding: 0;
|
|
451
|
-
margin: -1px;
|
|
452
|
-
overflow: hidden;
|
|
453
|
-
clip: rect(0, 0, 0, 0);
|
|
454
|
-
white-space: nowrap;
|
|
455
|
-
border: 0;
|
|
471
|
+
&:dir(rtl) {
|
|
472
|
+
--_carousel-flip-button-direction: -1;
|
|
456
473
|
}
|
|
457
474
|
|
|
458
|
-
.
|
|
475
|
+
.carousel-flip-track {
|
|
459
476
|
grid-area: carousel;
|
|
460
477
|
display: flex;
|
|
461
478
|
gap: var(--_carousel-item-track-gap);
|
|
462
479
|
overflow-x: hidden;
|
|
463
480
|
position: relative;
|
|
464
481
|
isolation: isolate;
|
|
482
|
+
min-inline-size: 0;
|
|
465
483
|
|
|
466
484
|
max-inline-size: var(--carousel-flip-display-max-width, 80rem);
|
|
467
485
|
margin-inline: auto;
|
|
468
486
|
|
|
469
|
-
|
|
487
|
+
&[data-allow-overflow] {
|
|
470
488
|
overflow-x: initial;
|
|
471
489
|
}
|
|
472
490
|
|
|
473
|
-
.item {
|
|
491
|
+
.carousel-flip-item {
|
|
474
492
|
display: flex;
|
|
475
493
|
flex: 0 0 100%;
|
|
476
494
|
position: relative;
|
|
495
|
+
min-inline-size: 0;
|
|
496
|
+
overflow-wrap: anywhere;
|
|
477
497
|
|
|
478
498
|
margin-inline: auto;
|
|
479
499
|
|
|
@@ -488,7 +508,7 @@ onMounted(() => {
|
|
|
488
508
|
)
|
|
489
509
|
0;
|
|
490
510
|
|
|
491
|
-
|
|
511
|
+
&[data-loaded] {
|
|
492
512
|
@media (prefers-reduced-motion: no-preference) {
|
|
493
513
|
transition: transform v-bind(transitionSpeedStr) ease;
|
|
494
514
|
}
|
|
@@ -496,90 +516,96 @@ onMounted(() => {
|
|
|
496
516
|
}
|
|
497
517
|
}
|
|
498
518
|
|
|
499
|
-
.controls
|
|
519
|
+
.carousel-flip-controls {
|
|
500
520
|
grid-area: controls;
|
|
501
521
|
display: flex;
|
|
502
522
|
align-items: center;
|
|
503
523
|
justify-content: center;
|
|
524
|
+
min-inline-size: 0;
|
|
504
525
|
max-inline-size: var(--carousel-flip-display-max-width, 80rem);
|
|
505
526
|
margin-inline: auto;
|
|
506
527
|
|
|
507
|
-
.markers
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
528
|
+
.carousel-flip-markers {
|
|
529
|
+
display: flex;
|
|
530
|
+
flex-wrap: wrap;
|
|
531
|
+
justify-content: center;
|
|
532
|
+
gap: var(--carousel-flip-marker-gap, 1rem);
|
|
533
|
+
list-style-type: none;
|
|
534
|
+
margin: unset;
|
|
535
|
+
padding: unset;
|
|
536
|
+
|
|
537
|
+
.carousel-flip-marker {
|
|
538
|
+
border: 1px solid transparent;
|
|
539
|
+
outline: 1px solid transparent;
|
|
540
|
+
box-shadow: none;
|
|
541
|
+
cursor: pointer;
|
|
542
|
+
transition: background-color v-bind(transitionSpeedStr) linear;
|
|
543
|
+
|
|
544
|
+
&[data-active] {
|
|
545
|
+
background-color: var(--carousel-flip-marker-active-colour, var(--theme-surface));
|
|
546
|
+
}
|
|
527
547
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
}
|
|
548
|
+
&:focus-visible {
|
|
549
|
+
outline: var(--carousel-flip-focus-outline-width, 0.2rem) solid
|
|
550
|
+
var(--carousel-flip-focus-outline-colour, var(--theme-ring));
|
|
551
|
+
outline-offset: var(--carousel-flip-focus-outline-offset, 0.2rem);
|
|
534
552
|
}
|
|
535
553
|
}
|
|
536
554
|
}
|
|
537
555
|
}
|
|
538
556
|
|
|
539
|
-
.buttons
|
|
557
|
+
.carousel-flip-buttons {
|
|
540
558
|
display: contents;
|
|
541
559
|
}
|
|
542
560
|
|
|
543
|
-
.
|
|
544
|
-
grid-area: prev;
|
|
545
|
-
align-self: center;
|
|
546
|
-
z-index: 1;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
.btn-next {
|
|
550
|
-
grid-area: next;
|
|
551
|
-
align-self: center;
|
|
552
|
-
z-index: 1;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
.btn-action {
|
|
561
|
+
.carousel-flip-button {
|
|
556
562
|
display: flex;
|
|
557
563
|
align-items: center;
|
|
558
564
|
justify-content: center;
|
|
565
|
+
align-self: var(--carousel-flip-button-align, center);
|
|
566
|
+
z-index: 1;
|
|
559
567
|
cursor: pointer;
|
|
560
568
|
height: fit-content;
|
|
569
|
+
min-height: 0;
|
|
561
570
|
|
|
562
|
-
|
|
571
|
+
color: var(--carousel-flip-button-icon-colour, var(--theme-text));
|
|
572
|
+
background-color: var(--carousel-flip-button-background-colour, var(--theme-surface-subtle));
|
|
563
573
|
border: var(--carousel-flip-button-border-width, 0.1rem) solid
|
|
564
|
-
var(--carousel-flip-button-border-colour,
|
|
574
|
+
var(--carousel-flip-button-border-colour, var(--theme-border));
|
|
565
575
|
border-radius: var(--carousel-flip-button-border-radius, 100vw);
|
|
566
576
|
padding: var(--carousel-flip-button-padding, 0.8rem);
|
|
567
577
|
|
|
568
|
-
|
|
578
|
+
&:focus-visible {
|
|
579
|
+
outline: var(--carousel-flip-focus-outline-width, 0.2rem) solid
|
|
580
|
+
var(--carousel-flip-focus-outline-colour, var(--theme-ring));
|
|
581
|
+
outline-offset: var(--carousel-flip-focus-outline-offset, 0.2rem);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.carousel-flip-button-icon {
|
|
569
585
|
width: var(--carousel-flip-button-icon-size, 2.4rem);
|
|
570
586
|
height: var(--carousel-flip-button-icon-size, 2.4rem);
|
|
571
587
|
}
|
|
572
588
|
}
|
|
573
589
|
|
|
590
|
+
.carousel-flip-button-prev {
|
|
591
|
+
grid-area: prev;
|
|
592
|
+
translate: calc(-1 * var(--_carousel-flip-button-direction) * var(--carousel-flip-button-inline-offset, 0px)) 0;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.carousel-flip-button-next {
|
|
596
|
+
grid-area: next;
|
|
597
|
+
translate: calc(var(--_carousel-flip-button-direction) * var(--carousel-flip-button-inline-offset, 0px)) 0;
|
|
598
|
+
}
|
|
599
|
+
|
|
574
600
|
/* ── controls-flanking: buttons flank the controls row ─────── */
|
|
575
601
|
&[data-button-layout="controls-flanking"] {
|
|
576
|
-
.
|
|
602
|
+
.carousel-flip-button-prev {
|
|
577
603
|
grid-area: unset;
|
|
578
604
|
grid-column: 1;
|
|
579
605
|
grid-row: 2;
|
|
580
606
|
}
|
|
581
607
|
|
|
582
|
-
.
|
|
608
|
+
.carousel-flip-button-next {
|
|
583
609
|
grid-area: unset;
|
|
584
610
|
grid-column: 3;
|
|
585
611
|
grid-row: 2;
|
|
@@ -588,23 +614,59 @@ onMounted(() => {
|
|
|
588
614
|
|
|
589
615
|
/* ── controls-grouped-right: markers centred, buttons end ────── */
|
|
590
616
|
&[data-button-layout="controls-grouped-right"] {
|
|
591
|
-
grid-template-columns: 1fr;
|
|
617
|
+
grid-template-columns: minmax(0, 1fr);
|
|
592
618
|
grid-template-areas:
|
|
593
619
|
"carousel"
|
|
594
620
|
"controls";
|
|
595
621
|
|
|
596
|
-
.buttons
|
|
622
|
+
.carousel-flip-buttons {
|
|
597
623
|
display: flex;
|
|
598
624
|
align-items: center;
|
|
599
625
|
gap: var(--carousel-flip-buttons-gap, 1rem);
|
|
600
626
|
grid-area: controls;
|
|
601
627
|
justify-self: end;
|
|
602
628
|
}
|
|
629
|
+
|
|
630
|
+
.carousel-flip-button-prev,
|
|
631
|
+
.carousel-flip-button-next {
|
|
632
|
+
translate: none;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/* ── track-edges: buttons overlay the ends of the visible track ── */
|
|
637
|
+
&[data-button-layout="track-edges"] {
|
|
638
|
+
grid-template-columns: minmax(0, 1fr);
|
|
639
|
+
grid-template-areas:
|
|
640
|
+
"carousel"
|
|
641
|
+
"controls";
|
|
642
|
+
|
|
643
|
+
.carousel-flip-buttons {
|
|
644
|
+
grid-area: carousel;
|
|
645
|
+
display: flex;
|
|
646
|
+
justify-content: space-between;
|
|
647
|
+
inline-size: 100%;
|
|
648
|
+
max-inline-size: var(--carousel-flip-display-max-width, 80rem);
|
|
649
|
+
margin-inline: auto;
|
|
650
|
+
padding-inline: var(
|
|
651
|
+
--carousel-flip-button-edge-inset,
|
|
652
|
+
calc(var(--carousel-flip-edge-preview-width, 4rem) + 0.8rem)
|
|
653
|
+
);
|
|
654
|
+
box-sizing: border-box;
|
|
655
|
+
z-index: 1;
|
|
656
|
+
pointer-events: none;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.carousel-flip-button-prev,
|
|
660
|
+
.carousel-flip-button-next {
|
|
661
|
+
grid-area: unset;
|
|
662
|
+
translate: none;
|
|
663
|
+
pointer-events: auto;
|
|
664
|
+
}
|
|
603
665
|
}
|
|
604
666
|
|
|
605
667
|
/* ── overlay: controls float at the bottom edge of the frame ── */
|
|
606
668
|
&[data-button-layout="overlay"] {
|
|
607
|
-
.controls
|
|
669
|
+
.carousel-flip-controls {
|
|
608
670
|
grid-area: carousel;
|
|
609
671
|
align-self: end;
|
|
610
672
|
z-index: 1;
|
|
@@ -614,3 +676,4 @@ onMounted(() => {
|
|
|
614
676
|
}
|
|
615
677
|
}
|
|
616
678
|
</style>
|
|
679
|
+
|