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,