import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import type { ScrollerSlots, ScrollerVariants } from './scroller.variants.js';
/**
* @summary A row that scrolls only when it has to — otherwise an ordinary row.
* @description Horizontal row of equal-rank items that becomes scrollable only
* when it runs out of room — an overflow behaviour, not a navigation pattern.
* On a wide viewport it is an ordinary row: no scrolling, no arrows, no dots,
* and none of the accessibility duties a scroll container carries. Once it does
* overflow it snaps to item boundaries, takes a tab stop (`role="group"` +
* `label`, so the keyboard can scroll it at all — the bug in most media rows),
* and can show jump buttons and dots. `align="center"` turns it into a centred
* stage whose middle item lifts via `animation-timeline: view(inline)` — pure
* CSS, and where that is unsupported the row is identical minus the lift. Use it
* for feature cards, product rows, media strips and chip/filter bars. It never
* auto-rotates. For one item at a time with paging semantics use Tab; for
* page-number navigation use Pagination.
*
* @tag layout
* @tag display
* @related Tab
* @related Toolbar
* @related Pagination
* @stability experimental
*
* @example
* ```svelte
*
* {#each features as feature (feature.id)}
*
* {/each}
*
* ```
*
* @example
* ```svelte
*
*
* {#each features as feature (feature.id)}
*
* {/each}
*
* ```
*/
export interface ScrollerProps extends Omit, Omit, 'children' | 'class'> {
/** The row's items. Each direct child becomes one snap target — width, snap alignment and (with `emphasis`) the lift are applied for you. Required. */
children: Snippet;
/**
* Accessible name for the row, e.g. "Main features". Required: once the row
* overflows it becomes a focusable `role="group"`, and an unnamed group is a
* nameless box to a screen reader. It is deliberately not optional-with-a-
* fallback — a generic default name would be worse than none.
*/
label: string;
/**
* Where an item comes to rest when the row snaps. `start` is the ordinary
* overflow row. `center` is a stage: the middle item is the subject, and the
* track is padded so the first and last item can reach the middle too.
*
* `center` needs items narrow enough that roughly three are visible with two
* peeking. Make them much wider and the centring padding takes over the row —
* one card adrift in empty space, which reads as a layout bug rather than a
* stage. The component warns about that in DEV.
* @default 'start'
* @summary Where an item comes to rest: an ordinary row, or a centre stage.
*/
align?: 'start' | 'center';
/**
* Snap strictness. `proximity` snaps when you release nearby and otherwise
* leaves scrolling alone; `mandatory` always lands on an item — right when one
* item at a time is the unit, but it can skip past content sitting between two
* snap points. `none` scrolls freely.
*
* The default follows `align`, because the two alignments mean different
* things: a `start` row is a list you sweep across (`proximity`), a `center`
* row is a stage whose middle has to land on something (`mandatory`).
* @default 'proximity' — or 'mandatory' when align="center"
* @summary How firmly scrolling lands on an item when you let go.
*/
snap?: 'proximity' | 'mandatory' | 'none';
/** Space between items. @default 'md' */
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
/**
* Width of each item, as any CSS length (`'18rem'`, `'clamp(14rem,60vw,22rem)'`).
* This is what decides when the row overflows, so it is a prop rather than a
* class: with `align="center"` the component needs it to compute the edge
* padding. Override per-item layout via `slotClasses.viewport` if you need
* responsive widths.
* @default '16rem'
* @summary Width of each item — what decides when the row starts to overflow.
*/
itemBasis?: string;
/**
* Previous/next buttons. `auto` shows them only while the row overflows —
* on a viewport where everything fits there is nothing to navigate, and
* controls for a problem that does not exist are just clutter. `always` keeps
* them mounted (disabled at the ends); `none` omits them.
* @default 'auto'
* @summary Whether the previous/next buttons show — always, or only while the row overflows.
*/
controls?: 'auto' | 'always' | 'none';
/**
* Position indicator. `dots` renders one button per **resting place** —
* every dot jumps to its own destination and the current one carries
* `aria-current`. On a centred row that is one dot per item. On a
* `start`-aligned row the trailing items that share the end of the scroll
* range share one dot, labelled with their range ("Items 4–5 of 5"): a row
* has only as many distinct resting places as it can scroll to, and a dot
* per item would light up elsewhere than the press. Leave it off for long
* chip bars, where a dot per position is noise either way.
* @default 'none'
* @summary A dot per resting place: shows how far the row goes, and jumps there.
*/
indicator?: 'none' | 'dots';
/**
* Lift the item in the middle of the scrollport — a scale plus an elevation
* step, driven by scroll position via CSS. `subtle` is a light touch, `strong`
* is visible across a room; past roughly `strong` a row wobbles while
* scrolling and pulls attention away from reading, which is why this is a
* scale and not a free number. Retune per instance with
* `--blocks-scroller-emphasis-scale` / `-shadow`.
*
* **Requires `align="center"`** and is a no-op otherwise (with a DEV warning):
* the lift marks the item that has arrived in the middle, and a start-aligned
* row has no middle. Neighbours are never dimmed or blurred — the point is to
* mark the middle, not to hide the rest. Respects `prefers-reduced-motion`,
* and where `animation-timeline` is unsupported it simply does nothing.
* @default 'none'
* @summary How strongly the middle item is lifted. Needs the centre alignment.
*/
emphasis?: 'none' | 'subtle' | 'strong';
/**
* Fires when the item at the snap anchor changes, with its zero-based index.
* Intentionally coarse — there is no per-frame scroll-position callback,
* because the CSS-native carousel primitives this component is meant to be
* swapped for one day could not honour one.
*/
onActiveChange?: (index: number) => void;
/** Accessible label for the previous button. @default 'Previous' (localised) */
previousLabel?: string;
/** Accessible label for the next button. @default 'Next' (localised) */
nextLabel?: string;
/** Extra classes merged onto the root container (the column holding the row and its control bar). */
class?: string;
/** Remove all default tv() classes; combine with `slotClasses` to rebuild the look. Note that this also strips the layout rules that make the row scroll and snap. */
unstyled?: boolean;
/**
* Per-slot class overrides merged with tv() styles. Slots: root (the column —
* what `class` also targets) | viewport (the scroll container; also where
* per-item rules like width live, via `[&>*]:…`) | controls (the bar under the
* row) | control (a previous/next button) | indicator (the dot group) | dot.
*/
slotClasses?: Partial>;
/**
* Apply a named preset registered via ``.
* Prefer this over `class` overrides when the requested look falls outside the
* semantic intent palette — presets keep hover/active/dark-mode logic coherent
* and make the custom look reusable across the project.
*/
preset?: string;
}
export { default as Scroller } from './Scroller.svelte';
export type { ScrollerAlign } from './scroller.utils.js';
export { type ScrollerVariants, scrollerVariants } from './scroller.variants.js';