Chart theming: selectable chart theme + spec↔config merge/extract

This commit is contained in:
2026-06-12 16:48:48 +03:00
parent fe9d588103
commit 44a601affd
27 changed files with 1103 additions and 121 deletions
@@ -1,9 +1,11 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { defaultSettings } from '@core/settings';
import {
loadChartTheme,
loadPreviewFitMode,
loadUiTheme,
loadUserSettings,
saveChartTheme,
saveManagedSettings,
savePreviewFitMode,
saveUiTheme,
@@ -119,6 +121,34 @@ describe('settings-store · ui.previewFitMode', () => {
});
});
describe('settings-store · ui.chartTheme', () => {
beforeEach(() => vi.stubGlobal('localStorage', makeStorageStub()));
afterEach(() => vi.unstubAllGlobals());
it('defaults to astrolabe when nothing is stored', () => {
expect(loadChartTheme()).toBe('astrolabe');
});
it('returns a stored valid chart theme', () => {
localStorage.setItem(KEY, JSON.stringify({ ui: { chartTheme: 'stock' } }));
expect(loadChartTheme()).toBe('stock');
});
it('falls back to astrolabe for an unrecognized id', () => {
localStorage.setItem(KEY, JSON.stringify({ ui: { chartTheme: 'neon' } }));
expect(loadChartTheme()).toBe('astrolabe');
});
it('round-trips and preserves the other ui slices', () => {
saveUiTheme('dark');
savePreviewFitMode('height');
saveChartTheme('powerbi');
expect(loadChartTheme()).toBe('powerbi');
expect(loadUiTheme()).toBe('dark');
expect(loadPreviewFitMode()).toBe('height');
});
});
describe('settings-store · full UserSettings record', () => {
beforeEach(() => vi.stubGlobal('localStorage', makeStorageStub()));
afterEach(() => vi.unstubAllGlobals());
+17 -1
View File
@@ -19,6 +19,7 @@
import type { FitMode } from '@core/rendering';
import { loadSettings, type UserSettings } from '@core/settings';
import type { UiTheme } from '@core/theme';
import { isChartThemeId, type ChartThemeId } from '@core/vega-themes';
const KEY = 'astrolabe:settings';
@@ -28,12 +29,15 @@ const DEFAULT_THEME: UiTheme = 'light';
/** Spec §04 — the Fit control defaults to Original. */
const DEFAULT_FIT_MODE: FitMode = 'default';
/** Spec §04 — the Chart theme picker defaults to the house style. */
const DEFAULT_CHART_THEME: ChartThemeId = 'astrolabe';
/** The managed slice the per-pane settings clusters own (theme + fit live in their own slices). */
export type ManagedSettings = Pick<UserSettings, 'editor' | 'performance' | 'formatting'>;
/** Loose view of the stored record for the per-slice write-through merges. */
interface StoredSettings {
ui?: { theme?: unknown; previewFitMode?: unknown; [k: string]: unknown };
ui?: { theme?: unknown; previewFitMode?: unknown; chartTheme?: unknown; [k: string]: unknown };
[k: string]: unknown;
}
@@ -118,6 +122,18 @@ export function loadPreviewFitMode(): FitMode {
return isFitMode(stored) ? stored : DEFAULT_FIT_MODE;
}
/** The persisted chart theme, or the default — unknown ids fall back. */
export function loadChartTheme(): ChartThemeId {
const stored = readRaw().ui?.chartTheme;
return isChartThemeId(stored) ? stored : DEFAULT_CHART_THEME;
}
/** Persist the chart theme, preserving every other key already in the record. */
export function saveChartTheme(chartTheme: ChartThemeId): void {
const current = readRaw();
writeRaw({ ...current, ui: { ...current.ui, chartTheme } });
}
/** Persist the preview fit mode, preserving every other key already in the record. */
export function savePreviewFitMode(fitMode: FitMode): void {
const current = readRaw();