From c5699bd019300ad9295fe536fa528cfc785a5db6 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 18 Jul 2026 19:39:15 +0200 Subject: [PATCH] uix(color): abre `color` en la familia lista/data-grid (10 componentes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migra table · tree-grid · tree-view · virtual-list · virtual-grid · feed · drag-drop · grid-list · clipboard · carousel al forward de paleta compartido. Patrón 2-slots: `--_{c}-accent` (indicador sort/activo) -> `--_{c}-palette-solid`, `--_{c}-accent-soft` (tinte de fila seleccionada) -> `--_{c}-palette-element`. Recipe nuevo con host = primary; el generador emite base default + forward. Se quita la cascada per-rol de cada css; tipo -> ComponentColorProp; wrapper vía `resolveComponentColor`. Detalles no uniformes resueltos: - Los 7 que estampan `data-color` en `[data-{c}-root]` usan `parts: ['root']` (forma declarations[], la única que el generador lee para parts) -> forward `[data-{c}-root][data-color]`. Los 3 bare (grid-list/clipboard/carousel) van al selector base. - drag-drop ya tenía recipe (preview-z) -> fusionado, sin duplicar clave. - tree-grid/tree-view solo tintan la fila (element); su `accent` (solid) no tenía consumidor -> declaran solo `_palette-element`. - virtual-list/virtual-grid tenían la cascada accent MUERTA (sin consumidor): contenedores virtualizados sin visual por color. Tipo abierto (guard) + wrapper inerte, pero SIN recipe (evita token de paleta huérfano). Verificado en navegador (demo table): data-color=risk -> palette-solid/element = tokens risk compartidos exactos; custom #00b894 -> solid=#00b894, element= color-mix(...). check: 76 ajenos, 0 propios. Contract 26/28 (2 = proof-of-human). Co-Authored-By: Claude Opus 4.8 --- .../eidos/components/carousel/carousel.css | 36 +---- .../eidos/components/carousel/carousel.svelte | 25 ++-- src/uix/eidos/components/carousel/types.ts | 4 +- .../eidos/components/clipboard/clipboard.css | 16 +-- .../components/clipboard/clipboard.svelte | 15 +- src/uix/eidos/components/clipboard/types.ts | 4 +- .../eidos/components/drag-drop/drag-drop.css | 21 +-- .../components/drag-drop/drag-drop.svelte | 15 +- src/uix/eidos/components/drag-drop/types.ts | 4 +- src/uix/eidos/components/feed/feed.css | 40 +----- src/uix/eidos/components/feed/feed.svelte | 11 +- src/uix/eidos/components/feed/types.ts | 4 +- .../eidos/components/grid-list/grid-list.css | 38 +----- .../components/grid-list/grid-list.svelte | 11 +- src/uix/eidos/components/grid-list/types.ts | 4 +- src/uix/eidos/components/table/table.css | 38 +----- src/uix/eidos/components/table/table.svelte | 12 +- src/uix/eidos/components/table/types.ts | 4 +- .../eidos/components/tree-grid/tree-grid.css | 34 +---- .../components/tree-grid/tree-grid.svelte | 12 +- src/uix/eidos/components/tree-grid/types.ts | 4 +- .../eidos/components/tree-view/tree-view.css | 34 +---- .../components/tree-view/tree-view.svelte | 17 ++- src/uix/eidos/components/tree-view/types.ts | 4 +- .../eidos/components/virtual-grid/types.ts | 4 +- .../components/virtual-grid/virtual-grid.css | 10 -- .../virtual-grid/virtual-grid.svelte | 12 +- .../eidos/components/virtual-list/types.ts | 12 +- .../components/virtual-list/virtual-list.css | 11 -- .../virtual-list/virtual-list.svelte | 12 +- src/uix/eidos/generated/base.css | 129 ++++++++++++++++++ src/uix/eidos/lib/recipes/base.ts | 111 ++++++++++++++- 32 files changed, 397 insertions(+), 311 deletions(-) diff --git a/src/uix/eidos/components/carousel/carousel.css b/src/uix/eidos/components/carousel/carousel.css index f0caed49f..2489eb058 100644 --- a/src/uix/eidos/components/carousel/carousel.css +++ b/src/uix/eidos/components/carousel/carousel.css @@ -28,8 +28,6 @@ --_carousel-indicator-size: 0.5rem; --_carousel-indicator-gap: var(--space-2); --_carousel-trigger-offset: var(--space-3); - --_carousel-accent: var(--color-primary-solid); - --_carousel-accent-soft: var(--color-primary-element); /* * Default vertical viewport block-size — vertical orientation needs a * bounded block axis or the viewport collapses and items overflow. @@ -142,36 +140,6 @@ --_carousel-indicator-gap: var(--space-3); } -/* ── Color cascades ─ */ -[data-carousel][data-color='secondary'] { - --_carousel-accent: var(--color-secondary-solid); - --_carousel-accent-soft: var(--color-secondary-element); -} -[data-carousel][data-color='affirm'] { - --_carousel-accent: var(--color-affirm-solid); - --_carousel-accent-soft: var(--color-affirm-element); -} -[data-carousel][data-color='risk'] { - --_carousel-accent: var(--color-risk-solid); - --_carousel-accent-soft: var(--color-risk-element); -} -[data-carousel][data-color='threat'] { - --_carousel-accent: var(--color-threat-solid); - --_carousel-accent-soft: var(--color-threat-element); -} -[data-carousel][data-color='fulfill'] { - --_carousel-accent: var(--color-fulfill-solid); - --_carousel-accent-soft: var(--color-fulfill-element); -} -[data-carousel][data-color='loss'] { - --_carousel-accent: var(--color-loss-solid); - --_carousel-accent-soft: var(--color-loss-element); -} -[data-carousel][data-color='neutral'] { - --_carousel-accent: var(--color-neutral-solid); - --_carousel-accent-soft: var(--color-neutral-element); -} - [data-carousel][data-disabled] { opacity: var(--opacity-disabled); pointer-events: none; @@ -318,11 +286,11 @@ } [data-carousel-indicator]:hover:not([data-disabled]) { - background: var(--_carousel-accent-soft); + background: var(--_carousel-palette-element); } [data-carousel-indicator][data-state='active'] { - background: var(--_carousel-accent); + background: var(--_carousel-palette-solid); /* Active indicator stretches horizontally for clearer current-position hint. */ inline-size: calc(var(--_carousel-indicator-size) * 2.25); } diff --git a/src/uix/eidos/components/carousel/carousel.svelte b/src/uix/eidos/components/carousel/carousel.svelte index 6d3ca8127..487130919 100644 --- a/src/uix/eidos/components/carousel/carousel.svelte +++ b/src/uix/eidos/components/carousel/carousel.svelte @@ -7,6 +7,8 @@ * NextTrigger / Indicator inherit them without duplicate props. */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import * as Carousel from '$soma/components/carousel'; import type { CarouselProps } from './types'; import { setCarouselEidosContext } from './carousel-context'; @@ -28,16 +30,18 @@ const eidos = ActiveEidos.require(); const resolvedSize = $derived(eidos.resolve(size, 'md')); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(color)); - // Compose inline style so consumers can still pass `style` AND we can - // override the vertical-block-size token. - const composedStyle = $derived( - [ + // Compose inline style so consumers can still pass `style`, we can override + // the vertical-block-size token, AND seed the custom-color derivation. + const mergedStyle = $derived( + composeInlineStyle( verticalBlockSize ? `--_carousel-vertical-block-size: ${verticalBlockSize};` : undefined, - typeof style === 'string' ? style : undefined - ] - .filter(Boolean) - .join(' ') || undefined + style, + colorAttrs.customStyle + ) ); setCarouselEidosContext({ @@ -60,10 +64,11 @@ {...rest} bind:index {orientation} - style={composedStyle} + style={mergedStyle} data-size={resolvedSize} data-variant={variant} - data-color={color} + data-color={colorAttrs.dataColor} + data-color-custom={colorAttrs.isCustom ? '' : undefined} data-block={block ? '' : undefined} data-hide-indicators={showIndicators ? undefined : ''} data-hide-controls={showControls ? undefined : ''} diff --git a/src/uix/eidos/components/carousel/types.ts b/src/uix/eidos/components/carousel/types.ts index c050aa2f5..b73a0899a 100644 --- a/src/uix/eidos/components/carousel/types.ts +++ b/src/uix/eidos/components/carousel/types.ts @@ -1,6 +1,6 @@ import type { Snippet } from 'svelte'; import type { - ColorRole, + ComponentColorProp, ControlVariant, ResponsiveProp, Size @@ -48,7 +48,7 @@ export type CarouselVariant = ControlVariant; * Color cascade — accents indicators, focus rings and the active * indicator/trigger highlight. Defaults to `primary`. */ -export type CarouselColor = ColorRole; +export type CarouselColor = ComponentColorProp; export type CarouselProps = ProviderProps & { /** Visual size preset. @default 'md' */ diff --git a/src/uix/eidos/components/clipboard/clipboard.css b/src/uix/eidos/components/clipboard/clipboard.css index 17646cb31..23da7b8d9 100644 --- a/src/uix/eidos/components/clipboard/clipboard.css +++ b/src/uix/eidos/components/clipboard/clipboard.css @@ -11,21 +11,9 @@ */ [data-clipboard] { - --_clipboard-accent: var(--color-affirm-solid); - --_clipboard-accent-soft: var(--color-affirm-element); - display: contents; } -[data-clipboard][data-color='primary'] { --_clipboard-accent: var(--color-primary-solid); --_clipboard-accent-soft: var(--color-primary-element); } -[data-clipboard][data-color='secondary'] { --_clipboard-accent: var(--color-secondary-solid); --_clipboard-accent-soft: var(--color-secondary-element); } -[data-clipboard][data-color='neutral'] { --_clipboard-accent: var(--color-neutral-solid); --_clipboard-accent-soft: var(--color-neutral-element); } -[data-clipboard][data-color='affirm'] { --_clipboard-accent: var(--color-affirm-solid); --_clipboard-accent-soft: var(--color-affirm-element); } -[data-clipboard][data-color='fulfill'] { --_clipboard-accent: var(--color-fulfill-solid); --_clipboard-accent-soft: var(--color-fulfill-element); } -[data-clipboard][data-color='risk'] { --_clipboard-accent: var(--color-risk-solid); --_clipboard-accent-soft: var(--color-risk-element); } -[data-clipboard][data-color='threat'] { --_clipboard-accent: var(--color-threat-solid); --_clipboard-accent-soft: var(--color-threat-element); } -[data-clipboard][data-color='loss'] { --_clipboard-accent: var(--color-loss-solid); --_clipboard-accent-soft: var(--color-loss-element); } - /* ── Indicator (visible during `copied=true`) ─ */ [data-clipboard-indicator] { display: inline-flex; @@ -33,8 +21,8 @@ gap: var(--space-1); padding: 0.125rem 0.5rem; border-radius: var(--radius-full); - background: var(--_clipboard-accent-soft); - color: var(--_clipboard-accent); + background: var(--_clipboard-palette-element); + color: var(--_clipboard-palette-solid); font-size: var(--font-size-xs); font-family: var(--style-label-font-family); font-weight: var(--font-weight-medium, 500); diff --git a/src/uix/eidos/components/clipboard/clipboard.svelte b/src/uix/eidos/components/clipboard/clipboard.svelte index 7f2f28d73..680119935 100644 --- a/src/uix/eidos/components/clipboard/clipboard.svelte +++ b/src/uix/eidos/components/clipboard/clipboard.svelte @@ -6,6 +6,8 @@ * Indicator (size + color cascade). */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import { setContext } from 'svelte'; import * as Clipboard from '$soma/components/clipboard'; import type { ClipboardProps, ClipboardSize, ClipboardColor } from './types'; @@ -13,12 +15,17 @@ let { size = 'md', color = 'affirm', + style, children: bodyContent, ...rest }: ClipboardProps = $props(); const eidos = ActiveEidos.require(); const resolvedSize = $derived(eidos.resolve(size, 'md')); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(color)); + const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle)); setContext('eidos:clipboard', { get size() { return resolvedSize; }, @@ -26,7 +33,13 @@ }); -
+
{#snippet children(snippetProps)} {@render bodyContent?.(snippetProps)} diff --git a/src/uix/eidos/components/clipboard/types.ts b/src/uix/eidos/components/clipboard/types.ts index 1e323ec84..14c0239e1 100644 --- a/src/uix/eidos/components/clipboard/types.ts +++ b/src/uix/eidos/components/clipboard/types.ts @@ -1,5 +1,5 @@ import type { - ColorRole, + ComponentColorProp, ResponsiveProp, Size } from '$uix/eidos/lib/types'; @@ -12,7 +12,7 @@ import type { import type { ButtonProps as EidosButtonProps } from '$uix/eidos/components/button'; export type ClipboardSize = Extract; -export type ClipboardColor = ColorRole; +export type ClipboardColor = ComponentColorProp; export type ClipboardProps = ProviderProps & { /** Visual size cascade for the Trigger Button. @default 'md' */ diff --git a/src/uix/eidos/components/drag-drop/drag-drop.css b/src/uix/eidos/components/drag-drop/drag-drop.css index e9cfb3bb9..5df1fb6f3 100644 --- a/src/uix/eidos/components/drag-drop/drag-drop.css +++ b/src/uix/eidos/components/drag-drop/drag-drop.css @@ -16,20 +16,9 @@ */ [data-drag-drop-root] { - --_drag-drop-accent: var(--color-primary-solid); - --_drag-drop-accent-soft: var(--color-primary-element); - display: contents; } -[data-drag-drop-root][data-color='secondary'] { --_drag-drop-accent: var(--color-secondary-solid); --_drag-drop-accent-soft: var(--color-secondary-element); } -[data-drag-drop-root][data-color='neutral'] { --_drag-drop-accent: var(--color-neutral-solid); --_drag-drop-accent-soft: var(--color-neutral-element); } -[data-drag-drop-root][data-color='affirm'] { --_drag-drop-accent: var(--color-affirm-solid); --_drag-drop-accent-soft: var(--color-affirm-element); } -[data-drag-drop-root][data-color='fulfill'] { --_drag-drop-accent: var(--color-fulfill-solid); --_drag-drop-accent-soft: var(--color-fulfill-element); } -[data-drag-drop-root][data-color='risk'] { --_drag-drop-accent: var(--color-risk-solid); --_drag-drop-accent-soft: var(--color-risk-element); } -[data-drag-drop-root][data-color='threat'] { --_drag-drop-accent: var(--color-threat-solid); --_drag-drop-accent-soft: var(--color-threat-element); } -[data-drag-drop-root][data-color='loss'] { --_drag-drop-accent: var(--color-loss-solid); --_drag-drop-accent-soft: var(--color-loss-element); } - /* ── Draggable ─ */ [data-drag-drop-draggable] { cursor: grab; @@ -69,15 +58,15 @@ /* Highlight all compatible drop targets while ANY drag is active. */ [data-drag-drop-droppable][data-accepting] { - --ring-inset-color: var(--_drag-drop-accent-soft); + --ring-inset-color: var(--_drag-drop-palette-element); --ring-inset-width: var(--border-width-thin); box-shadow: inset 0 0 0 var(--ring-inset-width) var(--ring-inset-color, currentColor); } /* Pointer / focus is over THIS droppable. */ [data-drag-drop-droppable][data-dragover] { - background: var(--_drag-drop-accent-soft); - --ring-inset-color: var(--_drag-drop-accent); + background: var(--_drag-drop-palette-element); + --ring-inset-color: var(--_drag-drop-palette-solid); box-shadow: inset 0 0 0 var(--ring-inset-width) var(--ring-inset-color, currentColor); } @@ -97,11 +86,11 @@ z-index: var(--drag-drop-preview-z); padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); - border: var(--border-width) solid var(--_drag-drop-accent); + border: var(--border-width) solid var(--_drag-drop-palette-solid); background: var(--color-surface-default); box-shadow: var(--shadow-raised), - 0 0 0 1px var(--_drag-drop-accent-soft); + 0 0 0 1px var(--_drag-drop-palette-element); color: var(--color-content-primary); font-family: var(--style-label-font-family); font-size: var(--font-size-sm); diff --git a/src/uix/eidos/components/drag-drop/drag-drop.svelte b/src/uix/eidos/components/drag-drop/drag-drop.svelte index 3beb8a976..caaa81821 100644 --- a/src/uix/eidos/components/drag-drop/drag-drop.svelte +++ b/src/uix/eidos/components/drag-drop/drag-drop.svelte @@ -5,6 +5,8 @@ * Sets a color/size context for descendants. */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import { setContext } from 'svelte'; import * as DragDrop from '$soma/components/drag-drop'; import type { DragDropProps, DragDropColor, DragDropSize } from './types'; @@ -12,12 +14,17 @@ let { size = 'md', color = 'primary', + style, children: bodyContent, ...rest }: DragDropProps = $props(); const eidos = ActiveEidos.require(); const resolvedSize = $derived(eidos.resolve(size, 'md')); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(color)); + const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle)); setContext('eidos:drag-drop', { get size() { return resolvedSize; }, @@ -25,7 +32,13 @@ }); -
+
{#snippet children(snippetProps)} {@render bodyContent?.(snippetProps)} diff --git a/src/uix/eidos/components/drag-drop/types.ts b/src/uix/eidos/components/drag-drop/types.ts index 737b8b983..0206f7c44 100644 --- a/src/uix/eidos/components/drag-drop/types.ts +++ b/src/uix/eidos/components/drag-drop/types.ts @@ -1,5 +1,5 @@ import type { - ColorRole, + ComponentColorProp, ResponsiveProp, Size } from '$uix/eidos/lib/types'; @@ -18,7 +18,7 @@ import type { } from '$soma/components/drag-drop'; export type DragDropSize = Extract; -export type DragDropColor = ColorRole; +export type DragDropColor = ComponentColorProp; export type DragDropProps = ProviderProps & { /** Visual size cascade for Draggable + Droppable. @default 'md' */ diff --git a/src/uix/eidos/components/feed/feed.css b/src/uix/eidos/components/feed/feed.css index 727d34e8f..f32f969d6 100644 --- a/src/uix/eidos/components/feed/feed.css +++ b/src/uix/eidos/components/feed/feed.css @@ -21,8 +21,6 @@ --_feed-thread-indent: var(--space-5); --_feed-font-size: var(--font-size-sm); --_feed-divider: var(--color-border-default); - --_feed-accent: var(--color-primary-solid); - --_feed-accent-soft: var(--color-primary-element); display: block; border-radius: var(--_feed-radius); @@ -67,36 +65,6 @@ --_feed-font-size: var(--font-size-lg); } -/* ── Color cascades ─ */ -[data-feed-root][data-color='secondary'] { - --_feed-accent: var(--color-secondary-solid); - --_feed-accent-soft: var(--color-secondary-element); -} -[data-feed-root][data-color='affirm'] { - --_feed-accent: var(--color-affirm-solid); - --_feed-accent-soft: var(--color-affirm-element); -} -[data-feed-root][data-color='risk'] { - --_feed-accent: var(--color-risk-solid); - --_feed-accent-soft: var(--color-risk-element); -} -[data-feed-root][data-color='threat'] { - --_feed-accent: var(--color-threat-solid); - --_feed-accent-soft: var(--color-threat-element); -} -[data-feed-root][data-color='fulfill'] { - --_feed-accent: var(--color-fulfill-solid); - --_feed-accent-soft: var(--color-fulfill-element); -} -[data-feed-root][data-color='loss'] { - --_feed-accent: var(--color-loss-solid); - --_feed-accent-soft: var(--color-loss-element); -} -[data-feed-root][data-color='neutral'] { - --_feed-accent: var(--color-neutral-solid); - --_feed-accent-soft: var(--color-neutral-element); -} - /* ── Feed root ─ */ [data-feed] { display: flex; @@ -171,12 +139,12 @@ gap: var(--_feed-gap); margin-block-start: var(--space-2); padding-inline-start: var(--_feed-thread-indent); - border-inline-start: var(--border-width-medium) solid var(--_feed-accent-soft); + border-inline-start: var(--border-width-medium) solid var(--_feed-palette-element); } [data-feed-root][dir='rtl'] [data-feed-thread] { border-inline-start: 0; - border-inline-end: var(--border-width-medium) solid var(--_feed-accent-soft); + border-inline-end: var(--border-width-medium) solid var(--_feed-palette-element); } /* ── Sentinel ─ @@ -200,8 +168,8 @@ inline-size: 0.75rem; block-size: 0.75rem; margin-inline-end: var(--space-2); - border: var(--border-width-medium) solid var(--_feed-accent-soft); - border-block-start-color: var(--_feed-accent); + border: var(--border-width-medium) solid var(--_feed-palette-element); + border-block-start-color: var(--_feed-palette-solid); border-radius: var(--radius-full); animation: feed-spin 0.7s linear infinite; } diff --git a/src/uix/eidos/components/feed/feed.svelte b/src/uix/eidos/components/feed/feed.svelte index b6dd9471b..914b13060 100644 --- a/src/uix/eidos/components/feed/feed.svelte +++ b/src/uix/eidos/components/feed/feed.svelte @@ -7,6 +7,8 @@ * scroll region). */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import * as Feed from '$soma/components/feed'; import type { FeedProps } from './types'; @@ -23,6 +25,9 @@ const eidos = ActiveEidos.require(); const resolvedSize = $derived(eidos.resolve(size, 'md')); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(color)); const composedStyle = $derived( [ @@ -32,16 +37,18 @@ .filter(Boolean) .join(' ') || undefined ); + const mergedStyle = $derived(composeInlineStyle(composedStyle, colorAttrs.customStyle));
{#snippet children(snippetProps)} diff --git a/src/uix/eidos/components/feed/types.ts b/src/uix/eidos/components/feed/types.ts index 1087e04ce..bd1fc1b61 100644 --- a/src/uix/eidos/components/feed/types.ts +++ b/src/uix/eidos/components/feed/types.ts @@ -1,5 +1,5 @@ import type { - ColorRole, + ComponentColorProp, ControlVariant, ResponsiveProp, Size @@ -30,7 +30,7 @@ export type FeedSize = Extract; export type FeedVariant = ControlVariant; /** Color cascade — accent for focused article + sentinel spinner. */ -export type FeedColor = ColorRole; +export type FeedColor = ComponentColorProp; export type FeedProps = ProviderProps & { /** Visual size preset. @default 'md' */ diff --git a/src/uix/eidos/components/grid-list/grid-list.css b/src/uix/eidos/components/grid-list/grid-list.css index ca7bf0a72..a0420ae6e 100644 --- a/src/uix/eidos/components/grid-list/grid-list.css +++ b/src/uix/eidos/components/grid-list/grid-list.css @@ -25,8 +25,6 @@ --_grid-list-row-padding-inline: var(--space-3); --_grid-list-font-size: var(--font-size-sm); --_grid-list-max-height: min(60vh, 22rem); - --_grid-list-accent: var(--color-primary-solid); - --_grid-list-accent-soft: var(--color-primary-element); display: flex; flex-direction: column; @@ -84,36 +82,6 @@ --_grid-list-font-size: var(--font-size-lg); } -/* ── Color cascades ─ */ -[data-grid-list][data-color='secondary'] { - --_grid-list-accent: var(--color-secondary-solid); - --_grid-list-accent-soft: var(--color-secondary-element); -} -[data-grid-list][data-color='affirm'] { - --_grid-list-accent: var(--color-affirm-solid); - --_grid-list-accent-soft: var(--color-affirm-element); -} -[data-grid-list][data-color='risk'] { - --_grid-list-accent: var(--color-risk-solid); - --_grid-list-accent-soft: var(--color-risk-element); -} -[data-grid-list][data-color='threat'] { - --_grid-list-accent: var(--color-threat-solid); - --_grid-list-accent-soft: var(--color-threat-element); -} -[data-grid-list][data-color='fulfill'] { - --_grid-list-accent: var(--color-fulfill-solid); - --_grid-list-accent-soft: var(--color-fulfill-element); -} -[data-grid-list][data-color='loss'] { - --_grid-list-accent: var(--color-loss-solid); - --_grid-list-accent-soft: var(--color-loss-element); -} -[data-grid-list][data-color='neutral'] { - --_grid-list-accent: var(--color-neutral-solid); - --_grid-list-accent-soft: var(--color-neutral-element); -} - [data-grid-list][data-disabled] { opacity: var(--opacity-disabled); cursor: not-allowed; @@ -152,7 +120,7 @@ } [data-grid-list-row][data-selected]:not([data-disabled]) { - background: var(--_grid-list-accent-soft); + background: var(--_grid-list-palette-element); } [data-grid-list-row][data-disabled] { @@ -210,8 +178,8 @@ } [data-grid-list-selection-checkbox][data-state='checked'] { - border-color: var(--_grid-list-accent); - background: var(--_grid-list-accent); + border-color: var(--_grid-list-palette-solid); + background: var(--_grid-list-palette-solid); } [data-grid-list-selection-checkbox][data-state='checked']::after { diff --git a/src/uix/eidos/components/grid-list/grid-list.svelte b/src/uix/eidos/components/grid-list/grid-list.svelte index 20138c23c..e53d98a62 100644 --- a/src/uix/eidos/components/grid-list/grid-list.svelte +++ b/src/uix/eidos/components/grid-list/grid-list.svelte @@ -21,6 +21,8 @@ * form input, Field merge. Eidos adds variant + size + color. */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import * as GridList from '$soma/components/grid-list'; import type { GridListProps } from './types'; @@ -32,6 +34,7 @@ // `$bindable([])` covers the uncontrolled-initial case — no separate // `defaultValue` prop (N8 poda, 2026-07-07). value = $bindable([]), + style, children: bodyContent, ...rest }: GridListProps = $props(); @@ -41,6 +44,10 @@ const resolvedVariant = $derived(eidos.resolve(variant, 'surface')); const resolvedColor = $derived(eidos.resolve(color, 'primary')); const resolvedBlock = $derived(eidos.resolve(block, false)); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(resolvedColor)); + const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle)); {#snippet children(snippetProps)} diff --git a/src/uix/eidos/components/grid-list/types.ts b/src/uix/eidos/components/grid-list/types.ts index b74de8675..ed254b542 100644 --- a/src/uix/eidos/components/grid-list/types.ts +++ b/src/uix/eidos/components/grid-list/types.ts @@ -1,4 +1,4 @@ -import type { ColorRole, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types'; +import type { ComponentColorProp, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types'; import type { ProviderProps, RowProps, @@ -17,7 +17,7 @@ import type { export type GridListSize = Extract; export type GridListVariant = ControlVariant; -export type GridListColor = ColorRole; +export type GridListColor = ComponentColorProp; export type GridListProps = ProviderProps & { /** Visual size — row height + font-size. @default 'md' */ diff --git a/src/uix/eidos/components/table/table.css b/src/uix/eidos/components/table/table.css index 00138f326..03c7e7755 100644 --- a/src/uix/eidos/components/table/table.css +++ b/src/uix/eidos/components/table/table.css @@ -37,8 +37,6 @@ --_table-row-hover-bg: var(--color-surface-overlay); --_table-row-striped-bg: var(--color-surface-muted); --_table-divider: var(--color-border-default); - --_table-accent: var(--color-primary-solid); - --_table-accent-soft: var(--color-primary-element); position: relative; display: block; @@ -94,36 +92,6 @@ --_table-font-size: var(--font-size-lg); } -/* ── Color cascades ─ */ -[data-table-root][data-color='secondary'] { - --_table-accent: var(--color-secondary-solid); - --_table-accent-soft: var(--color-secondary-element); -} -[data-table-root][data-color='affirm'] { - --_table-accent: var(--color-affirm-solid); - --_table-accent-soft: var(--color-affirm-element); -} -[data-table-root][data-color='risk'] { - --_table-accent: var(--color-risk-solid); - --_table-accent-soft: var(--color-risk-element); -} -[data-table-root][data-color='threat'] { - --_table-accent: var(--color-threat-solid); - --_table-accent-soft: var(--color-threat-element); -} -[data-table-root][data-color='fulfill'] { - --_table-accent: var(--color-fulfill-solid); - --_table-accent-soft: var(--color-fulfill-element); -} -[data-table-root][data-color='loss'] { - --_table-accent: var(--color-loss-solid); - --_table-accent-soft: var(--color-loss-element); -} -[data-table-root][data-color='neutral'] { - --_table-accent: var(--color-neutral-solid); - --_table-accent-soft: var(--color-neutral-element); -} - /* * Scrolling region — only when consumer sets `maxHeight`. Pure CSS * overflow auto with overscroll containment so wheel scrolling inside @@ -210,7 +178,7 @@ } [data-table-column-header][data-sorted] { - color: var(--_table-accent); + color: var(--_table-palette-solid); } [data-table-column-header][data-pinned] { @@ -243,7 +211,7 @@ /* Selected row — accent tint. Overrides hover + striped. */ [data-table-body] [data-table-row][data-selected] { - background: var(--_table-accent-soft); + background: var(--_table-palette-element); color: var(--color-content-primary); } @@ -329,7 +297,7 @@ } [data-table-sort-trigger][data-sorted] { - color: var(--_table-accent); + color: var(--_table-palette-solid); } [data-table-sort-indicator] { diff --git a/src/uix/eidos/components/table/table.svelte b/src/uix/eidos/components/table/table.svelte index 6493b7a5c..656ea2247 100644 --- a/src/uix/eidos/components/table/table.svelte +++ b/src/uix/eidos/components/table/table.svelte @@ -12,6 +12,8 @@ * context so the `RowDetailTrigger` Button matches the table density. */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import * as Table from '$soma/components/table'; import type { TableProps } from './types'; import { setTableEidosContext } from './table-context'; @@ -49,6 +51,11 @@ .join(' ') || undefined ); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(color)); + const mergedStyle = $derived(composeInlineStyle(composedStyle, colorAttrs.customStyle)); + setTableEidosContext({ get size() { return resolvedSize; @@ -66,14 +73,15 @@ data-table-root="" data-size={resolvedSize} data-variant={variant} - data-color={color} + data-color={colorAttrs.dataColor} + data-color-custom={colorAttrs.isCustom ? '' : undefined} data-block={block ? '' : undefined} data-striped={striped ? '' : undefined} data-hoverable={hoverable ? '' : undefined} data-sticky-header={stickyHeader ? '' : undefined} data-scrollable={maxHeight ? '' : undefined} data-stable-rows={stableRows && stableRows > 0 && !maxHeight ? '' : undefined} - style={composedStyle} + style={mergedStyle} > {#if bodyContent}{@render bodyContent()}{/if} diff --git a/src/uix/eidos/components/table/types.ts b/src/uix/eidos/components/table/types.ts index 9b342f555..60af4f0d1 100644 --- a/src/uix/eidos/components/table/types.ts +++ b/src/uix/eidos/components/table/types.ts @@ -1,6 +1,6 @@ import type { Snippet } from 'svelte'; import type { - ColorRole, + ComponentColorProp, ControlVariant, ResponsiveProp, Size @@ -43,7 +43,7 @@ export type TableVariant = ControlVariant; * Color cascade — accents the selected-row tint, focus rings and the * active sort indicator. Defaults to `primary`. */ -export type TableColor = ColorRole; +export type TableColor = ComponentColorProp; export type TableProps = ProviderProps & { /** Visual size preset. @default 'md' */ diff --git a/src/uix/eidos/components/tree-grid/tree-grid.css b/src/uix/eidos/components/tree-grid/tree-grid.css index bb379c8cf..f9b9eb42c 100644 --- a/src/uix/eidos/components/tree-grid/tree-grid.css +++ b/src/uix/eidos/components/tree-grid/tree-grid.css @@ -29,8 +29,6 @@ --_tree-grid-header-bg: var(--color-surface-muted); --_tree-grid-row-hover-bg: var(--color-surface-overlay); --_tree-grid-row-striped-bg: var(--color-surface-muted); - --_tree-grid-accent: var(--color-primary-solid); - --_tree-grid-accent-soft: var(--color-primary-element); --_tree-grid-guide: var(--color-border-default); --_tree-grid-columns: 1fr; @@ -92,36 +90,6 @@ --_tree-grid-font-size: var(--font-size-lg); } -/* ── Color cascades ─ */ -[data-tree-grid-root][data-color='secondary'] { - --_tree-grid-accent: var(--color-secondary-solid); - --_tree-grid-accent-soft: var(--color-secondary-element); -} -[data-tree-grid-root][data-color='affirm'] { - --_tree-grid-accent: var(--color-affirm-solid); - --_tree-grid-accent-soft: var(--color-affirm-element); -} -[data-tree-grid-root][data-color='risk'] { - --_tree-grid-accent: var(--color-risk-solid); - --_tree-grid-accent-soft: var(--color-risk-element); -} -[data-tree-grid-root][data-color='threat'] { - --_tree-grid-accent: var(--color-threat-solid); - --_tree-grid-accent-soft: var(--color-threat-element); -} -[data-tree-grid-root][data-color='fulfill'] { - --_tree-grid-accent: var(--color-fulfill-solid); - --_tree-grid-accent-soft: var(--color-fulfill-element); -} -[data-tree-grid-root][data-color='loss'] { - --_tree-grid-accent: var(--color-loss-solid); - --_tree-grid-accent-soft: var(--color-loss-element); -} -[data-tree-grid-root][data-color='neutral'] { - --_tree-grid-accent: var(--color-neutral-solid); - --_tree-grid-accent-soft: var(--color-neutral-element); -} - /* Scrollable cap when consumer sets `maxHeight`. */ [data-tree-grid-root][data-scrollable] { overflow: auto; @@ -219,7 +187,7 @@ } [data-tree-grid-row][aria-selected='true'] { - background: var(--_tree-grid-accent-soft); + background: var(--_tree-grid-palette-element); } [data-tree-grid-row][data-disabled] { diff --git a/src/uix/eidos/components/tree-grid/tree-grid.svelte b/src/uix/eidos/components/tree-grid/tree-grid.svelte index 85d09f351..51cf0a824 100644 --- a/src/uix/eidos/components/tree-grid/tree-grid.svelte +++ b/src/uix/eidos/components/tree-grid/tree-grid.svelte @@ -10,6 +10,8 @@ * `grid-template-columns` from `--_tree-grid-columns`. */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import * as TreeGrid from '$soma/components/tree-grid'; import type { TreeGridProps } from './types'; import { setTreeGridEidosContext } from './tree-grid-context'; @@ -48,6 +50,11 @@ .join(' ') || undefined ); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(color)); + const mergedStyle = $derived(composeInlineStyle(composedStyle, colorAttrs.customStyle)); + setTreeGridEidosContext({ get size() { return resolvedSize; @@ -65,7 +72,8 @@ data-tree-grid-root="" data-size={resolvedSize} data-variant={variant} - data-color={color} + data-color={colorAttrs.dataColor} + data-color-custom={colorAttrs.isCustom ? '' : undefined} data-block={block ? '' : undefined} data-striped={striped ? '' : undefined} data-hoverable={hoverable ? '' : undefined} @@ -73,7 +81,7 @@ data-sticky-header={maxHeight ? '' : undefined} data-scrollable={maxHeight ? '' : undefined} data-stable-rows={stableRows && stableRows > 0 && !maxHeight ? '' : undefined} - style={composedStyle} + style={mergedStyle} > {#snippet children(snippetProps)} diff --git a/src/uix/eidos/components/tree-grid/types.ts b/src/uix/eidos/components/tree-grid/types.ts index 2f08a30f8..373337e77 100644 --- a/src/uix/eidos/components/tree-grid/types.ts +++ b/src/uix/eidos/components/tree-grid/types.ts @@ -1,6 +1,6 @@ import type { Snippet } from 'svelte'; import type { - ColorRole, + ComponentColorProp, ControlVariant, ResponsiveProp, Size @@ -36,7 +36,7 @@ export type TreeGridVariant = ControlVariant; * Color cascade — accents the selected-row tint, indent guides and the * focus ring. Defaults to `primary`. */ -export type TreeGridColor = ColorRole; +export type TreeGridColor = ComponentColorProp; export type TreeGridProps = ProviderProps & { /** Visual size preset. @default 'md' */ diff --git a/src/uix/eidos/components/tree-view/tree-view.css b/src/uix/eidos/components/tree-view/tree-view.css index 033713290..cd0640b31 100644 --- a/src/uix/eidos/components/tree-view/tree-view.css +++ b/src/uix/eidos/components/tree-view/tree-view.css @@ -27,8 +27,6 @@ --_tree-view-row-padding-inline: var(--space-2); --_tree-view-indent: var(--space-4); --_tree-view-font-size: var(--font-size-sm); - --_tree-view-accent: var(--color-primary-solid); - --_tree-view-accent-soft: var(--color-primary-element); --_tree-view-guide: var(--color-border-default); display: block; @@ -90,36 +88,6 @@ --_tree-view-font-size: var(--font-size-lg); } -/* ── Color cascades ─ */ -[data-tree-view-root][data-color='secondary'] { - --_tree-view-accent: var(--color-secondary-solid); - --_tree-view-accent-soft: var(--color-secondary-element); -} -[data-tree-view-root][data-color='affirm'] { - --_tree-view-accent: var(--color-affirm-solid); - --_tree-view-accent-soft: var(--color-affirm-element); -} -[data-tree-view-root][data-color='risk'] { - --_tree-view-accent: var(--color-risk-solid); - --_tree-view-accent-soft: var(--color-risk-element); -} -[data-tree-view-root][data-color='threat'] { - --_tree-view-accent: var(--color-threat-solid); - --_tree-view-accent-soft: var(--color-threat-element); -} -[data-tree-view-root][data-color='fulfill'] { - --_tree-view-accent: var(--color-fulfill-solid); - --_tree-view-accent-soft: var(--color-fulfill-element); -} -[data-tree-view-root][data-color='loss'] { - --_tree-view-accent: var(--color-loss-solid); - --_tree-view-accent-soft: var(--color-loss-element); -} -[data-tree-view-root][data-color='neutral'] { - --_tree-view-accent: var(--color-neutral-solid); - --_tree-view-accent-soft: var(--color-neutral-element); -} - /* Scrollable cap when consumer sets `maxHeight`. */ [data-tree-view-root][data-scrollable] { overflow: auto; @@ -171,7 +139,7 @@ [data-tree-view-branch-control][data-selected]:not([data-disabled]), [data-tree-view-item][data-selected]:not([data-disabled]) { - background: var(--_tree-view-accent-soft); + background: var(--_tree-view-palette-element); } [data-tree-view-branch-control][data-disabled], diff --git a/src/uix/eidos/components/tree-view/tree-view.svelte b/src/uix/eidos/components/tree-view/tree-view.svelte index 813bf03c8..2b6f736b6 100644 --- a/src/uix/eidos/components/tree-view/tree-view.svelte +++ b/src/uix/eidos/components/tree-view/tree-view.svelte @@ -12,6 +12,8 @@ * chevron rotation and the IndentGuide opts into the depth lines. */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import * as TreeView from '$soma/components/tree-view'; import type { TreeViewProps } from './types'; import { setTreeViewEidosContext } from './tree-view-context'; @@ -33,14 +35,16 @@ const eidos = ActiveEidos.require(); const resolvedSize = $derived(eidos.resolve(size, 'md')); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(color)); const composedStyle = $derived( - [ + composeInlineStyle( maxHeight ? `max-block-size: ${maxHeight};` : undefined, - typeof style === 'string' ? style : undefined - ] - .filter(Boolean) - .join(' ') || undefined + style, + colorAttrs.customStyle + ) ); setTreeViewEidosContext({ @@ -63,7 +67,8 @@ data-tree-view-root="" data-size={resolvedSize} data-variant={variant} - data-color={color} + data-color={colorAttrs.dataColor} + data-color-custom={colorAttrs.isCustom ? '' : undefined} data-block={block ? '' : undefined} data-indent-guides={showIndentGuides ? '' : undefined} data-scrollable={maxHeight ? '' : undefined} diff --git a/src/uix/eidos/components/tree-view/types.ts b/src/uix/eidos/components/tree-view/types.ts index 8cf1d1cf4..22a4f212d 100644 --- a/src/uix/eidos/components/tree-view/types.ts +++ b/src/uix/eidos/components/tree-view/types.ts @@ -1,5 +1,5 @@ import type { - ColorRole, + ComponentColorProp, ControlVariant, ResponsiveProp, Size @@ -38,7 +38,7 @@ export type TreeViewVariant = ControlVariant; * Color cascade — accents the selected-item background, focus rings and * indent guides. Defaults to `primary`. */ -export type TreeViewColor = ColorRole; +export type TreeViewColor = ComponentColorProp; export type TreeViewProps = ProviderProps & { /** Visual size preset. @default 'md' */ diff --git a/src/uix/eidos/components/virtual-grid/types.ts b/src/uix/eidos/components/virtual-grid/types.ts index 0a2de5443..e243687d6 100644 --- a/src/uix/eidos/components/virtual-grid/types.ts +++ b/src/uix/eidos/components/virtual-grid/types.ts @@ -1,5 +1,5 @@ import type { - ColorRole, + ComponentColorProp, ControlVariant, ResponsiveProp, Size @@ -16,7 +16,7 @@ import type { export type VirtualGridSize = Extract; export type VirtualGridVariant = ControlVariant; -export type VirtualGridColor = ColorRole; +export type VirtualGridColor = ComponentColorProp; export type VirtualGridProps = ProviderProps & { /** Visual size preset. @default 'md' */ diff --git a/src/uix/eidos/components/virtual-grid/virtual-grid.css b/src/uix/eidos/components/virtual-grid/virtual-grid.css index b8eccc096..714102bda 100644 --- a/src/uix/eidos/components/virtual-grid/virtual-grid.css +++ b/src/uix/eidos/components/virtual-grid/virtual-grid.css @@ -12,8 +12,6 @@ /* Radius DECOUPLED from size (Radix model) — surface archetype default, constant across sizes. */ --_virtual-grid-radius: var(--radius-default); --_virtual-grid-font-size: var(--font-size-sm); - --_virtual-grid-accent: var(--color-primary-solid); - --_virtual-grid-accent-soft: var(--color-primary-element); display: block; border-radius: var(--_virtual-grid-radius); @@ -55,14 +53,6 @@ background: transparent; } -[data-virtual-grid-root][data-color='secondary'] { --_virtual-grid-accent: var(--color-secondary-solid); --_virtual-grid-accent-soft: var(--color-secondary-element); } -[data-virtual-grid-root][data-color='affirm'] { --_virtual-grid-accent: var(--color-affirm-solid); --_virtual-grid-accent-soft: var(--color-affirm-element); } -[data-virtual-grid-root][data-color='risk'] { --_virtual-grid-accent: var(--color-risk-solid); --_virtual-grid-accent-soft: var(--color-risk-element); } -[data-virtual-grid-root][data-color='threat'] { --_virtual-grid-accent: var(--color-threat-solid); --_virtual-grid-accent-soft: var(--color-threat-element); } -[data-virtual-grid-root][data-color='fulfill'] { --_virtual-grid-accent: var(--color-fulfill-solid); --_virtual-grid-accent-soft: var(--color-fulfill-element); } -[data-virtual-grid-root][data-color='loss'] { --_virtual-grid-accent: var(--color-loss-solid); --_virtual-grid-accent-soft: var(--color-loss-element); } -[data-virtual-grid-root][data-color='neutral'] { --_virtual-grid-accent: var(--color-neutral-solid); --_virtual-grid-accent-soft: var(--color-neutral-element); } - [data-virtual-grid-viewport] { position: relative; overflow: auto; diff --git a/src/uix/eidos/components/virtual-grid/virtual-grid.svelte b/src/uix/eidos/components/virtual-grid/virtual-grid.svelte index 22195f7e6..8f7517d88 100644 --- a/src/uix/eidos/components/virtual-grid/virtual-grid.svelte +++ b/src/uix/eidos/components/virtual-grid/virtual-grid.svelte @@ -4,6 +4,8 @@ * Eidos `` — visual wrapper around soma's `VirtualGrid.Provider`. */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import * as VirtualGrid from '$soma/components/virtual-grid'; import type { VirtualGridProps } from './types'; @@ -19,16 +21,20 @@ const eidos = ActiveEidos.require(); const resolvedSize = $derived(eidos.resolve(size, 'md')); - const styleString = $derived(typeof style === 'string' ? style : undefined); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(color)); + const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle));
{#snippet children(snippetProps)} diff --git a/src/uix/eidos/components/virtual-list/types.ts b/src/uix/eidos/components/virtual-list/types.ts index 0003ace6e..d86dd55f7 100644 --- a/src/uix/eidos/components/virtual-list/types.ts +++ b/src/uix/eidos/components/virtual-list/types.ts @@ -1,5 +1,5 @@ import type { - ColorRole, + ComponentColorProp, ControlVariant, ResponsiveProp, Size @@ -11,8 +11,10 @@ import type { ItemProps, ProviderSnippetProps, VirtualItem, + VirtualListAnchor, ScrollToIndexAlign, - ScrollToIndexOptions + ScrollToIndexOptions, + ScrollToEndOptions } from '$soma/components/virtual-list'; /** VirtualList visual size — drives default Viewport chrome scale. */ @@ -27,7 +29,7 @@ export type VirtualListSize = Extract; export type VirtualListVariant = ControlVariant; /** Color cascade — focus ring + selection accent. */ -export type VirtualListColor = ColorRole; +export type VirtualListColor = ComponentColorProp; export type VirtualListProps = ProviderProps & { /** Visual size preset. @default 'md' */ @@ -47,6 +49,8 @@ export type VirtualListItemProps = ItemProps; export type { ProviderSnippetProps as VirtualListProviderSnippetProps, VirtualItem, + VirtualListAnchor, ScrollToIndexAlign, - ScrollToIndexOptions + ScrollToIndexOptions, + ScrollToEndOptions }; diff --git a/src/uix/eidos/components/virtual-list/virtual-list.css b/src/uix/eidos/components/virtual-list/virtual-list.css index 76b4403c8..b4cf180d0 100644 --- a/src/uix/eidos/components/virtual-list/virtual-list.css +++ b/src/uix/eidos/components/virtual-list/virtual-list.css @@ -16,8 +16,6 @@ /* Radius DECOUPLED from size (Radix model) — surface archetype default, constant across sizes. */ --_virtual-list-radius: var(--radius-default); --_virtual-list-font-size: var(--font-size-sm); - --_virtual-list-accent: var(--color-primary-solid); - --_virtual-list-accent-soft: var(--color-primary-element); display: block; border-radius: var(--_virtual-list-radius); @@ -60,15 +58,6 @@ background: transparent; } -/* ── Color cascades ─ */ -[data-virtual-list-root][data-color='secondary'] { --_virtual-list-accent: var(--color-secondary-solid); --_virtual-list-accent-soft: var(--color-secondary-element); } -[data-virtual-list-root][data-color='affirm'] { --_virtual-list-accent: var(--color-affirm-solid); --_virtual-list-accent-soft: var(--color-affirm-element); } -[data-virtual-list-root][data-color='risk'] { --_virtual-list-accent: var(--color-risk-solid); --_virtual-list-accent-soft: var(--color-risk-element); } -[data-virtual-list-root][data-color='threat'] { --_virtual-list-accent: var(--color-threat-solid); --_virtual-list-accent-soft: var(--color-threat-element); } -[data-virtual-list-root][data-color='fulfill'] { --_virtual-list-accent: var(--color-fulfill-solid); --_virtual-list-accent-soft: var(--color-fulfill-element); } -[data-virtual-list-root][data-color='loss'] { --_virtual-list-accent: var(--color-loss-solid); --_virtual-list-accent-soft: var(--color-loss-element); } -[data-virtual-list-root][data-color='neutral'] { --_virtual-list-accent: var(--color-neutral-solid); --_virtual-list-accent-soft: var(--color-neutral-element); } - /* ── Viewport (internal scroll) ─ */ [data-virtual-list-viewport] { position: relative; diff --git a/src/uix/eidos/components/virtual-list/virtual-list.svelte b/src/uix/eidos/components/virtual-list/virtual-list.svelte index 7faeef8ac..4343b16ec 100644 --- a/src/uix/eidos/components/virtual-list/virtual-list.svelte +++ b/src/uix/eidos/components/virtual-list/virtual-list.svelte @@ -5,6 +5,8 @@ * Wraps the provider in `
` for chrome. */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import * as VirtualList from '$soma/components/virtual-list'; import type { VirtualListProps } from './types'; @@ -20,16 +22,20 @@ const eidos = ActiveEidos.require(); const resolvedSize = $derived(eidos.resolve(size, 'md')); - const styleString = $derived(typeof style === 'string' ? style : undefined); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed (shared derivation). + const colorAttrs = $derived(resolveComponentColor(color)); + const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle));
{#snippet children(snippetProps)} diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index e90be3665..98fa8d931 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -848,6 +848,59 @@ --card-risk-border: var(--color-risk-border); --card-threat-track: var(--color-threat-track); --card-threat-border: var(--color-threat-border); + --chat-message-gap: var(--space-2); + --chat-message-row-gap-run: var(--space-0-5); + --chat-message-row-gap-group: var(--space-3); + --chat-message-gutter-inline-size: var(--avatar-size-md, var(--space-8)); + --chat-message-bubble-padding-inline-sm: var(--space-2-5); + --chat-message-bubble-padding-inline-md: var(--space-3); + --chat-message-bubble-padding-inline-lg: var(--space-4); + --chat-message-bubble-padding-block-sm: var(--space-1-5); + --chat-message-bubble-padding-block-md: var(--space-2); + --chat-message-bubble-padding-block-lg: var(--space-2-5); + --chat-message-bubble-radius: var(--radius-lg); + --chat-message-bubble-radius-run: var(--radius-sm); + --chat-message-bubble-max-inline-size: min(var(--measure-narrow), 78%); + --chat-message-bubble-font-size-sm: var(--size-sm-font-size); + --chat-message-bubble-font-size-md: var(--size-md-font-size); + --chat-message-bubble-font-size-lg: var(--size-lg-font-size); + --chat-message-bubble-line-height: var(--leading-prose); + --chat-message-bubble-bg-in: var(--color-neutral-element); + --chat-message-bubble-fg-in: var(--color-content-primary); + --chat-message-bubble-bg-out: var(--color-primary-solid); + --chat-message-bubble-fg-out: var(--color-primary-contrast); + --chat-message-header-gap: var(--space-2); + --chat-message-header-font-size: var(--size-sm-font-size); + --chat-message-header-font-weight: var(--font-weight-medium); + --chat-message-header-color: var(--color-content-secondary); + --chat-message-meta-gap: var(--space-1); + --chat-message-meta-font-size: var(--size-xs-font-size); + --chat-message-meta-color: var(--color-content-muted); + --chat-message-status-size: 1em; + --chat-message-status-color: var(--color-content-muted); + --chat-message-status-color-read: var(--color-primary-solid); + --chat-message-status-color-failed: var(--color-threat-solid); + --chat-message-reply-padding-inline: var(--space-2); + --chat-message-reply-padding-block: var(--space-1); + --chat-message-reply-accent-size: var(--space-0-5); + --chat-message-reply-radius: var(--radius-sm); + --chat-message-reply-bg: var(--color-neutral-track); + --chat-message-reply-accent: var(--color-primary-solid); + --chat-message-reply-font-size: var(--size-xs-font-size); + --chat-message-reaction-gap: var(--space-1); + --chat-message-reaction-height: var(--size-xs-control-height); + --chat-message-reaction-padding-inline: var(--space-2); + --chat-message-reaction-radius: var(--radius-full); + --chat-message-reaction-font-size: var(--size-xs-font-size); + --chat-message-reaction-bg: var(--color-neutral-track); + --chat-message-reaction-border: var(--color-neutral-separator); + --chat-message-reaction-pressed-bg: var(--color-primary-surface); + --chat-message-reaction-pressed-border: var(--color-primary-border); + --chat-message-reaction-pressed-fg: var(--color-primary-text); + --chat-message-mentioned-bg: var(--color-primary-surface); + --chat-message-mentioned-accent: var(--color-primary-solid); + --chat-message-actions-gap: var(--space-0-5); + --chat-message-actions-offset: var(--space-1); --checkbox-size-xs-box-size: 14px; --checkbox-size-xs-indicator-size: 8px; --checkbox-size-xs-indicator-stroke: 2px; @@ -4370,6 +4423,72 @@ --_range-calendar-palette-text: var(--palette-text, var(--color-primary-text)); } +[data-table-root] { + --_table-palette-solid: var(--color-primary-solid); + --_table-palette-element: var(--color-primary-element); +} + +[data-table-root][data-color], [data-table-root][data-color-custom] { + --_table-palette-element: var(--palette-element, var(--color-primary-element)); + --_table-palette-solid: var(--palette-solid, var(--color-primary-solid)); +} + +[data-tree-grid-root] { + --_tree-grid-palette-element: var(--color-primary-element); +} + +[data-tree-grid-root][data-color], [data-tree-grid-root][data-color-custom] { + --_tree-grid-palette-element: var(--palette-element, var(--color-primary-element)); +} + +[data-tree-view-root] { + --_tree-view-palette-element: var(--color-primary-element); +} + +[data-tree-view-root][data-color], [data-tree-view-root][data-color-custom] { + --_tree-view-palette-element: var(--palette-element, var(--color-primary-element)); +} + +[data-feed-root] { + --_feed-palette-solid: var(--color-primary-solid); + --_feed-palette-element: var(--color-primary-element); +} + +[data-feed-root][data-color], [data-feed-root][data-color-custom] { + --_feed-palette-element: var(--palette-element, var(--color-primary-element)); + --_feed-palette-solid: var(--palette-solid, var(--color-primary-solid)); +} + +[data-grid-list] { + --_grid-list-palette-solid: var(--color-primary-solid); + --_grid-list-palette-element: var(--color-primary-element); +} + +[data-grid-list][data-color], [data-grid-list][data-color-custom] { + --_grid-list-palette-element: var(--palette-element, var(--color-primary-element)); + --_grid-list-palette-solid: var(--palette-solid, var(--color-primary-solid)); +} + +[data-clipboard] { + --_clipboard-palette-solid: var(--color-primary-solid); + --_clipboard-palette-element: var(--color-primary-element); +} + +[data-clipboard][data-color], [data-clipboard][data-color-custom] { + --_clipboard-palette-element: var(--palette-element, var(--color-primary-element)); + --_clipboard-palette-solid: var(--palette-solid, var(--color-primary-solid)); +} + +[data-carousel] { + --_carousel-palette-solid: var(--color-primary-solid); + --_carousel-palette-element: var(--color-primary-element); +} + +[data-carousel][data-color], [data-carousel][data-color-custom] { + --_carousel-palette-element: var(--palette-element, var(--color-primary-element)); + --_carousel-palette-solid: var(--palette-solid, var(--color-primary-solid)); +} + [data-calendar] { --_calendar-palette-solid: var(--color-primary-solid); --_calendar-palette-solid-hover: var(--color-primary-solid-hover); @@ -4969,6 +5088,16 @@ --toggle-palette-contrast: var(--palette-contrast, var(--toggle-neutral-contrast)); } +[data-drag-drop-root] { + --_drag-drop-palette-solid: var(--color-primary-solid); + --_drag-drop-palette-element: var(--color-primary-element); +} + +[data-drag-drop-root][data-color], [data-drag-drop-root][data-color-custom] { + --_drag-drop-palette-element: var(--palette-element, var(--color-primary-element)); + --_drag-drop-palette-solid: var(--palette-solid, var(--color-primary-solid)); +} + [data-depth='flush'] { box-shadow: var(--depth-flush-shadow); } diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 8bfe68a77..67a0c468a 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -660,6 +660,72 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ ] } }, + 'chat-message': { + // ── Row layout ──────────────────────────────────────────────────── + gap: 'var(--space-2)', + 'row-gap-run': 'var(--space-0-5)', + 'row-gap-group': 'var(--space-3)', + // Avatar column reserve — follows the composed Avatar's md diameter. + 'gutter-inline-size': 'var(--avatar-size-md, var(--space-8))', + // ── Bubble ──────────────────────────────────────────────────────── + 'bubble-padding-inline-sm': 'var(--space-2-5)', + 'bubble-padding-inline-md': 'var(--space-3)', + 'bubble-padding-inline-lg': 'var(--space-4)', + 'bubble-padding-block-sm': 'var(--space-1-5)', + 'bubble-padding-block-md': 'var(--space-2)', + 'bubble-padding-block-lg': 'var(--space-2-5)', + 'bubble-radius': 'var(--radius-lg)', + // Flattened corners between messages of one run. + 'bubble-radius-run': 'var(--radius-sm)', + 'bubble-max-inline-size': 'min(var(--measure-narrow), 78%)', + 'bubble-font-size-sm': 'var(--size-sm-font-size)', + 'bubble-font-size-md': 'var(--size-md-font-size)', + 'bubble-font-size-lg': 'var(--size-lg-font-size)', + 'bubble-line-height': 'var(--leading-prose)', + 'bubble-bg-in': 'var(--color-neutral-element)', + 'bubble-fg-in': 'var(--color-content-primary)', + 'bubble-bg-out': 'var(--color-primary-solid)', + 'bubble-fg-out': 'var(--color-primary-contrast)', + // ── Header (author + time, first of a run) ─────────────────────── + 'header-gap': 'var(--space-2)', + 'header-font-size': 'var(--size-sm-font-size)', + 'header-font-weight': 'var(--font-weight-medium)', + 'header-color': 'var(--color-content-secondary)', + // ── Meta (time + status) ───────────────────────────────────────── + 'meta-gap': 'var(--space-1)', + 'meta-font-size': 'var(--size-xs-font-size)', + 'meta-color': 'var(--color-content-muted)', + // ── Delivery status indicator ──────────────────────────────────── + 'status-size': '1em', + 'status-color': 'var(--color-content-muted)', + 'status-color-read': 'var(--color-primary-solid)', + 'status-color-failed': 'var(--color-threat-solid)', + // ── Reply (quoted preview) ─────────────────────────────────────── + 'reply-padding-inline': 'var(--space-2)', + 'reply-padding-block': 'var(--space-1)', + 'reply-accent-size': 'var(--space-0-5)', + 'reply-radius': 'var(--radius-sm)', + 'reply-bg': 'var(--color-neutral-track)', + 'reply-accent': 'var(--color-primary-solid)', + 'reply-font-size': 'var(--size-xs-font-size)', + // ── Reaction chips ─────────────────────────────────────────────── + 'reaction-gap': 'var(--space-1)', + 'reaction-height': 'var(--size-xs-control-height)', + 'reaction-padding-inline': 'var(--space-2)', + 'reaction-radius': 'var(--radius-full)', + 'reaction-font-size': 'var(--size-xs-font-size)', + 'reaction-bg': 'var(--color-neutral-track)', + 'reaction-border': 'var(--color-neutral-separator)', + 'reaction-pressed-bg': 'var(--color-primary-surface)', + 'reaction-pressed-border': 'var(--color-primary-border)', + 'reaction-pressed-fg': 'var(--color-primary-text)', + // ── Mention highlight ──────────────────────────────────────────── + 'mentioned-bg': 'var(--color-primary-surface)', + 'mentioned-accent': 'var(--color-primary-solid)', + // ── Actions cluster ────────────────────────────────────────────── + 'actions-gap': 'var(--space-0-5)', + 'actions-offset': 'var(--space-1)' + }, checkbox: { 'size-xs-box-size': '14px', 'size-xs-indicator-size': '8px', @@ -1588,6 +1654,46 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ '_palette-border': { value: 'var(--color-primary-border)', scope: 'host' }, '_palette-text': { value: 'var(--color-primary-text)', scope: 'host' } }, + // ── List / data-grid family — the shared 2-slot selection palette ────────── + // Uniform pattern: `--_{c}-palette-solid` (accent: sort/active indicator) + + // `--_{c}-palette-element` (soft: selected-row tint). Host default = primary; + // the shared layer routes roles + 33 scales + custom. Was a per-role cascade + // in each component's CSS (`--_{c}-accent` / `--_{c}-accent-soft`). The `-root` + // components stamp `data-color` on `[data-{c}-root]` (not the bare provider), + // so their forward targets that part via `parts: ['root']`. drag-drop / combobox + // / timeline already own a recipe → their palette slots live in those entries. + table: { + '_palette-solid': { parts: ['root'], declarations: [{ value: 'var(--color-primary-solid)', scope: 'host' }] }, + '_palette-element': { parts: ['root'], declarations: [{ value: 'var(--color-primary-element)', scope: 'host' }] } + }, + // tree-grid / tree-view only tint the SELECTED row (element); their `accent` + // (solid) token had no consumer, so they declare only `_palette-element`. + 'tree-grid': { + '_palette-element': { parts: ['root'], declarations: [{ value: 'var(--color-primary-element)', scope: 'host' }] } + }, + 'tree-view': { + '_palette-element': { parts: ['root'], declarations: [{ value: 'var(--color-primary-element)', scope: 'host' }] } + }, + // virtual-list / virtual-grid: their accent cascade was declaration-only (no CSS + // consumer) — pure virtualized containers with no colour-driven visual. The type + // stays open (guard/uniformity) + the wrapper stamps a harmless `data-color`, but + // there is nothing to forward, so NO recipe here (avoids an orphan palette token). + feed: { + '_palette-solid': { parts: ['root'], declarations: [{ value: 'var(--color-primary-solid)', scope: 'host' }] }, + '_palette-element': { parts: ['root'], declarations: [{ value: 'var(--color-primary-element)', scope: 'host' }] } + }, + 'grid-list': { + '_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' }, + '_palette-element': { value: 'var(--color-primary-element)', scope: 'host' } + }, + clipboard: { + '_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' }, + '_palette-element': { value: 'var(--color-primary-element)', scope: 'host' } + }, + carousel: { + '_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' }, + '_palette-element': { value: 'var(--color-primary-element)', scope: 'host' } + }, calendar: { gap: 'var(--space-3)', 'padding-xs': 'var(--space-2)', @@ -4127,7 +4233,10 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ // DragDrop — the drag preview/ghost that tracks the cursor. Floats above // local content during an active drag. 'drag-drop': { - 'preview-z': 'var(--z-index-overlay-detached)' + 'preview-z': 'var(--z-index-overlay-detached)', + // Selection palette (THM-2) — `data-color` sits on `[data-drag-drop-root]`. + '_palette-solid': { parts: ['root'], declarations: [{ value: 'var(--color-primary-solid)', scope: 'host' }] }, + '_palette-element': { parts: ['root'], declarations: [{ value: 'var(--color-primary-element)', scope: 'host' }] } }, 'navigation-menu': {