Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "feat: add themes.setDefaultTheme for application-wide default styling",
"type": "none",
"packageName": "@visactor/vtable"
}
],
"packageName": "@visactor/vtable",
"email": "30969393+kitee0325@users.noreply.github.com"
}
31 changes: 31 additions & 0 deletions docs/assets/guide/en/theme_and_style/theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -1198,3 +1198,34 @@ const tableInstance = new VTable.PivotTable(option);
```

In short, in VTable, by flexibly using Theme configuration items, we can easily create an exclusive data table style that meets our needs. Please refer to this tutorial to make reasonable configurations based on actual scenarios, and give full play to VTable's powerful Theme customization capabilities.

## Set the default theme

Use `VTable.themes.setDefaultTheme(theme)` during application or component-library initialization to configure the theme used when a table has no explicit `theme`. This applies to ListTable, PivotTable and PivotChart.

```javascript
const originalDefault = VTable.themes.DEFAULT;
VTable.themes.setDefaultTheme(
originalDefault.extends({
defaultStyle: { fontFamily: 'Arial, sans-serif' },
bodyStyle: { color: '#243447' },
headerStyle: { bgColor: '#f2f4f7' }
})
);

// Uses the configured default without specifying option.theme.
const table = new VTable.ListTable({ container, columns, records });

// Extends the configured default using the existing theme API.
const customTheme = VTable.themes.DEFAULT.extends({ headerStyle: { color: '#345678' } });

// Restore the original default for subsequent operations.
VTable.themes.setDefaultTheme(originalDefault);
```

The argument is an `ITableThemeDefine` object or a `TableTheme`. It **replaces** the previous default; use `themes.DEFAULT.extends(...)` explicitly to merge with the current default. The function returns `void`.

- `themes.DEFAULT`, the internal default fallback and the built-in `DEFAULT` name resolve to the new theme. User-registered names retain their existing lookup priority, including a user registration named `DEFAULT`.
- Existing instances and themes created earlier with `extends()` retain their theme objects and are not automatically redrawn. A later `updateOption()` without `theme` uses the current default. Use `table.updateTheme(VTable.themes.DEFAULT)` to explicitly apply it to an existing instance.
- Explicit instance themes, other built-in themes, theme registration and `updateTheme()` keep their existing behavior. Standalone partial themes do not inherit the configured default; use `extends()` when inheritance is intended.
- The setting is shared by callers using the same VTable runtime. Configure it before creating tables. It is not an instance-scoped setting or a mechanism for automatically switching all existing tables.
31 changes: 31 additions & 0 deletions docs/assets/guide/zh/theme_and_style/theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -1197,3 +1197,34 @@ const tableInstance = new VTable.PivotTable(document.getElementById(CONTAINER_ID
```

总之,在 VTable 中,通过灵活运用主题配置项,我们可以轻松打造出专属的、符合需求的数据表格样式。请参照本教程,结合实际场景进行合理配置,发挥 VTable 强大的主题定制能力。

## 设置默认主题

应用或组件库可以在初始化时调用 `VTable.themes.setDefaultTheme(theme)`,配置表格未显式指定 `theme` 时使用的默认主题。该设置适用于 ListTable、PivotTable 和 PivotChart。

```javascript
const originalDefault = VTable.themes.DEFAULT;
VTable.themes.setDefaultTheme(
originalDefault.extends({
defaultStyle: { fontFamily: 'Arial, sans-serif' },
bodyStyle: { color: '#243447' },
headerStyle: { bgColor: '#f2f4f7' }
})
);

// 无需传入 option.theme,即可使用配置后的默认主题。
const table = new VTable.ListTable({ container, columns, records });

// 通过原有主题 API 继承配置后的默认主题。
const customTheme = VTable.themes.DEFAULT.extends({ headerStyle: { color: '#345678' } });

// 恢复原始默认主题,供后续操作使用。
VTable.themes.setDefaultTheme(originalDefault);
```

参数为 `ITableThemeDefine` 对象或 `TableTheme`,会**替换**上一次设置的默认主题;如需与当前默认主题合并,请显式使用 `themes.DEFAULT.extends(...)`。函数返回 `void`。

