fix(docs): align canvas backgrounds with document flavors (#7491)

This commit is contained in:
Univer
2026-08-12 14:47:13 +08:00
committed by GitHub
parent 889d10961b
commit 770642a03e
6 changed files with 131 additions and 14 deletions
@@ -17,7 +17,7 @@
import type { ICommandInfo, IExecutionOptions } from '@univerjs/core';
import { DOCS_NORMAL_EDITOR_UNIT_ID_KEY, DocumentFlavor } from '@univerjs/core';
import { RichTextEditingMutation } from '@univerjs/docs';
import { Subject } from 'rxjs';
import { BehaviorSubject, Subject } from 'rxjs';
import { describe, expect, it, vi } from 'vitest';
import { DOCS_VIEW_KEY } from '../../basics/docs-view-key';
import { DocRenderController } from '../render-controllers/doc.render-controller';
@@ -107,7 +107,8 @@ function createControllerFixture(options?: {
}) {
mockScrollBarProps.length = 0;
const commandCallbacks: Array<(command: ICommandInfo, options?: IExecutionOptions) => void> = [];
const darkMode$ = new Subject<boolean>();
const darkMode$ = new BehaviorSubject<boolean>(false);
const currentTheme$ = new BehaviorSubject<unknown>({});
const canvasElement = { style: {} as Record<string, string> };
const canvasColorService = {
getRenderColor: vi.fn((color: string) => color),
@@ -214,7 +215,7 @@ function createControllerFixture(options?: {
align: options?.fitToWidth?.align ?? 'center',
})),
},
{ darkMode$ }
{ currentTheme$, darkMode$ }
);
return {
@@ -223,6 +224,8 @@ function createControllerFixture(options?: {
context,
canvasElement,
canvasColorService,
currentTheme$,
darkMode$,
skeletonManager,
pageLayoutService,
selectionManager,
@@ -230,6 +233,43 @@ function createControllerFixture(options?: {
}
describe('doc render controller', () => {
it.each([
[DocumentFlavor.TRADITIONAL, 'gray.100'],
[DocumentFlavor.MODERN, 'white'],
])('resolves the %s workspace background again when dark mode changes', (documentFlavor, backgroundToken) => {
const { canvasColorService, canvasElement, darkMode$ } = createControllerFixture({ documentFlavor });
canvasColorService.getRenderColor.mockImplementation((color: string) => `dark:${color}`);
darkMode$.next(true);
expect(canvasColorService.getRenderColor).toHaveBeenLastCalledWith(backgroundToken);
expect(canvasElement.style.backgroundColor).toBe(`dark:${backgroundToken}`);
});
it('keeps the unspecified workspace background unchanged when dark mode changes', () => {
const { canvasColorService, canvasElement, darkMode$ } = createControllerFixture({
documentFlavor: DocumentFlavor.UNSPECIFIED,
});
canvasColorService.getRenderColor.mockImplementation((color: string) => `dark:${color}`);
darkMode$.next(true);
expect(canvasColorService.getRenderColor).not.toHaveBeenCalled();
expect(canvasElement.style.backgroundColor).toBe('var(--univer-gray-100)');
});
it('resolves the workspace background again when the theme changes', () => {
const { canvasColorService, canvasElement, currentTheme$ } = createControllerFixture({
documentFlavor: DocumentFlavor.TRADITIONAL,
});
canvasColorService.getRenderColor.mockImplementation((color: string) => `theme:${color}`);
currentTheme$.next({});
expect(canvasColorService.getRenderColor).toHaveBeenLastCalledWith('gray.100');
expect(canvasElement.style.backgroundColor).toBe('theme:gray.100');
});
it('disables only the horizontal scrollbar for container-fitted embedded docs', () => {
createControllerFixture({
fitToWidth: {
@@ -20,7 +20,7 @@ import type { DocumentSkeleton, IDocumentSkeletonPage, IRenderContext, IRenderMo
import { DocumentFlavor, ICommandService, Inject, isInternalEditorID, IUniverInstanceService, RxDisposable, ThemeService, UniverInstanceType } from '@univerjs/core';
import { DocSelectionManagerService, DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs';
import { DocBackground, Documents, IRenderManagerService, Layer, PageLayoutType, ScrollBar, Viewport } from '@univerjs/engine-render';
import { takeUntil } from 'rxjs';
import { combineLatest, takeUntil } from 'rxjs';
import { DOCS_COMPONENT_BACKGROUND_LAYER_INDEX, DOCS_COMPONENT_DEFAULT_Z_INDEX, DOCS_COMPONENT_HEADER_LAYER_INDEX, DOCS_COMPONENT_MAIN_LAYER_INDEX, DOCS_VIEW_KEY, VIEWPORT_KEY } from '../../basics/docs-view-key';
import { DocPageLayoutService } from '../../services/doc-page-layout.service';
import { resolveDocRenderBackground } from '../../services/doc-render-background';
@@ -259,7 +259,7 @@ export class DocRenderController extends RxDisposable implements IRenderModule {
}
private _initThemeListener() {
this.disposeWithMe(this._themeService.darkMode$.pipe(takeUntil(this.dispose$)).subscribe(() => {
this.disposeWithMe(combineLatest([this._themeService.currentTheme$, this._themeService.darkMode$]).pipe(takeUntil(this.dispose$)).subscribe(() => {
this._syncCanvasBackground();
this._context.mainComponent?.makeDirty(true);
this._context.components.get(DOCS_VIEW_KEY.BACKGROUND)?.makeDirty(true);
@@ -162,6 +162,7 @@ describe('DocsRenderService', () => {
expect(renderManagerService.createdUnitIds).toEqual(['doc-existing']);
expect(renderManagerService.canvases.get('doc-existing')?.id).toBe('univer-doc-main-canvas');
expect(renderManagerService.canvases.get('doc-existing')?.contextId).toBe('univer-doc-main-canvas');
expect(renderManagerService.canvases.get('doc-existing')?.style.backgroundColor).toBe('var(--univer-white)');
const editorDoc = new DocumentDataModel({
id: DOCS_NORMAL_EDITOR_UNIT_ID_KEY,
documentStyle: { documentFlavor: DocumentFlavor.TRADITIONAL },
@@ -18,8 +18,11 @@ import type { ICanvasColorService } from '@univerjs/engine-render';
import { DocumentFlavor } from '@univerjs/core';
const DOC_TRADITIONAL_WORKSPACE_BACKGROUND_COLOR = 'var(--univer-gray-100)';
const DOC_MODERN_WORKSPACE_BACKGROUND_COLOR = 'var(--univer-gray-50)';
const DOC_MODERN_WORKSPACE_BACKGROUND_COLOR = 'var(--univer-white)';
const DOC_UNSPECIFIED_WORKSPACE_BACKGROUND_COLOR = 'var(--univer-gray-100)';
const DOC_EDITOR_INTERNAL_BACKGROUND_COLOR = 'transparent';
const DOC_TRADITIONAL_WORKSPACE_BACKGROUND_TOKEN = 'gray.100';
const DOC_MODERN_WORKSPACE_BACKGROUND_TOKEN = 'white';
export interface IResolveDocRenderBackgroundOptions {
documentFlavor?: DocumentFlavor;
@@ -44,10 +47,24 @@ export function resolveDocRenderBackground(options: IResolveDocRenderBackgroundO
};
}
if (editorBackgroundColor != null) {
return {
canvasElementBackgroundColor: canvasColorService?.getRenderColor(editorBackgroundColor) ?? editorBackgroundColor,
docBackgroundFillColor: undefined,
};
}
if (documentFlavor !== DocumentFlavor.TRADITIONAL && documentFlavor !== DocumentFlavor.MODERN) {
return {
canvasElementBackgroundColor: backgroundColor,
docBackgroundFillColor: undefined,
};
}
const renderBackgroundColor = getDefaultDocCanvasBackgroundToken(documentFlavor);
return {
canvasElementBackgroundColor: editorBackgroundColor == null
? backgroundColor
: canvasColorService?.getRenderColor(backgroundColor) ?? backgroundColor,
canvasElementBackgroundColor: canvasColorService?.getRenderColor(renderBackgroundColor) ?? backgroundColor,
docBackgroundFillColor: undefined,
};
}
@@ -57,7 +74,18 @@ export function getDefaultDocCanvasBackgroundColor(documentFlavor?: DocumentFlav
return DOC_EDITOR_INTERNAL_BACKGROUND_COLOR;
}
return documentFlavor === DocumentFlavor.MODERN
? DOC_MODERN_WORKSPACE_BACKGROUND_COLOR
: DOC_TRADITIONAL_WORKSPACE_BACKGROUND_COLOR;
switch (documentFlavor) {
case DocumentFlavor.MODERN:
return DOC_MODERN_WORKSPACE_BACKGROUND_COLOR;
case DocumentFlavor.TRADITIONAL:
return DOC_TRADITIONAL_WORKSPACE_BACKGROUND_COLOR;
default:
return DOC_UNSPECIFIED_WORKSPACE_BACKGROUND_COLOR;
}
}
function getDefaultDocCanvasBackgroundToken(documentFlavor: DocumentFlavor.TRADITIONAL | DocumentFlavor.MODERN) {
return documentFlavor === DocumentFlavor.MODERN
? DOC_MODERN_WORKSPACE_BACKGROUND_TOKEN
: DOC_TRADITIONAL_WORKSPACE_BACKGROUND_TOKEN;
}
@@ -66,6 +66,53 @@ describe('DocBackground', () => {
vi.restoreAllMocks();
});
it('draws traditional workspaces with gray 100 and pages with white', () => {
const background = DocBackground.create('traditional-background', createSkeleton([createPage()]));
background.resize(320, 180);
const rectDraw = vi.spyOn(Rect, 'drawWith').mockImplementation(() => {});
vi.spyOn(Path, 'drawWith').mockImplementation(() => {});
background.draw(createCtx());
expect(rectDraw.mock.calls[0][1]).toMatchObject({ fill: 'gray.100' });
expect(rectDraw.mock.calls[1][1]).toMatchObject({ fill: 'white' });
background.dispose();
});
it('draws modern workspaces with white', () => {
const background = DocBackground.create(
'modern-background',
createSkeleton([createPage()], DocumentFlavor.MODERN)
);
background.resize(320, 180);
const rectDraw = vi.spyOn(Rect, 'drawWith').mockImplementation(() => {});
background.draw(createCtx());
expect(rectDraw).toHaveBeenCalledTimes(1);
expect(rectDraw.mock.calls[0][1]).toMatchObject({ fill: 'white' });
background.dispose();
});
it('keeps unspecified documents on the legacy gray workspace and page fills', () => {
const background = DocBackground.create(
'unspecified-background',
createSkeleton([createPage()], DocumentFlavor.UNSPECIFIED)
);
background.resize(320, 180);
const rectDraw = vi.spyOn(Rect, 'drawWith').mockImplementation(() => {});
vi.spyOn(Path, 'drawWith').mockImplementation(() => {});
background.draw(createCtx());
expect(rectDraw.mock.calls[0][1]).toMatchObject({ fill: 'gray.100' });
expect(rectDraw.mock.calls[1][1]).toMatchObject({ fill: 'gray.50' });
background.dispose();
});
it('positions multiple traditional pages horizontally and draws their margin identifiers', () => {
const background = DocBackground.create(
'horizontal-background',
@@ -25,7 +25,8 @@ import { DocComponent } from './doc-component';
import { Liquid } from './liquid';
const PAGE_STROKE_COLOR = 'gray.200';
const PAGE_FILL_COLOR = 'gray.50';
const PAGE_FILL_COLOR = 'white';
const UNSPECIFIED_PAGE_FILL_COLOR = 'gray.50';
const DOCS_WORKSPACE_FILL_COLOR = 'gray.100';
const MARGIN_STROKE_COLOR = 'gray.300';
@@ -122,7 +123,7 @@ export class DocBackground extends DocComponent {
height: pageHeight ?? height,
strokeWidth: 1,
stroke: this._pageStrokeColor ?? PAGE_STROKE_COLOR,
fill: this._pageFillColor ?? PAGE_FILL_COLOR,
fill: this._pageFillColor ?? (documentFlavor === DocumentFlavor.TRADITIONAL ? PAGE_FILL_COLOR : UNSPECIFIED_PAGE_FILL_COLOR),
zIndex: 3,
};