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() }}
+
{{ strokeTransparent ? '—' : strokeColor.toUpperCase() }}
+
{{ shadowColorValue.toUpperCase() }}
+