- `themes.DEFAULT`、内部默认回退和内置名称 `DEFAULT` 使用新的主题。用户注册的主题名称仍遵循原有查询优先级,包括用户注册的同名 `DEFAULT`。
- 已有实例和此前通过 `extends()` 创建的主题保留其主题对象,不会自动重绘。之后调用未指定 `theme` 的 `updateOption()` 时使用当前默认主题;如需显式应用到已有实例,可调用 `table.updateTheme(VTable.themes.DEFAULT)`。
- 显式指定的实例主题、其他内置主题、主题注册和 `updateTheme()` 保持原有行为。独立的局部主题不会继承配置后的默认值;需要继承时请使用 `extends()`。
- 此设置由使用同一份 VTable 运行时的调用方共享,建议在创建表格前完成配置。它不是实例级设置,也不会自动切换所有已有表格的主题。
69 changes: 69 additions & 0 deletions packages/vtable/__tests__/default-theme-table.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ListTable, PivotTable, PivotChart, themes } from '../src';
import { createDiv, removeDom } from './dom';

describe('default theme instance behavior', () => {
const original = themes.DEFAULT;
const tables: (ListTable | PivotTable | PivotChart)[] = [];
const containers: HTMLElement[] = [];
const columns = [{ field: 'name', title: 'Name' }];
const records = [{ name: 'Example' }];

function container() {
const element = createDiv();
element.style.width = '600px';
element.style.height = '400px';
containers.push(element);
return element;
}

afterEach(() => {
tables.forEach(table => table.release());
tables.length = 0;
containers.forEach(removeDom);
containers.length = 0;
themes.setDefaultTheme(original);
});

test('uses the configured default for all table types and both constructor forms', () => {
themes.setDefaultTheme(original.extends({ bodyStyle: { color: '#123456' } }));
tables.push(
new ListTable(container(), { columns, records }),
new ListTable({ container: container(), columns, records }),
new PivotTable(container(), { records: [] }),
new PivotTable({ container: container(), records: [] }),
new PivotChart(container(), { records: [] }),
new PivotChart({ container: container(), records: [] })
);
tables.forEach(table => expect(table.theme.bodyStyle.color).toBe('#123456'));
});

test('leaves existing instances unchanged and uses the new default on updateOption', () => {
const table = new ListTable(container(), { columns, records });
tables.push(table);
const previous = table.theme;
const previousColor = previous.bodyStyle.color;
themes.setDefaultTheme(original.extends({ bodyStyle: { color: '#123456' } }));

expect(table.theme).toBe(previous);
expect(table.theme.bodyStyle.color).toBe(previousColor);
table.updateOption({ columns, records });
expect(table.theme.bodyStyle.color).toBe('#123456');
});

test('preserves explicit themes, replacement updates and property assignment', () => {
themes.setDefaultTheme(original.extends({ bodyStyle: { color: '#123456' } }));
const custom = themes.DARK.extends({ bodyStyle: { color: '#654321' } });
const table = new ListTable(container(), { columns, records, theme: custom });
tables.push(table);

expect(table.theme.bodyStyle.color).toBe('#654321');
table.updateTheme({ headerStyle: { color: '#abcdef' } });
expect(table.theme.bodyStyle.color).toBe(themes.of({}).bodyStyle.color);
table.theme = custom;
expect(table.theme.bodyStyle.color).toBe('#654321');
table.updateTheme(themes.DEFAULT);
expect(table.theme.bodyStyle.color).toBe('#123456');
table.updateOption({ columns, records, theme: custom });
expect(table.theme.bodyStyle.color).toBe('#654321');
});
});
72 changes: 72 additions & 0 deletions packages/vtable/__tests__/default-theme.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import internalThemes, * as themes from '../src/themes';
import { theme as registerTheme, clearAll } from '../src/register';

