@vielzeug/refine 2.2.5 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/carousel.cjs +1 -1
  2. package/dist/carousel.cjs.map +1 -1
  3. package/dist/carousel.js +1 -1
  4. package/dist/carousel.js.map +1 -1
  5. package/dist/chat-message.cjs +9 -9
  6. package/dist/chat-message.cjs.map +1 -1
  7. package/dist/chat-message.js +40 -37
  8. package/dist/chat-message.js.map +1 -1
  9. package/dist/command-palette.cjs +1 -1
  10. package/dist/command-palette.cjs.map +1 -1
  11. package/dist/command-palette.js +1 -1
  12. package/dist/command-palette.js.map +1 -1
  13. package/dist/core/announcer.cjs +1 -1
  14. package/dist/core/announcer.cjs.map +1 -1
  15. package/dist/core/announcer.d.ts +13 -3
  16. package/dist/core/announcer.d.ts.map +1 -1
  17. package/dist/core/announcer.js +16 -9
  18. package/dist/core/announcer.js.map +1 -1
  19. package/dist/custom-elements.json +4328 -4328
  20. package/dist/feedback/typing-indicator/typing-indicator.d.ts +2 -2
  21. package/dist/file-input.cjs +49 -49
  22. package/dist/file-input.cjs.map +1 -1
  23. package/dist/file-input.js +128 -127
  24. package/dist/file-input.js.map +1 -1
  25. package/dist/inputs/file-input/file-input-upload.cjs +1 -1
  26. package/dist/inputs/file-input/file-input-upload.cjs.map +1 -1
  27. package/dist/inputs/file-input/file-input-upload.d.ts +2 -0
  28. package/dist/inputs/file-input/file-input-upload.d.ts.map +1 -1
  29. package/dist/inputs/file-input/file-input-upload.js +5 -2
  30. package/dist/inputs/file-input/file-input-upload.js.map +1 -1
  31. package/dist/inputs/file-input/file-input.d.ts.map +1 -1
  32. package/dist/refine.cjs +62 -62
  33. package/dist/refine.cjs.map +1 -1
  34. package/dist/refine.iife.js +62 -62
  35. package/dist/refine.iife.js.map +1 -1
  36. package/dist/refine.js +62 -62
  37. package/dist/refine.js.map +1 -1
  38. package/dist/typing-indicator.cjs +1 -1
  39. package/dist/typing-indicator.cjs.map +1 -1
  40. package/dist/typing-indicator.js +8 -7
  41. package/dist/typing-indicator.js.map +1 -1
  42. package/package.json +4 -4
