From 184842b2e00b458581d3a4cc230c60b275066b35 Mon Sep 17 00:00:00 2001 From: th Date: Sun, 26 Apr 2026 21:21:14 +0200 Subject: [PATCH] =?UTF-8?q?1.2.12:=20eyedropper=20=E2=80=94=20sample=20col?= =?UTF-8?q?ours=20from=20anywhere=20on=20the=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tiny progressive-enhancement feature using the browser's native EyeDropper API (Chromium 95+). Adds a small pipette button next to every colour swatch in the properties panel; clicking it opens the OS picker and applies the chosen sRGBHex to the bound colour property. - src/composables/useEyeDropper.ts: returns { supported, pick }. `supported` re-detects on every call (not a singleton) so a late- loaded polyfill or a test-time monkey-patch is observed without juggling Vite's module cache. `pick()` resolves to the chosen hex string, returns null on cancel / abort / missing API. - PropertiesPanel: PhEyedropper button on each of the three colour rows (fill, stroke, shadow). v-if="eyeDropperSupported" hides the button entirely on Safari + Firefox — clean PE. Fill and stroke buttons disable when their "Transparent" toggle is on. Picked colour writes through the same fillColor / strokeColor / shadowColorValue computeds, so the keyframe path / asset writer is identical to a color-input drag. - properties.eyedropper i18n string. Tests (7 new under "eyedropper — color sampling from anywhere on the page"): - supported reflects window.EyeDropper presence at call time. - pick() resolves with the sRGBHex from a stubbed EyeDropper. - pick() returns null on AbortError. - pick() returns null when the API is missing. - PropertiesPanel renders eyedropper buttons when supported. - PropertiesPanel hides them when EyeDropper is undefined. - Clicking the fill eyedropper applies the picked colour through the layer's keyframe / asset writer. `claude.txt` backlog complete (wiggle / motion-paths / eyedropper all shipped). Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 2 +- src/components/PropertiesPanel.vue | 45 +++++++++ src/composables/useEyeDropper.ts | 40 ++++++++ src/devtools/tests.ts | 149 +++++++++++++++++++++++++++++ src/i18n/messages.ts | 1 + src/version.ts | 2 +- 6 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 src/composables/useEyeDropper.ts diff --git a/package.json b/package.json index ec97954..c1899c4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ruwio", "private": true, - "version": "1.2.11", + "version": "1.2.12", "type": "module", "scripts": { "dev": "vite", diff --git a/src/components/PropertiesPanel.vue b/src/components/PropertiesPanel.vue index b846618..081847e 100644 --- a/src/components/PropertiesPanel.vue +++ b/src/components/PropertiesPanel.vue @@ -6,6 +6,7 @@ import { PhDiamond, PhEye, PhEyeSlash, + PhEyedropper, PhFlower, PhMaskHappy, PhWaveSine, @@ -23,8 +24,23 @@ import ActionEditor from './ActionEditor.vue' import FontPicker from './FontPicker.vue' import { t } from '@/i18n' import { useTimeFormat } from '@/composables/useTimeFormat' +import { useEyeDropper } from '@/composables/useEyeDropper' const { formatTime, timeToInputValue, inputValueToSeconds, inputStep, unitLabel } = useTimeFormat() +const { supported: eyeDropperSupported, pick: pickColor } = useEyeDropper() + +async function pickFillColor() { + const c = await pickColor() + if (c) fillColor.value = c +} +async function pickStrokeColor() { + const c = await pickColor() + if (c) strokeColor.value = c +} +async function pickShadowColor() { + const c = await pickColor() + if (c) shadowColorValue.value = c +} const SECTION_STORAGE_KEY = 'animatr:propSections' @@ -645,6 +661,16 @@ function deleteSelection() { @input="fillColor = ($event.target as HTMLInputElement).value" /> {{ fillTransparent ? '—' : fillColor.toUpperCase() }} +