describe('setDefaultTheme', () => {
const original = themes.DEFAULT;

afterEach(() => {
clearAll();
themes.setDefaultTheme(original);
});

test('keeps the public export, internal fallback and name lookup in sync', () => {
const registry = themes.get();
themes.setDefaultTheme(original.extends({ bodyStyle: { color: '#123456' } }));

expect(internalThemes.DEFAULT).toBe(themes.DEFAULT);
expect(registry.DEFAULT).toBe(themes.DEFAULT);
expect(themes.of('default')).toBe(themes.DEFAULT);
expect(themes.DEFAULT.bodyStyle.color).toBe('#123456');
expect(themes.DEFAULT.extends({ headerStyle: { color: '#654321' } }).bodyStyle.color).toBe('#123456');
});

test('replaces styles without reusing previously resolved caches or accumulating overrides', () => {
themes.setDefaultTheme(original.extends({ bodyStyle: { color: '#123456' } }));
const previous = themes.DEFAULT;
const extended = previous.extends({ headerStyle: { color: '#654321' } });
expect(previous.bodyStyle.color).toBe('#123456');

themes.setDefaultTheme(original.extends({ headerStyle: { color: '#abcdef' } }));

expect(themes.DEFAULT).not.toBe(previous);
expect(themes.DEFAULT.bodyStyle.color).toBe(original.bodyStyle.color);
expect(themes.DEFAULT.headerStyle.color).toBe('#abcdef');
expect(previous.bodyStyle.color).toBe('#123456');
expect(extended.bodyStyle.color).toBe('#123456');
});

test('preserves inherited values and callback styles when installing a TableTheme', () => {
const bgColor = () => '#abcdef';
const parent = original.extends({ bodyStyle: { color: '#123456', bgColor } });
const child = parent.extends({ headerStyle: { color: '#654321' } });
themes.setDefaultTheme(child);

expect(themes.DEFAULT).not.toBe(child);
expect(themes.DEFAULT.bodyStyle.color).toBe('#123456');
expect(themes.DEFAULT.bodyStyle.bgColor).toBe(bgColor);
expect(themes.DEFAULT.headerStyle.color).toBe('#654321');
});

test('does not merge the default into standalone or registered themes', () => {
const custom = { headerStyle: { color: '#654321' } };
const fallbackColor = themes.of(custom).bodyStyle.color;
registerTheme('custom-default-test', custom);
themes.setDefaultTheme({ defaultStyle: { color: '#123456' } });

expect(themes.of(custom).bodyStyle.color).toBe(fallbackColor);
expect(themes.of('custom-default-test').bodyStyle.color).toBe(fallbackColor);
expect(themes.of('DARK')).toBe(themes.DARK);
expect(themes.of('missing-default-test')).toBeNull();
});

test('keeps the existing registered-name precedence and clear behavior', () => {
const registered = original.extends({ bodyStyle: { color: '#654321' } });
registerTheme('DEFAULT', registered);
themes.setDefaultTheme({ defaultStyle: { color: '#123456' } });

expect(themes.of('DEFAULT')).toBe(registered);
expect(internalThemes.DEFAULT.bodyStyle.color).toBe('#123456');
clearAll();
expect(themes.of('DEFAULT')).toBe(themes.DEFAULT);
});
});
20 changes: 16 additions & 4 deletions packages/vtable/src/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { ITableThemeDefine } from './ts-types';
export const DARK = new TableTheme(darkTheme, darkTheme);
export const BRIGHT = new TableTheme(brightTheme, brightTheme);
export const ARCO = new TableTheme(arcoTheme, arcoTheme);
export const DEFAULT = new TableTheme(defaultTheme, defaultTheme);
export let DEFAULT = new TableTheme(defaultTheme, defaultTheme);
export const SIMPLIFY = new TableTheme(materialDesignTheme, materialDesignTheme);

const builtin: { [key: string]: TableTheme } = {
Expand All @@ -21,7 +21,16 @@ const builtin: { [key: string]: TableTheme } = {
DARK,
BRIGHT
};
// let defTheme = DEFAULT;
/**
* Replace the default theme used when no instance theme is specified.
* Existing instances and previously extended themes are not updated.
* The supplied theme replaces the default; use DEFAULT.extends() to merge styles.
*/
export function setDefaultTheme(theme: ITableThemeDefine): void {
DEFAULT = theme instanceof TableTheme ? theme.extends({}) : new TableTheme(theme, theme);
builtin.DEFAULT = DEFAULT;
}

export const theme = { TableTheme };
export function of(value: ITableThemeDefine | string | undefined | null): TableTheme | null {
if (!value) {
Expand Down Expand Up @@ -52,9 +61,12 @@ export default {
DARK,
BRIGHT,
ARCO,
DEFAULT,
get DEFAULT() {
return DEFAULT;
},
SIMPLIFY,
theme,
of,
get
get,
setDefaultTheme
};
Loading