package/dist/carousel.cjs CHANGED
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./core/announcer.cjs"),t=require("./core/ore.cjs"),n=require("./core/nav.cjs"),r=require("./content/carousel/carousel.css.cjs"),i=require("./content/carousel/carousel-slide.cjs");let a=require("@vielzeug/ore"),o=require("@vielzeug/ripple"),s=require("@vielzeug/gesture");var c=`ore-carousel`,l=(e,t)=>{let n=e.length;e.forEach((e,r)=>{let i=r===t;e.toggleAttribute(`data-active`,i),e.setAttribute(`aria-hidden`,String(!i)),e.setAttribute(`aria-label`,e.getAttribute(`aria-label`)??`Slide ${r+1} of ${n}`),e.removeAttribute(`data-gallery-visible`),e.toggleAttribute(`data-before`,r<t),e.toggleAttribute(`data-after`,r>t)})},u=(e,t)=>{let n=e.length;e.forEach((e,r)=>{let i=r===t;e.toggleAttribute(`data-active`,i),e.setAttribute(`aria-hidden`,String(!i)),e.setAttribute(`aria-label`,e.getAttribute(`aria-label`)??`Slide ${r+1} of ${n}`),e.removeAttribute(`data-gallery-visible`),e.removeAttribute(`data-before`),e.removeAttribute(`data-after`)})},d=(e,t)=>{let n=e.length;e.forEach((e,r)=>{let i=r===t,a=t>0&&r===t-1,o=t<n-1&&r===t+1;e.toggleAttribute(`data-active`,i),e.setAttribute(`aria-hidden`,String(!i)),e.setAttribute(`aria-label`,e.getAttribute(`aria-label`)??`Slide ${r+1} of ${n}`),e.removeAttribute(`data-before`),e.removeAttribute(`data-after`),e.toggleAttribute(`data-gallery-visible`,i||a||o)})};(0,a.define)(c,{props:{autoplay:a.prop.bool(!1),"autoplay-interval":a.prop.number(5e3),color:a.prop.string(),label:a.prop.string(`Carousel`),loop:a.prop.bool(!0),orientation:a.prop.string(`horizontal`),"show-controls":a.prop.bool(!0),"show-indicators":a.prop.bool(!0),"slide-index":a.prop.number(0),variant:a.prop.string(`default`)},setup(r){let i=(0,a.getHost)(),c=(0,a.useEmit)(),f=(0,o.signal)(r[`slide-index`].value??0),p=null,m=Array.from(i.querySelectorAll(`:scope > ore-carousel-slide`)),h=(0,o.signal)(m.length),g=()=>{m=Array.from(i.querySelectorAll(`:scope > ore-carousel-slide`)),h.value=m.length},_=(0,o.computed)(()=>r.orientation.value!==`vertical`),v=(0,o.computed)(()=>r.loop.value!==!1),y=(0,o.computed)(()=>r[`show-controls`].value!==!1),b=(0,o.computed)(()=>r[`show-indicators`].value!==!1),x=(0,o.computed)(()=>v.value||f.value>0),S=(0,o.computed)(()=>v.value||f.value<h.value-1),C=(t,n=!0)=>{let r=h.value;if(r===0)return;let a=v.value?(t%r+r)%r:Math.max(0,Math.min(t,r-1));a!==f.value&&(f.value=a,i.setAttribute(`slide-index`,String(a)),c(`change`,{index:a}),n&&e.announce(m[a]?.getAttribute(`aria-label`)??`Slide ${a+1} of ${r}`))},w=()=>C(f.value-1),T=()=>C(f.value+1);(0,o.watch)(r[`slide-index`],e=>{typeof e==`number`&&e!==f.value&&C(e,!1)});let E=()=>{let e=r.variant.value??`default`,t=r.orientation.value??`horizontal`;m.forEach(n=>{n.setAttribute(`data-variant`,e),n.setAttribute(`data-orientation`,t)})};(0,o.watch)((0,o.computed)(()=>({orientation:r.orientation.value,variant:r.variant.value})),E,{immediate:!0});let D={default:l,fade:l,filmstrip:u,gallery:d},O=()=>{let e=r.variant.value??`default`;D[e](m,f.value)};(0,o.watch)(f,O,{immediate:!0});let k=()=>{p===null&&(p=setInterval(T,r[`autoplay-interval`].value??5e3))},A=()=>{p!==null&&(clearInterval(p),p=null)};(0,o.watch)((0,o.computed)(()=>({enabled:r.autoplay.value,interval:r[`autoplay-interval`].value})),({enabled:e})=>{A(),e&&k()});let j=e=>{let t=_.value,n=t?`ArrowLeft`:`ArrowUp`,r=t?`ArrowRight`:`ArrowDown`;e.key===n?(e.preventDefault(),w()):e.key===r?(e.preventDefault(),T()):e.key===`Home`?(e.preventDefault(),C(0)):e.key===`End`&&(e.preventDefault(),C(h.value-1))},M=n.createListControl({getItems:()=>Array.from({length:h.value},(e,t)=>t),loop:!0,onNavigate:({index:e})=>{C(e),(i.shadowRoot?.querySelector(`.indicator[data-index="${e}"]`))?.focus()},orientation:()=>_.value?`horizontal`:`vertical`,signal:t.lifecycleSignal(a.onCleanup)});(0,a.bind)({attr:{"aria-label":()=>r.label.value??`Carousel`,"aria-roledescription":()=>`carousel`,orientation:()=>r.orientation.value??`horizontal`,role:()=>`region`,style:()=>`touch-action:${_.value?`pan-y`:`pan-x`}`,variant:()=>r.variant.value??`default`},on:{focusin:()=>{A()},focusout:()=>{r.autoplay.value&&k()},keydown:j,pointerenter:()=>{A()},pointerleave:()=>{r.autoplay.value&&k()}}}),(0,a.onMounted)(()=>{let e=(0,s.createPanGesture)(i,{axis:()=>_.value?`x`:`y`,onEnd:({distance:e,reason:t})=>{t!==`release`||Math.abs(e)<48||(e<0?T():w())},shouldStart:e=>!e.composedPath().some(e=>e instanceof HTMLElement&&(e.tagName===`BUTTON`||e.tagName===`ORE-BUTTON`||e.tagName===`ORE-PROGRESS`))}),t=i.shadowRoot.querySelector(`slot`),n=()=>{g(),E(),O()};return t.addEventListener(`slotchange`,n),r.autoplay.value&&k(),()=>{e.dispose(),A(),t.removeEventListener(`slotchange`,n)}});let N=()=>y.value?a.html`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./core/announcer.cjs"),t=require("./core/ore.cjs"),n=require("./core/nav.cjs"),r=require("./content/carousel/carousel.css.cjs"),i=require("./content/carousel/carousel-slide.cjs");let a=require("@vielzeug/ore"),o=require("@vielzeug/ripple"),s=require("@vielzeug/gesture");var c=`ore-carousel`,l=(e,t)=>{let n=e.length;e.forEach((e,r)=>{let i=r===t;e.toggleAttribute(`data-active`,i),e.setAttribute(`aria-hidden`,String(!i)),e.setAttribute(`aria-label`,e.getAttribute(`aria-label`)??`Slide ${r+1} of ${n}`),e.removeAttribute(`data-gallery-visible`),e.toggleAttribute(`data-before`,r<t),e.toggleAttribute(`data-after`,r>t)})},u=(e,t)=>{let n=e.length;e.forEach((e,r)=>{let i=r===t;e.toggleAttribute(`data-active`,i),e.setAttribute(`aria-hidden`,String(!i)),e.setAttribute(`aria-label`,e.getAttribute(`aria-label`)??`Slide ${r+1} of ${n}`),e.removeAttribute(`data-gallery-visible`),e.removeAttribute(`data-before`),e.removeAttribute(`data-after`)})},d=(e,t)=>{let n=e.length;e.forEach((e,r)=>{let i=r===t,a=t>0&&r===t-1,o=t<n-1&&r===t+1;e.toggleAttribute(`data-active`,i),e.setAttribute(`aria-hidden`,String(!i)),e.setAttribute(`aria-label`,e.getAttribute(`aria-label`)??`Slide ${r+1} of ${n}`),e.removeAttribute(`data-before`),e.removeAttribute(`data-after`),e.toggleAttribute(`data-gallery-visible`,i||a||o)})};(0,a.define)(c,{props:{autoplay:a.prop.bool(!1),"autoplay-interval":a.prop.number(5e3),color:a.prop.string(),label:a.prop.string(`Carousel`),loop:a.prop.bool(!0),orientation:a.prop.string(`horizontal`),"show-controls":a.prop.bool(!0),"show-indicators":a.prop.bool(!0),"slide-index":a.prop.number(0),variant:a.prop.string(`default`)},setup(r){let i=(0,a.getHost)(),c=(0,a.useEmit)(),f=(0,o.signal)(r[`slide-index`].value??0),p=null,m=Array.from(i.querySelectorAll(`:scope > ore-carousel-slide`)),h=(0,o.signal)(m.length),g=()=>{m=Array.from(i.querySelectorAll(`:scope > ore-carousel-slide`)),h.value=m.length},_=(0,o.computed)(()=>r.orientation.value!==`vertical`),v=(0,o.computed)(()=>r.loop.value!==!1),y=(0,o.computed)(()=>r[`show-controls`].value!==!1),b=(0,o.computed)(()=>r[`show-indicators`].value!==!1),x=(0,o.computed)(()=>v.value||f.value>0),S=(0,o.computed)(()=>v.value||f.value<h.value-1),C=(t,n=!0)=>{let r=h.value;if(r===0)return;let a=v.value?(t%r+r)%r:Math.max(0,Math.min(t,r-1));a!==f.value&&(f.value=a,i.setAttribute(`slide-index`,String(a)),c(`change`,{index:a}),n&&e.announce(m[a]?.getAttribute(`aria-label`)??`Slide ${a+1} of ${r}`,{document:i.ownerDocument}))},w=()=>C(f.value-1),T=()=>C(f.value+1);(0,o.watch)(r[`slide-index`],e=>{typeof e==`number`&&e!==f.value&&C(e,!1)});let E=()=>{let e=r.variant.value??`default`,t=r.orientation.value??`horizontal`;m.forEach(n=>{n.setAttribute(`data-variant`,e),n.setAttribute(`data-orientation`,t)})};(0,o.watch)((0,o.computed)(()=>({orientation:r.orientation.value,variant:r.variant.value})),E,{immediate:!0});let D={default:l,fade:l,filmstrip:u,gallery:d},O=()=>{let e=r.variant.value??`default`;D[e](m,f.value)};(0,o.watch)(f,O,{immediate:!0});let k=()=>{p===null&&(p=setInterval(T,r[`autoplay-interval`].value??5e3))},A=()=>{p!==null&&(clearInterval(p),p=null)};(0,o.watch)((0,o.computed)(()=>({enabled:r.autoplay.value,interval:r[`autoplay-interval`].value})),({enabled:e})=>{A(),e&&k()});let j=e=>{let t=_.value,n=t?`ArrowLeft`:`ArrowUp`,r=t?`ArrowRight`:`ArrowDown`;e.key===n?(e.preventDefault(),w()):e.key===r?(e.preventDefault(),T()):e.key===`Home`?(e.preventDefault(),C(0)):e.key===`End`&&(e.preventDefault(),C(h.value-1))},M=n.createListControl({getItems:()=>Array.from({length:h.value},(e,t)=>t),loop:!0,onNavigate:({index:e})=>{C(e),(i.shadowRoot?.querySelector(`.indicator[data-index="${e}"]`))?.focus()},orientation:()=>_.value?`horizontal`:`vertical`,signal:t.lifecycleSignal(a.onCleanup)});(0,a.bind)({attr:{"aria-label":()=>r.label.value??`Carousel`,"aria-roledescription":()=>`carousel`,orientation:()=>r.orientation.value??`horizontal`,role:()=>`region`,style:()=>`touch-action:${_.value?`pan-y`:`pan-x`}`,variant:()=>r.variant.value??`default`},on:{focusin:()=>{A()},focusout:()=>{r.autoplay.value&&k()},keydown:j,pointerenter:()=>{A()},pointerleave:()=>{r.autoplay.value&&k()}}}),(0,a.onMounted)(()=>{let e=(0,s.createPanGesture)(i,{axis:()=>_.value?`x`:`y`,onEnd:({distance:e,reason:t})=>{t!==`release`||Math.abs(e)<48||(e<0?T():w())},shouldStart:e=>!e.composedPath().some(e=>e instanceof HTMLElement&&(e.tagName===`BUTTON`||e.tagName===`ORE-BUTTON`||e.tagName===`ORE-PROGRESS`))}),t=i.shadowRoot.querySelector(`slot`),n=()=>{g(),E(),O()};return t.addEventListener(`slotchange`,n),r.autoplay.value&&k(),()=>{e.dispose(),A(),t.removeEventListener(`slotchange`,n)}});let N=()=>y.value?a.html`
2
2
  <div class="controls" part="controls">
3
3
  <ore-button
4
4
  class="nav-btn prev-btn"
@@ -1 +1 @@
1
- {"version":3,"file":"carousel.cjs","names":[],"sources":["../src/content/carousel/carousel.ts"],"sourcesContent":["import { createPanGesture } from '@vielzeug/gesture';\nimport { bind, define, getHost, html, onCleanup, onMounted, prop, useEmit } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\n\nimport type { ThemeColor } from '../../types';\n\nimport '../../content/icon/icon';\nimport '../../feedback/progress/progress';\nimport { announce, createListControl, lifecycleSignal } from '../../core';\nimport componentStyles from './carousel.css?inline';\nimport './carousel-slide';\n\nexport { CAROUSEL_SLIDE_TAG } from './carousel-slide';\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type CarouselOrientation = 'horizontal' | 'vertical';\nexport type CarouselVariant = 'default' | 'fade' | 'filmstrip' | 'gallery';\n\nexport type OreCarouselEvents = {\n /** Fired when the active slide changes. */\n change: { index: number };\n};\n\nexport type OreCarouselProps = {\n /**\n * Whether to advance slides automatically. Defaults to `false`.\n * Opt in explicitly — autoplay on by default is a WCAG 2.1 SC 2.2.2 violation for many use cases.\n */\n autoplay?: boolean;\n /** Interval in milliseconds between automatic slide advances. Defaults to `5000`. */\n 'autoplay-interval'?: number;\n /** Theme color passed to the prev/next navigation buttons. */\n color?: ThemeColor;\n /** Accessible label for the carousel region. */\n label?: string;\n /** Whether the carousel loops from the last slide back to the first. Defaults to `true`. */\n loop?: boolean;\n /** Carousel orientation. Defaults to `'horizontal'`. */\n orientation?: CarouselOrientation;\n /** Show next/prev navigation buttons. Defaults to `true`. */\n 'show-controls'?: boolean;\n /** Show dot/indicator navigation. Defaults to `true`. */\n 'show-indicators'?: boolean;\n /** Index of the currently active slide (zero-based). Defaults to `0`. */\n 'slide-index'?: number;\n /**\n * Layout variant.\n * - `'default'` — slides translate in/out (default)\n * - `'fade'` — slides crossfade; no movement\n * - `'filmstrip'` — all slides visible side-by-side; active expands\n * - `'gallery'` — active slide fills the majority; adjacent slides show as thumbnails\n */\n variant?: CarouselVariant;\n};\n\n/**\n * An accessible, keyboard-navigable carousel / slideshow with optional\n * autoplay, swipe support, and indicator dots.\n *\n * Place `<ore-carousel-slide>` elements as direct children.\n *\n * @element ore-carousel\n * @element ore-carousel-slide - Child element for individual slides\n *\n * @attr {string} color - Theme color for navigation buttons: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {boolean} autoplay - Advance slides automatically (default false)\n * @attr {number} autoplay-interval - Milliseconds between automatic advances (default 5000)\n * @attr {string} label - Accessible label for the carousel region\n * @attr {boolean} loop - Loop from last slide to first (default true)\n * @attr {string} orientation - 'horizontal' (default) | 'vertical'\n * @attr {string} variant - 'default' | 'fade' | 'filmstrip' | 'gallery'\n * @attr {boolean} show-controls - Show prev/next buttons (default true)\n * @attr {boolean} show-indicators - Show indicator dots (default true)\n * @attr {number} slide-index - Active slide index (zero-based, default 0)\n *\n * @fires change - Fires when the active slide changes. detail: { index: number }\n *\n * @slot - Place `<ore-carousel-slide>` elements here\n *\n * @cssprop --carousel-bg - Slide area background\n * @cssprop --carousel-radius - Border radius of the carousel\n * @cssprop --carousel-dot-bg - Inactive indicator color (default: var(--color-contrast-300))\n * @cssprop --carousel-dot-active-bg - Active indicator / fill color (default: var(--color-contrast-700))\n * @cssprop --carousel-transition-duration - Slide transition duration (default 0.35s; 0s under prefers-reduced-motion).\n * @cssprop --carousel-min-height - Minimum height when no explicit height is set (default 240px)\n * @cssprop --carousel-filmstrip-inactive - Width (horizontal) or height (vertical) of inactive slides in filmstrip mode\n * @cssprop --carousel-filmstrip-gap - Gap between slides in filmstrip mode\n * @cssprop --carousel-gallery-thumbnail - Width (horizontal) or height (vertical) of thumbnail slides in gallery mode (default var(--size-24))\n * @cssprop --carousel-gallery-gap - Gap between slides in gallery mode (default var(--size-2))\n *\n * @part track - The scrolling slide track element\n * @part controls - The prev/next button container\n * @part indicators - The indicator dots container\n * @part prev-btn - The previous-slide button\n * @part next-btn - The next-slide button\n *\n * @example\n * ```html\n * <ore-carousel label=\"Product highlights\" loop>\n * <ore-carousel-slide>Slide 1</ore-carousel-slide>\n * <ore-carousel-slide>Slide 2</ore-carousel-slide>\n * <ore-carousel-slide>Slide 3</ore-carousel-slide>\n * </ore-carousel>\n * ```\n */\nexport const CAROUSEL_TAG = 'ore-carousel' as const;\n\n// ── Slide-state sync helpers ──────────────────────────────────────────────────\n// Each function handles exactly one variant's attribute bookkeeping.\n// Called from a dispatch table in syncActiveState.\n\nconst syncDefaultSlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-gallery-visible');\n slide.toggleAttribute('data-before', i < current);\n slide.toggleAttribute('data-after', i > current);\n });\n};\n\nconst syncFilmstripSlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-gallery-visible');\n slide.removeAttribute('data-before');\n slide.removeAttribute('data-after');\n });\n};\n\nconst syncGallerySlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n const isPrev = current > 0 && i === current - 1;\n const isNext = current < count - 1 && i === current + 1;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-before');\n slide.removeAttribute('data-after');\n slide.toggleAttribute('data-gallery-visible', active || isPrev || isNext);\n });\n};\n\n// ── ore-carousel ──────────────────────────────────────────────────────────────\n\ndefine<OreCarouselProps>(CAROUSEL_TAG, {\n props: {\n autoplay: prop.bool(false),\n 'autoplay-interval': prop.number(5000),\n color: prop.string<ThemeColor>(),\n label: prop.string('Carousel'),\n loop: prop.bool(true),\n orientation: prop.string<CarouselOrientation>('horizontal'),\n 'show-controls': prop.bool(true),\n 'show-indicators': prop.bool(true),\n 'slide-index': prop.number(0),\n variant: prop.string<CarouselVariant>('default'),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreCarouselEvents>();\n\n // ── State ────────────────────────────────────────────────────────────────\n\n const activeIndex = signal<number>(props['slide-index'].value ?? 0);\n let autoplayTimer: ReturnType<typeof setInterval> | null = null;\n\n // Slide cache — populated immediately, refreshed on slotchange.\n let slides: HTMLElement[] = Array.from(el.querySelectorAll<HTMLElement>(':scope > ore-carousel-slide'));\n const slideCount = signal(slides.length);\n\n const refreshSlides = (): void => {\n slides = Array.from(el.querySelectorAll<HTMLElement>(':scope > ore-carousel-slide'));\n slideCount.value = slides.length;\n };\n\n const isHorizontal = computed(() => props.orientation.value !== 'vertical');\n const looping = computed(() => props.loop.value !== false);\n const showControls = computed(() => props['show-controls'].value !== false);\n const showIndicators = computed(() => props['show-indicators'].value !== false);\n\n const canGoPrev = computed(() => looping.value || activeIndex.value > 0);\n const canGoNext = computed(() => looping.value || activeIndex.value < slideCount.value - 1);\n\n // ── Navigation ───────────────────────────────────────────────────────────\n\n const goTo = (index: number, announce_: boolean = true): void => {\n const count = slideCount.value;\n\n if (count === 0) return;\n\n const next = looping.value ? ((index % count) + count) % count : Math.max(0, Math.min(index, count - 1));\n\n if (next === activeIndex.value) return;\n\n activeIndex.value = next;\n el.setAttribute('slide-index', String(next));\n emit('change', { index: next });\n\n if (announce_) {\n announce(slides[next]?.getAttribute('aria-label') ?? `Slide ${next + 1} of ${count}`);\n }\n };\n\n const prev = (): void => goTo(activeIndex.value - 1);\n const next = (): void => goTo(activeIndex.value + 1);\n\n // ── Sync prop → state ────────────────────────────────────────────────────\n\n watch(props['slide-index'], (v) => {\n if (typeof v === 'number' && v !== activeIndex.value) goTo(v, false);\n });\n\n // ── Sync variant + orientation → slides ──────────────────────────────────\n\n const syncSlideVariants = (): void => {\n const variant = props.variant.value ?? 'default';\n const orientation = props.orientation.value ?? 'horizontal';\n\n slides.forEach((slide) => {\n slide.setAttribute('data-variant', variant);\n slide.setAttribute('data-orientation', orientation);\n });\n };\n\n watch(\n computed(() => ({ orientation: props.orientation.value, variant: props.variant.value })),\n syncSlideVariants,\n { immediate: true },\n );\n\n // ── Sync active index → slides — dispatch table ───────────────────────────\n\n const syncTable: Record<CarouselVariant, (slides: HTMLElement[], current: number) => void> = {\n default: syncDefaultSlides,\n fade: syncDefaultSlides,\n filmstrip: syncFilmstripSlides,\n gallery: syncGallerySlides,\n };\n\n const syncActiveState = (): void => {\n const variant = (props.variant.value ?? 'default') as CarouselVariant;\n\n syncTable[variant](slides, activeIndex.value);\n };\n\n watch(activeIndex, syncActiveState, { immediate: true });\n\n // ── Autoplay ─────────────────────────────────────────────────────────────\n\n const startAutoplay = (): void => {\n if (autoplayTimer !== null) return;\n\n autoplayTimer = setInterval(next, props['autoplay-interval'].value ?? 5000);\n };\n\n const stopAutoplay = (): void => {\n if (autoplayTimer !== null) {\n clearInterval(autoplayTimer);\n autoplayTimer = null;\n }\n };\n\n watch(\n computed(() => ({ enabled: props.autoplay.value, interval: props['autoplay-interval'].value })),\n ({ enabled }) => {\n stopAutoplay();\n\n if (enabled) startAutoplay();\n },\n );\n\n // ── Keyboard navigation ──────────────────────────────────────────────────\n\n const handleKeydown = (e: KeyboardEvent): void => {\n const isH = isHorizontal.value;\n const prevKey = isH ? 'ArrowLeft' : 'ArrowUp';\n const nextKey = isH ? 'ArrowRight' : 'ArrowDown';\n\n if (e.key === prevKey) {\n e.preventDefault();\n prev();\n } else if (e.key === nextKey) {\n e.preventDefault();\n next();\n } else if (e.key === 'Home') {\n e.preventDefault();\n goTo(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n goTo(slideCount.value - 1);\n }\n };\n\n const indicatorNav = createListControl<number>({\n getItems: () => Array.from({ length: slideCount.value }, (_, index) => index),\n loop: true,\n onNavigate: ({ index }) => {\n goTo(index);\n const dot = el.shadowRoot?.querySelector<HTMLElement>(`.indicator[data-index=\"${index}\"]`);\n\n dot?.focus();\n },\n orientation: () => (isHorizontal.value ? 'horizontal' : 'vertical'),\n signal: lifecycleSignal(onCleanup),\n });\n\n // ── Host bindings ────────────────────────────────────────────────────────\n\n bind({\n attr: {\n 'aria-label': () => props.label.value ?? 'Carousel',\n 'aria-roledescription': () => 'carousel',\n orientation: () => props.orientation.value ?? 'horizontal',\n role: () => 'region',\n style: () => `touch-action:${isHorizontal.value ? 'pan-y' : 'pan-x'}`,\n variant: () => props.variant.value ?? 'default',\n },\n on: {\n focusin: () => {\n stopAutoplay();\n },\n focusout: () => {\n if (props.autoplay.value) startAutoplay();\n },\n keydown: handleKeydown,\n pointerenter: () => {\n stopAutoplay();\n },\n pointerleave: () => {\n if (props.autoplay.value) startAutoplay();\n },\n },\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n onMounted(() => {\n const pan = createPanGesture(el, {\n axis: () => (isHorizontal.value ? 'x' : 'y'),\n onEnd: ({ distance, reason }) => {\n if (reason !== 'release' || Math.abs(distance) < 48) return;\n\n if (distance < 0) next();\n else prev();\n },\n shouldStart: (event) => {\n const path = event.composedPath();\n\n return !path.some(\n (node) =>\n node instanceof HTMLElement &&\n (node.tagName === 'BUTTON' || node.tagName === 'ORE-BUTTON' || node.tagName === 'ORE-PROGRESS'),\n );\n },\n });\n const shadowRoot = el.shadowRoot!;\n const slot = shadowRoot.querySelector<HTMLSlotElement>('slot')!;\n\n // slotchange replaces MutationObserver — fires exactly when assigned\n // nodes change, scoped to this component's slot, no polling overhead.\n const onSlotChange = (): void => {\n refreshSlides();\n syncSlideVariants();\n syncActiveState();\n };\n\n slot.addEventListener('slotchange', onSlotChange);\n\n if (props.autoplay.value) {\n startAutoplay();\n }\n\n return () => {\n pan.dispose();\n stopAutoplay();\n slot.removeEventListener('slotchange', onSlotChange);\n };\n });\n\n // ── Template ─────────────────────────────────────────────────────────────\n\n const renderControls = () =>\n showControls.value\n ? html`\n <div class=\"controls\" part=\"controls\">\n <ore-button\n class=\"nav-btn prev-btn\"\n part=\"prev-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n aria-label=\"Previous slide\"\n disabled=${() => (!canGoPrev.value ? true : undefined)}\n @click=${(e: Event) => {\n e.stopPropagation();\n prev();\n }}>\n <ore-icon\n name=${() => (isHorizontal.value ? 'chevron-left' : 'chevron-up')}\n size=\"16\"\n stroke-width=\"2\"\n aria-hidden=\"true\"></ore-icon>\n </ore-button>\n <ore-button\n class=\"nav-btn next-btn\"\n part=\"next-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n aria-label=\"Next slide\"\n disabled=${() => (!canGoNext.value ? true : undefined)}\n @click=${(e: Event) => {\n e.stopPropagation();\n next();\n }}>\n <ore-icon\n name=${() => (isHorizontal.value ? 'chevron-right' : 'chevron-down')}\n size=\"16\"\n stroke-width=\"2\"\n aria-hidden=\"true\"></ore-icon>\n </ore-button>\n </div>\n `\n : html``;\n\n return html`\n <div class=\"track\" part=\"track\" aria-live=${() => (props.autoplay.value ? 'off' : 'polite')}>\n <slot></slot>\n </div>\n\n ${() =>\n showIndicators.value && slideCount.value > 1\n ? html`\n <div class=\"indicators\" part=\"indicators\" role=\"tablist\" aria-label=\"Slide indicators\">\n ${() =>\n Array.from(\n { length: slideCount.value },\n (_, i) => html`\n <button\n type=\"button\"\n role=\"tab\"\n data-index=\"${i}\"\n class=${() => `indicator${i === activeIndex.value ? ' indicator-active' : ''}`}\n aria-selected=${() => String(i === activeIndex.value)}\n tabindex=${() => (i === activeIndex.value ? '0' : '-1')}\n aria-label=\"${`Go to slide ${i + 1}`}\"\n @focus=${() => indicatorNav.set(i)}\n @click=${() => goTo(i)}\n @keydown=${(e: KeyboardEvent) => {\n const target = e.currentTarget;\n\n if (target instanceof HTMLElement) {\n const index = Number(target.dataset.index);\n\n if (Number.isInteger(index)) indicatorNav.set(index);\n }\n\n indicatorNav.handleKeydown(e);\n }}>\n <ore-progress\n aria-hidden=\"true\"\n tabindex=\"-1\"\n color=${() => props.color.value}\n type=${() => (isHorizontal.value ? 'linear' : 'vertical')}\n value=${() => (i === activeIndex.value ? 100 : 0)}\n style=${() => {\n const fillAnim = isHorizontal.value ? 'carousel-fill' : 'carousel-fill-v';\n\n return `--carousel-timeout:${props['autoplay-interval'].value ?? 5000};--carousel-animation-name:${i === activeIndex.value && props.autoplay.value ? fillAnim : 'none'}`;\n }}></ore-progress>\n </button>\n `,\n )}\n ${renderControls}\n </div>\n `\n : html`\n ${renderControls}\n `}\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"carousel.cjs","names":[],"sources":["../src/content/carousel/carousel.ts"],"sourcesContent":["import { createPanGesture } from '@vielzeug/gesture';\nimport { bind, define, getHost, html, onCleanup, onMounted, prop, useEmit } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\n\nimport type { ThemeColor } from '../../types';\n\nimport '../../content/icon/icon';\nimport '../../feedback/progress/progress';\nimport { announce, createListControl, lifecycleSignal } from '../../core';\nimport componentStyles from './carousel.css?inline';\nimport './carousel-slide';\n\nexport { CAROUSEL_SLIDE_TAG } from './carousel-slide';\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type CarouselOrientation = 'horizontal' | 'vertical';\nexport type CarouselVariant = 'default' | 'fade' | 'filmstrip' | 'gallery';\n\nexport type OreCarouselEvents = {\n /** Fired when the active slide changes. */\n change: { index: number };\n};\n\nexport type OreCarouselProps = {\n /**\n * Whether to advance slides automatically. Defaults to `false`.\n * Opt in explicitly — autoplay on by default is a WCAG 2.1 SC 2.2.2 violation for many use cases.\n */\n autoplay?: boolean;\n /** Interval in milliseconds between automatic slide advances. Defaults to `5000`. */\n 'autoplay-interval'?: number;\n /** Theme color passed to the prev/next navigation buttons. */\n color?: ThemeColor;\n /** Accessible label for the carousel region. */\n label?: string;\n /** Whether the carousel loops from the last slide back to the first. Defaults to `true`. */\n loop?: boolean;\n /** Carousel orientation. Defaults to `'horizontal'`. */\n orientation?: CarouselOrientation;\n /** Show next/prev navigation buttons. Defaults to `true`. */\n 'show-controls'?: boolean;\n /** Show dot/indicator navigation. Defaults to `true`. */\n 'show-indicators'?: boolean;\n /** Index of the currently active slide (zero-based). Defaults to `0`. */\n 'slide-index'?: number;\n /**\n * Layout variant.\n * - `'default'` — slides translate in/out (default)\n * - `'fade'` — slides crossfade; no movement\n * - `'filmstrip'` — all slides visible side-by-side; active expands\n * - `'gallery'` — active slide fills the majority; adjacent slides show as thumbnails\n */\n variant?: CarouselVariant;\n};\n\n/**\n * An accessible, keyboard-navigable carousel / slideshow with optional\n * autoplay, swipe support, and indicator dots.\n *\n * Place `<ore-carousel-slide>` elements as direct children.\n *\n * @element ore-carousel\n * @element ore-carousel-slide - Child element for individual slides\n *\n * @attr {string} color - Theme color for navigation buttons: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {boolean} autoplay - Advance slides automatically (default false)\n * @attr {number} autoplay-interval - Milliseconds between automatic advances (default 5000)\n * @attr {string} label - Accessible label for the carousel region\n * @attr {boolean} loop - Loop from last slide to first (default true)\n * @attr {string} orientation - 'horizontal' (default) | 'vertical'\n * @attr {string} variant - 'default' | 'fade' | 'filmstrip' | 'gallery'\n * @attr {boolean} show-controls - Show prev/next buttons (default true)\n * @attr {boolean} show-indicators - Show indicator dots (default true)\n * @attr {number} slide-index - Active slide index (zero-based, default 0)\n *\n * @fires change - Fires when the active slide changes. detail: { index: number }\n *\n * @slot - Place `<ore-carousel-slide>` elements here\n *\n * @cssprop --carousel-bg - Slide area background\n * @cssprop --carousel-radius - Border radius of the carousel\n * @cssprop --carousel-dot-bg - Inactive indicator color (default: var(--color-contrast-300))\n * @cssprop --carousel-dot-active-bg - Active indicator / fill color (default: var(--color-contrast-700))\n * @cssprop --carousel-transition-duration - Slide transition duration (default 0.35s; 0s under prefers-reduced-motion).\n * @cssprop --carousel-min-height - Minimum height when no explicit height is set (default 240px)\n * @cssprop --carousel-filmstrip-inactive - Width (horizontal) or height (vertical) of inactive slides in filmstrip mode\n * @cssprop --carousel-filmstrip-gap - Gap between slides in filmstrip mode\n * @cssprop --carousel-gallery-thumbnail - Width (horizontal) or height (vertical) of thumbnail slides in gallery mode (default var(--size-24))\n * @cssprop --carousel-gallery-gap - Gap between slides in gallery mode (default var(--size-2))\n *\n * @part track - The scrolling slide track element\n * @part controls - The prev/next button container\n * @part indicators - The indicator dots container\n * @part prev-btn - The previous-slide button\n * @part next-btn - The next-slide button\n *\n * @example\n * ```html\n * <ore-carousel label=\"Product highlights\" loop>\n * <ore-carousel-slide>Slide 1</ore-carousel-slide>\n * <ore-carousel-slide>Slide 2</ore-carousel-slide>\n * <ore-carousel-slide>Slide 3</ore-carousel-slide>\n * </ore-carousel>\n * ```\n */\nexport const CAROUSEL_TAG = 'ore-carousel' as const;\n\n// ── Slide-state sync helpers ──────────────────────────────────────────────────\n// Each function handles exactly one variant's attribute bookkeeping.\n// Called from a dispatch table in syncActiveState.\n\nconst syncDefaultSlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-gallery-visible');\n slide.toggleAttribute('data-before', i < current);\n slide.toggleAttribute('data-after', i > current);\n });\n};\n\nconst syncFilmstripSlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-gallery-visible');\n slide.removeAttribute('data-before');\n slide.removeAttribute('data-after');\n });\n};\n\nconst syncGallerySlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n const isPrev = current > 0 && i === current - 1;\n const isNext = current < count - 1 && i === current + 1;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-before');\n slide.removeAttribute('data-after');\n slide.toggleAttribute('data-gallery-visible', active || isPrev || isNext);\n });\n};\n\n// ── ore-carousel ──────────────────────────────────────────────────────────────\n\ndefine<OreCarouselProps>(CAROUSEL_TAG, {\n props: {\n autoplay: prop.bool(false),\n 'autoplay-interval': prop.number(5000),\n color: prop.string<ThemeColor>(),\n label: prop.string('Carousel'),\n loop: prop.bool(true),\n orientation: prop.string<CarouselOrientation>('horizontal'),\n 'show-controls': prop.bool(true),\n 'show-indicators': prop.bool(true),\n 'slide-index': prop.number(0),\n variant: prop.string<CarouselVariant>('default'),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreCarouselEvents>();\n\n // ── State ────────────────────────────────────────────────────────────────\n\n const activeIndex = signal<number>(props['slide-index'].value ?? 0);\n let autoplayTimer: ReturnType<typeof setInterval> | null = null;\n\n // Slide cache — populated immediately, refreshed on slotchange.\n let slides: HTMLElement[] = Array.from(el.querySelectorAll<HTMLElement>(':scope > ore-carousel-slide'));\n const slideCount = signal(slides.length);\n\n const refreshSlides = (): void => {\n slides = Array.from(el.querySelectorAll<HTMLElement>(':scope > ore-carousel-slide'));\n slideCount.value = slides.length;\n };\n\n const isHorizontal = computed(() => props.orientation.value !== 'vertical');\n const looping = computed(() => props.loop.value !== false);\n const showControls = computed(() => props['show-controls'].value !== false);\n const showIndicators = computed(() => props['show-indicators'].value !== false);\n\n const canGoPrev = computed(() => looping.value || activeIndex.value > 0);\n const canGoNext = computed(() => looping.value || activeIndex.value < slideCount.value - 1);\n\n // ── Navigation ───────────────────────────────────────────────────────────\n\n const goTo = (index: number, announce_: boolean = true): void => {\n const count = slideCount.value;\n\n if (count === 0) return;\n\n const next = looping.value ? ((index % count) + count) % count : Math.max(0, Math.min(index, count - 1));\n\n if (next === activeIndex.value) return;\n\n activeIndex.value = next;\n el.setAttribute('slide-index', String(next));\n emit('change', { index: next });\n\n if (announce_) {\n announce(slides[next]?.getAttribute('aria-label') ?? `Slide ${next + 1} of ${count}`, {\n document: el.ownerDocument,\n });\n }\n };\n\n const prev = (): void => goTo(activeIndex.value - 1);\n const next = (): void => goTo(activeIndex.value + 1);\n\n // ── Sync prop → state ────────────────────────────────────────────────────\n\n watch(props['slide-index'], (v) => {\n if (typeof v === 'number' && v !== activeIndex.value) goTo(v, false);\n });\n\n // ── Sync variant + orientation → slides ──────────────────────────────────\n\n const syncSlideVariants = (): void => {\n const variant = props.variant.value ?? 'default';\n const orientation = props.orientation.value ?? 'horizontal';\n\n slides.forEach((slide) => {\n slide.setAttribute('data-variant', variant);\n slide.setAttribute('data-orientation', orientation);\n });\n };\n\n watch(\n computed(() => ({ orientation: props.orientation.value, variant: props.variant.value })),\n syncSlideVariants,\n { immediate: true },\n );\n\n // ── Sync active index → slides — dispatch table ───────────────────────────\n\n const syncTable: Record<CarouselVariant, (slides: HTMLElement[], current: number) => void> = {\n default: syncDefaultSlides,\n fade: syncDefaultSlides,\n filmstrip: syncFilmstripSlides,\n gallery: syncGallerySlides,\n };\n\n const syncActiveState = (): void => {\n const variant = (props.variant.value ?? 'default') as CarouselVariant;\n\n syncTable[variant](slides, activeIndex.value);\n };\n\n watch(activeIndex, syncActiveState, { immediate: true });\n\n // ── Autoplay ─────────────────────────────────────────────────────────────\n\n const startAutoplay = (): void => {\n if (autoplayTimer !== null) return;\n\n autoplayTimer = setInterval(next, props['autoplay-interval'].value ?? 5000);\n };\n\n const stopAutoplay = (): void => {\n if (autoplayTimer !== null) {\n clearInterval(autoplayTimer);\n autoplayTimer = null;\n }\n };\n\n watch(\n computed(() => ({ enabled: props.autoplay.value, interval: props['autoplay-interval'].value })),\n ({ enabled }) => {\n stopAutoplay();\n\n if (enabled) startAutoplay();\n },\n );\n\n // ── Keyboard navigation ──────────────────────────────────────────────────\n\n const handleKeydown = (e: KeyboardEvent): void => {\n const isH = isHorizontal.value;\n const prevKey = isH ? 'ArrowLeft' : 'ArrowUp';\n const nextKey = isH ? 'ArrowRight' : 'ArrowDown';\n\n if (e.key === prevKey) {\n e.preventDefault();\n prev();\n } else if (e.key === nextKey) {\n e.preventDefault();\n next();\n } else if (e.key === 'Home') {\n e.preventDefault();\n goTo(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n goTo(slideCount.value - 1);\n }\n };\n\n const indicatorNav = createListControl<number>({\n getItems: () => Array.from({ length: slideCount.value }, (_, index) => index),\n loop: true,\n onNavigate: ({ index }) => {\n goTo(index);\n const dot = el.shadowRoot?.querySelector<HTMLElement>(`.indicator[data-index=\"${index}\"]`);\n\n dot?.focus();\n },\n orientation: () => (isHorizontal.value ? 'horizontal' : 'vertical'),\n signal: lifecycleSignal(onCleanup),\n });\n\n // ── Host bindings ────────────────────────────────────────────────────────\n\n bind({\n attr: {\n 'aria-label': () => props.label.value ?? 'Carousel',\n 'aria-roledescription': () => 'carousel',\n orientation: () => props.orientation.value ?? 'horizontal',\n role: () => 'region',\n style: () => `touch-action:${isHorizontal.value ? 'pan-y' : 'pan-x'}`,\n variant: () => props.variant.value ?? 'default',\n },\n on: {\n focusin: () => {\n stopAutoplay();\n },\n focusout: () => {\n if (props.autoplay.value) startAutoplay();\n },\n keydown: handleKeydown,\n pointerenter: () => {\n stopAutoplay();\n },\n pointerleave: () => {\n if (props.autoplay.value) startAutoplay();\n },\n },\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n onMounted(() => {\n const pan = createPanGesture(el, {\n axis: () => (isHorizontal.value ? 'x' : 'y'),\n onEnd: ({ distance, reason }) => {\n if (reason !== 'release' || Math.abs(distance) < 48) return;\n\n if (distance < 0) next();\n else prev();\n },\n shouldStart: (event) => {\n const path = event.composedPath();\n\n return !path.some(\n (node) =>\n node instanceof HTMLElement &&\n (node.tagName === 'BUTTON' || node.tagName === 'ORE-BUTTON' || node.tagName === 'ORE-PROGRESS'),\n );\n },\n });\n const shadowRoot = el.shadowRoot!;\n const slot = shadowRoot.querySelector<HTMLSlotElement>('slot')!;\n\n // slotchange replaces MutationObserver — fires exactly when assigned\n // nodes change, scoped to this component's slot, no polling overhead.\n const onSlotChange = (): void => {\n refreshSlides();\n syncSlideVariants();\n syncActiveState();\n };\n\n slot.addEventListener('slotchange', onSlotChange);\n\n if (props.autoplay.value) {\n startAutoplay();\n }\n\n return () => {\n pan.dispose();\n stopAutoplay();\n slot.removeEventListener('slotchange', onSlotChange);\n };\n });\n\n // ── Template ─────────────────────────────────────────────────────────────\n\n const renderControls = () =>\n showControls.value\n ? html`\n <div class=\"controls\" part=\"controls\">\n <ore-button\n class=\"nav-btn prev-btn\"\n part=\"prev-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n aria-label=\"Previous slide\"\n disabled=${() => (!canGoPrev.value ? true : undefined)}\n @click=${(e: Event) => {\n e.stopPropagation();\n prev();\n }}>\n <ore-icon\n name=${() => (isHorizontal.value ? 'chevron-left' : 'chevron-up')}\n size=\"16\"\n stroke-width=\"2\"\n aria-hidden=\"true\"></ore-icon>\n </ore-button>\n <ore-button\n class=\"nav-btn next-btn\"\n part=\"next-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n aria-label=\"Next slide\"\n disabled=${() => (!canGoNext.value ? true : undefined)}\n @click=${(e: Event) => {\n e.stopPropagation();\n next();\n }}>\n <ore-icon\n name=${() => (isHorizontal.value ? 'chevron-right' : 'chevron-down')}\n size=\"16\"\n stroke-width=\"2\"\n aria-hidden=\"true\"></ore-icon>\n </ore-button>\n </div>\n `\n : html``;\n\n return html`\n <div class=\"track\" part=\"track\" aria-live=${() => (props.autoplay.value ? 'off' : 'polite')}>\n <slot></slot>\n </div>\n\n ${() =>\n showIndicators.value && slideCount.value > 1\n ? html`\n <div class=\"indicators\" part=\"indicators\" role=\"tablist\" aria-label=\"Slide indicators\">\n ${() =>\n Array.from(\n { length: slideCount.value },\n (_, i) => html`\n <button\n type=\"button\"\n role=\"tab\"\n data-index=\"${i}\"\n class=${() => `indicator${i === activeIndex.value ? ' indicator-active' : ''}`}\n aria-selected=${() => String(i === activeIndex.value)}\n tabindex=${() => (i === activeIndex.value ? '0' : '-1')}\n aria-label=\"${`Go to slide ${i + 1}`}\"\n @focus=${() => indicatorNav.set(i)}\n @click=${() => goTo(i)}\n @keydown=${(e: KeyboardEvent) => {\n const target = e.currentTarget;\n\n if (target instanceof HTMLElement) {\n const index = Number(target.dataset.index);\n\n if (Number.isInteger(index)) indicatorNav.set(index);\n }\n\n indicatorNav.handleKeydown(e);\n }}>\n <ore-progress\n aria-hidden=\"true\"\n tabindex=\"-1\"\n color=${() => props.color.value}\n type=${() => (isHorizontal.value ? 'linear' : 'vertical')}\n value=${() => (i === activeIndex.value ? 100 : 0)}\n style=${() => {\n const fillAnim = isHorizontal.value ? 'carousel-fill' : 'carousel-fill-v';\n\n return `--carousel-timeout:${props['autoplay-interval'].value ?? 5000};--carousel-animation-name:${i === activeIndex.value && props.autoplay.value ? fillAnim : 'none'}`;\n }}></ore-progress>\n </button>\n `,\n )}\n ${renderControls}\n </div>\n `\n : html`\n ${renderControls}\n `}\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":"mWA0GA,IAAa,EAAe,eAMtB,GAAqB,EAAuB,IAA0B,CAC1E,IAAM,EAAQ,EAAO,OAErB,EAAO,SAAS,EAAO,IAAM,CAC3B,IAAM,EAAS,IAAM,EAErB,EAAM,gBAAgB,cAAe,CAAM,EAC3C,EAAM,aAAa,cAAe,OAAO,CAAC,CAAM,CAAC,EACjD,EAAM,aAAa,aAAc,EAAM,aAAa,YAAY,GAAK,SAAS,EAAI,EAAE,MAAM,GAAO,EACjG,EAAM,gBAAgB,sBAAsB,EAC5C,EAAM,gBAAgB,cAAe,EAAI,CAAO,EAChD,EAAM,gBAAgB,aAAc,EAAI,CAAO,CACjD,CAAC,CACH,EAEM,GAAuB,EAAuB,IAA0B,CAC5E,IAAM,EAAQ,EAAO,OAErB,EAAO,SAAS,EAAO,IAAM,CAC3B,IAAM,EAAS,IAAM,EAErB,EAAM,gBAAgB,cAAe,CAAM,EAC3C,EAAM,aAAa,cAAe,OAAO,CAAC,CAAM,CAAC,EACjD,EAAM,aAAa,aAAc,EAAM,aAAa,YAAY,GAAK,SAAS,EAAI,EAAE,MAAM,GAAO,EACjG,EAAM,gBAAgB,sBAAsB,EAC5C,EAAM,gBAAgB,aAAa,EACnC,EAAM,gBAAgB,YAAY,CACpC,CAAC,CACH,EAEM,GAAqB,EAAuB,IAA0B,CAC1E,IAAM,EAAQ,EAAO,OAErB,EAAO,SAAS,EAAO,IAAM,CAC3B,IAAM,EAAS,IAAM,EACf,EAAS,EAAU,GAAK,IAAM,EAAU,EACxC,EAAS,EAAU,EAAQ,GAAK,IAAM,EAAU,EAEtD,EAAM,gBAAgB,cAAe,CAAM,EAC3C,EAAM,aAAa,cAAe,OAAO,CAAC,CAAM,CAAC,EACjD,EAAM,aAAa,aAAc,EAAM,aAAa,YAAY,GAAK,SAAS,EAAI,EAAE,MAAM,GAAO,EACjG,EAAM,gBAAgB,aAAa,EACnC,EAAM,gBAAgB,YAAY,EAClC,EAAM,gBAAgB,uBAAwB,GAAU,GAAU,CAAM,CAC1E,CAAC,CACH,GAIA,EAAA,EAAA,OAAA,CAAyB,EAAc,CACrC,MAAO,CACL,SAAU,EAAA,KAAK,KAAK,EAAK,EACzB,oBAAqB,EAAA,KAAK,OAAO,GAAI,EACrC,MAAO,EAAA,KAAK,OAAmB,EAC/B,MAAO,EAAA,KAAK,OAAO,UAAU,EAC7B,KAAM,EAAA,KAAK,KAAK,EAAI,EACpB,YAAa,EAAA,KAAK,OAA4B,YAAY,EAC1D,gBAAiB,EAAA,KAAK,KAAK,EAAI,EAC/B,kBAAmB,EAAA,KAAK,KAAK,EAAI,EACjC,cAAe,EAAA,KAAK,OAAO,CAAC,EAC5B,QAAS,EAAA,KAAK,OAAwB,SAAS,CACjD,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAK,EAAA,QAAA,CAAQ,EACb,GAAA,EAAO,EAAA,QAAA,CAA2B,EAIlC,GAAA,EAAc,EAAA,OAAA,CAAe,EAAM,cAAc,CAAC,OAAS,CAAC,EAC9D,EAAuD,KAGvD,EAAwB,MAAM,KAAK,EAAG,iBAA8B,6BAA6B,CAAC,EAChG,GAAA,EAAa,EAAA,OAAA,CAAO,EAAO,MAAM,EAEjC,MAA4B,CAChC,EAAS,MAAM,KAAK,EAAG,iBAA8B,6BAA6B,CAAC,EACnF,EAAW,MAAQ,EAAO,MAC5B,EAEM,GAAA,EAAe,EAAA,SAAA,KAAe,EAAM,YAAY,QAAU,UAAU,EACpE,GAAA,EAAU,EAAA,SAAA,KAAe,EAAM,KAAK,QAAU,EAAK,EACnD,GAAA,EAAe,EAAA,SAAA,KAAe,EAAM,gBAAgB,CAAC,QAAU,EAAK,EACpE,GAAA,EAAiB,EAAA,SAAA,KAAe,EAAM,kBAAkB,CAAC,QAAU,EAAK,EAExE,GAAA,EAAY,EAAA,SAAA,KAAe,EAAQ,OAAS,EAAY,MAAQ,CAAC,EACjE,GAAA,EAAY,EAAA,SAAA,KAAe,EAAQ,OAAS,EAAY,MAAQ,EAAW,MAAQ,CAAC,EAIpF,GAAQ,EAAe,EAAqB,KAAe,CAC/D,IAAM,EAAQ,EAAW,MAEzB,GAAI,IAAU,EAAG,OAEjB,IAAM,EAAO,EAAQ,OAAU,EAAQ,EAAS,GAAS,EAAQ,KAAK,IAAI,EAAG,KAAK,IAAI,EAAO,EAAQ,CAAC,CAAC,EAEnG,IAAS,EAAY,QAEzB,EAAY,MAAQ,EACpB,EAAG,aAAa,cAAe,OAAO,CAAI,CAAC,EAC3C,EAAK,SAAU,CAAE,MAAO,CAAK,CAAC,EAE1B,GACF,EAAA,SAAS,EAAO,EAAK,EAAE,aAAa,YAAY,GAAK,SAAS,EAAO,EAAE,MAAM,IAAS,CACpF,SAAU,EAAG,aACf,CAAC,EAEL,EAEM,MAAmB,EAAK,EAAY,MAAQ,CAAC,EAC7C,MAAmB,EAAK,EAAY,MAAQ,CAAC,GAInD,EAAA,EAAA,MAAA,CAAM,EAAM,eAAiB,GAAM,CAC7B,OAAO,GAAM,UAAY,IAAM,EAAY,OAAO,EAAK,EAAG,EAAK,CACrE,CAAC,EAID,IAAM,MAAgC,CACpC,IAAM,EAAU,EAAM,QAAQ,OAAS,UACjC,EAAc,EAAM,YAAY,OAAS,aAE/C,EAAO,QAAS,GAAU,CACxB,EAAM,aAAa,eAAgB,CAAO,EAC1C,EAAM,aAAa,mBAAoB,CAAW,CACpD,CAAC,CACH,GAEA,EAAA,EAAA,MAAA,EAAA,EACE,EAAA,SAAA,MAAgB,CAAE,YAAa,EAAM,YAAY,MAAO,QAAS,EAAM,QAAQ,KAAM,EAAE,EACvF,EACA,CAAE,UAAW,EAAK,CACpB,EAIA,IAAM,EAAuF,CAC3F,QAAS,EACT,KAAM,EACN,UAAW,EACX,QAAS,CACX,EAEM,MAA8B,CAClC,IAAM,EAAW,EAAM,QAAQ,OAAS,UAExC,EAAU,EAAQ,CAAC,EAAQ,EAAY,KAAK,CAC9C,GAEA,EAAA,EAAA,MAAA,CAAM,EAAa,EAAiB,CAAE,UAAW,EAAK,CAAC,EAIvD,IAAM,MAA4B,CAC5B,IAAkB,OAEtB,EAAgB,YAAY,EAAM,EAAM,oBAAoB,CAAC,OAAS,GAAI,EAC5E,EAEM,MAA2B,CAC3B,IAAkB,OACpB,cAAc,CAAa,EAC3B,EAAgB,KAEpB,GAEA,EAAA,EAAA,MAAA,EAAA,EACE,EAAA,SAAA,MAAgB,CAAE,QAAS,EAAM,SAAS,MAAO,SAAU,EAAM,oBAAoB,CAAC,KAAM,EAAE,GAC7F,CAAE,aAAc,CACf,EAAa,EAET,GAAS,EAAc,CAC7B,CACF,EAIA,IAAM,EAAiB,GAA2B,CAChD,IAAM,EAAM,EAAa,MACnB,EAAU,EAAM,YAAc,UAC9B,EAAU,EAAM,aAAe,YAEjC,EAAE,MAAQ,GACZ,EAAE,eAAe,EACjB,EAAK,GACI,EAAE,MAAQ,GACnB,EAAE,eAAe,EACjB,EAAK,GACI,EAAE,MAAQ,QACnB,EAAE,eAAe,EACjB,EAAK,CAAC,GACG,EAAE,MAAQ,QACnB,EAAE,eAAe,EACjB,EAAK,EAAW,MAAQ,CAAC,EAE7B,EAEM,EAAe,EAAA,kBAA0B,CAC7C,aAAgB,MAAM,KAAK,CAAE,OAAQ,EAAW,KAAM,GAAI,EAAG,IAAU,CAAK,EAC5E,KAAM,GACN,YAAa,CAAE,WAAY,CACzB,EAAK,CAAK,GACE,EAAG,YAAY,cAA2B,0BAA0B,EAAM,GAAG,EAAA,EAEpF,MAAM,CACb,EACA,gBAAoB,EAAa,MAAQ,aAAe,WACxD,OAAQ,EAAA,gBAAgB,EAAA,SAAS,CACnC,CAAC,GAID,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,iBAAoB,EAAM,MAAM,OAAS,WACzC,2BAA8B,WAC9B,gBAAmB,EAAM,YAAY,OAAS,aAC9C,SAAY,SACZ,UAAa,gBAAgB,EAAa,MAAQ,QAAU,UAC5D,YAAe,EAAM,QAAQ,OAAS,SACxC,EACA,GAAI,CACF,YAAe,CACb,EAAa,CACf,EACA,aAAgB,CACV,EAAM,SAAS,OAAO,EAAc,CAC1C,EACA,QAAS,EACT,iBAAoB,CAClB,EAAa,CACf,EACA,iBAAoB,CACd,EAAM,SAAS,OAAO,EAAc,CAC1C,CACF,CACF,CAAC,GAID,EAAA,EAAA,UAAA,KAAgB,CACd,IAAM,GAAA,EAAM,EAAA,iBAAA,CAAiB,EAAI,CAC/B,SAAa,EAAa,MAAQ,IAAM,IACxC,OAAQ,CAAE,WAAU,YAAa,CAC3B,IAAW,WAAa,KAAK,IAAI,CAAQ,EAAI,KAE7C,EAAW,EAAG,EAAK,EAClB,EAAK,EACZ,EACA,YAAc,GAGL,CAFM,EAAM,aAEX,CAAA,CAAK,KACV,GACC,aAAgB,cACf,EAAK,UAAY,UAAY,EAAK,UAAY,cAAgB,EAAK,UAAY,eACpF,CAEJ,CAAC,EAEK,EADa,EAAG,WACE,cAA+B,MAAM,EAIvD,MAA2B,CAC/B,EAAc,EACd,EAAkB,EAClB,EAAgB,CAClB,EAQA,OANA,EAAK,iBAAiB,aAAc,CAAY,EAE5C,EAAM,SAAS,OACjB,EAAc,MAGH,CACX,EAAI,QAAQ,EACZ,EAAa,EACb,EAAK,oBAAoB,aAAc,CAAY,CACrD,CACF,CAAC,EAID,IAAM,MACJ,EAAa,MACT,EAAA,IAAI;;;;;;4BAMgB,EAAM,MAAM,MAAM;;;;+BAId,CAAC,EAAU,OAAe,IAAA,GAAW;yBAC7C,GAAa,CACrB,EAAE,gBAAgB,EAClB,EAAK,CACP,EAAE;;6BAEc,EAAa,MAAQ,eAAiB,aAAc;;;;;;;;;4BAStD,EAAM,MAAM,MAAM;;;;+BAId,CAAC,EAAU,OAAe,IAAA,GAAW;yBAC7C,GAAa,CACrB,EAAE,gBAAgB,EAClB,EAAK,CACP,EAAE;;6BAEc,EAAa,MAAQ,gBAAkB,eAAgB;;;;;;YAO7E,EAAA,IAAI,GAEV,MAAO,GAAA,IAAI;sDAC0C,EAAM,SAAS,MAAQ,MAAQ,SAAU;;;;YAK1F,EAAe,OAAS,EAAW,MAAQ,EACvC,EAAA,IAAI;;sBAGE,MAAM,KACJ,CAAE,OAAQ,EAAW,KAAM,GAC1B,EAAG,IAAM,EAAA,IAAI;;;;sCAII,EAAE;oCACF,YAAY,IAAM,EAAY,MAAQ,oBAAsB,KAAK;4CACzD,OAAO,IAAM,EAAY,KAAK,EAAE;uCACpC,IAAM,EAAY,MAAQ,IAAM,KAAM;sCAC1C,eAAe,EAAI,IAAI;qCACtB,EAAa,IAAI,CAAC,EAAE;qCACpB,EAAK,CAAC,EAAE;mCACX,GAAqB,CAC/B,IAAM,EAAS,EAAE,cAEjB,GAAI,aAAkB,YAAa,CACjC,IAAM,EAAQ,OAAO,EAAO,QAAQ,KAAK,EAErC,OAAO,UAAU,CAAK,GAAG,EAAa,IAAI,CAAK,CACrD,CAEA,EAAa,cAAc,CAAC,CAC9B,EAAE;;;;sCAIc,EAAM,MAAM,MAAM;qCAClB,EAAa,MAAQ,SAAW,WAAY;sCAC3C,IAAM,EAAY,MAAQ,IAAM,EAAG;sCACpC,CACZ,IAAM,EAAW,EAAa,MAAQ,gBAAkB,kBAExD,MAAO,sBAAsB,EAAM,oBAAoB,CAAC,OAAS,IAAK,6BAA6B,IAAM,EAAY,OAAS,EAAM,SAAS,MAAQ,EAAW,QAClK,EAAE;;qBAGV,EAAE;kBACF,EAAe;;cAGrB,EAAA,IAAI;gBACA,EAAe;cACjB;KAEZ,EACA,OAAQ,CAAC,EAAA,OAAe,CAC1B,CAAC"}
package/dist/carousel.js CHANGED
@@ -46,7 +46,7 @@ o(_, {
46
46
  let r = S.value;
47
47
  if (r === 0) return;
48
48
  let a = T.value ? (t % r + r) % r : Math.max(0, Math.min(t, r - 1));
49
- a !== d.value && (d.value = a, i.setAttribute("slide-index", String(a)), o("change", { index: a }), n && e(x[a]?.getAttribute("aria-label") ?? `Slide ${a + 1} of ${r}`));
49
+ a !== d.value && (d.value = a, i.setAttribute("slide-index", String(a)), o("change", { index: a }), n && e(x[a]?.getAttribute("aria-label") ?? `Slide ${a + 1} of ${r}`, { document: i.ownerDocument }));
50
50
  }, j = () => A(d.value - 1), M = () => A(d.value + 1);
51
51
  h(r["slide-index"], (e) => {
52
52
  typeof e == "number" && e !== d.value && A(e, !1);
@@ -1 +1 @@
1
- {"version":3,"file":"carousel.js","names":[],"sources":["../src/content/carousel/carousel.ts"],"sourcesContent":["import { createPanGesture } from '@vielzeug/gesture';\nimport { bind, define, getHost, html, onCleanup, onMounted, prop, useEmit } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\n\nimport type { ThemeColor } from '../../types';\n\nimport '../../content/icon/icon';\nimport '../../feedback/progress/progress';\nimport { announce, createListControl, lifecycleSignal } from '../../core';\nimport componentStyles from './carousel.css?inline';\nimport './carousel-slide';\n\nexport { CAROUSEL_SLIDE_TAG } from './carousel-slide';\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type CarouselOrientation = 'horizontal' | 'vertical';\nexport type CarouselVariant = 'default' | 'fade' | 'filmstrip' | 'gallery';\n\nexport type OreCarouselEvents = {\n /** Fired when the active slide changes. */\n change: { index: number };\n};\n\nexport type OreCarouselProps = {\n /**\n * Whether to advance slides automatically. Defaults to `false`.\n * Opt in explicitly — autoplay on by default is a WCAG 2.1 SC 2.2.2 violation for many use cases.\n */\n autoplay?: boolean;\n /** Interval in milliseconds between automatic slide advances. Defaults to `5000`. */\n 'autoplay-interval'?: number;\n /** Theme color passed to the prev/next navigation buttons. */\n color?: ThemeColor;\n /** Accessible label for the carousel region. */\n label?: string;\n /** Whether the carousel loops from the last slide back to the first. Defaults to `true`. */\n loop?: boolean;\n /** Carousel orientation. Defaults to `'horizontal'`. */\n orientation?: CarouselOrientation;\n /** Show next/prev navigation buttons. Defaults to `true`. */\n 'show-controls'?: boolean;\n /** Show dot/indicator navigation. Defaults to `true`. */\n 'show-indicators'?: boolean;\n /** Index of the currently active slide (zero-based). Defaults to `0`. */\n 'slide-index'?: number;\n /**\n * Layout variant.\n * - `'default'` — slides translate in/out (default)\n * - `'fade'` — slides crossfade; no movement\n * - `'filmstrip'` — all slides visible side-by-side; active expands\n * - `'gallery'` — active slide fills the majority; adjacent slides show as thumbnails\n */\n variant?: CarouselVariant;\n};\n\n/**\n * An accessible, keyboard-navigable carousel / slideshow with optional\n * autoplay, swipe support, and indicator dots.\n *\n * Place `<ore-carousel-slide>` elements as direct children.\n *\n * @element ore-carousel\n * @element ore-carousel-slide - Child element for individual slides\n *\n * @attr {string} color - Theme color for navigation buttons: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {boolean} autoplay - Advance slides automatically (default false)\n * @attr {number} autoplay-interval - Milliseconds between automatic advances (default 5000)\n * @attr {string} label - Accessible label for the carousel region\n * @attr {boolean} loop - Loop from last slide to first (default true)\n * @attr {string} orientation - 'horizontal' (default) | 'vertical'\n * @attr {string} variant - 'default' | 'fade' | 'filmstrip' | 'gallery'\n * @attr {boolean} show-controls - Show prev/next buttons (default true)\n * @attr {boolean} show-indicators - Show indicator dots (default true)\n * @attr {number} slide-index - Active slide index (zero-based, default 0)\n *\n * @fires change - Fires when the active slide changes. detail: { index: number }\n *\n * @slot - Place `<ore-carousel-slide>` elements here\n *\n * @cssprop --carousel-bg - Slide area background\n * @cssprop --carousel-radius - Border radius of the carousel\n * @cssprop --carousel-dot-bg - Inactive indicator color (default: var(--color-contrast-300))\n * @cssprop --carousel-dot-active-bg - Active indicator / fill color (default: var(--color-contrast-700))\n * @cssprop --carousel-transition-duration - Slide transition duration (default 0.35s; 0s under prefers-reduced-motion).\n * @cssprop --carousel-min-height - Minimum height when no explicit height is set (default 240px)\n * @cssprop --carousel-filmstrip-inactive - Width (horizontal) or height (vertical) of inactive slides in filmstrip mode\n * @cssprop --carousel-filmstrip-gap - Gap between slides in filmstrip mode\n * @cssprop --carousel-gallery-thumbnail - Width (horizontal) or height (vertical) of thumbnail slides in gallery mode (default var(--size-24))\n * @cssprop --carousel-gallery-gap - Gap between slides in gallery mode (default var(--size-2))\n *\n * @part track - The scrolling slide track element\n * @part controls - The prev/next button container\n * @part indicators - The indicator dots container\n * @part prev-btn - The previous-slide button\n * @part next-btn - The next-slide button\n *\n * @example\n * ```html\n * <ore-carousel label=\"Product highlights\" loop>\n * <ore-carousel-slide>Slide 1</ore-carousel-slide>\n * <ore-carousel-slide>Slide 2</ore-carousel-slide>\n * <ore-carousel-slide>Slide 3</ore-carousel-slide>\n * </ore-carousel>\n * ```\n */\nexport const CAROUSEL_TAG = 'ore-carousel' as const;\n\n// ── Slide-state sync helpers ──────────────────────────────────────────────────\n// Each function handles exactly one variant's attribute bookkeeping.\n// Called from a dispatch table in syncActiveState.\n\nconst syncDefaultSlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-gallery-visible');\n slide.toggleAttribute('data-before', i < current);\n slide.toggleAttribute('data-after', i > current);\n });\n};\n\nconst syncFilmstripSlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-gallery-visible');\n slide.removeAttribute('data-before');\n slide.removeAttribute('data-after');\n });\n};\n\nconst syncGallerySlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n const isPrev = current > 0 && i === current - 1;\n const isNext = current < count - 1 && i === current + 1;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-before');\n slide.removeAttribute('data-after');\n slide.toggleAttribute('data-gallery-visible', active || isPrev || isNext);\n });\n};\n\n// ── ore-carousel ──────────────────────────────────────────────────────────────\n\ndefine<OreCarouselProps>(CAROUSEL_TAG, {\n props: {\n autoplay: prop.bool(false),\n 'autoplay-interval': prop.number(5000),\n color: prop.string<ThemeColor>(),\n label: prop.string('Carousel'),\n loop: prop.bool(true),\n orientation: prop.string<CarouselOrientation>('horizontal'),\n 'show-controls': prop.bool(true),\n 'show-indicators': prop.bool(true),\n 'slide-index': prop.number(0),\n variant: prop.string<CarouselVariant>('default'),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreCarouselEvents>();\n\n // ── State ────────────────────────────────────────────────────────────────\n\n const activeIndex = signal<number>(props['slide-index'].value ?? 0);\n let autoplayTimer: ReturnType<typeof setInterval> | null = null;\n\n // Slide cache — populated immediately, refreshed on slotchange.\n let slides: HTMLElement[] = Array.from(el.querySelectorAll<HTMLElement>(':scope > ore-carousel-slide'));\n const slideCount = signal(slides.length);\n\n const refreshSlides = (): void => {\n slides = Array.from(el.querySelectorAll<HTMLElement>(':scope > ore-carousel-slide'));\n slideCount.value = slides.length;\n };\n\n const isHorizontal = computed(() => props.orientation.value !== 'vertical');\n const looping = computed(() => props.loop.value !== false);\n const showControls = computed(() => props['show-controls'].value !== false);\n const showIndicators = computed(() => props['show-indicators'].value !== false);\n\n const canGoPrev = computed(() => looping.value || activeIndex.value > 0);\n const canGoNext = computed(() => looping.value || activeIndex.value < slideCount.value - 1);\n\n // ── Navigation ───────────────────────────────────────────────────────────\n\n const goTo = (index: number, announce_: boolean = true): void => {\n const count = slideCount.value;\n\n if (count === 0) return;\n\n const next = looping.value ? ((index % count) + count) % count : Math.max(0, Math.min(index, count - 1));\n\n if (next === activeIndex.value) return;\n\n activeIndex.value = next;\n el.setAttribute('slide-index', String(next));\n emit('change', { index: next });\n\n if (announce_) {\n announce(slides[next]?.getAttribute('aria-label') ?? `Slide ${next + 1} of ${count}`);\n }\n };\n\n const prev = (): void => goTo(activeIndex.value - 1);\n const next = (): void => goTo(activeIndex.value + 1);\n\n // ── Sync prop → state ────────────────────────────────────────────────────\n\n watch(props['slide-index'], (v) => {\n if (typeof v === 'number' && v !== activeIndex.value) goTo(v, false);\n });\n\n // ── Sync variant + orientation → slides ──────────────────────────────────\n\n const syncSlideVariants = (): void => {\n const variant = props.variant.value ?? 'default';\n const orientation = props.orientation.value ?? 'horizontal';\n\n slides.forEach((slide) => {\n slide.setAttribute('data-variant', variant);\n slide.setAttribute('data-orientation', orientation);\n });\n };\n\n watch(\n computed(() => ({ orientation: props.orientation.value, variant: props.variant.value })),\n syncSlideVariants,\n { immediate: true },\n );\n\n // ── Sync active index → slides — dispatch table ───────────────────────────\n\n const syncTable: Record<CarouselVariant, (slides: HTMLElement[], current: number) => void> = {\n default: syncDefaultSlides,\n fade: syncDefaultSlides,\n filmstrip: syncFilmstripSlides,\n gallery: syncGallerySlides,\n };\n\n const syncActiveState = (): void => {\n const variant = (props.variant.value ?? 'default') as CarouselVariant;\n\n syncTable[variant](slides, activeIndex.value);\n };\n\n watch(activeIndex, syncActiveState, { immediate: true });\n\n // ── Autoplay ─────────────────────────────────────────────────────────────\n\n const startAutoplay = (): void => {\n if (autoplayTimer !== null) return;\n\n autoplayTimer = setInterval(next, props['autoplay-interval'].value ?? 5000);\n };\n\n const stopAutoplay = (): void => {\n if (autoplayTimer !== null) {\n clearInterval(autoplayTimer);\n autoplayTimer = null;\n }\n };\n\n watch(\n computed(() => ({ enabled: props.autoplay.value, interval: props['autoplay-interval'].value })),\n ({ enabled }) => {\n stopAutoplay();\n\n if (enabled) startAutoplay();\n },\n );\n\n // ── Keyboard navigation ──────────────────────────────────────────────────\n\n const handleKeydown = (e: KeyboardEvent): void => {\n const isH = isHorizontal.value;\n const prevKey = isH ? 'ArrowLeft' : 'ArrowUp';\n const nextKey = isH ? 'ArrowRight' : 'ArrowDown';\n\n if (e.key === prevKey) {\n e.preventDefault();\n prev();\n } else if (e.key === nextKey) {\n e.preventDefault();\n next();\n } else if (e.key === 'Home') {\n e.preventDefault();\n goTo(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n goTo(slideCount.value - 1);\n }\n };\n\n const indicatorNav = createListControl<number>({\n getItems: () => Array.from({ length: slideCount.value }, (_, index) => index),\n loop: true,\n onNavigate: ({ index }) => {\n goTo(index);\n const dot = el.shadowRoot?.querySelector<HTMLElement>(`.indicator[data-index=\"${index}\"]`);\n\n dot?.focus();\n },\n orientation: () => (isHorizontal.value ? 'horizontal' : 'vertical'),\n signal: lifecycleSignal(onCleanup),\n });\n\n // ── Host bindings ────────────────────────────────────────────────────────\n\n bind({\n attr: {\n 'aria-label': () => props.label.value ?? 'Carousel',\n 'aria-roledescription': () => 'carousel',\n orientation: () => props.orientation.value ?? 'horizontal',\n role: () => 'region',\n style: () => `touch-action:${isHorizontal.value ? 'pan-y' : 'pan-x'}`,\n variant: () => props.variant.value ?? 'default',\n },\n on: {\n focusin: () => {\n stopAutoplay();\n },\n focusout: () => {\n if (props.autoplay.value) startAutoplay();\n },\n keydown: handleKeydown,\n pointerenter: () => {\n stopAutoplay();\n },\n pointerleave: () => {\n if (props.autoplay.value) startAutoplay();\n },\n },\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n onMounted(() => {\n const pan = createPanGesture(el, {\n axis: () => (isHorizontal.value ? 'x' : 'y'),\n onEnd: ({ distance, reason }) => {\n if (reason !== 'release' || Math.abs(distance) < 48) return;\n\n if (distance < 0) next();\n else prev();\n },\n shouldStart: (event) => {\n const path = event.composedPath();\n\n return !path.some(\n (node) =>\n node instanceof HTMLElement &&\n (node.tagName === 'BUTTON' || node.tagName === 'ORE-BUTTON' || node.tagName === 'ORE-PROGRESS'),\n );\n },\n });\n const shadowRoot = el.shadowRoot!;\n const slot = shadowRoot.querySelector<HTMLSlotElement>('slot')!;\n\n // slotchange replaces MutationObserver — fires exactly when assigned\n // nodes change, scoped to this component's slot, no polling overhead.\n const onSlotChange = (): void => {\n refreshSlides();\n syncSlideVariants();\n syncActiveState();\n };\n\n slot.addEventListener('slotchange', onSlotChange);\n\n if (props.autoplay.value) {\n startAutoplay();\n }\n\n return () => {\n pan.dispose();\n stopAutoplay();\n slot.removeEventListener('slotchange', onSlotChange);\n };\n });\n\n // ── Template ─────────────────────────────────────────────────────────────\n\n const renderControls = () =>\n showControls.value\n ? html`\n <div class=\"controls\" part=\"controls\">\n <ore-button\n class=\"nav-btn prev-btn\"\n part=\"prev-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n aria-label=\"Previous slide\"\n disabled=${() => (!canGoPrev.value ? true : undefined)}\n @click=${(e: Event) => {\n e.stopPropagation();\n prev();\n }}>\n <ore-icon\n name=${() => (isHorizontal.value ? 'chevron-left' : 'chevron-up')}\n size=\"16\"\n stroke-width=\"2\"\n aria-hidden=\"true\"></ore-icon>\n </ore-button>\n <ore-button\n class=\"nav-btn next-btn\"\n part=\"next-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n aria-label=\"Next slide\"\n disabled=${() => (!canGoNext.value ? true : undefined)}\n @click=${(e: Event) => {\n e.stopPropagation();\n next();\n }}>\n <ore-icon\n name=${() => (isHorizontal.value ? 'chevron-right' : 'chevron-down')}\n size=\"16\"\n stroke-width=\"2\"\n aria-hidden=\"true\"></ore-icon>\n </ore-button>\n </div>\n `\n : html``;\n\n return html`\n <div class=\"track\" part=\"track\" aria-live=${() => (props.autoplay.value ? 'off' : 'polite')}>\n <slot></slot>\n </div>\n\n ${() =>\n showIndicators.value && slideCount.value > 1\n ? html`\n <div class=\"indicators\" part=\"indicators\" role=\"tablist\" aria-label=\"Slide indicators\">\n ${() =>\n Array.from(\n { length: slideCount.value },\n (_, i) => html`\n <button\n type=\"button\"\n role=\"tab\"\n data-index=\"${i}\"\n class=${() => `indicator${i === activeIndex.value ? ' indicator-active' : ''}`}\n aria-selected=${() => String(i === activeIndex.value)}\n tabindex=${() => (i === activeIndex.value ? '0' : '-1')}\n aria-label=\"${`Go to slide ${i + 1}`}\"\n @focus=${() => indicatorNav.set(i)}\n @click=${() => goTo(i)}\n @keydown=${(e: KeyboardEvent) => {\n const target = e.currentTarget;\n\n if (target instanceof HTMLElement) {\n const index = Number(target.dataset.index);\n\n if (Number.isInteger(index)) indicatorNav.set(index);\n }\n\n indicatorNav.handleKeydown(e);\n }}>\n <ore-progress\n aria-hidden=\"true\"\n tabindex=\"-1\"\n color=${() => props.color.value}\n type=${() => (isHorizontal.value ? 'linear' : 'vertical')}\n value=${() => (i === activeIndex.value ? 100 : 0)}\n style=${() => {\n const fillAnim = isHorizontal.value ? 'carousel-fill' : 'carousel-fill-v';\n\n return `--carousel-timeout:${props['autoplay-interval'].value ?? 5000};--carousel-animation-name:${i === activeIndex.value && props.autoplay.value ? fillAnim : 'none'}`;\n }}></ore-progress>\n </button>\n `,\n )}\n ${renderControls}\n </div>\n `\n : html`\n ${renderControls}\n `}\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"carousel.js","names":[],"sources":["../src/content/carousel/carousel.ts"],"sourcesContent":["import { createPanGesture } from '@vielzeug/gesture';\nimport { bind, define, getHost, html, onCleanup, onMounted, prop, useEmit } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\n\nimport type { ThemeColor } from '../../types';\n\nimport '../../content/icon/icon';\nimport '../../feedback/progress/progress';\nimport { announce, createListControl, lifecycleSignal } from '../../core';\nimport componentStyles from './carousel.css?inline';\nimport './carousel-slide';\n\nexport { CAROUSEL_SLIDE_TAG } from './carousel-slide';\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type CarouselOrientation = 'horizontal' | 'vertical';\nexport type CarouselVariant = 'default' | 'fade' | 'filmstrip' | 'gallery';\n\nexport type OreCarouselEvents = {\n /** Fired when the active slide changes. */\n change: { index: number };\n};\n\nexport type OreCarouselProps = {\n /**\n * Whether to advance slides automatically. Defaults to `false`.\n * Opt in explicitly — autoplay on by default is a WCAG 2.1 SC 2.2.2 violation for many use cases.\n */\n autoplay?: boolean;\n /** Interval in milliseconds between automatic slide advances. Defaults to `5000`. */\n 'autoplay-interval'?: number;\n /** Theme color passed to the prev/next navigation buttons. */\n color?: ThemeColor;\n /** Accessible label for the carousel region. */\n label?: string;\n /** Whether the carousel loops from the last slide back to the first. Defaults to `true`. */\n loop?: boolean;\n /** Carousel orientation. Defaults to `'horizontal'`. */\n orientation?: CarouselOrientation;\n /** Show next/prev navigation buttons. Defaults to `true`. */\n 'show-controls'?: boolean;\n /** Show dot/indicator navigation. Defaults to `true`. */\n 'show-indicators'?: boolean;\n /** Index of the currently active slide (zero-based). Defaults to `0`. */\n 'slide-index'?: number;\n /**\n * Layout variant.\n * - `'default'` — slides translate in/out (default)\n * - `'fade'` — slides crossfade; no movement\n * - `'filmstrip'` — all slides visible side-by-side; active expands\n * - `'gallery'` — active slide fills the majority; adjacent slides show as thumbnails\n */\n variant?: CarouselVariant;\n};\n\n/**\n * An accessible, keyboard-navigable carousel / slideshow with optional\n * autoplay, swipe support, and indicator dots.\n *\n * Place `<ore-carousel-slide>` elements as direct children.\n *\n * @element ore-carousel\n * @element ore-carousel-slide - Child element for individual slides\n *\n * @attr {string} color - Theme color for navigation buttons: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {boolean} autoplay - Advance slides automatically (default false)\n * @attr {number} autoplay-interval - Milliseconds between automatic advances (default 5000)\n * @attr {string} label - Accessible label for the carousel region\n * @attr {boolean} loop - Loop from last slide to first (default true)\n * @attr {string} orientation - 'horizontal' (default) | 'vertical'\n * @attr {string} variant - 'default' | 'fade' | 'filmstrip' | 'gallery'\n * @attr {boolean} show-controls - Show prev/next buttons (default true)\n * @attr {boolean} show-indicators - Show indicator dots (default true)\n * @attr {number} slide-index - Active slide index (zero-based, default 0)\n *\n * @fires change - Fires when the active slide changes. detail: { index: number }\n *\n * @slot - Place `<ore-carousel-slide>` elements here\n *\n * @cssprop --carousel-bg - Slide area background\n * @cssprop --carousel-radius - Border radius of the carousel\n * @cssprop --carousel-dot-bg - Inactive indicator color (default: var(--color-contrast-300))\n * @cssprop --carousel-dot-active-bg - Active indicator / fill color (default: var(--color-contrast-700))\n * @cssprop --carousel-transition-duration - Slide transition duration (default 0.35s; 0s under prefers-reduced-motion).\n * @cssprop --carousel-min-height - Minimum height when no explicit height is set (default 240px)\n * @cssprop --carousel-filmstrip-inactive - Width (horizontal) or height (vertical) of inactive slides in filmstrip mode\n * @cssprop --carousel-filmstrip-gap - Gap between slides in filmstrip mode\n * @cssprop --carousel-gallery-thumbnail - Width (horizontal) or height (vertical) of thumbnail slides in gallery mode (default var(--size-24))\n * @cssprop --carousel-gallery-gap - Gap between slides in gallery mode (default var(--size-2))\n *\n * @part track - The scrolling slide track element\n * @part controls - The prev/next button container\n * @part indicators - The indicator dots container\n * @part prev-btn - The previous-slide button\n * @part next-btn - The next-slide button\n *\n * @example\n * ```html\n * <ore-carousel label=\"Product highlights\" loop>\n * <ore-carousel-slide>Slide 1</ore-carousel-slide>\n * <ore-carousel-slide>Slide 2</ore-carousel-slide>\n * <ore-carousel-slide>Slide 3</ore-carousel-slide>\n * </ore-carousel>\n * ```\n */\nexport const CAROUSEL_TAG = 'ore-carousel' as const;\n\n// ── Slide-state sync helpers ──────────────────────────────────────────────────\n// Each function handles exactly one variant's attribute bookkeeping.\n// Called from a dispatch table in syncActiveState.\n\nconst syncDefaultSlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-gallery-visible');\n slide.toggleAttribute('data-before', i < current);\n slide.toggleAttribute('data-after', i > current);\n });\n};\n\nconst syncFilmstripSlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-gallery-visible');\n slide.removeAttribute('data-before');\n slide.removeAttribute('data-after');\n });\n};\n\nconst syncGallerySlides = (slides: HTMLElement[], current: number): void => {\n const count = slides.length;\n\n slides.forEach((slide, i) => {\n const active = i === current;\n const isPrev = current > 0 && i === current - 1;\n const isNext = current < count - 1 && i === current + 1;\n\n slide.toggleAttribute('data-active', active);\n slide.setAttribute('aria-hidden', String(!active));\n slide.setAttribute('aria-label', slide.getAttribute('aria-label') ?? `Slide ${i + 1} of ${count}`);\n slide.removeAttribute('data-before');\n slide.removeAttribute('data-after');\n slide.toggleAttribute('data-gallery-visible', active || isPrev || isNext);\n });\n};\n\n// ── ore-carousel ──────────────────────────────────────────────────────────────\n\ndefine<OreCarouselProps>(CAROUSEL_TAG, {\n props: {\n autoplay: prop.bool(false),\n 'autoplay-interval': prop.number(5000),\n color: prop.string<ThemeColor>(),\n label: prop.string('Carousel'),\n loop: prop.bool(true),\n orientation: prop.string<CarouselOrientation>('horizontal'),\n 'show-controls': prop.bool(true),\n 'show-indicators': prop.bool(true),\n 'slide-index': prop.number(0),\n variant: prop.string<CarouselVariant>('default'),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreCarouselEvents>();\n\n // ── State ────────────────────────────────────────────────────────────────\n\n const activeIndex = signal<number>(props['slide-index'].value ?? 0);\n let autoplayTimer: ReturnType<typeof setInterval> | null = null;\n\n // Slide cache — populated immediately, refreshed on slotchange.\n let slides: HTMLElement[] = Array.from(el.querySelectorAll<HTMLElement>(':scope > ore-carousel-slide'));\n const slideCount = signal(slides.length);\n\n const refreshSlides = (): void => {\n slides = Array.from(el.querySelectorAll<HTMLElement>(':scope > ore-carousel-slide'));\n slideCount.value = slides.length;\n };\n\n const isHorizontal = computed(() => props.orientation.value !== 'vertical');\n const looping = computed(() => props.loop.value !== false);\n const showControls = computed(() => props['show-controls'].value !== false);\n const showIndicators = computed(() => props['show-indicators'].value !== false);\n\n const canGoPrev = computed(() => looping.value || activeIndex.value > 0);\n const canGoNext = computed(() => looping.value || activeIndex.value < slideCount.value - 1);\n\n // ── Navigation ───────────────────────────────────────────────────────────\n\n const goTo = (index: number, announce_: boolean = true): void => {\n const count = slideCount.value;\n\n if (count === 0) return;\n\n const next = looping.value ? ((index % count) + count) % count : Math.max(0, Math.min(index, count - 1));\n\n if (next === activeIndex.value) return;\n\n activeIndex.value = next;\n el.setAttribute('slide-index', String(next));\n emit('change', { index: next });\n\n if (announce_) {\n announce(slides[next]?.getAttribute('aria-label') ?? `Slide ${next + 1} of ${count}`, {\n document: el.ownerDocument,\n });\n }\n };\n\n const prev = (): void => goTo(activeIndex.value - 1);\n const next = (): void => goTo(activeIndex.value + 1);\n\n // ── Sync prop → state ────────────────────────────────────────────────────\n\n watch(props['slide-index'], (v) => {\n if (typeof v === 'number' && v !== activeIndex.value) goTo(v, false);\n });\n\n // ── Sync variant + orientation → slides ──────────────────────────────────\n\n const syncSlideVariants = (): void => {\n const variant = props.variant.value ?? 'default';\n const orientation = props.orientation.value ?? 'horizontal';\n\n slides.forEach((slide) => {\n slide.setAttribute('data-variant', variant);\n slide.setAttribute('data-orientation', orientation);\n });\n };\n\n watch(\n computed(() => ({ orientation: props.orientation.value, variant: props.variant.value })),\n syncSlideVariants,\n { immediate: true },\n );\n\n // ── Sync active index → slides — dispatch table ───────────────────────────\n\n const syncTable: Record<CarouselVariant, (slides: HTMLElement[], current: number) => void> = {\n default: syncDefaultSlides,\n fade: syncDefaultSlides,\n filmstrip: syncFilmstripSlides,\n gallery: syncGallerySlides,\n };\n\n const syncActiveState = (): void => {\n const variant = (props.variant.value ?? 'default') as CarouselVariant;\n\n syncTable[variant](slides, activeIndex.value);\n };\n\n watch(activeIndex, syncActiveState, { immediate: true });\n\n // ── Autoplay ─────────────────────────────────────────────────────────────\n\n const startAutoplay = (): void => {\n if (autoplayTimer !== null) return;\n\n autoplayTimer = setInterval(next, props['autoplay-interval'].value ?? 5000);\n };\n\n const stopAutoplay = (): void => {\n if (autoplayTimer !== null) {\n clearInterval(autoplayTimer);\n autoplayTimer = null;\n }\n };\n\n watch(\n computed(() => ({ enabled: props.autoplay.value, interval: props['autoplay-interval'].value })),\n ({ enabled }) => {\n stopAutoplay();\n\n if (enabled) startAutoplay();\n },\n );\n\n // ── Keyboard navigation ──────────────────────────────────────────────────\n\n const handleKeydown = (e: KeyboardEvent): void => {\n const isH = isHorizontal.value;\n const prevKey = isH ? 'ArrowLeft' : 'ArrowUp';\n const nextKey = isH ? 'ArrowRight' : 'ArrowDown';\n\n if (e.key === prevKey) {\n e.preventDefault();\n prev();\n } else if (e.key === nextKey) {\n e.preventDefault();\n next();\n } else if (e.key === 'Home') {\n e.preventDefault();\n goTo(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n goTo(slideCount.value - 1);\n }\n };\n\n const indicatorNav = createListControl<number>({\n getItems: () => Array.from({ length: slideCount.value }, (_, index) => index),\n loop: true,\n onNavigate: ({ index }) => {\n goTo(index);\n const dot = el.shadowRoot?.querySelector<HTMLElement>(`.indicator[data-index=\"${index}\"]`);\n\n dot?.focus();\n },\n orientation: () => (isHorizontal.value ? 'horizontal' : 'vertical'),\n signal: lifecycleSignal(onCleanup),\n });\n\n // ── Host bindings ────────────────────────────────────────────────────────\n\n bind({\n attr: {\n 'aria-label': () => props.label.value ?? 'Carousel',\n 'aria-roledescription': () => 'carousel',\n orientation: () => props.orientation.value ?? 'horizontal',\n role: () => 'region',\n style: () => `touch-action:${isHorizontal.value ? 'pan-y' : 'pan-x'}`,\n variant: () => props.variant.value ?? 'default',\n },\n on: {\n focusin: () => {\n stopAutoplay();\n },\n focusout: () => {\n if (props.autoplay.value) startAutoplay();\n },\n keydown: handleKeydown,\n pointerenter: () => {\n stopAutoplay();\n },\n pointerleave: () => {\n if (props.autoplay.value) startAutoplay();\n },\n },\n });\n\n // ── Lifecycle ────────────────────────────────────────────────────────────\n\n onMounted(() => {\n const pan = createPanGesture(el, {\n axis: () => (isHorizontal.value ? 'x' : 'y'),\n onEnd: ({ distance, reason }) => {\n if (reason !== 'release' || Math.abs(distance) < 48) return;\n\n if (distance < 0) next();\n else prev();\n },\n shouldStart: (event) => {\n const path = event.composedPath();\n\n return !path.some(\n (node) =>\n node instanceof HTMLElement &&\n (node.tagName === 'BUTTON' || node.tagName === 'ORE-BUTTON' || node.tagName === 'ORE-PROGRESS'),\n );\n },\n });\n const shadowRoot = el.shadowRoot!;\n const slot = shadowRoot.querySelector<HTMLSlotElement>('slot')!;\n\n // slotchange replaces MutationObserver — fires exactly when assigned\n // nodes change, scoped to this component's slot, no polling overhead.\n const onSlotChange = (): void => {\n refreshSlides();\n syncSlideVariants();\n syncActiveState();\n };\n\n slot.addEventListener('slotchange', onSlotChange);\n\n if (props.autoplay.value) {\n startAutoplay();\n }\n\n return () => {\n pan.dispose();\n stopAutoplay();\n slot.removeEventListener('slotchange', onSlotChange);\n };\n });\n\n // ── Template ─────────────────────────────────────────────────────────────\n\n const renderControls = () =>\n showControls.value\n ? html`\n <div class=\"controls\" part=\"controls\">\n <ore-button\n class=\"nav-btn prev-btn\"\n part=\"prev-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n aria-label=\"Previous slide\"\n disabled=${() => (!canGoPrev.value ? true : undefined)}\n @click=${(e: Event) => {\n e.stopPropagation();\n prev();\n }}>\n <ore-icon\n name=${() => (isHorizontal.value ? 'chevron-left' : 'chevron-up')}\n size=\"16\"\n stroke-width=\"2\"\n aria-hidden=\"true\"></ore-icon>\n </ore-button>\n <ore-button\n class=\"nav-btn next-btn\"\n part=\"next-btn\"\n variant=\"ghost\"\n color=${() => props.color.value}\n rounded\n icon-only\n aria-label=\"Next slide\"\n disabled=${() => (!canGoNext.value ? true : undefined)}\n @click=${(e: Event) => {\n e.stopPropagation();\n next();\n }}>\n <ore-icon\n name=${() => (isHorizontal.value ? 'chevron-right' : 'chevron-down')}\n size=\"16\"\n stroke-width=\"2\"\n aria-hidden=\"true\"></ore-icon>\n </ore-button>\n </div>\n `\n : html``;\n\n return html`\n <div class=\"track\" part=\"track\" aria-live=${() => (props.autoplay.value ? 'off' : 'polite')}>\n <slot></slot>\n </div>\n\n ${() =>\n showIndicators.value && slideCount.value > 1\n ? html`\n <div class=\"indicators\" part=\"indicators\" role=\"tablist\" aria-label=\"Slide indicators\">\n ${() =>\n Array.from(\n { length: slideCount.value },\n (_, i) => html`\n <button\n type=\"button\"\n role=\"tab\"\n data-index=\"${i}\"\n class=${() => `indicator${i === activeIndex.value ? ' indicator-active' : ''}`}\n aria-selected=${() => String(i === activeIndex.value)}\n tabindex=${() => (i === activeIndex.value ? '0' : '-1')}\n aria-label=\"${`Go to slide ${i + 1}`}\"\n @focus=${() => indicatorNav.set(i)}\n @click=${() => goTo(i)}\n @keydown=${(e: KeyboardEvent) => {\n const target = e.currentTarget;\n\n if (target instanceof HTMLElement) {\n const index = Number(target.dataset.index);\n\n if (Number.isInteger(index)) indicatorNav.set(index);\n }\n\n indicatorNav.handleKeydown(e);\n }}>\n <ore-progress\n aria-hidden=\"true\"\n tabindex=\"-1\"\n color=${() => props.color.value}\n type=${() => (isHorizontal.value ? 'linear' : 'vertical')}\n value=${() => (i === activeIndex.value ? 100 : 0)}\n style=${() => {\n const fillAnim = isHorizontal.value ? 'carousel-fill' : 'carousel-fill-v';\n\n return `--carousel-timeout:${props['autoplay-interval'].value ?? 5000};--carousel-animation-name:${i === activeIndex.value && props.autoplay.value ? fillAnim : 'none'}`;\n }}></ore-progress>\n </button>\n `,\n )}\n ${renderControls}\n </div>\n `\n : html`\n ${renderControls}\n `}\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":"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"}
@@ -1,23 +1,23 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./core/announcer.cjs"),n=require("./content/chat-message/chat-message.css.cjs");let r=require("@vielzeug/ore"),i=require("@vielzeug/ripple");var a={assistant:`Assistant`,system:`System`,user:`You`},o=`ore-chat-message`;(0,r.define)(o,{props:{error:r.prop.string(),name:r.prop.string(),sender:r.prop.oneOf([`user`,`assistant`,`system`],`assistant`),status:r.prop.string(),streaming:r.prop.bool(!1),timestamp:r.prop.string()},setup(e){let n=(0,r.useEmit)(),o=(0,r.useSlots)(),s=()=>a[e.sender.value??`assistant`],c=(0,i.computed)(()=>`Message from ${e.name.value||s()}`),l=(0,i.computed)(()=>{let t=e.timestamp.value;if(!t)return``;let n=new Date(t);return Number.isNaN(n.getTime())?``:new Intl.DateTimeFormat(void 0,{hour:`numeric`,minute:`2-digit`}).format(n)}),u=(0,i.computed)(()=>!!l.value||!!e.status.value||!!e.error.value);(0,i.watch)(()=>e.status.value===`error`?e.error.value??``:null,e=>{e!==null&&t.announce(`Message failed to send${e?`: ${e}`:``}`,{politeness:`assertive`})});function d(e){n(`retry`,{originalEvent:e})}let f=(0,r.ref)();function p(){let e=f.value?.assignedNodes({flatten:!0});if(!(!e||e.length===0)){for(let t of e){if(t.nodeType!==Node.TEXT_NODE)break;let e=(t.textContent??``).replace(/^\s+/,``);if(t.textContent=e,e!==``)break}for(let t=e.length-1;t>=0;t--){let n=e[t];if(n.nodeType!==Node.TEXT_NODE)break;let r=(n.textContent??``).replace(/\s+$/,``);if(n.textContent=r,r!==``)break}}}return(0,r.onMounted)(()=>{p();let e=f.value;e&&(0,r.onEvent)(e,`slotchange`,p)}),r.html`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./core/announcer.cjs"),n=require("./content/chat-message/chat-message.css.cjs");let r=require("@vielzeug/ore"),i=require("@vielzeug/ripple");var a={assistant:`Assistant`,system:`System`,user:`You`},o=`ore-chat-message`;(0,r.define)(o,{props:{error:r.prop.string(),name:r.prop.string(),sender:r.prop.oneOf([`user`,`assistant`,`system`],`assistant`),status:r.prop.string(),streaming:r.prop.bool(!1),timestamp:r.prop.string()},setup(e){let n=(0,r.getHost)(),o=(0,r.useEmit)(),s=(0,r.useSlots)(),c=()=>a[e.sender.value??`assistant`],l=(0,i.computed)(()=>`Message from ${e.name.value||c()}`),u=(0,i.computed)(()=>{let t=e.timestamp.value;if(!t)return``;let n=new Date(t);return Number.isNaN(n.getTime())?``:new Intl.DateTimeFormat(void 0,{hour:`numeric`,minute:`2-digit`}).format(n)}),d=(0,i.computed)(()=>!!u.value||!!e.status.value||!!e.error.value);(0,i.watch)(()=>e.status.value===`error`?e.error.value??``:null,e=>{e!==null&&t.announce(`Message failed to send${e?`: ${e}`:``}`,{document:n.ownerDocument,politeness:`assertive`})});function f(e){o(`retry`,{originalEvent:e})}let p=(0,r.ref)();function m(){let e=p.value?.assignedNodes({flatten:!0});if(!(!e||e.length===0)){for(let t of e){if(t.nodeType!==Node.TEXT_NODE)break;let e=(t.textContent??``).replace(/^\s+/,``);if(t.textContent=e,e!==``)break}for(let t=e.length-1;t>=0;t--){let n=e[t];if(n.nodeType!==Node.TEXT_NODE)break;let r=(n.textContent??``).replace(/\s+$/,``);if(n.textContent=r,r!==``)break}}}return(0,r.onMounted)(()=>{m();let e=p.value;e&&(0,r.onEvent)(e,`slotchange`,m)}),r.html`
2
2
  <div class="row" part="row">
3
- <span class="avatar" part="avatar" ?hidden="${()=>!o.has(`avatar`).value}">
3
+ <span class="avatar" part="avatar" ?hidden="${()=>!s.has(`avatar`).value}">
4
4
  <slot name="avatar"></slot>
5
5
  </span>
6
6
  <div class="column" part="column">
7
7
  <span class="name" part="name" ?hidden="${()=>!e.name.value}">${e.name}</span>
8
- <div class="bubble" part="bubble" role="article" aria-label="${c}">
8
+ <div class="bubble" part="bubble" role="article" aria-label="${l}">
9
9
  <div class="content" part="content">
10
- <slot class="content-slot" ref="${f}"></slot>
10
+ <slot class="content-slot" ref="${p}"></slot>
11
11
  <span class="cursor" part="cursor" aria-hidden="true" ?hidden="${()=>!e.streaming.value}"></span>
12
12
  </div>
13
13
  </div>
14
- <div class="meta" part="meta" ?hidden="${()=>!u.value}">
14
+ <div class="meta" part="meta" ?hidden="${()=>!d.value}">
15
15
  <time
16
16
  class="timestamp"
17
17
  part="timestamp"
18
18
  datetime="${e.timestamp}"
19
- ?hidden="${()=>!l.value}">
20
- ${l}
19
+ ?hidden="${()=>!u.value}">
20
+ ${u}
21
21
  </time>
22
22
  <span class="status" part="status" data-status="${e.status}" ?hidden="${()=>!e.status.value}">
23
23
  ${()=>{switch(e.status.value){case`error`:return r.html`
@@ -36,11 +36,11 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
36
36
  part="retry"
37
37
  type="button"
38
38
  ?hidden="${()=>e.status.value!==`error`}"
39
- @click="${d}">
39
+ @click="${f}">
40
40
  Retry
41
41
  </button>
42
42
  </div>
43
- <div class="actions" part="actions" ?hidden="${()=>!o.has(`actions`).value}">
43
+ <div class="actions" part="actions" ?hidden="${()=>!s.has(`actions`).value}">
44
44
  <slot name="actions"></slot>
45
45
  </div>
46
46
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"chat-message.cjs","names":[],"sources":["../src/content/chat-message/chat-message.ts"],"sourcesContent":["import { define, html, onEvent, onMounted, prop, ref, useEmit, useSlots } from '@vielzeug/ore';\nimport { computed, watch } from '@vielzeug/ripple';\n\nimport { announce } from '../../core';\nimport { reducedMotionMixin } from '../../styles';\nimport '../icon/icon';\nimport componentStyles from './chat-message.css?inline';\n\nexport type ChatMessageSender = 'assistant' | 'system' | 'user';\nexport type ChatMessageStatus = 'error' | 'sending' | 'sent';\n\nconst SENDER_LABELS: Record<ChatMessageSender, string> = {\n assistant: 'Assistant',\n system: 'System',\n user: 'You',\n};\n\n/** Events emitted by the chat-message component */\nexport type OreChatMessageEvents = {\n /** Fired when the retry action is activated on a `status=\"error\"` message */\n retry: { originalEvent?: Event };\n};\n\n/** Chat message component properties */\nexport type OreChatMessageProps = {\n /** Error detail shown beneath the bubble; only visible when `status=\"error\"` */\n error?: string;\n /** Display name shown above the bubble (falls back to a generic label per `sender`) */\n name?: string;\n /** Who sent the message — controls alignment and bubble styling */\n sender?: ChatMessageSender;\n /** Delivery status for outgoing messages — shows an inline indicator and, for `\"error\"`, a retry action */\n status?: ChatMessageStatus;\n /** Append a blinking cursor after the content, for a message still streaming in */\n streaming?: boolean;\n /** ISO 8601 timestamp; rendered as a localized short time in a semantic `<time>` element */\n timestamp?: string;\n};\n\n/**\n * A single message bubble for chat/conversation UIs — sender-aware alignment, an optional\n * avatar slot, delivery status (sending/sent/error with retry), and a streaming cursor for\n * assistant messages still generating. Content is provided via the default slot, so any\n * markdown-to-HTML rendering stays the consumer's choice.\n *\n * The default slot's leading/trailing whitespace-only text nodes are trimmed in place once\n * per slot assignment (mount, and again on `slotchange`) — pretty-printed HTML's\n * indentation would otherwise render as blank lines, since the bubble preserves line breaks\n * (`white-space: pre-wrap`) for genuine multi-paragraph replies. This mutates those specific\n * text nodes' `textContent` directly; it never touches nodes appended afterward (e.g. by\n * `el.textContent += token` while streaming), only the ones present at assignment time.\n *\n * @element ore-chat-message\n *\n * @attr {string} sender - Who sent the message: 'user' | 'assistant' | 'system' (default 'assistant')\n * @attr {string} name - Display name shown above the bubble\n * @attr {string} timestamp - ISO 8601 timestamp, rendered as a localized short time\n * @attr {string} status - Delivery status: 'sending' | 'sent' | 'error'\n * @attr {string} error - Error detail shown beneath the bubble when status=\"error\"\n * @attr {boolean} streaming - Append a blinking cursor after the content\n *\n * @fires retry - Fired when the retry action is activated. detail: { originalEvent }\n *\n * @slot - Message content\n * @slot avatar - Avatar element (e.g. `<ore-avatar>`)\n * @slot actions - Action buttons shown beneath the message (copy, regenerate, feedback, …)\n *\n * @cssprop --chat-message-bg - Bubble background color (assistant/system)\n * @cssprop --chat-message-color - Bubble text color (assistant/system)\n * @cssprop --chat-message-user-bg - Bubble background color (user)\n * @cssprop --chat-message-user-color - Bubble text color (user)\n * @cssprop --chat-message-radius - Bubble border radius\n * @cssprop --chat-message-max-width - Maximum bubble width\n *\n * @part row - Root row container (avatar + column)\n * @part avatar - Avatar slot container\n * @part column - Column containing name, bubble, meta, and actions\n * @part name - Sender display name\n * @part bubble - Message bubble container\n * @part content - Content container inside the bubble\n * @part cursor - Blinking streaming cursor\n * @part meta - Row of timestamp, status, and error text below the bubble\n * @part timestamp - Timestamp `<time>` element\n * @part status - Status indicator\n * @part error - Error text\n * @part retry - Retry button (status=\"error\" only)\n * @part actions - Actions slot container\n *\n * @example\n * ```html\n * <ore-chat-message sender=\"user\" timestamp=\"2024-01-01T12:00:00Z\" status=\"sent\">\n * What's the weather like today?\n * </ore-chat-message>\n *\n * <ore-chat-message sender=\"assistant\" name=\"Assistant\" streaming>\n * Let me check that for you\n * </ore-chat-message>\n * ```\n */\nexport const CHAT_MESSAGE_TAG = 'ore-chat-message' as const;\ndefine<OreChatMessageProps>(CHAT_MESSAGE_TAG, {\n props: {\n error: prop.string(),\n name: prop.string(),\n sender: prop.oneOf(['user', 'assistant', 'system'] as const, 'assistant'),\n status: prop.string<ChatMessageStatus>(),\n streaming: prop.bool(false),\n timestamp: prop.string(),\n },\n setup(props) {\n const emit = useEmit<OreChatMessageEvents>();\n const slots = useSlots();\n\n const senderLabel = () => SENDER_LABELS[props.sender.value ?? 'assistant'];\n\n const bubbleLabel = computed(() => `Message from ${props.name.value || senderLabel()}`);\n\n const formattedTime = computed(() => {\n const raw = props.timestamp.value;\n\n if (!raw) return '';\n\n const date = new Date(raw);\n\n if (Number.isNaN(date.getTime())) return '';\n\n return new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit' }).format(date);\n });\n\n const hasMeta = computed(\n () => Boolean(formattedTime.value) || Boolean(props.status.value) || Boolean(props.error.value),\n );\n\n // Announce delivery failures — the visible retry button and error text are always present\n // for sighted users, but a failed send is exactly the kind of state change a screen\n // reader user could otherwise miss entirely. Keyed on `error` text too (not just\n // `status`) so a retry that fails again with a *different* reason re-announces even\n // though `status` never left `\"error\"` in between; a second failure with the identical\n // message doesn't re-fire, since the source value hasn't actually changed.\n watch(\n () => (props.status.value === 'error' ? (props.error.value ?? '') : null),\n (errorText) => {\n if (errorText !== null) {\n announce(`Message failed to send${errorText ? `: ${errorText}` : ''}`, { politeness: 'assertive' });\n }\n },\n );\n\n function handleRetry(e: Event): void {\n emit('retry', { originalEvent: e });\n }\n\n // ── Trim author-time indentation whitespace from the default slot ──\n // `.content` uses `white-space: pre-wrap` so genuine multi-paragraph replies keep their\n // line breaks — but that also preserves the leading/trailing newline + indentation from\n // pretty-printed HTML (`<ore-chat-message>\\n Hello\\n</ore-chat-message>`), rendering as\n // visible blank lines around the text. Trim only the outermost edges once per slot\n // assignment; appending tokens to an existing text node for streaming doesn't re-fire\n // `slotchange`, so this never interferes with in-progress streaming updates.\n //\n // A named-slotted sibling (e.g. `<ore-avatar slot=\"avatar\">` between the opening tag and\n // the message text) splits the default slot's light-DOM text into *multiple* text nodes\n // — text nodes can't target a named slot, so each run on either side of the element is\n // assigned separately. The \"real\" content can start on a later node than index 0 (it's\n // still preceded by its own leading indentation), so this walks inward from each end,\n // fully clearing whitespace-only nodes and stopping at the first node with real content\n // on each side — rather than only touching the very first/last assigned node.\n const contentSlotRef = ref<HTMLSlotElement>();\n\n function trimSlotEdgeWhitespace(): void {\n const nodes = contentSlotRef.value?.assignedNodes({ flatten: true });\n\n if (!nodes || nodes.length === 0) return;\n\n for (const node of nodes) {\n if (node.nodeType !== Node.TEXT_NODE) break;\n\n const trimmed = (node.textContent ?? '').replace(/^\\s+/, '');\n\n node.textContent = trimmed;\n\n if (trimmed !== '') break;\n }\n\n for (let i = nodes.length - 1; i >= 0; i--) {\n const node = nodes[i];\n\n if (node.nodeType !== Node.TEXT_NODE) break;\n\n const trimmed = (node.textContent ?? '').replace(/\\s+$/, '');\n\n node.textContent = trimmed;\n\n if (trimmed !== '') break;\n }\n }\n\n onMounted(() => {\n trimSlotEdgeWhitespace();\n\n const slotEl = contentSlotRef.value;\n\n if (slotEl) onEvent(slotEl, 'slotchange', trimSlotEdgeWhitespace);\n });\n\n return html`\n <div class=\"row\" part=\"row\">\n <span class=\"avatar\" part=\"avatar\" ?hidden=\"${() => !slots.has('avatar').value}\">\n <slot name=\"avatar\"></slot>\n </span>\n <div class=\"column\" part=\"column\">\n <span class=\"name\" part=\"name\" ?hidden=\"${() => !props.name.value}\">${props.name}</span>\n <div class=\"bubble\" part=\"bubble\" role=\"article\" aria-label=\"${bubbleLabel}\">\n <div class=\"content\" part=\"content\">\n <slot class=\"content-slot\" ref=\"${contentSlotRef}\"></slot>\n <span class=\"cursor\" part=\"cursor\" aria-hidden=\"true\" ?hidden=\"${() => !props.streaming.value}\"></span>\n </div>\n </div>\n <div class=\"meta\" part=\"meta\" ?hidden=\"${() => !hasMeta.value}\">\n <time\n class=\"timestamp\"\n part=\"timestamp\"\n datetime=\"${props.timestamp}\"\n ?hidden=\"${() => !formattedTime.value}\">\n ${formattedTime}\n </time>\n <span class=\"status\" part=\"status\" data-status=\"${props.status}\" ?hidden=\"${() => !props.status.value}\">\n ${() => {\n switch (props.status.value) {\n case 'error':\n return html`\n <ore-icon name=\"alert-circle\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n `;\n case 'sending':\n return html`\n <span class=\"spinner\" aria-hidden=\"true\"></span>\n `;\n case 'sent':\n return html`\n <ore-icon name=\"check\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n `;\n default:\n return '';\n }\n }}\n </span>\n <span class=\"error-text\" part=\"error\" role=\"alert\" ?hidden=\"${() => !props.error.value}\">\n ${props.error}\n </span>\n <button\n class=\"retry\"\n part=\"retry\"\n type=\"button\"\n ?hidden=\"${() => props.status.value !== 'error'}\"\n @click=\"${handleRetry}\">\n Retry\n </button>\n </div>\n <div class=\"actions\" part=\"actions\" ?hidden=\"${() => !slots.has('actions').value}\">\n <slot name=\"actions\"></slot>\n </div>\n </div>\n </div>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"chat-message.cjs","names":[],"sources":["../src/content/chat-message/chat-message.ts"],"sourcesContent":["import { define, getHost, html, onEvent, onMounted, prop, ref, useEmit, useSlots } from '@vielzeug/ore';\nimport { computed, watch } from '@vielzeug/ripple';\n\nimport { announce } from '../../core';\nimport { reducedMotionMixin } from '../../styles';\nimport '../icon/icon';\nimport componentStyles from './chat-message.css?inline';\n\nexport type ChatMessageSender = 'assistant' | 'system' | 'user';\nexport type ChatMessageStatus = 'error' | 'sending' | 'sent';\n\nconst SENDER_LABELS: Record<ChatMessageSender, string> = {\n assistant: 'Assistant',\n system: 'System',\n user: 'You',\n};\n\n/** Events emitted by the chat-message component */\nexport type OreChatMessageEvents = {\n /** Fired when the retry action is activated on a `status=\"error\"` message */\n retry: { originalEvent?: Event };\n};\n\n/** Chat message component properties */\nexport type OreChatMessageProps = {\n /** Error detail shown beneath the bubble; only visible when `status=\"error\"` */\n error?: string;\n /** Display name shown above the bubble (falls back to a generic label per `sender`) */\n name?: string;\n /** Who sent the message — controls alignment and bubble styling */\n sender?: ChatMessageSender;\n /** Delivery status for outgoing messages — shows an inline indicator and, for `\"error\"`, a retry action */\n status?: ChatMessageStatus;\n /** Append a blinking cursor after the content, for a message still streaming in */\n streaming?: boolean;\n /** ISO 8601 timestamp; rendered as a localized short time in a semantic `<time>` element */\n timestamp?: string;\n};\n\n/**\n * A single message bubble for chat/conversation UIs — sender-aware alignment, an optional\n * avatar slot, delivery status (sending/sent/error with retry), and a streaming cursor for\n * assistant messages still generating. Content is provided via the default slot, so any\n * markdown-to-HTML rendering stays the consumer's choice.\n *\n * The default slot's leading/trailing whitespace-only text nodes are trimmed in place once\n * per slot assignment (mount, and again on `slotchange`) — pretty-printed HTML's\n * indentation would otherwise render as blank lines, since the bubble preserves line breaks\n * (`white-space: pre-wrap`) for genuine multi-paragraph replies. This mutates those specific\n * text nodes' `textContent` directly; it never touches nodes appended afterward (e.g. by\n * `el.textContent += token` while streaming), only the ones present at assignment time.\n *\n * @element ore-chat-message\n *\n * @attr {string} sender - Who sent the message: 'user' | 'assistant' | 'system' (default 'assistant')\n * @attr {string} name - Display name shown above the bubble\n * @attr {string} timestamp - ISO 8601 timestamp, rendered as a localized short time\n * @attr {string} status - Delivery status: 'sending' | 'sent' | 'error'\n * @attr {string} error - Error detail shown beneath the bubble when status=\"error\"\n * @attr {boolean} streaming - Append a blinking cursor after the content\n *\n * @fires retry - Fired when the retry action is activated. detail: { originalEvent }\n *\n * @slot - Message content\n * @slot avatar - Avatar element (e.g. `<ore-avatar>`)\n * @slot actions - Action buttons shown beneath the message (copy, regenerate, feedback, …)\n *\n * @cssprop --chat-message-bg - Bubble background color (assistant/system)\n * @cssprop --chat-message-color - Bubble text color (assistant/system)\n * @cssprop --chat-message-user-bg - Bubble background color (user)\n * @cssprop --chat-message-user-color - Bubble text color (user)\n * @cssprop --chat-message-radius - Bubble border radius\n * @cssprop --chat-message-max-width - Maximum bubble width\n *\n * @part row - Root row container (avatar + column)\n * @part avatar - Avatar slot container\n * @part column - Column containing name, bubble, meta, and actions\n * @part name - Sender display name\n * @part bubble - Message bubble container\n * @part content - Content container inside the bubble\n * @part cursor - Blinking streaming cursor\n * @part meta - Row of timestamp, status, and error text below the bubble\n * @part timestamp - Timestamp `<time>` element\n * @part status - Status indicator\n * @part error - Error text\n * @part retry - Retry button (status=\"error\" only)\n * @part actions - Actions slot container\n *\n * @example\n * ```html\n * <ore-chat-message sender=\"user\" timestamp=\"2024-01-01T12:00:00Z\" status=\"sent\">\n * What's the weather like today?\n * </ore-chat-message>\n *\n * <ore-chat-message sender=\"assistant\" name=\"Assistant\" streaming>\n * Let me check that for you\n * </ore-chat-message>\n * ```\n */\nexport const CHAT_MESSAGE_TAG = 'ore-chat-message' as const;\ndefine<OreChatMessageProps>(CHAT_MESSAGE_TAG, {\n props: {\n error: prop.string(),\n name: prop.string(),\n sender: prop.oneOf(['user', 'assistant', 'system'] as const, 'assistant'),\n status: prop.string<ChatMessageStatus>(),\n streaming: prop.bool(false),\n timestamp: prop.string(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreChatMessageEvents>();\n const slots = useSlots();\n\n const senderLabel = () => SENDER_LABELS[props.sender.value ?? 'assistant'];\n\n const bubbleLabel = computed(() => `Message from ${props.name.value || senderLabel()}`);\n\n const formattedTime = computed(() => {\n const raw = props.timestamp.value;\n\n if (!raw) return '';\n\n const date = new Date(raw);\n\n if (Number.isNaN(date.getTime())) return '';\n\n return new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit' }).format(date);\n });\n\n const hasMeta = computed(\n () => Boolean(formattedTime.value) || Boolean(props.status.value) || Boolean(props.error.value),\n );\n\n // Announce delivery failures — the visible retry button and error text are always present\n // for sighted users, but a failed send is exactly the kind of state change a screen\n // reader user could otherwise miss entirely. Keyed on `error` text too (not just\n // `status`) so a retry that fails again with a *different* reason re-announces even\n // though `status` never left `\"error\"` in between; a second failure with the identical\n // message doesn't re-fire, since the source value hasn't actually changed.\n watch(\n () => (props.status.value === 'error' ? (props.error.value ?? '') : null),\n (errorText) => {\n if (errorText !== null) {\n announce(`Message failed to send${errorText ? `: ${errorText}` : ''}`, {\n document: el.ownerDocument,\n politeness: 'assertive',\n });\n }\n },\n );\n\n function handleRetry(e: Event): void {\n emit('retry', { originalEvent: e });\n }\n\n // ── Trim author-time indentation whitespace from the default slot ──\n // `.content` uses `white-space: pre-wrap` so genuine multi-paragraph replies keep their\n // line breaks — but that also preserves the leading/trailing newline + indentation from\n // pretty-printed HTML (`<ore-chat-message>\\n Hello\\n</ore-chat-message>`), rendering as\n // visible blank lines around the text. Trim only the outermost edges once per slot\n // assignment; appending tokens to an existing text node for streaming doesn't re-fire\n // `slotchange`, so this never interferes with in-progress streaming updates.\n //\n // A named-slotted sibling (e.g. `<ore-avatar slot=\"avatar\">` between the opening tag and\n // the message text) splits the default slot's light-DOM text into *multiple* text nodes\n // — text nodes can't target a named slot, so each run on either side of the element is\n // assigned separately. The \"real\" content can start on a later node than index 0 (it's\n // still preceded by its own leading indentation), so this walks inward from each end,\n // fully clearing whitespace-only nodes and stopping at the first node with real content\n // on each side — rather than only touching the very first/last assigned node.\n const contentSlotRef = ref<HTMLSlotElement>();\n\n function trimSlotEdgeWhitespace(): void {\n const nodes = contentSlotRef.value?.assignedNodes({ flatten: true });\n\n if (!nodes || nodes.length === 0) return;\n\n for (const node of nodes) {\n if (node.nodeType !== Node.TEXT_NODE) break;\n\n const trimmed = (node.textContent ?? '').replace(/^\\s+/, '');\n\n node.textContent = trimmed;\n\n if (trimmed !== '') break;\n }\n\n for (let i = nodes.length - 1; i >= 0; i--) {\n const node = nodes[i];\n\n if (node.nodeType !== Node.TEXT_NODE) break;\n\n const trimmed = (node.textContent ?? '').replace(/\\s+$/, '');\n\n node.textContent = trimmed;\n\n if (trimmed !== '') break;\n }\n }\n\n onMounted(() => {\n trimSlotEdgeWhitespace();\n\n const slotEl = contentSlotRef.value;\n\n if (slotEl) onEvent(slotEl, 'slotchange', trimSlotEdgeWhitespace);\n });\n\n return html`\n <div class=\"row\" part=\"row\">\n <span class=\"avatar\" part=\"avatar\" ?hidden=\"${() => !slots.has('avatar').value}\">\n <slot name=\"avatar\"></slot>\n </span>\n <div class=\"column\" part=\"column\">\n <span class=\"name\" part=\"name\" ?hidden=\"${() => !props.name.value}\">${props.name}</span>\n <div class=\"bubble\" part=\"bubble\" role=\"article\" aria-label=\"${bubbleLabel}\">\n <div class=\"content\" part=\"content\">\n <slot class=\"content-slot\" ref=\"${contentSlotRef}\"></slot>\n <span class=\"cursor\" part=\"cursor\" aria-hidden=\"true\" ?hidden=\"${() => !props.streaming.value}\"></span>\n </div>\n </div>\n <div class=\"meta\" part=\"meta\" ?hidden=\"${() => !hasMeta.value}\">\n <time\n class=\"timestamp\"\n part=\"timestamp\"\n datetime=\"${props.timestamp}\"\n ?hidden=\"${() => !formattedTime.value}\">\n ${formattedTime}\n </time>\n <span class=\"status\" part=\"status\" data-status=\"${props.status}\" ?hidden=\"${() => !props.status.value}\">\n ${() => {\n switch (props.status.value) {\n case 'error':\n return html`\n <ore-icon name=\"alert-circle\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n `;\n case 'sending':\n return html`\n <span class=\"spinner\" aria-hidden=\"true\"></span>\n `;\n case 'sent':\n return html`\n <ore-icon name=\"check\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n `;\n default:\n return '';\n }\n }}\n </span>\n <span class=\"error-text\" part=\"error\" role=\"alert\" ?hidden=\"${() => !props.error.value}\">\n ${props.error}\n </span>\n <button\n class=\"retry\"\n part=\"retry\"\n type=\"button\"\n ?hidden=\"${() => props.status.value !== 'error'}\"\n @click=\"${handleRetry}\">\n Retry\n </button>\n </div>\n <div class=\"actions\" part=\"actions\" ?hidden=\"${() => !slots.has('actions').value}\">\n <slot name=\"actions\"></slot>\n </div>\n </div>\n </div>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"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"}
@@ -1,29 +1,29 @@
1
1
  import { reducedMotionMixin as e } from "./styles/mixins/accessibility.css.js";
2
2
  import { announce as t } from "./core/announcer.js";
3
3
  import n from "./content/chat-message/chat-message.css.js";
4
- import { define as r, html as i, onEvent as a, onMounted as o, prop as s, ref as c, useEmit as l, useSlots as u } from "@vielzeug/ore";
5
- import { computed as d, watch as f } from "@vielzeug/ripple";
4
+ import { define as r, getHost as i, html as a, onEvent as o, onMounted as s, prop as c, ref as l, useEmit as u, useSlots as d } from "@vielzeug/ore";
5
+ import { computed as f, watch as p } from "@vielzeug/ripple";
6
6
  //#region src/content/chat-message/chat-message.ts
7
- var p = {
7
+ var m = {
8
8
  assistant: "Assistant",
9
9
  system: "System",
10
10
  user: "You"
11
- }, m = "ore-chat-message";
12
- r(m, {
11
+ }, h = "ore-chat-message";
12
+ r(h, {
13
13
  props: {
14
- error: s.string(),
15
- name: s.string(),
16
- sender: s.oneOf([
14
+ error: c.string(),
15
+ name: c.string(),
16
+ sender: c.oneOf([
17
17
  "user",
18
18
  "assistant",
19
19
  "system"
20
20
  ], "assistant"),
21
- status: s.string(),
22
- streaming: s.bool(!1),
23
- timestamp: s.string()
21
+ status: c.string(),
22
+ streaming: c.bool(!1),
23
+ timestamp: c.string()
24
24
  },
25
25
  setup(e) {
26
- let n = l(), r = u(), s = () => p[e.sender.value ?? "assistant"], m = d(() => `Message from ${e.name.value || s()}`), h = d(() => {
26
+ let n = i(), r = u(), c = d(), h = () => m[e.sender.value ?? "assistant"], g = f(() => `Message from ${e.name.value || h()}`), _ = f(() => {
27
27
  let t = e.timestamp.value;
28
28
  if (!t) return "";
29
29
  let n = new Date(t);
@@ -31,16 +31,19 @@ r(m, {
31
31
  hour: "numeric",
32
32
  minute: "2-digit"
33
33
  }).format(n);
34
- }), g = d(() => !!h.value || !!e.status.value || !!e.error.value);
35
- f(() => e.status.value === "error" ? e.error.value ?? "" : null, (e) => {
36
- e !== null && t(`Message failed to send${e ? `: ${e}` : ""}`, { politeness: "assertive" });
34
+ }), v = f(() => !!_.value || !!e.status.value || !!e.error.value);
35
+ p(() => e.status.value === "error" ? e.error.value ?? "" : null, (e) => {
36
+ e !== null && t(`Message failed to send${e ? `: ${e}` : ""}`, {
37
+ document: n.ownerDocument,
38
+ politeness: "assertive"
39
+ });
37
40
  });
38
- function _(e) {
39
- n("retry", { originalEvent: e });
41
+ function y(e) {
42
+ r("retry", { originalEvent: e });
40
43
  }
41
- let v = c();
42
- function y() {
43
- let e = v.value?.assignedNodes({ flatten: !0 });
44
+ let b = l();
45
+ function x() {
46
+ let e = b.value?.assignedNodes({ flatten: !0 });
44
47
  if (!(!e || e.length === 0)) {
45
48
  for (let t of e) {
46
49
  if (t.nodeType !== Node.TEXT_NODE) break;
@@ -55,41 +58,41 @@ r(m, {
55
58
  }
56
59
  }
57
60
  }
58
- return o(() => {
59
- y();
60
- let e = v.value;
61
- e && a(e, "slotchange", y);
62
- }), i`
61
+ return s(() => {
62
+ x();
63
+ let e = b.value;
64
+ e && o(e, "slotchange", x);
65
+ }), a`
63
66
  <div class="row" part="row">
64
- <span class="avatar" part="avatar" ?hidden="${() => !r.has("avatar").value}">
67
+ <span class="avatar" part="avatar" ?hidden="${() => !c.has("avatar").value}">
65
68
  <slot name="avatar"></slot>
66
69
  </span>
67
70
  <div class="column" part="column">
68
71
  <span class="name" part="name" ?hidden="${() => !e.name.value}">${e.name}</span>
69
- <div class="bubble" part="bubble" role="article" aria-label="${m}">
72
+ <div class="bubble" part="bubble" role="article" aria-label="${g}">
70
73
  <div class="content" part="content">
71
- <slot class="content-slot" ref="${v}"></slot>
74
+ <slot class="content-slot" ref="${b}"></slot>
72
75
  <span class="cursor" part="cursor" aria-hidden="true" ?hidden="${() => !e.streaming.value}"></span>
73
76
  </div>
74
77
  </div>
75
- <div class="meta" part="meta" ?hidden="${() => !g.value}">
78
+ <div class="meta" part="meta" ?hidden="${() => !v.value}">
76
79
  <time
77
80
  class="timestamp"
78
81
  part="timestamp"
79
82
  datetime="${e.timestamp}"
80
- ?hidden="${() => !h.value}">
81
- ${h}
83
+ ?hidden="${() => !_.value}">
84
+ ${_}
82
85
  </time>
83
86
  <span class="status" part="status" data-status="${e.status}" ?hidden="${() => !e.status.value}">
84
87
  ${() => {
85
88
  switch (e.status.value) {
86
- case "error": return i`
89
+ case "error": return a`
87
90
  <ore-icon name="alert-circle" size="12" stroke-width="2.5" aria-hidden="true"></ore-icon>
88
91
  `;
89
- case "sending": return i`
92
+ case "sending": return a`
90
93
  <span class="spinner" aria-hidden="true"></span>
91
94
  `;
92
- case "sent": return i`
95
+ case "sent": return a`
93
96
  <ore-icon name="check" size="12" stroke-width="2.5" aria-hidden="true"></ore-icon>
94
97
  `;
95
98
  default: return "";
@@ -104,11 +107,11 @@ r(m, {
104
107
  part="retry"
105
108
  type="button"
106
109
  ?hidden="${() => e.status.value !== "error"}"
107
- @click="${_}">
110
+ @click="${y}">
108
111
  Retry
109
112
  </button>
110
113
  </div>
111
- <div class="actions" part="actions" ?hidden="${() => !r.has("actions").value}">
114
+ <div class="actions" part="actions" ?hidden="${() => !c.has("actions").value}">
112
115
  <slot name="actions"></slot>
113
116
  </div>
114
117
  </div>
@@ -118,6 +121,6 @@ r(m, {
118
121
  styles: [e, n]
119
122
  });
120
123
  //#endregion
121
- export { m as CHAT_MESSAGE_TAG };
124
+ export { h as CHAT_MESSAGE_TAG };
122
125
 
123
126
  //# sourceMappingURL=chat-message.js.map