diff --git a/src/uix/eidos/components/carousel/carousel-context.ts b/src/uix/eidos/components/carousel/carousel-context.ts new file mode 100644 index 000000000..2a9064fb7 --- /dev/null +++ b/src/uix/eidos/components/carousel/carousel-context.ts @@ -0,0 +1,23 @@ +import { getContext, setContext } from 'svelte'; +import type { CarouselColor, CarouselSize, CarouselVariant } from './types'; + +/** + * Eidos-only context: visual concerns shared from the Provider down to + * sub-parts (prev/next triggers consume `size`, indicators consume + * `size` + `color`). + */ +export type CarouselEidosContext = { + size: CarouselSize; + variant: CarouselVariant; + color: CarouselColor; +}; + +const KEY = Symbol('eidos:carousel'); + +export function setCarouselEidosContext(ctx: CarouselEidosContext) { + setContext(KEY, ctx); +} + +export function getCarouselEidosContext(): CarouselEidosContext | undefined { + return getContext(KEY); +} diff --git a/src/uix/eidos/components/carousel/carousel-indicator-group.svelte b/src/uix/eidos/components/carousel/carousel-indicator-group.svelte new file mode 100644 index 000000000..7d6d4fac0 --- /dev/null +++ b/src/uix/eidos/components/carousel/carousel-indicator-group.svelte @@ -0,0 +1,8 @@ + + +{@render children?.()} diff --git a/src/uix/eidos/components/carousel/carousel-indicator.svelte b/src/uix/eidos/components/carousel/carousel-indicator.svelte new file mode 100644 index 000000000..e8ca40b5b --- /dev/null +++ b/src/uix/eidos/components/carousel/carousel-indicator.svelte @@ -0,0 +1,12 @@ + + + + {#snippet children(snippetProps)} + {@render bodyContent?.(snippetProps)} + {/snippet} + diff --git a/src/uix/eidos/components/carousel/carousel-item-group.svelte b/src/uix/eidos/components/carousel/carousel-item-group.svelte new file mode 100644 index 000000000..81e513970 --- /dev/null +++ b/src/uix/eidos/components/carousel/carousel-item-group.svelte @@ -0,0 +1,8 @@ + + +{@render children?.()} diff --git a/src/uix/eidos/components/carousel/carousel-item.svelte b/src/uix/eidos/components/carousel/carousel-item.svelte new file mode 100644 index 000000000..9a8463d34 --- /dev/null +++ b/src/uix/eidos/components/carousel/carousel-item.svelte @@ -0,0 +1,12 @@ + + + + {#snippet children(snippetProps)} + {@render bodyContent?.(snippetProps)} + {/snippet} + diff --git a/src/uix/eidos/components/carousel/carousel-next-trigger.svelte b/src/uix/eidos/components/carousel/carousel-next-trigger.svelte new file mode 100644 index 000000000..fadfa9b51 --- /dev/null +++ b/src/uix/eidos/components/carousel/carousel-next-trigger.svelte @@ -0,0 +1,52 @@ + + +{#snippet defaultIcon()} + +{/snippet} + + + {#snippet child({ props })} + {#if outerChild} + {@render outerChild({ props })} + {:else} + + {/if} + {/snippet} + diff --git a/src/uix/eidos/components/carousel/carousel-prev-trigger.svelte b/src/uix/eidos/components/carousel/carousel-prev-trigger.svelte new file mode 100644 index 000000000..c408e0d60 --- /dev/null +++ b/src/uix/eidos/components/carousel/carousel-prev-trigger.svelte @@ -0,0 +1,57 @@ + + +{#snippet defaultIcon()} + +{/snippet} + + + {#snippet child({ props })} + {#if outerChild} + {@render outerChild({ props })} + {:else} + + {/if} + {/snippet} + diff --git a/src/uix/eidos/components/carousel/carousel-viewport.svelte b/src/uix/eidos/components/carousel/carousel-viewport.svelte new file mode 100644 index 000000000..7194c189e --- /dev/null +++ b/src/uix/eidos/components/carousel/carousel-viewport.svelte @@ -0,0 +1,8 @@ + + +{@render children?.()} diff --git a/src/uix/eidos/components/carousel/carousel.css b/src/uix/eidos/components/carousel/carousel.css new file mode 100644 index 000000000..8b0c99680 --- /dev/null +++ b/src/uix/eidos/components/carousel/carousel.css @@ -0,0 +1,261 @@ +/* + * Carousel recipe. + * + * [data-carousel] → root region + * [data-carousel-viewport] → clipping box (overflow hidden) + * [data-carousel-item-group] → translatable strip + * [data-carousel-item] → single slide + * [data-carousel-prev-trigger] → previous button (Button consumer) + * [data-carousel-next-trigger] → next button (Button consumer) + * [data-carousel-indicator-group] → tablist of indicators + * [data-carousel-indicator] → individual indicator dot + * + * Sizes — xs..xl on the root; cascades to indicator dot size + default + * gap. Trigger size is forwarded to + + + + + + + + {#if tab === 'live'} +
+

Controls

+

+ Soma owns the index, autoplay timer, drag gesture, keyboard nav + and orientation. Eidos adds chrome (variant / size / color) and + renders the prev/next triggers as eidos <Button iconOnly variant="ghost"> via + the canonical Button consumer pattern. +

+ +
+ soma Provider props +
+
+ + + + + + + +
+ +
+ eidos visual props +
+
+ + + +
+ +
+
+ eidos + composition · with bind:value + svelte +
+
{eidosSnippet}
+
+
+ {/if} + + {#if tab === 'api'} +
+

API reference

+

8 parts: Provider, Viewport, ItemGroup, Item, PrevTrigger, NextTrigger, IndicatorGroup, Indicator.

+ +
Provider (Carousel)
+
+ + + + + + + + + + + + + + + + + + + +
PropTypeDefaultDescription
value bindablenumber0Current slide index (0-based).
onValueChange(i: number) => void—Called on every index change.
orientation'horizontal' | 'vertical''horizontal'Scroll axis.
loopbooleanfalseWrap at edges (also wraps autoplay).
dragEnabledbooleantrueAllow pointer drag to scrub.
dragThresholdnumber0.25Release fraction past which next slide commits.
autoplaybooleanfalseEnable timer-driven advance.
autoplayDelaynumber4000Interval in milliseconds.
pauseOnHoverbooleantruePause autoplay while hovered / focused.
disabledbooleanfalseDisable all navigation.
aria-labelstring—Override accessible name (falls back to lang ref).
variant eidosControlVariant: 'surface' | 'outline' | 'ghost''surface'Container chrome.
size eidos'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Cascades to indicator + trigger size.
color eidosColorRole'primary'Indicator + focus accent.
block eidosbooleantrueStretch root inline-size to container.
+
+ +
Viewport · ItemGroup
+

+ Viewport is the overflow: hidden clipping + box. ItemGroup is the translatable strip whose + transform the provider drives. Both are pass-through — + no eidos-specific props. +

+ +
Item
+
+ + + + + + +
PropTypeDefaultDescription
indexnumberrequired0-based slide index. Used for aria-roledescription + indicator wiring.
childrenSnippet<[{`{ index, isActive }`}]>—Slide body. Receives active flag for per-slide effects.
+
+ +
PrevTrigger · NextTrigger
+
+ + + + + + + + + + + + +
PropTypeDefaultDescription
variant eidosControlVariant: 'surface' | 'outline' | 'ghost''ghost'Button variant.
size eidos'xs' | 'sm' | 'md' | 'lg' | 'xl'cascades from CarouselButton size override.
rounded eidosButtonRounded'full'Corner radius.
intent eidosIntent—Evaluative intent.
color eidosColorRole—Hierarchical color (under intent='neutral').
iconOnly eidosbooleantrueSquare aspect, label sr-only.
icon eidosSnippetchevronOverride default arrow glyph.
child somaSnippet—Bypass Button entirely; render custom element.
+
+ +
IndicatorGroup · Indicator
+
+ + + + + + + +
PropTypeDefaultDescription
index (Indicator)numberrequired0-based slide this indicator navigates to.
aria-label (Indicator)string—Override accessible name; falls back to "Go to slide {`{n}`}".
children (Indicator)Snippet<[{`{ index, isActive }`}]>—Custom indicator body (default is a dot).
+
+
+ {/if} + + {#if tab === 'morfo'} +
+

+ morfo + · declarative contract +

+

Source: src/uix/morfo/components/carousel.ts.

+ +
+ + + + + + + + + +
FieldValue
name"{carouselMorfo.name}"
kebab"{carouselMorfo.kebab}"
apg{carouselMorfo.apg}
parts.length{carouselMorfo.parts.length}
events.length{events.length}
+
+ +
Parts
+
+ + + + {#each partsList as part (part.kebab)} + + + + + + + + + + {/each} + +
PartMarkerElementRoleArchetypeStatesOptional
{part.kebab}[{part.marker}]<{part.defaultElement}>{part.role ?? '—'}{part.archetype ?? '—'}{part.states.length ? part.states.join(' | ') : '—'}{part.optional ? 'yes' : 'no'}
+
+ +
Events declaration
+
+ + + + {#each events as action (action.name)} + {@const sem = action.semantic} + {@const intentDecl = 'intent' in sem ? sem.intent : undefined} + {@const intentStr = typeof intentDecl === 'string' ? intentDecl : intentDecl ? `fromProp:${(intentDecl as { fromProp?: string }).fromProp ?? '?'}` : '—'} + + + + + + + + + {/each} + +
namefamilyverbsequenceintenttarget
{action.name}{sem.family}{sem.verb ?? '—'}{sem.sequence ?? 'pre'}{intentStr}{action.target}
+
+
+ {/if} + + {#if tab === 'sema'} +
+

+ sema + · events +

+
+ + + + {#each events as action (action.name)} + {@const intentDecl = 'intent' in action.semantic ? action.semantic.intent : undefined} + {@const effectiveIntent = typeof intentDecl === 'string' ? intentDecl : undefined} + + + + + + + + + {/each} + +
NameFamilyVerbSequenceIntentPlay
{action.name}{action.semantic.family}{action.semantic.verb ?? '—'}{action.semantic.sequence ?? 'pre'}{effectiveIntent ?? '—'} + +
+
+
+ {/if} + + {#if tab === 'recipe'} +
+

Eidos recipe

+

Selectors at src/uix/eidos/components/carousel/carousel.css.

+
+ + + + + + + + + + + + + +
SelectorSourceWhat it paints
[data-carousel]eidosRoot — flex column, gap, font.
[data-carousel][data-variant]eidosSurface / outline / ghost chrome around the rail.
[data-carousel][data-size]eidosIndicator dot diameter + gap.
[data-carousel][data-color]eidosActive-indicator accent + focus ring.
[data-carousel-viewport]morfoClipping box (overflow hidden, inline soma styles).
[data-carousel-item-group][data-dragging]morfoTransform transition off while dragging.
[data-carousel-item]eidosSlide — 100% basis along axis.
[data-carousel-prev-trigger] · [data-carousel-next-trigger]eidosAbsolute-positioned over viewport edges; vertical flips to top/bottom.
[data-carousel-indicator][data-state='active']eidosActive indicator: accent fill + 2.25× inline-size stretch.
+
+
+ {/if} + + {#if tab === 'a11y'} +
+

Accessibility

+

+ Follows the APG carousel pattern. + The root is role="region" with aria-roledescription="carousel" + + an accessible name. Each slide is role="group" with + aria-roledescription="slide". Indicators form a + role="tablist" with role="tab" children. +

+ +
Keyboard
+
+ + + + + + + + + + +
KeyAction
ArrowRight / ArrowDownNext slide.
ArrowLeft / ArrowUpPrevious slide.
HomeFirst slide.
EndLast slide.
TabMove focus through prev / next / indicators / inner slide controls.
pointer dragScrub between slides; commits when past dragThreshold.
+
+ +
ARIA contract
+
+ + + + + + + + + + + + +
PartAttributeValue
providerrole"region"
provideraria-roledescription"carousel"
provideraria-labellocalized "Carousel"
itemrole"group"
itemaria-roledescription"slide"
prev-trigger / next-triggertype / aria-label"button" / "Previous slide" | "Next slide"
indicator-grouprole / aria-orientation"tablist" / orientation
indicatorrole / aria-selected"tab" / "true|false"
+
+
+ {/if} +