|
|
5 months ago | |
|---|---|---|
| .. | ||
| README.md | 5 months ago | |
| index.ts | 5 months ago | |
| scroll-area-corner.svelte | 5 months ago | |
| scroll-area-scrollbar.svelte | 5 months ago | |
| scroll-area-thumb.svelte | 5 months ago | |
| scroll-area-viewport.svelte | 5 months ago | |
| scroll-area.css | 5 months ago | |
| scroll-area.svelte | 5 months ago | |
| types.ts | 5 months ago | |
README.md
Eidos ScrollArea
Custom-scrollbar container. Hides the native scrollbar and renders a
custom-painted track + thumb on top of a scrollable viewport. The
headless Soma layer owns the scroll math, the visibility timer
(type='hover' | 'scroll' | 'auto' | 'always') and the thumb
position. Eidos paints chrome only — track, thumb, corner, hover and
drag states.
Superficie
<ScrollArea type="hover" scrollHideDelay={600}>
<ScrollArea.Viewport>
<!-- scrollable content goes here -->
</ScrollArea.Viewport>
<ScrollArea.Scrollbar orientation="vertical">
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
<ScrollArea.Scrollbar orientation="horizontal">
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
<ScrollArea.Corner />
</ScrollArea>
The compound shape mirrors Radix Primitives and Bits UI 1:1: the consumer composes every part explicitly so a vertical-only ScrollArea omits the horizontal scrollbar (and the Corner) without prop gymnastics on the root.
Baseline
Origin: air/components/layout/scroll-area (branch morfo-runtime),
which itself wrapped the legacy Terra scroll-area implementation.
Adjustments applied during the port:
- Drop the
air-scroll-area-*class namespace. Eidos targets[data-scroll-area]/[data-scroll-area-viewport]/ etc., emitted by the Soma provider so the recipe never depends on consumer classes. - Replace the
--air-scroll-area-*token namespace with bare--scroll-area-*names per the eidos naming convention. The identifiers map to canonical UIX primitives:--scroll-area-track-bg→color-mixof--color-neutral-track--scroll-area-thumb-bg→--color-content-muted--scroll-area-scrollbar-size→--space-2-5--scroll-area-thumb-min-size→--space-3
- Soma is the source of truth. The eidos wrappers are pure
pass-through with no additional ARIA, no additional data-attrs,
no extra props. The morfo (
src/uix/morfo/components/scroll-area.ts) declares the contract — Provider / Viewport / Scrollbar / Thumb / Corner — and the Soma provider emits the markers; eidos just styles them. - Hover and drag states use the morfo's
data-hover/data-draggingpresence flags rather than CSS pseudo-classes, matching the rest of the UIX runtime.
Comparativa
| Capability | UIX (eidos) | Radix Primitives | Bits UI | react-aria-components |
|---|---|---|---|---|
Compound API (Provider/Viewport/Scrollbar/Thumb/Corner) |
Yes | Yes | Yes | Partial — single ScrollArea + virtual list |
| Scrollbar visibility modes | hover / scroll / auto / always (soma) |
Same four | Same four | always / auto |
| Horizontal + vertical scrollbars composed independently | Yes | Yes | Yes | Yes |
| Corner element | Yes | Yes | Yes | No |
| Auto-hide delay configurable | Yes (scrollHideDelay on soma) |
Yes (scrollHideDelay) |
Yes | No |
| RTL support | Yes (via soma dir) |
Yes | Yes | Yes |
| Touch action discipline | touch-action: none on bar (recipe) |
Same | Same | Same |
| Drag state styling | Yes (data-dragging) |
Yes (data-state) |
Yes | n/a |
| Custom thumb min size | Yes (--scroll-area-thumb-min-size) |
CSS variable | CSS variable | n/a |
Decisiones
- Soma is the source of state and ARIA. Eidos adds no
evaluative props — neither
sizenorvariant. Scrollbar visibility timing belongs to soma'stype/scrollHideDelaypair; the chrome's color and thickness live in CSS tokens. - Compound API only. No flat
<ScrollArea>shortcut. The consumer composes Viewport + Scrollbar(s) + Corner explicitly so a single-axis container doesn't pay for the unused parts. Mirrors Radix Primitives. Thumbalways sits insideScrollbar. The morfo declares it as an optional child (no compositional gymnastics needed): the Thumb's position and ARIA are wired through the parent Scrollbar's provider context, so the consumer can omit the Thumb to render a track-only scrollbar (rare but legal).- No prop on the eidos root. The headless
type/scrollHideDelay/dirgo through pass-through; the demo's controls bind directly to those soma props.
Eventos Sema
ScrollArea declares 0 events. Scrolling is continuous and
non-evaluative — there is no commit, no emerge, no contact. Soma
emits the data-state="visible" | "hidden" markers as the
scrollbar visibility transitions; if a consumer needs a sema
signature on visibility change, it composes ScrollArea inside a
larger primitive (e.g. a Drawer or Popover) that owns the relevant
verb.
The Scrollbar drag does not commit a sema event either: scroll position is a viewport concern, not a value the surrounding form should react to. Slider / NumberField / Splitter are the right primitives when a drag commits a value.
Gaps
| Gap | Disposition | Detail |
|---|---|---|
viewport style escape for inner padding |
implementar | Today the consumer applies padding inside the viewport's child. Adding a padding prop on Viewport (passed through to soma) would centralise the API. |
Sema event on scroll-to-edge (commit-end) |
diferir | Some apps need to know when the viewport hits top / bottom / start / end. Soma can emit data-at-top / data-at-bottom already; promoting that to a sema event is a v2 decision. |
intersectionThreshold for sticky headers |
diferir | Out of scope for the layout-primitive sweep. Belongs to a future <Sticky> primitive on top of ScrollArea. |
Theme variant subtle / strong for track |
descartar | Token overrides cover this already — var(--scroll-area-track-bg: …) is enough; a typed variant would manufacture an unused surface. |
| Auto-handle hover targeting via padding-box | descartar | Air shipped the padding directly on the bar; modern UAs honour the existing approach. |
Referencias
- Radix Primitives ScrollArea: https://www.radix-ui.com/primitives/docs/components/scroll-area
- Bits UI ScrollArea: https://bits-ui.com/docs/components/scroll-area
- react-aria-components ScrollArea: https://react-spectrum.adobe.com/react-aria/ScrollArea.html
- WAI-ARIA
scrollbarrole: https://www.w3.org/TR/wai-aria-1.2/#scrollbar
Passive justification
The morfo declares scope: ['soma'] (the headless layer owns
hover-timer state, drag math and scroll position), but no
semantic events. Scrolling itself is continuous and non-evaluative
— the user moves the viewport, the surrounding application reads
the scroll position; there is no commit / emerge / contact verb to
project onto a perceptual channel.
The Scrollbar's data-state (visible / hidden) IS a transition,
but it is a CSS visibility transition driven by the soma timer
rather than a user-authored intent. Promoting it to a sema event
would either:
- fire on every show / hide of the scrollbar (high-frequency pollution, equivalent to firing on every frame of a drag), or
- require an arbitrary debounce that the consumer would have to reverse-engineer.
For consumers that need to react to scroll position (sticky
headers, lazy-load), data-at-top / data-at-bottom /
data-at-left / data-at-right on the Viewport are sufficient and
do not require a sema layer.