From a8be47319d533d089dd30c0e05ac3857796635a1 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 27 Sep 2026 04:01:27 +0200 Subject: [PATCH] audit(uix): characterize ColorPicker and deduplicate bindings --- docs/process/PLAN-uix-quality-2026-09-19.md | 28 ++ .../audit-uix-2026-09-19/color-picker.md | 134 +++++++++ .../audit-uix-2026-09-19/findings-register.md | 54 +++- .../eidos/components/color-picker/README.md | 37 +-- .../color-picker/color-picker.svelte.test.ts | 263 ++++++++++++++++++ .../test/color-picker-audit-harness.svelte | 158 +++++++++++ .../test/color-picker-audit-view.ts | 72 +++++ .../soma/components/color-picker/README.md | 63 ++--- .../components/color-picker-area.svelte | 19 +- .../color-picker-channel-slider.svelte | 16 +- .../color-picker-eye-dropper.svelte | 17 +- .../color-picker-swatch-group.svelte | 17 +- .../color-picker-swatch-trigger.svelte | 17 +- .../components/color-picker-swatch.svelte | 17 +- .../color-picker-transparency-grid.svelte | 17 +- .../components/color-picker-value-text.svelte | 17 +- 16 files changed, 807 insertions(+), 139 deletions(-) create mode 100644 docs/process/audit-uix-2026-09-19/color-picker.md create mode 100644 src/uix/eidos/components/color-picker/color-picker.svelte.test.ts create mode 100644 src/uix/eidos/components/color-picker/test/color-picker-audit-harness.svelte create mode 100644 src/uix/eidos/components/color-picker/test/color-picker-audit-view.ts diff --git a/docs/process/PLAN-uix-quality-2026-09-19.md b/docs/process/PLAN-uix-quality-2026-09-19.md index 99072abbf..1c529a629 100644 --- a/docs/process/PLAN-uix-quality-2026-09-19.md +++ b/docs/process/PLAN-uix-quality-2026-09-19.md @@ -1171,6 +1171,34 @@ pasa 511/511 archivos y 5622/5622 tests; src/scripts sin errores nuevos, 89 erro de web dentro del ledger, 898 documentos y 25 arts sin incidencias, smoke apps/base 27/27 sin errores de página, consola ni CSP. +### ColorPicker: formatos, canales y swatches (2026-09-27) + +La [ficha de ColorPicker](./audit-uix-2026-09-19/color-picker.md) relee las +cuatro capas, pre-flight, receta, historia y Air. Distingue 21 partes Morfo, +23 wrappers locales Soma, 24 Eidos y 34 miembros attached. Consulta cuatro +bibliotecas primarias, MDN y APG; concilia comparaciones desfasadas. + +Once canarios nativos y las suites de provider/opts pasan 25/25 en tres archivos. +Seis sondas únicas fallan en otro y registran Q94–Q99 para reparación posterior; +la de formato requiere alternancia con autoFormat, no el cambio externo aislado. +Siete bolsas props→opts conservan por AST 22 conexiones y sus templates y +reducen siete líneas físicas/no vacías. Después se mantienen los 25 positivos +y los seis fallos; dos sondas nuevas antes de step confirman Q100 (swatch por +defecto transparente) y Q101 (estado visual de drag ausente), sin tocar sus +rutas. El switch de step pasa a una expresión idéntica en quince casos y resta +otras doce líneas: balance de ocho archivos ejecutables, 19 líneas menos. +Tras step pasan los mismos 25 positivos y fallan las ocho sondas, retiradas con +fuentes/logs/artefactos. Visual: 11/11 y dos repeticiones con cuatro capturas +inspeccionadas. La vertical revela Q102, confirmado por pseudo-elementos de +6×160px frente a 320×6px; CSS intacto y novena sonda retirada. Contratos 104/104, +899 documentos sin incidencias. La fixture corrige sólo la ubicación de su +snippet auxiliar body (no es prop pública) y pasa de nuevo 25/25. El gate +completo repetido pasa 512/512 archivos y 5633/5633 tests, src/scripts sin errores +nuevos, 89 errores de web dentro del ledger y smoke apps/base 27/27 sin errores. +El inventario alcanza 73 fichas revisadas; +fase 6 abierta. component-audit conserva su baseline NEEDS-WORK por acciones +frente a eventos y la demo congelada; no se altera web ni el sistema probado. + ### Revisión de Chronos (2026-09-22) El provider contiene 1.723 líneas físicas no vacías, pero su volumen no procede diff --git a/docs/process/audit-uix-2026-09-19/color-picker.md b/docs/process/audit-uix-2026-09-19/color-picker.md new file mode 100644 index 000000000..f5c54e1bc --- /dev/null +++ b/docs/process/audit-uix-2026-09-19/color-picker.md @@ -0,0 +1,134 @@ +--- +title: UIX — auditoría de ColorPicker +type: notes +audience: human + agent +authority: process — caracterización y extracción neutral del contrato existente +status: reviewed +--- + +# ColorPicker + +Fecha: 2026-09-27. Rama: astra. Fase 6 abierta. Se conservan comportamiento, +API, geometría y CSS; web queda excluido. Las reparaciones se difieren. + +## Corpus y procedencia + +Se releen completos los cuatro pre-flight canónicos, Morfo, el provider Soma +(1403 líneas físicas), sus tests, tipos, exports, 23 wrappers locales Soma, +24 locales Eidos, contexto, CSS, spectrum, Sema, traducciones y receta de +71 claves públicas más el forward de paleta. Se contrastan las fichas históricas +de julio y agosto y los 16 archivos ColorPicker de Air en morfo-runtime. +Las medidas históricas de theming conservan su fecha, no se repiten ni se +presentan como cobertura nueva. ColorField/Popover/Slider se leen sólo como +composiciones necesarias: no se declara una nueva auditoría íntegra de ellos. + +El censo distingue 21 partes Morfo y 34 miembros attached Eidos, incluidos los +delegados de Slider, Popover y PickerShell. No compara recuentos de unidades +distintas como porcentajes de cobertura. La base privada de diez partes Soma +ya extraída el 22 de septiembre (19 líneas no vacías menos) no se cuenta otra vez. +L-152 conserva identidad para los bindings ref; este lote no lo repara. + +## Matriz primaria y decisión + +| Fuente | Contrato contrastado | Decisión UIX | +| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| [Ark UI](https://ark-ui.com/docs/components/color-picker) | Área configurable, sliders por canal, value/open controlados y formulario. | Conservar el modelo 2D y retirar la comparación que fijaba sus ejes. | +| [Chakra UI](https://chakra-ui.com/docs/components/color-picker) | Sliders RGB/alpha, swatches y composición controlada. | Retirar la afirmación de que no ofrece RGB. | +| [React Aria ColorArea](https://react-aria.adobe.com/ColorArea) y [hook](https://react-aria.adobe.com/ColorArea/useColorArea) | Ejes configurables y composición accesible de dos canales. | Mantener el área 2D; adjudicar ARIA aparte, sin convertirla en dos sliders. | +| [Mantine](https://mantine.dev/core/color-picker/) | Formatos de string, swatches, tallas y teclas nativas. | Comparar contratos sin inferir carencias por ejemplos. | +| [MDN EyeDropper](https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper_API) | API con soporte limitado, gesto de usuario y contexto seguro. | No sustituir ni simular captura física de pantalla. | + +Consulta del 2026-09-27. APG [slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider/) +y [radio](https://www.w3.org/WAI/ARIA/apg/patterns/radio/) fundamentan la +adjudicación de valores numéricos y navegación/foco; no amplían la API. +La separación entre formato externo y formato de edición es un contrato local, +no una afirmación de exclusividad mundial. No se adopta una biblioteca nueva. + +## Caracterización + +Once canarios Eidos montan ActiveUix, Soma y ActiveEidos reales: ocho teclas del +área, hue/alpha/RGB, drag con captura real y un commit, ejes RGB sin pérdida, +RTL y rampas, formatos/autoFormat, presets, guardado/borrado/cap, locks, +Popover modal/Escape/foco y talla/variante/checker/formato reactivos. +Se esperan dos frames nativos tras montaje por autofocus. No se sustituyen +servicios ni pointer capture. No se invoca la captura física EyeDropper. + +La primera ejecución aislada da diez canarios correctos y uno fallido más +cinco sondas negativas. Se separa el fallo de display del select del resto del +contrato de formatos. La actualización externa aislada sí pasa; la secuencia +con autoFormat, HSL, RGB y cambio externo HSL reproduce la discrepancia Q99. +La baseline del árbol mantenido pasa 25/25 positivos en tres archivos (11 +canarios, siete tests del provider y siete de opts) y falla las seis sondas +únicas en otro archivo. El fallo esperado de esas sondas no es un gate verde. + +## Defectos diferidos + +| ID | Evidencia | Límite / prueba de cierre | +| --- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| Q94 | name pasado por rest no llega al provider; HiddenInput no aporta brandColor a FormData. | Adjudicar API pública de name y cableado; probar formatos, reset, disabled y FormData. | +| Q95 | AreaThumb role=slider expone valuetext pero carece de now/min/max numéricos. | DOM confirmado; adjudicar representación 2D y probar tecnología de asistencia. | +| Q96 | ArrowRight tras seleccionar rojo no mueve foco ni selecciona verde en Presets. | Clic y teclado nativos; cubrir roving, Home/End, RTL y SavedSwatches. | +| Q97 | Preset verde tiene aria-checked=true pero su indicador computa display:none. | DOM/CSS reales; conectar selección y probar presets/saved/parte independiente. | +| Q98 | Bajo readonly Save sigue habilitado pero el clic no muta la colección ni llama al callback. | Discrepancia de affordance, no escritura indebida; adjudicar disabled/readonly y Save/Delete. | +| Q99 | Tras alternar con autoFormat, cambio externo HSL actualiza segmentos pero select.value queda rgb. | Secuencia nativa confirmada esperando actualización; probar select, segmentos, callbacks y flags. | + +Las sondas añadidas después de bindProps confirman otros dos defectos: Q100, +SwatchTrigger por defecto selecciona verde pero su Swatch queda transparente; +Q101, hay captura y commit nativos pero el thumb nunca expone data-dragging. +Sus rutas de provider y templates no se modifican por este lote. La ejecución +ampliada mantiene 25 positivos y ocho sondas negativas en cuatro archivos. +No se presenta esta ampliación como una medición anterior a bindProps. + +## Extracción y validación + +Se sustituyen sólo siete bolsas props→opts por el bindProps existente: Area +(cuatro conexiones), ValueText, TransparencyGrid, SwatchGroup, SwatchTrigger, +Swatch y EyeDropper (tres cada una). La prueba AST conserva las 22 conexiones, +su orden, getters/setters y los siete templates byte a byte. Cada wrapper pierde +una línea física y no vacía: siete menos. Root y Trigger conservan las bolsas +que coordinan varios providers; no se unifican políticas de composición. + +Después de bindProps pasan los mismos 25/25 positivos y fallan las seis sondas +originales. Antes de simplificar step se añaden las dos sondas descritas: 25 +positivos y ocho fallos esperados. El switch de ChannelSlider devuelve 0.01 para +alpha y 1 en cualquier otra rama; se reduce a una expresión derived. Object.is +conserva quince casos (siete canales, fallback y valores runtime ajenos al tipo). +Pasa de 104 a 92 líneas físicas y 96 a 84 no vacías. Balance de ocho archivos +ejecutables: 19 líneas físicas/no vacías menos. Se precisa el comentario de +unidades; no cambian pasos ni Shift. Después de step vuelven a pasar 25/25 +positivos y fallan las mismas ocho sondas únicas. Sus fuentes, logs y artefactos +se retiran a tmp/color-picker-audit-evidence-2026-09-27. + +Visual nativo: once canarios pasan y dos seleccionados se repiten para capturas +adicionales. Se inspeccionan cuatro imágenes a escala nativa: surface RTL, +ghost lg con checker/formato actualizados, RGB vertical y contenido modal con +Footer. No hay comparación pixel baseline. La imagen vertical revela Q102: +pista de 6×160px frente a checker de 320×6px. Una sonda de pseudo-elementos en +Chromium lo confirma; CSS intacto. Se retira esa novena sonda y se conserva en +la misma carpeta, subdirectorio vertical-visual. Cierre: adjudicar geometría del +checker horizontal/vertical, tallas y RTL, sin alterar el gradiente ni valores. +No se afirma que la composición visual esté libre de defectos. + +Los contratos pasan 104/104 en cuatro archivos. docs-check cierra 899 documentos +sin incidencias tras precisar que el recuento pertenece a partes ColorPicker, +no al catálogo completo. El comparador de referencias requiere filas de tabla; +se conserva una matriz primaria de cuatro fuentes en Eidos además de Soma. +El primer gate supera lint y se detiene en check:gate por una única deuda nueva +de la fixture: body declarado dentro del componente se convierte en prop que +ColorPickerProps no admite. El diagnóstico completo confirma 90 errores frente +a los 89 del ledger. Se mueve la declaración auxiliar fuera del componente; +no se añade esa prop ni se toca producción. La fixture corregida pasa 25/25 +pruebas en tres archivos. El gate completo repetido pasa 512/512 archivos y +5633/5633 tests: src/scripts sin errores nuevos, los 89 errores de web dentro +del ledger, 899 documentos y 25 arts sin incidencias, smoke apps/base 27/27 +sin errores de página, consola ni CSP. +component-audit conserva +NEEDS-WORK: A-3.7 cuenta ocho acciones de teclado frente a cuatro eventos y +D-1.2 detecta la demo congelada. No se inventan eventos ni se cambia web. + +## Límites + +Chromium no acredita lectores de pantalla, touch físico, otros motores ni +EyeDropper físico. No se reejecuta toda la medición de tokens de agosto. +Las nueve reproducciones negativas quedan retiradas de la suite mantenida con +fuentes, logs y artefactos en tmp; se convertirán en pruebas de cierre al reparar. diff --git a/docs/process/audit-uix-2026-09-19/findings-register.md b/docs/process/audit-uix-2026-09-19/findings-register.md index 693fccb15..3a0de934f 100644 --- a/docs/process/audit-uix-2026-09-19/findings-register.md +++ b/docs/process/audit-uix-2026-09-19/findings-register.md @@ -176,10 +176,19 @@ Lotes posteriores de ciclo de vida de MediaPlayer: Lotes posteriores de los controles MediaPlayer usados por AudioPlayer: -| Lote posterior | ID | Condición y prueba de cierre | -| --------------------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Nombres de tiempo y volumen | Q86 | Nombrar los thumbs de TimeSlider con y sin peaks y de VolumeSlider; cubrir traducciones, teclado y composición independiente del skin. | -| Texto accesible del tiempo | Q87 | Adjudicar y conectar el formato temporal en ambos scrubbers, conservar dominio numérico y probar cambios de tiempo/duración e idiomas con tecnología de asistencia. | +| Lote posterior | ID | Condición y prueba de cierre | +| ---------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Nombres de tiempo y volumen | Q86 | Nombrar los thumbs de TimeSlider con y sin peaks y de VolumeSlider; cubrir traducciones, teclado y composición independiente del skin. | +| Texto accesible del tiempo | Q87 | Adjudicar y conectar el formato temporal en ambos scrubbers, conservar dominio numérico y probar cambios de tiempo/duración e idiomas con tecnología de asistencia. | +| Formulario ColorPicker | Q94 | Adjudicar y conectar name; probar FormData, formatos, reset y disabled. | +| ARIA numérica del área | Q95 | Adjudicar la superficie accesible 2D y comprobarla con tecnología de asistencia. | +| Navegación de swatches | Q96 | Cubrir flechas, foco, roving, Home/End, RTL y ambas colecciones. | +| Indicador seleccionado | Q97 | Conectar estado checked, probar presets, guardados y partes independientes. | +| Affordance Save/Delete | Q98 | Adjudicar disabled/readonly, conservar guards y callbacks. | +| Display nativo de formato | Q99 | Cubrir alternancia autoFormat y cambios externos con select/segmentos sincronizados. | +| Color del swatch por defecto | Q100 | Reenviar o heredar color; probar ambas composiciones y cambios reactivos. | +| Estado visual de arrastre | Q101 | Conectar estado de drag y cubrir inicio/movimiento/up/cancel/locks/limpieza. | +| Checker vertical del canal | Q102 | Corregir geometría tras cubrir orientación, talla, RTL, alfa y gradiente. | Al aparecer otro defecto durante la auditoría se añade aquí y en la tabla de evidencia de abajo con un ID existente del ledger o un `Q*` nuevo, reproducción @@ -275,6 +284,43 @@ Ninguno entra en la cola de correcciones confirmadas por inferencia. | Q73 | Cropper puntero | Down ID1 en Selection y move ID2 de 32px desplazan crop.x de 0.25 a 0.35; la sesión no conserva pointerId. | Chromium montado, eventos sintéticos y captura mock; sonda retirada | Cubrir selección, handle y pan; reproducir multi-touch nativo. | | Q74 | Cropper metadatos | apg afirma keyboard nudge desde C5, pero Selection/Handle no tienen handler de teclado y README lo difiere. El campo de metadatos se conserva. | Discrepancia confirmada por lectura; efecto en consumidores de metadatos sin reproducir | Adjudicar y alinear la justificación; no implementar teclado por inferencia. | +### ColorPicker: Q94–Q102 + +Fecha: 2026-09-27. La [ficha de ColorPicker](./color-picker.md) conserva el +contrato existente. Baseline nativa: 25 positivos en tres archivos y seis +sondas negativas únicas en otro; no se arreglan durante la extracción. + +| ID | Área | Evidencia reproducida | Límite | Prueba posterior | +| --- | --------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| Q94 | HiddenInput | FormData(form).get('brandColor') es null aunque name se pasa por rest. | name no se desestructura ni llama setName; adjudicar su API pública. | Cablear nombre y probar formatos, reset y locks. | +| Q95 | Área 2D / ARIA | role=slider con valuetext sin aria-valuenow/min/max. | DOM real; no se ha probado lector de pantalla. | Adjudicar representación 2D y probar asistencia. | +| Q96 | Presets / radio | Tras clic en rojo, ArrowRight deja foco en rojo y no selecciona verde. | Teclado nativo; no generalizar aún a todos los grupos. | Roving/flechas, Home/End, RTL, presets y guardados. | +| Q97 | Indicador de swatch | Verde aria-checked=true pero indicador display:none. | DOM/CSS real; no hay una fuente checked propia del indicador. | Conectar selección y comprobar las composiciones. | +| Q98 | SaveAction / readonly | Save sigue habilitado; clic no escribe ni llama al callback. | El guard de mutación sí funciona; discrepancia de affordance. | Adjudicar readonly/disabled para Save y Delete. | +| Q99 | FormatSelect | Con autoFormat, HSL→RGB y externo HSL, segmentos cambian a HSL pero select.value sigue rgb. | La actualización externa aislada sí pasa; reproducción con espera. | Sincronizar display con segmentos, preservar callbacks y separación de formatos. | + +Las primeras cinco sondas se reproducen en la ejecución aislada y en el árbol +mantenido; Q99 requiere la secuencia completa tras activar autoFormat. No se +presenta como un fallo universal del binding externo. L-152 mantiene su ID. +Q100 y Q101 se añaden tras bindProps, antes de step: la ejecución ampliada +mantiene 25 positivos y reproduce ocho fallos únicos. Q100: un SwatchTrigger +por defecto elige verde y queda checked, pero su Swatch computa fondo transparente; +Soma no hereda color y Eidos no lo reenvía al hijo. Q101: drag nativo acredita +captura y un commit, mientras el thumb nunca expone data-dragging; su estado +isDragging se inicializa false y no recibe escrituras. No se afirma ausencia +de drag ni de commit. Cierre: color heredado/composición explícita y estados +inicio/movimiento/up/cancel, locks, RTL y limpieza. Esas rutas del provider y +los templates permanecen intactos. Después de step se mantienen los 25 positivos +y los ocho fallos esperados. Las fuentes, logs y artefactos se retiran a +tmp/color-picker-audit-evidence-2026-09-27. + +Q102 se detecta al inspeccionar la captura RGB vertical y se confirma por +pseudo-elementos en Chromium: pista ::before 6×160px, checker ::after 320×6px. +La regla checker fija geometría horizontal para toda orientación. CSS intacto, +valores y teclado del slider sí pasan; no es un fallo del motor de color. +La novena sonda se retira con su log y screenshot en vertical-visual. Cierre: +geometría horizontal/vertical, tallas/RTL, alfa y gradiente sin pérdidas. + ### FloatPanel: Q90–Q93 Reproducción del 2026-09-27 en Chromium, con ActiveUix, Soma y ActiveEidos diff --git a/src/uix/eidos/components/color-picker/README.md b/src/uix/eidos/components/color-picker/README.md index 80a9cac90..2fc741667 100644 --- a/src/uix/eidos/components/color-picker/README.md +++ b/src/uix/eidos/components/color-picker/README.md @@ -13,7 +13,7 @@ color-picker/`) ya implementaba una superficie de color-picker con Provider · Trigger · Content · Area + AreaThumb · HueSlider · AlphaSlider · Input · Swatches/Swatch · EyeDropper. Esta receta sigue las mismas líneas pero monta sobre la implementación Soma actual, que -es más rica: 23 partes públicas (incluyendo `ValueSwatch`, `ValueText`, +tiene 24 wrappers locales y 34 miembros attached (incluyendo `ValueSwatch`, `ValueText`, `TransparencyGrid`, `SwatchIndicator`, control sliders por canal RGB además del par HSV, `ChannelInput`+`ChannelSegment`+`FormatSelect` compuestos desde `ColorField`). @@ -116,22 +116,25 @@ puede escribir, y el guard R-5.4 comprueba que ninguna miente. ## Comparativa -| Capacidad | Soma/Eidos | Air (morfo-runtime) | Ark UI | Chakra UI | -| --------------------------------- | ------------------------------------ | ------------------- | ------------ | ------------ | -| 2D area (sat/bright) | Sí | Sí | Sí | Sí | -| Hue slider | Sí (`ChannelSlider channel='hue'`) | Sí | Sí | Sí | -| Alpha slider | Sí (`ChannelSlider channel='alpha'`) | Sí | Sí | Sí | -| RGB sliders | Sí (red/green/blue channels) | No | No | No | -| Saturation/brightness sliders | Sí | No | No | No | -| Channel input (segmented) | Sí (compone ColorField) | Input simple | Input simple | Input simple | -| Format switch (hex/rgb/hsl) | Sí (`FormatSelect`) | Sí | Sí | Sí | -| `allowedFormats` whitelist | Sí | No | No | No | -| Eye dropper | Sí (native API) | Sí | Sí | Sí | -| Preset swatches | Sí (`SwatchGroup`) | Sí | Sí | Sí | -| Hidden input para forms | Sí | No | Via picker | Via picker | -| `closeOnSelect` | Sí | No | Sí | Sí | -| Inline mode | Sí (`inline={true}`) | No | Sí | Sí | -| Variante / tamaño / paleta visual | Sí, Eidos | CSS air-specific | CSS externo | CSS externo | +| Fuente primaria | Contrato contrastado | Decisión | +| --------------------------------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------- | +| [Ark UI](https://ark-ui.com/docs/components/color-picker) | Ejes configurables y sliders por canal. | Conservar los ejes actuales; no tratarlos como ventaja exclusiva. | +| [Chakra UI](https://chakra-ui.com/docs/components/color-picker) | Sliders RGB/alpha y swatches. | Retirar la comparación que negaba RGB. | +| [React Aria](https://react-aria.adobe.com/ColorArea) | Área configurable y composición accesible. | Adjudicar ARIA 2D por separado. | +| [Mantine](https://mantine.dev/core/color-picker/) | Formatos, swatches, tallas y teclado. | Comparar contratos sin portar funcionalidad. | + +Consulta primaria del 2026-09-27. Ark y Chakra documentan sliders RGB y ejes +configurables; las anteriores celdas "No" no describían su API vigente. La +[matriz Soma](../../../soma/components/color-picker/README.md#comparison-with-reference-libraries) +recoge contratos y decisiones. UIX conserva su segmentación, separación de +formatos y composición de Popover; no introduce funciones de esas bibliotecas. +El censo distingue 21 partes Morfo, 23 wrappers locales Soma, 24 locales Eidos +y 34 miembros attached, incluyendo delegados de Slider, Popover y PickerShell. + +HiddenInput requiere adjudicar su conexión de nombre (Q94). Los presets tienen +estado radio comprobado por clic, con navegación por flechas (Q96) e indicador +seleccionado (Q97) pendientes de corrección. Las medidas de theming de agosto +anteriores conservan su fecha y no representan una medición nueva de este lote. ## Decisiones diff --git a/src/uix/eidos/components/color-picker/color-picker.svelte.test.ts b/src/uix/eidos/components/color-picker/color-picker.svelte.test.ts new file mode 100644 index 000000000..b3e9b2463 --- /dev/null +++ b/src/uix/eidos/components/color-picker/color-picker.svelte.test.ts @@ -0,0 +1,263 @@ +import { beforeAll, expect, it, vi } from 'vitest'; +import { page, userEvent } from 'vitest/browser'; +import { renderColorPicker } from './test/color-picker-audit-view'; +beforeAll(async () => { + await page.viewport(1100, 1000); + await userEvent.keyboard('{Shift}'); +}, 60_000); +it('edits both area axes with native keys, preserving hue and alpha', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + const thumb = v.part('area-thumb'); + thumb.focus(); + await userEvent.keyboard('{ArrowRight}{Shift>}{ArrowUp}{/Shift}'); + expect(v.value()?.hsv).toEqual({ h: 210, s: 51, v: 70, a: 0.8 }); + expect(v.onValueChangeEnd).toHaveBeenCalledTimes(2); + await userEvent.keyboard('{Home}{End}{PageDown}{PageUp}'); + expect(v.value()?.hsv.s).toBe(100); + expect(v.value()?.hsv.v).toBe(100); + expect(thumb.getAttribute('aria-valuetext')).toBe('saturation: 100, brightness: 100'); + } finally { + await v.dispose(); + } +}, 30_000); +it('mirrors the area and horizontal channel ramp reactively in RTL', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + await userEvent.click(v.button('Toggle direction')); + expect(v.root.dir).toBe('rtl'); + const thumb = v.part('area-thumb'); + thumb.focus(); + await userEvent.keyboard('{ArrowRight}'); + expect(v.value()?.hsv.s).toBe(49); + expect(parseFloat(thumb.style.left)).toBe(51); + expect(getComputedStyle(v.part('area-background'), '::before').backgroundImage).toContain( + 'to left' + ); + expect( + v.part('channel-slider').style.getPropertyValue('--_color-picker-channel-gradient') + ).toContain('to left'); + if (import.meta.env.VITE_UIX_AUDIT_SCREENSHOTS === '1') + await page.screenshot({ + path: 'tmp/color-picker-rtl.png', + element: page.elementLocator(v.target) + }); + } finally { + await v.dispose(); + } +}, 30_000); +it('captures a native drag and commits once after the last continuous value', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + const area = v.part('area'); + let trusted = false; + let captured = false; + area.addEventListener('pointermove', (e) => { + trusted ||= e.isTrusted; + captured ||= area.hasPointerCapture(e.pointerId); + }); + await userEvent.dragAndDrop( + v.part('area-thumb'), + v.target.querySelector('[data-destination]')! + ); + expect(trusted).toBe(true); + expect(captured).toBe(true); + expect(v.value()?.hsv.s).toBe(0); + expect(v.value()?.hsv.v).toBe(0); + expect(v.onValueChange).toHaveBeenCalled(); + expect(v.onValueChangeEnd).toHaveBeenCalledTimes(1); + expect(v.onValueChangeEnd).toHaveBeenLastCalledWith(v.value()); + } finally { + await v.dispose(); + } +}, 30_000); +it('changes area channels to RGB without a lossy HSV round trip', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + const old = v.value()!.rgb; + await userEvent.click(v.button('RGB area')); + expect(v.part('area').dataset.xChannel).toBe('red'); + const thumb = v.part('area-thumb'); + thumb.focus(); + await userEvent.keyboard('{ArrowRight}{ArrowUp}'); + expect(v.value()?.rgb).toEqual({ r: old.r + 1, g: old.g + 1, b: old.b, a: old.a }); + } finally { + await v.dispose(); + } +}, 30_000); +it('composes native hue, alpha and reactive vertical RGB sliders', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + const sliders = () => + Array.from(document.querySelectorAll('[data-color-picker-channel-slider]')); + let thumb = sliders()[0].querySelector('[data-slider-thumb]')!; + thumb.focus(); + await userEvent.keyboard('{ArrowRight}'); + expect(v.value()?.hsv.h).toBe(211); + expect(thumb.getAttribute('aria-valuemin')).toBe('0'); + expect(thumb.getAttribute('aria-valuemax')).toBe('360'); + sliders()[1].querySelector('[data-slider-thumb]')!.focus(); + await userEvent.keyboard('{Home}'); + expect(v.value()?.rgb.a).toBe(0); + await userEvent.click(v.button('Red vertical')); + thumb = sliders()[0].querySelector('[data-slider-thumb]')!; + expect(thumb.getAttribute('aria-orientation')).toBe('vertical'); + expect(sliders()[0].style.getPropertyValue('--_color-picker-channel-gradient')).toContain( + 'to top' + ); + thumb.focus(); + await userEvent.keyboard('{End}'); + expect(v.value()?.rgb.r).toBe(255); + if (import.meta.env.VITE_UIX_AUDIT_SCREENSHOTS === '1') + await page.screenshot({ + path: 'tmp/color-picker-rgb-vertical.png', + element: page.elementLocator(v.target) + }); + } finally { + await v.dispose(); + } +}, 30_000); +it('keeps the edit format separate and propagates it only with autoFormat', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + const select = v.part('format-select'); + await userEvent.selectOptions(select, 'rgb'); + expect(v.format()).toBe('hex'); + expect(v.onFormatChange).not.toHaveBeenCalled(); + expect( + v.part('channel-input').querySelectorAll('[data-color-picker-channel-segment]').length + ).toBeGreaterThan(1); + await userEvent.click(v.button('Toggle auto format')); + await userEvent.selectOptions(select, 'hsl'); + expect(v.format()).toBe('hsl'); + expect(v.onFormatChange).toHaveBeenCalledTimes(1); + await userEvent.selectOptions(select, 'rgb'); + await userEvent.click(v.button('External HSL')); + expect(v.format()).toBe('hsl'); + expect(v.part('channel-input').dataset.format).toBe('hsl'); + await userEvent.click(v.button('Lock format')); + expect(v.part('format-select').tagName).toBe('SPAN'); + expect(v.part('format-select').hasAttribute('data-locked')).toBe(true); + } finally { + await v.dispose(); + } +}, 30_000); +it('commits preset radios and updates checked state and composed swatch paint', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + const radios = v.radios('Presets'); + await userEvent.click(radios[1]); + expect(v.value()?.hex).toBe('#00ff00'); + expect(radios[1].getAttribute('aria-checked')).toBe('true'); + expect(radios[0].getAttribute('aria-checked')).toBe('false'); + expect(v.onValueChangeEnd).toHaveBeenCalledTimes(1); + expect(radios[1].querySelector('[data-color-swatch]')).not.toBeNull(); + } finally { + await v.dispose(); + } +}, 30_000); +it('saves and deletes bound swatches, enforces the cap and avoids duplicates', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + const initial = v.value()!.hex; + await userEvent.click(v.button('Save color')); + expect(v.saved()).toEqual(['#ff0000', initial]); + expect(v.onSavedColorsChange).toHaveBeenCalledTimes(1); + await userEvent.click(v.radios('Saved')[1]); + await userEvent.click(v.button('Delete color')); + expect(v.saved()).toEqual(['#ff0000']); + expect(v.onSavedColorsChange).toHaveBeenCalledTimes(2); + await userEvent.click(v.radios('Presets')[1]); + await userEvent.click(v.button('Save color')); + expect(v.saved()).toEqual(['#ff0000', '#00ff00']); + await userEvent.click(v.radios('Presets')[2]); + expect((v.button('Save color') as HTMLButtonElement).disabled).toBe(true); + expect(v.saved()).toHaveLength(2); + } finally { + await v.dispose(); + } +}, 30_000); +it('locks native edits under readonly and disabled while retaining readouts', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + const original = v.value(); + await userEvent.click(v.button('Toggle readonly')); + v.part('area-thumb').focus(); + await userEvent.keyboard('{ArrowRight}'); + await userEvent.click(v.radios('Presets')[1]); + expect(v.value()).toEqual(original); + expect(v.onValueChangeEnd).not.toHaveBeenCalled(); + await userEvent.click(v.button('Toggle disabled')); + expect(v.part('area-thumb').tabIndex).toBe(-1); + expect(v.radios('Presets')[0].disabled).toBe(true); + await expect.element(v.part('value-text')).toHaveTextContent(original!.hex); + } finally { + await v.dispose(); + } +}, 30_000); +it('opens a native popover and restores focus and modal snapshot on Escape', async () => { + const v = renderColorPicker({ inline: false, mode: 'modal' }); + try { + await v.ready(); + const original = v.value(); + const trigger = v.part('trigger'); + trigger.focus(); + await userEvent.keyboard('{Enter}'); + await vi.waitFor(() => expect(v.open()).toBe(true)); + await vi.waitFor(() => expect(v.part('area-thumb')).not.toBeNull()); + await v.ready(); + v.part('area-thumb').focus(); + await userEvent.keyboard('{ArrowRight}'); + expect(v.value()).not.toEqual(original); + expect(v.onValueChangeEnd).toHaveBeenCalledTimes(1); + if (import.meta.env.VITE_UIX_AUDIT_SCREENSHOTS === '1') + await page.screenshot({ + path: 'tmp/color-picker-popup.png', + element: page.elementLocator(document.body) + }); + await userEvent.keyboard('{Escape}'); + await vi.waitFor(() => expect(v.open()).toBe(false)); + expect(v.value()).toEqual(original); + await vi.waitFor(() => expect(document.activeElement).toBe(trigger)); + } finally { + await v.dispose(); + } +}, 30_000); +it('updates sizes, variants, format override and checker cells on the same root', async () => { + const v = renderColorPicker(); + try { + await v.ready(); + const root = v.root; + const trigger = v.part('trigger'); + const medium = getComputedStyle(trigger).minHeight; + await userEvent.click(v.button('Next size')); + const small = getComputedStyle(trigger).minHeight; + expect(parseFloat(small)).toBeLessThan(parseFloat(medium)); + await userEvent.click(v.button('Next size')); + expect(parseFloat(getComputedStyle(trigger).minHeight)).toBeGreaterThan(parseFloat(medium)); + await userEvent.click(v.button('Next variant')); + expect(trigger.dataset.variant).toBe('outline'); + await userEvent.click(v.button('Next variant')); + expect(trigger.dataset.variant).toBe('ghost'); + expect(v.root).toBe(root); + await userEvent.click(v.button('Change displays')); + expect(v.part('transparency-grid').style.backgroundSize).toBe('24px 24px'); + expect(v.target.querySelector('[data-override-display]')!.textContent).toContain('rgba('); + if (import.meta.env.VITE_UIX_AUDIT_SCREENSHOTS === '1') + await page.screenshot({ + path: 'tmp/color-picker-displays.png', + element: page.elementLocator(v.target) + }); + } finally { + await v.dispose(); + } +}, 30_000); diff --git a/src/uix/eidos/components/color-picker/test/color-picker-audit-harness.svelte b/src/uix/eidos/components/color-picker/test/color-picker-audit-harness.svelte new file mode 100644 index 000000000..ce2be7841 --- /dev/null +++ b/src/uix/eidos/components/color-picker/test/color-picker-audit-harness.svelte @@ -0,0 +1,158 @@ + + +{#snippet body()} +
+ + + + {#snippet children({ + segments + })}{#each segments as segment, i (`${segment.part}:${i}`)}{segment.value}{/each}{/snippet} + + + + + + + + + {#if !inline}ClearCancelDone{/if} +
+{/snippet} + +
+

ColorPicker audit

+
e.preventDefault()}> + + Accent + + + {#if inline}{@render body()}{:else}{@render body()}{/if} + +
+ + + + + + + + {JSON.stringify(value)}{format}{String(open)}{JSON.stringify(savedColors)} +
+
diff --git a/src/uix/eidos/components/color-picker/test/color-picker-audit-view.ts b/src/uix/eidos/components/color-picker/test/color-picker-audit-view.ts new file mode 100644 index 000000000..50383315d --- /dev/null +++ b/src/uix/eidos/components/color-picker/test/color-picker-audit-view.ts @@ -0,0 +1,72 @@ +import { flushSync, mount, unmount } from 'svelte'; +import { expect, vi } from 'vitest'; +import { createActiveUix } from '$active-uix'; +import type { ColorValue } from '$libs/color'; +import '../../../index.css'; +import ColorPickerAuditHarness from './color-picker-audit-harness.svelte'; +export function renderColorPicker(options: { inline?: boolean; mode?: 'inline' | 'modal' } = {}) { + const uix = createActiveUix({ + langs: { schema: {}, defaultLocale: 'es' }, + prefs: { storage: false }, + projectPrefs: false + }); + const target = document.createElement('div'); + document.body.append(target); + const callbacks = { + onValueChange: vi.fn(), + onValueChangeEnd: vi.fn(), + onFormatChange: vi.fn(), + onOpenChange: vi.fn(), + onSavedColorsChange: vi.fn() + }; + const component = mount(ColorPickerAuditHarness, { + target, + props: { uix, ...options, ...callbacks } + }); + flushSync(); + return { + target, + uix, + ...callbacks, + get root() { + return target.querySelector('[data-color-picker]')!; + }, + part(name: string) { + return document.querySelector(`[data-color-picker-${name}]`)!; + }, + button(text: string) { + return Array.from(document.querySelectorAll('button')).find( + (b) => b.textContent?.trim() === text + )!; + }, + value(): ColorValue | undefined { + return JSON.parse(target.querySelector('output[data-value]')!.textContent!); + }, + format() { + return target.querySelector('output[data-format]')!.textContent!; + }, + open() { + return target.querySelector('output[data-open]')!.textContent === 'true'; + }, + saved(): string[] { + return JSON.parse(target.querySelector('output[data-saved]')!.textContent!); + }, + radios(label: string) { + return Array.from( + document + .querySelector(`[role="radiogroup"][aria-label="${label}"]`)! + .querySelectorAll('[role="radio"]') + ); + }, + async ready() { + await new Promise((r) => requestAnimationFrame(() => r())); + await new Promise((r) => requestAnimationFrame(() => r())); + await vi.waitFor(() => expect(this.root).not.toBeNull()); + }, + async dispose() { + await unmount(component); + uix.dispose(); + target.remove(); + } + }; +} diff --git a/src/uix/soma/components/color-picker/README.md b/src/uix/soma/components/color-picker/README.md index c804bd65e..a718b53c1 100644 --- a/src/uix/soma/components/color-picker/README.md +++ b/src/uix/soma/components/color-picker/README.md @@ -1,6 +1,6 @@ # ColorPicker -Full-featured headless color picker — 22 parts, composes `Popover` + `ColorField`. Exposes a 2D saturation/brightness area, per-channel sliders (hue, alpha, or any RGB/HSV channel), the browser's native EyeDropper API, preset swatches, and the segmented `ColorField` as `ChannelInput`. +Full-featured headless color picker — 23 local wrappers plus composed parts, composes `Popover` + `ColorField`. Exposes a 2D saturation/brightness area, per-channel sliders (hue, alpha, or any RGB/HSV channel), the browser's native EyeDropper API, preset swatches, and the segmented `ColorField` as `ChannelInput`. ## Anatomy @@ -67,7 +67,7 @@ Full-featured headless color picker — 22 parts, composes `Popover` + `ColorFie Set `inline` on `Provider` to skip the `Popover` and render the content surface directly — the picker then behaves as an always-open panel. -## Parts (22) +## Local and composed parts | Part | Source | Notes | | ------------------------------------- | ---------- | ----------------------------------------------------------------------------- | @@ -91,10 +91,10 @@ Set `inline` on `Provider` to skip the `Popover` and render the content surface | `ChannelSegment` | ColorField | Re-export. Single segment of ChannelInput. | | `SwatchGroup` | picker | `role="radiogroup"` of preset colors. | | `SwatchTrigger` | picker | `role="radio"` focusable swatch (takes `color: string`). | -| `Swatch` | picker | Visual color block (takes `color` or inherits from `SwatchTrigger`). | +| `Swatch` | picker | Visual color block; pass `color` explicitly (inheritance is not implemented). | | `SwatchIndicator` | picker | Indicator shown when `SwatchTrigger[data-checked]`. | | `EyeDropper` | picker | Invokes the browser's native EyeDropper API. `data-unsupported` when missing. | -| `HiddenInput` | picker | Rendered form input (needs `name` on Provider via `restProps`). | +| `HiddenInput` | picker | Conditional form input; provider name wiring remains an audit defect (Q94). | ## Composition @@ -255,42 +255,25 @@ Wrapping in `Field.Provider` inherits its `disabled`/`readonly`/`required`/`inva ## Comparison with reference libraries -> **Format-independence is unique to soma.** Every other mainstream headless picker ties the `format` prop to a _single_ reactive state — changing format anywhere inside the picker immediately flips what the `value` text / hidden input / callbacks report. Soma splits this into an external format (canonical) and a popover-internal format (view) so the user can inspect / edit in any representation without mutating the committed output unless `autoFormat=true`. - -| Feature | React Aria | Ark UI | Zag.js | Chakra (v3) | **Soma** | -| -------------------------------------------------- | --------------------- | ------------------- | ------------------- | ------------------- | --------------------------------------------------- | -| Composed parts | 8 (split hooks) | 22 | 22 | 22 (via Ark) | **22** | -| `value` / `onValueChange` | ✓ (Color obj) | ✓ | ✓ | ✓ | ✓ (`ColorValue` with `hex / rgb / hsl / hsv` views) | -| `onValueChangeEnd` (drag end) | ✓ | ✓ | ✓ | ✓ | ✓ | -| `format` / `defaultFormat` / `onFormatChange` | per-field only | ✓ | ✓ | ✓ | ✓ | -| **External ↔ popover format decoupling** | ✗ (single state) | ✗ (single state) | ✗ (single state) | ✗ (single state) | ✓ **Soma design (`autoFormat` toggle)** | -| `allowedFormats` restriction | ✗ | ✗ | ✗ | ✗ | ✓ (list; single entry renders a label) | -| 2D Area + `xChannel / yChannel` configurable | ✓ | fixed (S × V) | fixed (S × V) | fixed (S × V) | ✓ (configurable — React Aria parity) | -| ChannelSlider (any channel) | ✓ | ✓ | ✓ | ✓ | ✓ | -| ChannelInput — unified segmented hex / rgb / hsl | ✗ (one per channel) | ✗ (one per channel) | ✗ (one per channel) | ✗ (one per channel) | ✓ (single component; composes `ColorField`) | -| Segmented HEX (6-char single segment + `A–F` keys) | ✗ (text) | ✗ (text) | ✗ (text) | ✗ (text) | ✓ **Soma design** | -| FormatSelect | ✗ | ✓ | ✓ | ✓ | ✓ (auto-renders as label when locked) | -| Swatches (`role="radiogroup"`) | ✓ `ColorSwatchPicker` | ✓ | ✓ | ✓ | ✓ | -| EyeDropper (native browser API) | ✗ | ✓ | ✓ | ✓ | ✓ | -| TransparencyGrid (checkerboard) | ✗ | ✓ | ✓ | ✓ | ✓ | -| Inline vs popover | compose yourself | `inline` prop | `inline` prop | `inline` prop | ✓ (`inline` prop — skips `Popover` entirely) | -| `closeOnSelect` | ✗ | ✓ | ✓ | ✓ | ✓ | -| Lossless round-trip preserving edited channel | ✓ | partial | partial | partial | ✓ (`colorValueFromRgb / Hsl` bypass HSV) | -| Parent Field integration (OR-merge flags) | via `
` | via `FieldRoot` | ✗ | via Ark | ✓ (soma `Field`) | -| ColorWheel (circular hue) | ✓ | ✗ | ✗ | ✗ | ✗ (deliberately omitted — per study §16.6) | - -### Format-independence — why soma does it differently - -Every other library treats the picker's format as a **single reactive knob**. The consumer passes `format="hex"` → the `ChannelInput` shows hex, the textual output reads hex, form submission reads hex. If the user clicks `FormatSelect` and picks RGB, _all three_ switch. The consumer has no way to keep "the value the user picked is committed to my backend as hex, regardless of what format they chose to edit it in." - -Soma splits this into two concerns: - -1. **Canonical / external format** — used by anything that exits the component (Trigger value, hidden ``, `onFormatChange`). This is what the consumer controls with `bind:format`. -2. **View / edit format** — used only by `FormatSelect` + `ChannelInput` inside the popover, so the user can switch representation freely while editing. - -Both are synced external → internal on every external change (so `bind:format` still drives the popover), but internal → external is gated by the `autoFormat` opt-in. This is the only new format-related concept in soma's API — everything else (`format`, `onFormatChange`, `allowedFormats`) behaves as other libraries do. - -The precedent for this split in the broader ecosystem is the common "display format vs value format" pattern in date / number fields (where the user might see a formatted date while the value is always ISO). Soma applies the same idea to color. +Checked on 2026-09-27 against the current primary documentation. Counts of +Morfo parts, local wrappers and composed namespace members are different units: +UIX has 21 parts declared by [ColorPicker Morfo](../../../morfo/components/color-picker.ts), 23 local Soma wrappers, 24 local Eidos wrappers and 34 +attached Eidos members. They are not library-to-library coverage scores. + +| Reference | Documented surface | UIX decision | +| --------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | +| [Ark UI](https://ark-ui.com/docs/components/color-picker) | Configurable Area channels, channel sliders, controlled value/open and hidden input. | Preserve the existing configurable axes and slider bridge. | +| [Chakra UI](https://chakra-ui.com/docs/components/color-picker) | RGB/alpha channel sliders, swatches and controlled composition. | Remove the old claim that RGB sliders are absent. | +| [React Aria ColorArea](https://react-aria.adobe.com/ColorArea) | Configurable channel axes and composed accessible color controls. | Keep the 2D channel model; adjudicate the slider's numeric ARIA surface separately (Q95). | +| [Mantine](https://mantine.dev/core/color-picker/) | String formats, swatches, sizes and native keyboard interaction. | Compare public contracts without inferring missing features from examples. | + +UIX keeps an external format for Trigger/ValueText and a separate edit format +for FormatSelect/ChannelInput. Internal writes propagate outward only with +`autoFormat=true`; explicit external changes propagate inward. This is a local +contract, not a claim of uniqueness across every library. Native select display +after alternating formats requires the separate audit reproduction Q99. +HiddenInput form wiring, radio navigation and selected indicator visibility +remain recorded defects; this comparison does not repair them. ## Example — form field with EyeDropper and presets diff --git a/src/uix/soma/components/color-picker/components/color-picker-area.svelte b/src/uix/soma/components/color-picker/components/color-picker-area.svelte index 18c2c4bb1..aaf63553e 100644 --- a/src/uix/soma/components/color-picker/components/color-picker-area.svelte +++ b/src/uix/soma/components/color-picker/components/color-picker-area.svelte @@ -1,5 +1,5 @@ diff --git a/src/uix/soma/components/color-picker/components/color-picker-swatch-group.svelte b/src/uix/soma/components/color-picker/components/color-picker-swatch-group.svelte index e17087ba8..d003f6808 100644 --- a/src/uix/soma/components/color-picker/components/color-picker-swatch-group.svelte +++ b/src/uix/soma/components/color-picker/components/color-picker-swatch-group.svelte @@ -1,5 +1,5 @@ diff --git a/src/uix/soma/components/color-picker/components/color-picker-swatch-trigger.svelte b/src/uix/soma/components/color-picker/components/color-picker-swatch-trigger.svelte index 436258519..b976fde32 100644 --- a/src/uix/soma/components/color-picker/components/color-picker-swatch-trigger.svelte +++ b/src/uix/soma/components/color-picker/components/color-picker-swatch-trigger.svelte @@ -1,5 +1,5 @@ diff --git a/src/uix/soma/components/color-picker/components/color-picker-swatch.svelte b/src/uix/soma/components/color-picker/components/color-picker-swatch.svelte index a17c3d0ff..15ef9a350 100644 --- a/src/uix/soma/components/color-picker/components/color-picker-swatch.svelte +++ b/src/uix/soma/components/color-picker/components/color-picker-swatch.svelte @@ -1,5 +1,5 @@ diff --git a/src/uix/soma/components/color-picker/components/color-picker-transparency-grid.svelte b/src/uix/soma/components/color-picker/components/color-picker-transparency-grid.svelte index 5764cc27a..7a433e5a7 100644 --- a/src/uix/soma/components/color-picker/components/color-picker-transparency-grid.svelte +++ b/src/uix/soma/components/color-picker/components/color-picker-transparency-grid.svelte @@ -1,5 +1,5 @@ diff --git a/src/uix/soma/components/color-picker/components/color-picker-value-text.svelte b/src/uix/soma/components/color-picker/components/color-picker-value-text.svelte index 25b00ad20..402441a05 100644 --- a/src/uix/soma/components/color-picker/components/color-picker-value-text.svelte +++ b/src/uix/soma/components/color-picker/components/color-picker-value-text.svelte @@ -1,5 +1,5 @@