diff --git a/.gitignore b/.gitignore index f025af13fc..b25bde8401 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,4 @@ packages/vchart/__tests__/runtime/node/**.png *.tsbuildinfo .github/hooks/copilot-hooks.json +.omx/ diff --git a/packages/vchart/__tests__/unit/theme/tooltip.test.ts b/packages/vchart/__tests__/unit/theme/tooltip.test.ts new file mode 100644 index 0000000000..7d46340c60 --- /dev/null +++ b/packages/vchart/__tests__/unit/theme/tooltip.test.ts @@ -0,0 +1,85 @@ +import { mergeSpec } from '@visactor/vutils-extension'; +import { TooltipSpecTransformer } from '../../../src/component/tooltip/tooltip-transformer'; +import { ComponentTypeEnum } from '../../../src/component/interface/type'; +import { tooltip as builtInTooltipTheme } from '../../../src/theme/builtin/common/component/tooltip'; + +describe('tooltip theme transformer', () => { + const createTransformer = (tooltipTheme: Record) => + new TooltipSpecTransformer({ + type: ComponentTypeEnum.tooltip, + mode: 'node', + getTheme: (...keys: string[]) => { + if (keys[0] === 'component' && keys[1] === ComponentTypeEnum.tooltip) { + return mergeSpec({}, builtInTooltipTheme, tooltipTheme); + } + return undefined; + } + }); + + it('supports tooltip theme fields declared under component.tooltip.style', () => { + const transformer = createTransformer({ + style: { + panel: { + backgroundColor: '#123456' + }, + titleLabel: { + fill: '#abcdef', + fontSize: 18 + }, + keyLabel: { + fill: '#ff0000' + } + } + }); + + const { spec } = transformer.transformSpec({}, {}); + + expect(spec.style.panel.backgroundColor).toBe('#123456'); + expect(spec.style.titleLabel.fill).toBe('#abcdef'); + expect(spec.style.titleLabel.fontSize).toBe(18); + expect(spec.style.keyLabel.fill).toBe('#ff0000'); + expect(spec.style.style).toBeUndefined(); + }); + + it('keeps root tooltip theme fields at root instead of leaking them into style', () => { + const transformer = createTransformer({ + offset: { + x: 24, + y: 16 + }, + trigger: 'click', + transitionDuration: 0, + panel: { + backgroundColor: '#654321' + } + }); + + const { spec } = transformer.transformSpec({}, {}); + + expect(spec.offset).toEqual({ x: 24, y: 16 }); + expect(spec.trigger).toBe('click'); + expect(spec.transitionDuration).toBe(0); + expect(spec.style.panel.backgroundColor).toBe('#654321'); + expect(spec.style.offset).toBeUndefined(); + expect(spec.style.trigger).toBeUndefined(); + expect(spec.style.transitionDuration).toBeUndefined(); + }); + + it('supports active-type visibility declared in tooltip theme', () => { + const transformer = createTransformer({ + mark: { + visible: false + }, + dimension: { + visible: true + } + }); + + const { spec } = transformer.transformSpec({}, {}); + + expect(spec.mark.visible).toBe(false); + expect(spec.dimension.visible).toBe(true); + expect(spec.activeType).not.toContain('mark'); + expect(spec.activeType).toContain('dimension'); + }); +}); diff --git a/packages/vchart/src/component/interface/theme.ts b/packages/vchart/src/component/interface/theme.ts index f0472bf886..5cd6b06500 100644 --- a/packages/vchart/src/component/interface/theme.ts +++ b/packages/vchart/src/component/interface/theme.ts @@ -11,7 +11,7 @@ import type { IMarkLineTheme } from '../marker/mark-line/interface'; import type { IMarkPointTheme } from '../marker/mark-point/interface'; import type { IPlayerTheme } from '../player/interface'; import type { ITitleTheme } from '../title/interface'; -import type { ITooltipTheme } from '../tooltip/interface'; +import type { ITooltipSpec, ITooltipTheme } from '../tooltip/interface'; import type { ComponentTypeEnum } from './type'; import type { ITotalLabelTheme } from '../label/interface'; import type { IPoptipTheme } from '../poptip/interface'; @@ -80,7 +80,28 @@ export interface IComponentTheme { /** * tooltip 组件配置 */ - [ComponentTypeEnum.tooltip]?: ITooltipTheme; + [ComponentTypeEnum.tooltip]?: ITooltipTheme & + Partial< + Pick< + ITooltipSpec, + | 'visible' + | 'activeType' + | 'mark' + | 'dimension' + | 'group' + | 'trigger' + | 'triggerOff' + | 'showDelay' + | 'hideTimer' + | 'lockAfterClick' + | 'renderMode' + | 'confine' + | 'className' + | 'parentElement' + | 'enterable' + | 'throttleInterval' + > + >; /** * crosshair 配置 */ diff --git a/packages/vchart/src/component/tooltip/tooltip-transformer.ts b/packages/vchart/src/component/tooltip/tooltip-transformer.ts index 4c9052e418..bd3d43d486 100644 --- a/packages/vchart/src/component/tooltip/tooltip-transformer.ts +++ b/packages/vchart/src/component/tooltip/tooltip-transformer.ts @@ -6,6 +6,17 @@ import { TOOLTIP_EL_CLASS_NAME } from './constant'; import { getTooltipActualActiveType } from './utils/common'; import { mergeSpec } from '@visactor/vutils-extension'; +const TOOLTIP_STYLE_THEME_KEYS = [ + 'panel', + 'shape', + 'titleLabel', + 'keyLabel', + 'valueLabel', + 'spaceRow', + 'maxContentHeight', + 'align' +] as const; + export class TooltipSpecTransformer extends BaseComponentSpecTransformer { protected _shouldMergeThemeToSpec() { return false; @@ -13,17 +24,24 @@ export class TooltipSpecTransformer extends BaseComponentSpecTransformer { protected _initTheme(spec: any, chartSpec: any): { spec: any; theme: any } { const { spec: newSpec, theme } = super._initTheme(spec, chartSpec); + const themeStyle = mergeSpec( + {}, + ...TOOLTIP_STYLE_THEME_KEYS.map(key => (theme?.[key] !== undefined ? { [key]: theme[key] } : undefined)), + theme?.style + ); + const themeSpec = mergeSpec({}, theme); + + TOOLTIP_STYLE_THEME_KEYS.forEach(key => { + delete themeSpec[key]; + }); + delete themeSpec.style; - // 合并样式和配置 - newSpec.style = mergeSpec({}, this._theme, newSpec.style); - newSpec.offset = mergeSpec({}, theme.offset, spec.offset); - newSpec.transitionDuration = spec.transitionDuration ?? theme.transitionDuration; + const mergedSpec = mergeSpec({}, themeSpec, newSpec); - // 合并交互相关配置 - newSpec.trigger = spec.trigger ?? theme.trigger; - newSpec.triggerOff = spec.triggerOff ?? theme.triggerOff; + // 合并样式配置 + mergedSpec.style = mergeSpec({}, themeStyle, mergedSpec.style); - return { spec: newSpec, theme }; + return { spec: mergedSpec, theme }; } protected _transformSpecAfterMergingTheme(spec: any, chartSpec: any, chartSpecInfo?: IChartSpecInfo) {