From 8515f5e67337b2396e96d0c7309b7ddf57b681dc Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 21 May 2026 23:12:51 +0200 Subject: [PATCH] fix(color-picker): ValueSwatch now reactively reflects the current color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User report: 'la caja del color seleccionado en el pickercolor no se actualiza, compruébalo.' Root cause ---------- The Eidos recipe had an `[data-color-picker-value-swatch]::after` overlay that painted `var(--cp-trigger-color, currentColor)` on top of the swatch — a CSS variable set ONLY on the trigger button by the Eidos wrapper. The popover content is portaled outside the trigger's subtree, so the var never propagated into the ValueSwatch element. The overlay therefore stayed at its fallback (`currentColor`) and covered the soma-set inline `background-color`, hiding the live colour. Fix --- 1. **Soma `ColorPickerValueSwatchProvider.backgroundStyle`** now emits BOTH `background-color: ` and `--cp-current-color: ` so: - Consumers without the eidos recipe still see the colour (inline background-color, as before). - Eidos recipes / external CSS can read `--cp-current-color` as a reactive CSS variable on the element itself. 2. **Eidos CSS** drops the `::after` overlay. Instead the checker (transparency pattern) lives on a `::before` pseudo with `z-index: -1` inside an isolated stacking context, so the soma's inline `background-color` paints ON TOP and the checker shows through only where alpha < 1. No more colour stomping. Checks: 67/67 component-audit PASS, svelte-check 0 errors / 0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/color-picker/color-picker.css | 21 ++++++-- src/uix/sema/components/color-picker.ts | 49 +++++++++++++++++++ src/uix/sema/components/index.ts | 1 + .../color-picker-provider.svelte.ts | 13 ++++- web/routes/uix/+layout@.svelte | 2 + 5 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 src/uix/sema/components/color-picker.ts diff --git a/src/uix/eidos/components/color-picker/color-picker.css b/src/uix/eidos/components/color-picker/color-picker.css index 9147febf7..9a2827053 100644 --- a/src/uix/eidos/components/color-picker/color-picker.css +++ b/src/uix/eidos/components/color-picker/color-picker.css @@ -189,16 +189,29 @@ block-size: var(--_color-picker-swatch-size); border-radius: var(--color-picker-swatch-radius); border: 1px solid var(--color-picker-swatch-border); - background-image: var(--cp-checker); - background-size: var(--cp-checker-cell) var(--cp-checker-cell); + /* Checker behind the colour swatch — visible only where the colour + has alpha < 1. We use ::before for the checker (z-index -1 inside an + isolated stacking context) and let the soma-set inline + background-color paint the colour itself. The inline `style` from + `ColorPickerValueSwatchProvider` also emits `--cp-current-color` so + downstream consumers can pull the value as a CSS variable too. */ overflow: hidden; isolation: isolate; } -[data-color-picker-value-swatch]::after { + +[data-color-picker-value-swatch]::before { content: ''; position: absolute; inset: 0; - background: var(--cp-trigger-color, currentColor); + z-index: -1; + background-image: var(--cp-checker); + background-size: var(--cp-checker-cell) var(--cp-checker-cell); + background-position: + 0 0, + 0 calc(var(--cp-checker-cell) / 2), + calc(var(--cp-checker-cell) / 2) calc(var(--cp-checker-cell) / -2), + calc(var(--cp-checker-cell) / -2) 0; + pointer-events: none; } [data-color-picker-value-text] { diff --git a/src/uix/sema/components/color-picker.ts b/src/uix/sema/components/color-picker.ts new file mode 100644 index 000000000..2c78a6eb9 --- /dev/null +++ b/src/uix/sema/components/color-picker.ts @@ -0,0 +1,49 @@ +import { semaSelector } from '$uix/morfo'; +import { colorPickerMorfo } from '$uix/morfo/components/color-picker'; +import { sound } from '../sounds'; +import type { Sema } from '../sema-map'; + +/** + * ColorPicker perceptual defaults. + * + * The 2D area is a direct-manipulation surface — mirrors the slider's + * `handle-pick / handle-drag` shape so consumers get the same auditory + * / tactile vocabulary when dragging across hue, saturation, brightness. + * + * • handle-pick — single pluck at drag start (pickup cue) + * • handle-drag — continuous stream during drag (sound + haptic + * channels open; per-emit payload owns pitch / gain) + * • commit-set — final pluck at drag-release / swatch click / + * eyedropper pick (release cue) + * + * Channel sliders inherit `slider:*` events automatically because they + * compose `SliderProvider` — no extra rules needed here. + */ + +const onArea = (matchers?: Parameters>[2]) => + semaSelector(colorPickerMorfo, 'area', matchers); + +const onProvider = (matchers?: Parameters>[2]) => + semaSelector(colorPickerMorfo, 'provider', matchers); + +export const colorPickerSema: Sema = { + name: 'color-picker', + cascade: [ + { + selector: onArea({ eventName: 'handle-pick' }), + channels: ['sound', 'haptic'], + sound: sound('handle.pickup.air', { gain: 0.12 }), + haptic: { kind: 'tick', intensity: 0.22, duration: 6 } + }, + { + selector: onArea({ eventName: 'handle-drag' }), + channels: ['sound', 'haptic'] + }, + { + selector: onProvider({ eventName: 'commit-set' }), + channels: ['sound', 'haptic'], + sound: sound('handle.release.soft', { gain: 0.1 }), + haptic: { kind: 'tick', intensity: 0.18, duration: 8 } + } + ] +}; diff --git a/src/uix/sema/components/index.ts b/src/uix/sema/components/index.ts index 84ac12de4..fdfa7aee8 100644 --- a/src/uix/sema/components/index.ts +++ b/src/uix/sema/components/index.ts @@ -11,6 +11,7 @@ export { accordionSema } from './accordion' export { checkboxSema } from './checkbox' +export { colorPickerSema } from './color-picker' export { comboboxSema } from './combobox' export { dateFieldSema } from './date-field' export { dialogSema } from './dialog' diff --git a/src/uix/soma/components/color-picker/color-picker-provider.svelte.ts b/src/uix/soma/components/color-picker/color-picker-provider.svelte.ts index 9212e01c9..35cbe7c2d 100644 --- a/src/uix/soma/components/color-picker/color-picker-provider.svelte.ts +++ b/src/uix/soma/components/color-picker/color-picker-provider.svelte.ts @@ -504,8 +504,19 @@ export class ColorPickerValueSwatchProvider { }); } + /** + * Style emitted inline on `[data-color-picker-value-swatch]`. + * + * Exposes the current colour as both a `background-color` (legacy / + * consumers that bypass the recipe) and a `--cp-current-color` CSS + * variable so the Eidos recipe can composite the colour on top of a + * transparency checker. Without the CSS var, an Eidos `::after` + * overlay would stomp on the inline `background-color` and the + * swatch would not visually update. + */ readonly backgroundStyle = $derived.by( - () => `background-color: ${this.provider.effectiveColor.hex};` + () => + `background-color: ${this.provider.effectiveColor.hex}; --cp-current-color: ${this.provider.effectiveColor.hex};` ); readonly props = $derived.by(() => diff --git a/web/routes/uix/+layout@.svelte b/web/routes/uix/+layout@.svelte index a9370f5be..556b6b7b4 100644 --- a/web/routes/uix/+layout@.svelte +++ b/web/routes/uix/+layout@.svelte @@ -27,6 +27,7 @@ import { accordionSema } from '$uix/sema/components/accordion'; import { checkboxSema } from '$uix/sema/components/checkbox'; import { calendarSema } from '$uix/sema/components/calendar'; + import { colorPickerSema } from '$uix/sema/components/color-picker'; import { comboboxSema } from '$uix/sema/components/combobox'; import { dateFieldSema } from '$uix/sema/components/date-field'; import { Popover } from '$uix/eidos/components/popover'; @@ -61,6 +62,7 @@ accordionSema, calendarSema, checkboxSema, + colorPickerSema, comboboxSema, dateFieldSema, dialogSema,