diff --git a/package.json b/package.json
index c382c7a..d9220b3 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "ruwio",
"private": true,
- "version": "1.2.5",
+ "version": "1.2.6",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src/components/FontPicker.vue b/src/components/FontPicker.vue
new file mode 100644
index 0000000..03bc600
--- /dev/null
+++ b/src/components/FontPicker.vue
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/PropertiesPanel.vue b/src/components/PropertiesPanel.vue
index 1175b94..2e42f80 100644
--- a/src/components/PropertiesPanel.vue
+++ b/src/components/PropertiesPanel.vue
@@ -18,8 +18,8 @@ import { sampleLayer } from '@/lib/interpolate'
import type { Action, AnimatableProps, Layer } from '@/types/scene'
import RangeSlider from './RangeSlider.vue'
import ActionEditor from './ActionEditor.vue'
+import FontPicker from './FontPicker.vue'
import { t } from '@/i18n'
-import { AVAILABLE_FONTS } from '@/lib/fonts'
const SECTION_STORAGE_KEY = 'animatr:propSections'
@@ -138,11 +138,14 @@ const textAsset = computed(() => {
return a && a.kind === 'text' ? a : null
})
+// Empty string represents the system-sans default. Stored as
+// `undefined` on the asset so the model field round-trips cleanly
+// through JSON without leaking a bogus 'system-sans' id.
const textFontModel = computed({
- get: () => textAsset.value?.fontFamily ?? 'system-sans',
+ get: () => textAsset.value?.fontFamily ?? '',
set: (id: string) => {
if (!textAsset.value) return
- textAsset.value.fontFamily = id === 'system-sans' ? undefined : id
+ textAsset.value.fontFamily = id || undefined
},
})
@@ -497,9 +500,7 @@ function deleteSelection() {
diff --git a/src/devtools/tests.ts b/src/devtools/tests.ts
index f06e088..a6a6139 100644
--- a/src/devtools/tests.ts
+++ b/src/devtools/tests.ts
@@ -15032,7 +15032,7 @@ export function registerAllTests(): void {
expect(fontCssFamily('does-not-exist')).toBe(SYSTEM_SANS.family)
})
- test('PropertiesPanel renders a font dropdown for text assets and writes back', async () => {
+ test('PropertiesPanel renders the font picker for text assets and writes back', async () => {
const s = makeFreshStore()
const a = s.addAsset({
kind: 'text', name: 'T', text: 'hello', fontSize: 32, fill: '#000',
@@ -15043,30 +15043,40 @@ export function registerAllTests(): void {
const handle = mountComponent(PropertiesPanel, {})
try {
await nextTick()
- const select = handle.host.querySelector('select') as HTMLSelectElement | null
- expect(select).toBeTruthy()
- const options = Array.from(select!.options).map(o => o.value)
- expect(options).toContain('system-sans')
- expect(options).toContain('inter')
- expect(options).toContain('marker')
- // Default selection: system-sans (asset.fontFamily is undefined).
- expect(select!.value).toBe('system-sans')
- // Pick Inter — the asset should mutate.
- select!.value = 'inter'
- select!.dispatchEvent(new Event('change'))
+ const trigger = handle.host.querySelector('.font-picker .trigger') as HTMLButtonElement | null
+ expect(!!trigger).toBe(true)
+ // Closed state: the label shows the default — System Sans.
+ expect((trigger!.textContent ?? '').includes('System Sans')).toBe(true)
+ trigger!.click()
+ await nextTick()
+ // Popover lives in document.body via Teleport.
+ const opts = Array.from(document.querySelectorAll('.font-popover .font-option')) as HTMLButtonElement[]
+ expect(opts.length).toBeGreaterThan(5)
+ const labels = opts.map(o => o.textContent?.trim() ?? '')
+ expect(labels.some(l => l.includes('System Sans'))).toBe(true)
+ expect(labels.some(l => l.includes('Inter'))).toBe(true)
+ expect(labels.some(l => l.includes('Permanent Marker'))).toBe(true)
+ // Click Inter.
+ const interOpt = opts.find(o => (o.textContent ?? '').trim() === 'Inter')!
+ interOpt.click()
await nextTick()
const textAsset = s.scene.assets.find(x => x.id === a.id)!
if (textAsset.kind !== 'text') throw new Error('unexpected kind')
expect(textAsset.fontFamily).toBe('inter')
- // Picking system-sans clears the field (undefined, not the literal id).
- select!.value = 'system-sans'
- select!.dispatchEvent(new Event('change'))
+ // Re-open + click System Sans → clears (undefined).
+ trigger!.click()
+ await nextTick()
+ const opts2 = Array.from(document.querySelectorAll('.font-popover .font-option')) as HTMLButtonElement[]
+ const sysOpt = opts2.find(o => (o.textContent ?? '').trim() === 'System Sans')!
+ sysOpt.click()
await nextTick()
const textAsset2 = s.scene.assets.find(x => x.id === a.id)!
if (textAsset2.kind !== 'text') throw new Error('unexpected kind')
expect(textAsset2.fontFamily === undefined).toBe(true)
} finally {
handle.unmount()
+ // Make sure the teleported popover doesn't leak into other tests.
+ document.querySelectorAll('.font-popover').forEach(el => el.remove())
}
})
diff --git a/src/version.ts b/src/version.ts
index a6846ca..580b2d8 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1,2 +1,2 @@
-export const APP_VERSION = '1.2.5'
+export const APP_VERSION = '1.2.6'
export const APP_NAME = 'RUWIO'