@ -52,7 +52,12 @@
// direction. `locale` is forwarded for downstream lang-aware behavior
// (announcer text, future label formatting).
type Direction = 'ltr' | 'rtl';
let dir = $state< Direction > ('ltr');
// `auto` passes no `dir` prop at all, so the component resolves the
// documented chain `prop -> soma.prefs.getDir() -> 'ltr'` and follows the
// shell's direction switch. That is what a real consumer gets, so it is
// the default here; picking ltr/rtl shows the prop overriding prefs.
let dirMode = $state< 'auto' | Direction>('auto');
const dir = $derived(dirMode === 'auto' ? undefined : dirMode);
let locale = $state('en-US');
let size = $state< CarouselSize > ('md');
@ -104,7 +109,11 @@
}
const slides = [
{ tone : 'var(--color-primary-element)' , label : 'Aurora · primary element' , sub : 'slide 1 of 4' } ,
{
tone: 'var(--color-primary-element)',
label: 'Aurora · primary element',
sub: 'slide 1 of 4'
},
{ tone : 'var(--color-affirm-element)' , label : 'Meadow · affirm element' , sub : 'slide 2 of 4' } ,
{ tone : 'var(--color-risk-element)' , label : 'Ember · risk element' , sub : 'slide 3 of 4' } ,
{ tone : 'var(--color-fulfill-element)' , label : 'Tide · fulfill element' , sub : 'slide 4 of 4' }
@ -137,7 +146,7 @@
autoplay & & stopOnMouseEnter & & ' stopOnMouseEnter',
autoplay & & stopOnLastSnap & & ' stopOnLastSnap',
disabled & & ' disabled',
dir !== 'ltr ' && ` dir="${ dir } "`,
dirMode !== 'auto ' && ` dir="${ dir } "`,
locale !== 'en-US' && ` locale="${ locale } "`,
'>',
' { # snippet children ({ api , isPlaying })} ',
@ -176,7 +185,9 @@
slidesToScroll !== 1 && ` slidesToScroll={ $ { slidesToScroll }} `,
align !== 'start' && ` align="${ align } "`,
gap !== 0 && ` gap="${ gap } rem"`,
orientation === 'vertical' && verticalBlockSize !== 16 && ` verticalBlockSize="${ verticalBlockSize } rem"`,
orientation === 'vertical' & &
verticalBlockSize !== 16 & &
` verticalBlockSize="${ verticalBlockSize } rem"`,
size !== 'md' && ` size="${ size } "`,
variant !== 'surface' && ` variant="${ variant } "`,
color !== 'primary' && ` color="${ color } "`,
@ -192,7 +203,7 @@
autoplay & & stopOnMouseEnter & & ' stopOnMouseEnter',
autoplay & & stopOnLastSnap & & ' stopOnLastSnap',
disabled & & ' disabled',
dir && ` dir="${ dir } "`,
dirMode !== 'auto' && ` dir="${ dir } "`,
!showIndicators && ' showIndicators={ false } ',
!showControls && ' showControls={ false } ',
'>',
@ -222,11 +233,10 @@
< div data-uix-eyebrow > Media · Carousel< / div >
< h1 data-uix-page-title > Carousel< / h1 >
< p data-uix-page-lede >
Horizontally or vertically scrollable rail of slides with keyboard,
button, indicator, drag and autoplay navigation. One slide per page.
Pattern follows the WAI-ARIA carousel
pattern with < code > role="region"< / code > + < code > aria-roledescription="carousel"< / code > ;
indicators map 1:1 with slides as a < code > tablist< / code > .
Horizontally or vertically scrollable rail of slides with keyboard, button, indicator, drag
and autoplay navigation. One slide per page. Pattern follows the WAI-ARIA carousel pattern
with < code > role="region"< / code > + < code > aria-roledescription="carousel"< / code > ; indicators map
1:1 with slides as a < code > tablist< / code > .
< / p >
< div data-uix-page-meta >
< span data-uix-meta-pill >
@ -243,13 +253,18 @@
< a href = { carouselMorfo . apg } target="_blank" rel = "noreferrer" > carousel ↗</ a >
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > index</ span > { index }
< span data-uix-meta-key > index< / span >
{ index }
< / span >
< / div >
< / header >
< div data-uix-stage >
< div data-uix-stage-area bind:this = { stageRef } style="padding: var ( --uix-space-3 ); display: flex ; justify-content: center ;" >
< div
data-uix-stage-area
bind:this={ stageRef }
style="padding: var(--uix-space-3); display: flex; justify-content: center;"
>
< div style = "inline-size: min(40rem, 100%);" >
< Carousel
bind:index
@ -280,7 +295,7 @@
{ showControls }
>
{ # snippet children ( snippet : CarouselProviderSnippetProps )}
{( api = snippet . api , isPlaying = snippet . isPlaying , '' )}
{( ( api = snippet . api ) , ( isPlaying = snippet . isPlaying ) , '' )}
< Carousel.Viewport >
< Carousel.ItemGroup >
{ #each slides as slide , i ( slide . label )}
@ -299,8 +314,14 @@
user-select: none;
"
>
< strong style = "font-size: var(--font-size-lg); margin-block-end: var(--space-2);" > { slide . label } </ strong >
< span style = "font-size: var(--font-size-sm); color: var(--color-content-secondary);" > { slide . sub } </ span >
< strong
style="font-size: var(--font-size-lg); margin-block-end: var(--space-2);"
>{ slide . label } < /strong
>
< span
style="font-size: var(--font-size-sm); color: var(--color-content-secondary);"
>{ slide . sub } < /span
>
< / div >
< / Carousel.Item >
{ /each }
@ -329,12 +350,17 @@
< span > arrow keys · drag · click indicators< / span >
{ : else }
{ #each trace . slice ( 0 , 3 ) as entry }
< span >< span data-uix-stage-trace-event > { entry . event } </ span > · { entry . family }{ entry . intent ? ' · ' + entry . intent : '' } </ span >
< span
>< span data-uix-stage-trace-event > { entry . event } </ span > · { entry . family }{ entry . intent
? ' · ' + entry.intent
: ''}< /span
>
< span style = "color: var(--uix-text-faint)" > { fmtTime ( entry . at )} </ span >
{ /each }
{ /if }
< span style = "margin-inline-start: auto;" >
< span data-uix-stage-trace-key > index</ span > { index }
< span data-uix-stage-trace-key > index< / span >
{ index }
< / span >
< / div >
< / div >
@ -352,7 +378,9 @@
< span data-uix-layer-badge = "sema" > sema< / span >
< span data-uix-tab-count > { events . length } </ span >
< / button >
< button data-uix-tab data-active = { tab === 'recipe' } onclick= {() => ( tab = 'recipe' )} > Recipe</ button >
< button data-uix-tab data-active = { tab === 'recipe' } onclick= {() => ( tab = 'recipe' )}
>Recipe< /button
>
< button data-uix-tab data-active = { tab === 'a11y' } onclick= {() => ( tab = 'a11y' )} > A11y</ button >
< / div >
@ -360,10 +388,10 @@
< section data-uix-section >
< h2 data-uix-section-title > Controls< / h2 >
< p data-uix-section-desc >
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 < code > < Button iconOnly variant="ghost"> < / code > via
the canonical Button consumer pattern.
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 < code
>< Button iconOnly variant="ghost"> < /code
> via the canonical Button consumer pattern.
< / p >
< div data-uix-subsection-head >
@ -374,7 +402,11 @@
< span data-uix-control-label > orientation< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'horizontal' , 'vertical' ] as o ( o )}
< button data-uix-chip data-active = { orientation === o } onclick= {() => ( orientation = o as 'horizontal' | 'vertical' )} > { o } </ button >
< button
data-uix-chip
data-active={ orientation === o }
onclick={() => ( orientation = o as 'horizontal' | 'vertical' )} >{ o } < /button
>
{ /each }
< / span >
< / label >
@ -382,7 +414,11 @@
< span data-uix-control-label > slidesPerView< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 1 , 2 , 3 , 4 ] as n ( n )}
< button data-uix-chip data-active = { slidesPerView === n } onclick= {() => ( slidesPerView = n )} > { n } </ button >
< button
data-uix-chip
data-active={ slidesPerView === n }
onclick={() => ( slidesPerView = n )} >{ n } < /button
>
{ /each }
< / span >
< / label >
@ -390,7 +426,11 @@
< span data-uix-control-label > slidesToScroll< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 1 , 2 , 3 ] as n ( n )}
< button data-uix-chip data-active = { slidesToScroll === n } onclick= {() => ( slidesToScroll = n )} > { n } </ button >
< button
data-uix-chip
data-active={ slidesToScroll === n }
onclick={() => ( slidesToScroll = n )} >{ n } < /button
>
{ /each }
< / span >
< / label >
@ -398,7 +438,11 @@
< span data-uix-control-label > align< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'start' , 'center' , 'end' ] as a ( a )}
< button data-uix-chip data-active = { align === a } onclick= {() => ( align = a as CarouselAlign )} > { a } </ button >
< button
data-uix-chip
data-active={ align === a }
onclick={() => ( align = a as CarouselAlign )} >{ a } < /button
>
{ /each }
< / span >
< / label >
@ -409,7 +453,14 @@
< / label >
< label data-uix-control >
< span data-uix-control-label > verticalBlockSize (rem)< / span >
< input type = "range" min = "8" max = "32" step = "1" bind:value = { verticalBlockSize } disabled= { orientation !== 'vertical' } />
< input
type="range"
min="8"
max="32"
step="1"
bind:value={ verticalBlockSize }
disabled={ orientation !== 'vertical' }
/>
< span data-uix-control-label-aux > { verticalBlockSize } </ span >
< / label >
< label data-uix-control >
@ -441,7 +492,14 @@
< / label >
< label data-uix-control >
< span data-uix-control-label > dragThreshold (0– 1)< / span >
< input type = "range" min = "0.05" max = "0.6" step = "0.05" bind:value = { dragThreshold } disabled= { ! dragEnabled } />
< input
type="range"
min="0.05"
max="0.6"
step="0.05"
bind:value={ dragThreshold }
disabled={ ! dragEnabled }
/>
< span data-uix-control-label-aux > { dragThreshold . toFixed ( 2 )} </ span >
< / label >
< / div >
@ -459,7 +517,14 @@
< / label >
< label data-uix-control >
< span data-uix-control-label > autoplayDelay (ms)< / span >
< input type = "range" min = "1000" max = "8000" step = "500" bind:value = { autoplayDelay } disabled= { ! autoplay } />
< input
type="range"
min="1000"
max="8000"
step="500"
bind:value={ autoplayDelay }
disabled={ ! autoplay }
/>
< span data-uix-control-label-aux > { autoplayDelay } </ span >
< / label >
< label data-uix-control >
@ -508,7 +573,9 @@
< div data-uix-subsection-head >
< span data-uix-layer-badge = "soma" > soma< / span > imperative API
< span style = "margin-inline-start: auto; color: var(--color-content-secondary); font-size: var(--font-size-xs);" >
< span
style="margin-inline-start: auto; color: var(--color-content-secondary); font-size: var(--font-size-xs);"
>
exposed via the < code > api< / code > snippet prop — drive the carousel from external buttons
< / span >
< / div >
@ -516,9 +583,15 @@
< button data-uix-chip onclick = {() => api ? . scrollPrev ()} > scrollPrev()</button >
< button data-uix-chip onclick = {() => api ? . scrollNext ()} > scrollNext()</button >
< button data-uix-chip onclick = {() => api ? . scrollTo ( 0 )} > scrollTo(0)</button >
< button data-uix-chip onclick = {() => api ? . scrollTo ( slides . length - 1 )} > scrollTo(last)</button >
< button data-uix-chip onclick = {() => api ? . play ()} disabled= { ! autoplay || isPlaying } > play()</ button >
< button data-uix-chip onclick = {() => api ? . pause ()} disabled= { ! autoplay || ! isPlaying } > pause()</ button >
< button data-uix-chip onclick = {() => api ? . scrollTo ( slides . length - 1 )}
>scrollTo(last)< /button
>
< button data-uix-chip onclick = {() => api ? . play ()} disabled= { ! autoplay || isPlaying }
>play()< /button
>
< button data-uix-chip onclick = {() => api ? . pause ()} disabled= { ! autoplay || ! isPlaying }
>pause()< /button
>
< button data-uix-chip onclick = {() => api ? . reset ()} disabled= { ! autoplay } > reset()</ button >
< span data-uix-control-label-aux > isPlaying = { isPlaying } </ span >
< / div >
@ -536,10 +609,17 @@
< / select >
< / label >
< label data-uix-control >
< span data-uix-control-label > direction< / span >
< span data-uix-control-label
>direction < span data-uix-control-hint > auto = no prop, follows uix.prefs.direction< /span
>< /span
>
< span data-uix-chips role = "radiogroup" >
{ #each [ 'ltr' , 'rtl' ] as value ( value )}
< button data-uix-chip data-active = { dir === value } onclick= {() => ( dir = value as Direction )} > { value } </ button >
{ #each [ 'auto' , 'ltr' , 'rtl' ] as value ( value )}
< button
data-uix-chip
data-active={ dirMode === value }
onclick={() => ( dirMode = value as typeof dirMode )} >{ value } < /button
>
{ /each }
< / span >
< / label >
@ -553,7 +633,11 @@
< span data-uix-control-label > variant< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'surface' , 'outline' , 'ghost' ] as v ( v )}
< button data-uix-chip data-active = { variant === v } onclick= {() => ( variant = v as CarouselVariant )} > { v } </ button >
< button
data-uix-chip
data-active={ variant === v }
onclick={() => ( variant = v as CarouselVariant )} >{ v } < /button
>
{ /each }
< / span >
< / label >
@ -561,7 +645,11 @@
< span data-uix-control-label > size< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'xs' , 'sm' , 'md' , 'lg' , 'xl' ] as s ( s )}
< button data-uix-chip data-active = { size === s } onclick= {() => ( size = s as CarouselSize )} > { s } </ button >
< button
data-uix-chip
data-active={ size === s }
onclick={() => ( size = s as CarouselSize )} >{ s } < /button
>
{ /each }
< / span >
< / label >
@ -569,7 +657,11 @@
< span data-uix-control-label > color< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'primary' , 'secondary' , 'neutral' , 'affirm' , 'fulfill' , 'risk' , 'threat' , 'loss' ] as c ( c )}
< button data-uix-chip data-active = { color === c } onclick= {() => ( color = c as CarouselColor )} > { c } </ button >
< button
data-uix-chip
data-active={ color === c }
onclick={() => ( color = c as CarouselColor )} >{ c } < /button
>
{ /each }
< / span >
< / label >
@ -612,48 +704,153 @@
{ #if tab === 'api' }
< section data-uix-section >
< h2 data-uix-section-title > API reference< / h2 >
< p data-uix-section-desc > 8 parts: Provider, Viewport, ItemGroup, Item, PrevTrigger, NextTrigger, IndicatorGroup, Indicator.< / p >
< p data-uix-section-desc >
8 parts: Provider, Viewport, ItemGroup, Item, PrevTrigger, NextTrigger, IndicatorGroup,
Indicator.
< / p >
< div data-uix-subsection-head > Provider (Carousel)< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Prop< / th > < th > Type< / th > < th > Default< / th > < th > Description< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > index < span data-uix-tag data-kind = "bindable" > bindable< / span > < / td > < td class = "type" > number< / td > < td class = "default" > 0< / td > < td > Current slide index (0-based). Pass without bind: for the initial index.< / td > < / tr >
< tr > < td class = "name" > onIndexChange< / td > < td class = "type" > (i: number) => void< / td > < td class = "default empty" > —< / td > < td > Called on every index change.< / td > < / tr >
< tr > < td class = "name" > orientation< / td > < td class = "type" > 'horizontal' | 'vertical'< / td > < td class = "default" > 'horizontal'< / td > < td > Scroll axis.< / td > < / tr >
< tr > < td class = "name" > slidesPerView< / td > < td class = "type" > number< / td > < td class = "default" > 1< / td > < td > Slides visible at once (each Item basis = 100% / slidesPerView).< / td > < / tr >
< tr > < td class = "name" > slidesToScroll< / td > < td class = "type" > number< / td > < td class = "default" > 1< / td > < td > Slides advanced per next() / prev().< / td > < / tr >
< tr > < td class = "name" > align< / td > < td class = "type" > 'start' | 'center' | 'end'< / td > < td class = "default" > 'start'< / td > < td > Alignment of active slide(s) within viewport.< / td > < / tr >
< tr > < td class = "name" > gap< / td > < td class = "type" > string (CSS length)< / td > < td class = "default" > '0px'< / td > < td > Inline-axis gap between slides.< / td > < / tr >
< tr > < td class = "name" > loop< / td > < td class = "type" > boolean< / td > < td class = "default" > false< / td > < td > Wrap at edges (also wraps autoplay).< / td > < / tr >
< tr > < td class = "name" > dragEnabled< / td > < td class = "type" > boolean< / td > < td class = "default" > true< / td > < td > Allow pointer drag to scrub.< / td > < / tr >
< tr > < td class = "name" > dragThreshold< / td > < td class = "type" > number (0– 1)< / td > < td class = "default" > 0.25< / td > < td > Release fraction past which next slide commits.< / td > < / tr >
< tr > < td class = "name" > autoplay< / td > < td class = "type" > boolean< / td > < td class = "default" > false< / td > < td > Enable timer-driven advance.< / td > < / tr >
< tr > < td class = "name" > autoplayDelay< / td > < td class = "type" > number (ms)< / td > < td class = "default" > 4000< / td > < td > Interval in milliseconds.< / td > < / tr >
< tr > < td class = "name" > playOnInit< / td > < td class = "type" > boolean< / td > < td class = "default" > true< / td > < td > Start the autoplay timer on mount.< / td > < / tr >
< tr > < td class = "name" > pauseOnHover< / td > < td class = "type" > boolean< / td > < td class = "default" > true< / td > < td > Pause autoplay while hovered.< / td > < / tr >
< tr > < td class = "name" > stopOnInteraction< / td > < td class = "type" > boolean< / td > < td class = "default" > false< / td > < td > Latch-stop after user clicks prev/next/indicator or drags.< / td > < / tr >
< tr > < td class = "name" > stopOnFocusIn< / td > < td class = "type" > boolean< / td > < td class = "default" > true< / td > < td > Pause while focus is inside root.< / td > < / tr >
< tr > < td class = "name" > stopOnMouseEnter< / td > < td class = "type" > boolean< / td > < td class = "default" > false< / td > < td > Pause while cursor over root (Embla parity).< / td > < / tr >
< tr > < td class = "name" > stopOnLastSnap< / td > < td class = "type" > boolean< / td > < td class = "default" > false< / td > < td > Latch-stop when last snap reached (loop=false only).< / td > < / tr >
< tr > < td class = "name" > disabled< / td > < td class = "type" > boolean< / td > < td class = "default" > false< / td > < td > Disable all navigation.< / td > < / tr >
< tr > < td class = "name" > aria-label< / td > < td class = "type" > string< / td > < td class = "default empty" > —< / td > < td > Override accessible name (falls back to lang ref).< / td > < / tr >
< tr > < td class = "name" > variant < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > ControlVariant: 'surface' | 'outline' | 'ghost'< / td > < td class = "default" > 'surface'< / td > < td > Container chrome.< / td > < / tr >
< tr > < td class = "name" > size < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > 'xs' | 'sm' | 'md' | 'lg' | 'xl'< / td > < td class = "default" > 'md'< / td > < td > Cascades to indicator + trigger size.< / td > < / tr >
< tr > < td class = "name" > color < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > ColorRole< / td > < td class = "default" > 'primary'< / td > < td > Indicator + focus accent.< / td > < / tr >
< tr > < td class = "name" > block < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > boolean< / td > < td class = "default" > true< / td > < td > Stretch root inline-size to container.< / td > < / tr >
< tr > < td class = "name" > verticalBlockSize < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > string (CSS length)< / td > < td class = "default" > '16rem'< / td > < td > Viewport block-size in vertical orientation.< / td > < / tr >
< tr
>< td class = "name" > index < span data-uix-tag data-kind = "bindable" > bindable< / span > < /td
>< td class = "type" > number< / td > < td class = "default" > 0< / td > < td
>Current slide index (0-based). Pass without bind: for the initial index.< /td
>< /tr
>
< tr
>< td class = "name" > onIndexChange< / td > < td class = "type" > (i: number) => void< / td > < td
class="default empty">—< /td
>< td > Called on every index change.< / td > < /tr
>
< tr
>< td class = "name" > orientation< / td > < td class = "type" > 'horizontal' | 'vertical'< / td > < td
class="default">'horizontal'< /td
>< td > Scroll axis.< / td > < /tr
>
< tr
>< td class = "name" > slidesPerView< / td > < td class = "type" > number< / td > < td class = "default"
>1< /td
>< td > Slides visible at once (each Item basis = 100% / slidesPerView).< / td > < /tr
>
< tr
>< td class = "name" > slidesToScroll< / td > < td class = "type" > number< / td > < td class = "default"
>1< /td
>< td > Slides advanced per next() / prev().< / td > < /tr
>
< tr
>< td class = "name" > align< / td > < td class = "type" > 'start' | 'center' | 'end'< / td > < td
class="default">'start'< /td
>< td > Alignment of active slide(s) within viewport.< / td > < /tr
>
< tr
>< td class = "name" > gap< / td > < td class = "type" > string (CSS length)< / td > < td class = "default"
>'0px'< /td
>< td > Inline-axis gap between slides.< / td > < /tr
>
< tr
>< td class = "name" > loop< / td > < td class = "type" > boolean< / td > < td class = "default" > false< /td
>< td > Wrap at edges (also wraps autoplay).< / td > < /tr
>
< tr
>< td class = "name" > dragEnabled< / td > < td class = "type" > boolean< / td > < td class = "default"
>true< /td
>< td > Allow pointer drag to scrub.< / td > < /tr
>
< tr
>< td class = "name" > dragThreshold< / td > < td class = "type" > number (0– 1)< / td > < td
class="default">0.25< /td
>< td > Release fraction past which next slide commits.< / td > < /tr
>
< tr
>< td class = "name" > autoplay< / td > < td class = "type" > boolean< / td > < td class = "default"
>false< /td
>< td > Enable timer-driven advance.< / td > < /tr
>
< tr
>< td class = "name" > autoplayDelay< / td > < td class = "type" > number (ms)< / td > < td
class="default">4000< /td
>< td > Interval in milliseconds.< / td > < /tr
>
< tr
>< td class = "name" > playOnInit< / td > < td class = "type" > boolean< / td > < td class = "default"
>true< /td
>< td > Start the autoplay timer on mount.< / td > < /tr
>
< tr
>< td class = "name" > pauseOnHover< / td > < td class = "type" > boolean< / td > < td class = "default"
>true< /td
>< td > Pause autoplay while hovered.< / td > < /tr
>
< tr
>< td class = "name" > stopOnInteraction< / td > < td class = "type" > boolean< / td > < td
class="default">false< /td
>< td > Latch-stop after user clicks prev/next/indicator or drags.< / td > < /tr
>
< tr
>< td class = "name" > stopOnFocusIn< / td > < td class = "type" > boolean< / td > < td class = "default"
>true< /td
>< td > Pause while focus is inside root.< / td > < /tr
>
< tr
>< td class = "name" > stopOnMouseEnter< / td > < td class = "type" > boolean< / td > < td
class="default">false< /td
>< td > Pause while cursor over root (Embla parity).< / td > < /tr
>
< tr
>< td class = "name" > stopOnLastSnap< / td > < td class = "type" > boolean< / td > < td class = "default"
>false< /td
>< td > Latch-stop when last snap reached (loop=false only).< / td > < /tr
>
< tr
>< td class = "name" > disabled< / td > < td class = "type" > boolean< / td > < td class = "default"
>false< /td
>< td > Disable all navigation.< / td > < /tr
>
< tr
>< td class = "name" > aria-label< / td > < td class = "type" > string< / td > < td class = "default empty"
>—< /td
>< td > Override accessible name (falls back to lang ref).< / td > < /tr
>
< tr
>< td class = "name" > variant < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">ControlVariant: 'surface' | 'outline' | 'ghost'< /td
>< td class = "default" > 'surface'< / td > < td > Container chrome.< / td > < /tr
>
< tr
>< td class = "name" > size < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">'xs' | 'sm' | 'md' | 'lg' | 'xl'< /td
>< td class = "default" > 'md'< / td > < td > Cascades to indicator + trigger size.< / td > < /tr
>
< tr
>< td class = "name" > color < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">ColorRole< /td
>< td class = "default" > 'primary'< / td > < td > Indicator + focus accent.< / td > < /tr
>
< tr
>< td class = "name" > block < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">boolean< /td
>< td class = "default" > true< / td > < td > Stretch root inline-size to container.< / td > < /tr
>
< tr
>< td class = "name"
>verticalBlockSize < span data-uix-tag data-kind = "eidos" > eidos< / span > < /td
>< td class = "type" > string (CSS length)< / td > < td class = "default" > '16rem'< / td > < td
>Viewport block-size in vertical orientation.< /td
>< /tr
>
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Viewport · ItemGroup< / div >
< p data-uix-section-desc style = "margin-top: 0.25rem;" >
< code > Viewport< / code > is the < code > overflow: hidden< / code > clipping
box. < code > ItemGroup< / code > is the translatable strip whose
< code > transform< / code > the provider drives. Both are pass-through —
no eidos-specific props.
< code > Viewport< / code > is the < code > overflow: hidden< / code > clipping box.
< code > ItemGroup< / code >
is the translatable strip whose
< code > transform< / code > the provider drives. Both are pass-through — no eidos-specific props.
< / p >
< div data-uix-subsection-head > Item< / div >
@ -661,8 +858,20 @@
< table data-uix-table >
< thead > < tr > < th > Prop< / th > < th > Type< / th > < th > Default< / th > < th > Description< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > index< / td > < td class = "type" > number< / td > < td class = "default empty" > required< / td > < td > 0-based slide index. Used for < code > aria-roledescription< / code > + indicator wiring.< / td > < / tr >
< tr >< td class = "name" > children</ td >< td class = "type" > Snippet< [{ `{ index, isActive }` } ]> </ td >< td class = "default empty" > —</ td >< td > Slide body. Receives active flag for per-slide effects.</ td ></ tr >
< tr
>< td class = "name" > index< / td > < td class = "type" > number< / td > < td class = "default empty"
>required< /td
>< td
>0-based slide index. Used for < code > aria-roledescription< / code > + indicator wiring.< /td
>< /tr
>
< tr
>< td class = "name" > children< / td > < td class = "type"
>Snippet< [{ `{ index, isActive }` } ]> < /td
>< td class = "default empty" > —< / td > < td
>Slide body. Receives active flag for per-slide effects.< /td
>< /tr
>
< / tbody >
< / table >
< / div >
@ -672,14 +881,49 @@
< table data-uix-table >
< thead > < tr > < th > Prop< / th > < th > Type< / th > < th > Default< / th > < th > Description< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > variant < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > ControlVariant: 'surface' | 'outline' | 'ghost'< / td > < td class = "default" > 'ghost'< / td > < td > Button variant.< / td > < / tr >
< tr > < td class = "name" > size < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > 'xs' | 'sm' | 'md' | 'lg' | 'xl'< / td > < td class = "default" > cascades from Carousel< / td > < td > Button size override.< / td > < / tr >
< tr > < td class = "name" > rounded < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > ButtonRounded< / td > < td class = "default" > 'full'< / td > < td > Corner radius.< / td > < / tr >
< tr > < td class = "name" > intent < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > Intent< / td > < td class = "default empty" > —< / td > < td > Evaluative intent.< / td > < / tr >
< tr > < td class = "name" > color < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > ColorRole< / td > < td class = "default empty" > —< / td > < td > Hierarchical color (under intent='neutral').< / td > < / tr >
< tr > < td class = "name" > iconOnly < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > boolean< / td > < td class = "default" > true< / td > < td > Square aspect, label sr-only.< / td > < / tr >
< tr > < td class = "name" > icon < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td class = "type" > Snippet< / td > < td class = "default" > chevron< / td > < td > Override default arrow glyph.< / td > < / tr >
< tr > < td class = "name" > child < span data-uix-tag data-kind = "soma" > soma< / span > < / td > < td class = "type" > Snippet< / td > < td class = "default empty" > —< / td > < td > Bypass Button entirely; render custom element.< / td > < / tr >
< tr
>< td class = "name" > variant < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">ControlVariant: 'surface' | 'outline' | 'ghost'< /td
>< td class = "default" > 'ghost'< / td > < td > Button variant.< / td > < /tr
>
< tr
>< td class = "name" > size < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">'xs' | 'sm' | 'md' | 'lg' | 'xl'< /td
>< td class = "default" > cascades from Carousel< / td > < td > Button size override.< / td > < /tr
>
< tr
>< td class = "name" > rounded < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">ButtonRounded< /td
>< td class = "default" > 'full'< / td > < td > Corner radius.< / td > < /tr
>
< tr
>< td class = "name" > intent < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">Intent< /td
>< td class = "default empty" > —< / td > < td > Evaluative intent.< / td > < /tr
>
< tr
>< td class = "name" > color < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">ColorRole< /td
>< td class = "default empty" > —< / td > < td > Hierarchical color (under intent='neutral').< /td
>< /tr
>
< tr
>< td class = "name" > iconOnly < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">boolean< /td
>< td class = "default" > true< / td > < td > Square aspect, label sr-only.< / td > < /tr
>
< tr
>< td class = "name" > icon < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td
class="type">Snippet< /td
>< td class = "default" > chevron< / td > < td > Override default arrow glyph.< / td > < /tr
>
< tr
>< td class = "name" > child < span data-uix-tag data-kind = "soma" > soma< / span > < / td > < td
class="type">Snippet< /td
>< td class = "default empty" > —< / td > < td
>Bypass Button entirely; render custom element.< /td
>< /tr
>
< / tbody >
< / table >
< / div >
@ -689,9 +933,22 @@
< table data-uix-table >
< thead > < tr > < th > Prop< / th > < th > Type< / th > < th > Default< / th > < th > Description< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > index (Indicator)< / td > < td class = "type" > number< / td > < td class = "default empty" > required< / td > < td > 0-based slide this indicator navigates to.< / td > < / tr >
< tr >< td class = "name" > aria-label (Indicator)</ td >< td class = "type" > string</ td >< td class = "default empty" > —</ td >< td > Override accessible name; falls back to "Go to slide { `{n}` } ".</ td ></ tr >
< tr >< td class = "name" > children (Indicator)</ td >< td class = "type" > Snippet< [{ `{ index, isActive }` } ]> </ td >< td class = "default empty" > —</ td >< td > Custom indicator body (default is a dot).</ td ></ tr >
< tr
>< td class = "name" > index (Indicator)< / td > < td class = "type" > number< / td > < td
class="default empty">required< /td
>< td > 0-based slide this indicator navigates to.< / td > < /tr
>
< tr
>< td class = "name" > aria-label (Indicator)< / td > < td class = "type" > string< / td > < td
class="default empty">—< /td
>< td > Override accessible name; falls back to "Go to slide { `{n}` } ".</ td > < /tr
>
< tr
>< td class = "name" > children (Indicator)< / td > < td class = "type"
>Snippet< [{ `{ index, isActive }` } ]> < /td
>< td class = "default empty" > —< / td > < td > Custom indicator body (default is a dot).< /td
>< /tr
>
< / tbody >
< / table >
< / div >
@ -712,8 +969,17 @@
< tbody >
< tr >< td class = "name" > name</ td >< td class = "type" > "{ carouselMorfo . name } "</ td ></ tr >
< tr >< td class = "name" > kebab</ td >< td class = "type" > "{ carouselMorfo . kebab } "</ td ></ tr >
< tr >< td class = "name" > apg</ td >< td >< a href = { carouselMorfo . apg } target="_blank" rel = "noreferrer" > { carouselMorfo . apg } </ a ></ td ></ tr >
< tr >< td class = "name" > parts.length</ td >< td class = "default" > { carouselMorfo . parts . length } </ td ></ tr >
< tr
>< td class = "name" > apg< / td > < td
>< a href = { carouselMorfo . apg } target="_blank" rel = "noreferrer" > { carouselMorfo . apg } < /a
>< /td
>< /tr
>
< tr
>< td class = "name" > parts.length< / td > < td class = "default"
>{ carouselMorfo . parts . length } < /td
>< /tr
>
< tr >< td class = "name" > events.length</ td >< td class = "default" > { events . length } </ td ></ tr >
< / tbody >
< / table >
@ -722,7 +988,13 @@
< div data-uix-subsection-head > Parts< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Part< / th > < th > Marker< / th > < th > Element< / th > < th > Role< / th > < th > Archetype< / th > < th > States< / th > < th > Optional< / th > < / tr > < / thead >
< thead
>< tr
>< th > Part< / th > < th > Marker< / th > < th > Element< / th > < th > Role< / th > < th > Archetype< / th > < th
>States< /th
>< th > Optional< / th > < /tr
>< /thead
>
< tbody >
{ #each partsList as part ( part . kebab )}
< tr >
@ -742,12 +1014,23 @@
< div data-uix-subsection-head > Events declaration< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > name< / th > < th > family< / th > < th > verb< / th > < th > sequence< / th > < th > intent< / th > < th > target< / th > < / tr > < / thead >
< thead
>< tr
>< th > name< / th > < th > family< / th > < th > verb< / th > < th > sequence< / th > < th > intent< / th > < th
>target< /th
>< /tr
>< /thead
>
< tbody >
{ #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 ?? '?' } ` : '—' }
{ @const intentStr =
typeof intentDecl === 'string'
? intentDecl
: intentDecl
? `fromProp:${( intentDecl as { fromProp? : string }). fromProp ?? '?' } `
: '—'}
< tr >
< td class = "name" > { action . name } </ td >
< td class = "type" > { sem . family } </ td >
@ -771,7 +1054,12 @@
< / h2 >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Name< / th > < th > Family< / th > < th > Verb< / th > < th > Sequence< / th > < th > Intent< / th > < th > Play< / th > < / tr > < / thead >
< thead
>< tr
>< th > Name< / th > < th > Family< / th > < th > Verb< / th > < th > Sequence< / th > < th > Intent< / th > < th > Play< /th
>< /tr
>< /thead
>
< tbody >
{ #each events as action ( action . name )}
{ @const intentDecl = 'intent' in action . semantic ? action.semantic.intent : undefined }
@ -786,7 +1074,8 @@
< button
data-uix-play
onclick={() => {
const target = (stageRef?.querySelector('[data-carousel]') ?? stageRef) as HTMLElement | null;
const target = (stageRef?.querySelector('[data-carousel]') ??
stageRef) as HTMLElement | null;
if (!target) return;
void uix.events?.emit({
name: action.name,
@ -794,8 +1083,8 @@
target,
...(effectiveIntent ? { intent : effectiveIntent } : {} )
});
}}
>▶ play< / button >
}}>▶ play< /button
>
< / td >
< / tr >
{ /each }
@ -808,20 +1097,58 @@
{ #if tab === 'recipe' }
< section data-uix-section >
< h2 data-uix-section-title > Eidos recipe< / h2 >
< p data-uix-section-desc > Selectors at < code > src/uix/eidos/components/carousel/carousel.css< / code > .< / p >
< p data-uix-section-desc >
Selectors at < code > src/uix/eidos/components/carousel/carousel.css< / code > .
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Selector< / th > < th > Source< / th > < th > What it paints< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > [data-carousel]< / td > < td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > Root — flex column, gap, font.< / td > < / tr >
< tr > < td class = "name" > [data-carousel][data-variant]< / td > < td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > Surface / outline / ghost chrome around the rail.< / td > < / tr >
< tr > < td class = "name" > [data-carousel][data-size]< / td > < td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > Indicator dot diameter + gap.< / td > < / tr >
< tr > < td class = "name" > [data-carousel][data-color]< / td > < td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > Active-indicator accent + focus ring.< / td > < / tr >
< tr > < td class = "name" > [data-carousel-viewport]< / td > < td > < span data-uix-tag data-kind = "soma" > morfo< / span > < / td > < td > Clipping box (overflow hidden, inline soma styles).< / td > < / tr >
< tr > < td class = "name" > [data-carousel-item-group][data-dragging]< / td > < td > < span data-uix-tag data-kind = "soma" > morfo< / span > < / td > < td > Transform transition off while dragging.< / td > < / tr >
< tr > < td class = "name" > [data-carousel-item]< / td > < td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > Slide — 100% basis along axis.< / td > < / tr >
< tr > < td class = "name" > [data-carousel-prev-trigger] · [data-carousel-next-trigger]< / td > < td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > Absolute-positioned over viewport edges; vertical flips to top/bottom.< / td > < / tr >
< tr > < td class = "name" > [data-carousel-indicator][data-state='active']< / td > < td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > Active indicator: accent fill + 2.25× inline-size stretch.< / td > < / tr >
< tr
>< td class = "name" > [data-carousel]< / td > < td
>< span data-uix-tag data-kind = "eidos" > eidos< / span > < /td
>< td > Root — flex column, gap, font.< / td > < /tr
>
< tr
>< td class = "name" > [data-carousel][data-variant]< / td > < td
>< span data-uix-tag data-kind = "eidos" > eidos< / span > < /td
>< td > Surface / outline / ghost chrome around the rail.< / td > < /tr
>
< tr
>< td class = "name" > [data-carousel][data-size]< / td > < td
>< span data-uix-tag data-kind = "eidos" > eidos< / span > < /td
>< td > Indicator dot diameter + gap.< / td > < /tr
>
< tr
>< td class = "name" > [data-carousel][data-color]< / td > < td
>< span data-uix-tag data-kind = "eidos" > eidos< / span > < /td
>< td > Active-indicator accent + focus ring.< / td > < /tr
>
< tr
>< td class = "name" > [data-carousel-viewport]< / td > < td
>< span data-uix-tag data-kind = "soma" > morfo< / span > < /td
>< td > Clipping box (overflow hidden, inline soma styles).< / td > < /tr
>
< tr
>< td class = "name" > [data-carousel-item-group][data-dragging]< / td > < td
>< span data-uix-tag data-kind = "soma" > morfo< / span > < /td
>< td > Transform transition off while dragging.< / td > < /tr
>
< tr
>< td class = "name" > [data-carousel-item]< / td > < td
>< span data-uix-tag data-kind = "eidos" > eidos< / span > < /td
>< td > Slide — 100% basis along axis.< / td > < /tr
>
< tr
>< td class = "name" > [data-carousel-prev-trigger] · [data-carousel-next-trigger]< / td > < td
>< span data-uix-tag data-kind = "eidos" > eidos< / span > < /td
>< td > Absolute-positioned over viewport edges; vertical flips to top/bottom.< / td > < /tr
>
< tr
>< td class = "name" > [data-carousel-indicator][data-state='active']< / td > < td
>< span data-uix-tag data-kind = "eidos" > eidos< / span > < /td
>< td > Active indicator: accent fill + 2.25× inline-size stretch.< / td > < /tr
>
< / tbody >
< / table >
< / div >
@ -832,9 +1159,10 @@
< section data-uix-section >
< h2 data-uix-section-title > Accessibility< / h2 >
< p data-uix-section-desc >
Follows the APG < a href = { carouselMorfo . apg } target="_blank" rel = "noreferrer" > carousel pattern</ a > .
The root is < code > role="region"< / code > with < code > aria-roledescription="carousel"< / code > +
an accessible name. Each slide is < code > role="group"< / code > with
Follows the APG < a href = { carouselMorfo . apg } target="_blank" rel = "noreferrer"
>carousel pattern< /a
>. The root is < code > role="region"< / code > with < code > aria-roledescription="carousel"< / code >
+ an accessible name. Each slide is < code > role="group"< / code > with
< code > aria-roledescription="slide"< / code > . Indicators form a
< code > role="tablist"< / code > with < code > role="tab"< / code > children.
< / p >
@ -844,12 +1172,27 @@
< table data-uix-table >
< thead > < tr > < th > Key< / th > < th > Action< / th > < / tr > < / thead >
< tbody >
< tr > < td > < span data-uix-kbd > ArrowRight< / span > / < span data-uix-kbd > ArrowDown< / span > < / td > < td > Next slide.< / td > < / tr >
< tr > < td > < span data-uix-kbd > ArrowLeft< / span > / < span data-uix-kbd > ArrowUp< / span > < / td > < td > Previous slide.< / td > < / tr >
< tr
>< td > < span data-uix-kbd > ArrowRight< / span > / < span data-uix-kbd > ArrowDown< / span > < /td
>< td > Next slide.< / td > < /tr
>
< tr
>< td > < span data-uix-kbd > ArrowLeft< / span > / < span data-uix-kbd > ArrowUp< / span > < / td > < td
>Previous slide.< /td
>< /tr
>
< tr > < td > < span data-uix-kbd > Home< / span > < / td > < td > First slide.< / td > < / tr >
< tr > < td > < span data-uix-kbd > End< / span > < / td > < td > Last slide.< / td > < / tr >
< tr > < td > < span data-uix-kbd > Tab< / span > < / td > < td > Move focus through prev / next / indicators / inner slide controls.< / td > < / tr >
< tr > < td > pointer drag< / td > < td > Scrub between slides; commits when past < code > dragThreshold< / code > .< / td > < / tr >
< tr
>< td > < span data-uix-kbd > Tab< / span > < / td > < td
>Move focus through prev / next / indicators / inner slide controls.< /td
>< /tr
>
< tr
>< td > pointer drag< / td > < td
>Scrub between slides; commits when past < code > dragThreshold< / code > .< /td
>< /tr
>
< / tbody >
< / table >
< / div >
@ -860,13 +1203,36 @@
< thead > < tr > < th > Part< / th > < th > Attribute< / th > < th > Value< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > provider< / td > < td > role< / td > < td class = "type" > "region"< / td > < / tr >
< tr > < td class = "name" > provider< / td > < td > aria-roledescription< / td > < td class = "type" > "carousel"< / td > < / tr >
< tr > < td class = "name" > provider< / td > < td > aria-label< / td > < td class = "type" > localized "Carousel"< / td > < / tr >
< tr
>< td class = "name" > provider< / td > < td > aria-roledescription< / td > < td class = "type"
>"carousel"< /td
>< /tr
>
< tr
>< td class = "name" > provider< / td > < td > aria-label< / td > < td class = "type"
>localized "Carousel"< /td
>< /tr
>
< tr > < td class = "name" > item< / td > < td > role< / td > < td class = "type" > "group"< / td > < / tr >
< tr > < td class = "name" > item< / td > < td > aria-roledescription< / td > < td class = "type" > "slide"< / td > < / tr >
< tr > < td class = "name" > prev-trigger / next-trigger< / td > < td > type / aria-label< / td > < td class = "type" > "button" / "Previous slide" | "Next slide"< / td > < / tr >
< tr > < td class = "name" > indicator-group< / td > < td > role / aria-orientation< / td > < td class = "type" > "tablist" / orientation< / td > < / tr >
< tr > < td class = "name" > indicator< / td > < td > role / aria-selected< / td > < td class = "type" > "tab" / "true|false"< / td > < / tr >
< tr
>< td class = "name" > item< / td > < td > aria-roledescription< / td > < td class = "type" > "slide"< /td
>< /tr
>
< tr
>< td class = "name" > prev-trigger / next-trigger< / td > < td > type / aria-label< / td > < td
class="type">"button" / "Previous slide" | "Next slide"< /td
>< /tr
>
< tr
>< td class = "name" > indicator-group< / td > < td > role / aria-orientation< / td > < td class = "type"
>"tablist" / orientation< /td
>< /tr
>
< tr
>< td class = "name" > indicator< / td > < td > role / aria-selected< / td > < td class = "type"
>"tab" / "true|false"< /td
>< /tr
>
< / tbody >
< / table >
< / div >