Skip to content

Commit 5321b05

Browse files
authored
Support setting variant through the theme injector (#711)
* Support variants when using the theme context from registering a theme * Support custom variants via set
1 parent 3064b7c commit 5321b05

File tree

5 files changed

+125
-21
lines changed

5 files changed

+125
-21
lines changed

src/core/ThemeInjector.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Theme, ThemeWithVariants, ThemeWithVariant, Variant } from './interfaces';
2+
import Injector from './Injector';
3+
4+
function isThemeVariantConfig(theme: Theme | ThemeWithVariants | ThemeWithVariant): theme is ThemeWithVariants {
5+
return theme.hasOwnProperty('variants');
6+
}
7+
8+
function isVariantModule(variant: string | Variant): variant is Variant {
9+
return typeof variant !== 'string';
10+
}
11+
12+
function createThemeInjectorPayload(theme: Theme | ThemeWithVariants | ThemeWithVariant, variant?: string | Variant) {
13+
if (isThemeVariantConfig(theme)) {
14+
variant = variant || 'default';
15+
if (isVariantModule(variant)) {
16+
return { theme: theme.theme, variant };
17+
}
18+
19+
return { theme: theme.theme, variant: theme.variants[variant] };
20+
}
21+
return theme;
22+
}
23+
24+
export class ThemeInjector extends Injector {
25+
constructor(theme?: Theme | ThemeWithVariants | ThemeWithVariant) {
26+
theme = theme ? createThemeInjectorPayload(theme) : theme;
27+
super(theme);
28+
}
29+
30+
set<T extends ThemeWithVariants>(theme: T, variant?: keyof T['variants'] | Variant): void;
31+
set(theme: ThemeWithVariant): void;
32+
set(theme: Theme): void;
33+
set(theme: Theme | ThemeWithVariants | ThemeWithVariant, variant?: string | Variant) {
34+
super.set(createThemeInjectorPayload(theme, variant));
35+
}
36+
}

src/core/interfaces.d.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ export type ClassNames = {
121121
};
122122

123123
export interface Theme {
124-
[key: string]: object;
124+
[key: string]: {
125+
[key: string]: string;
126+
};
125127
}
126128

127129
export interface Variant {

src/core/middleware/theme.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Injector from '../Injector';
66
import Set from '../../shim/Set';
77
import { shallow, auto } from '../diff';
88
import Registry from '../Registry';
9+
import { ThemeInjector } from '../ThemeInjector';
910

1011
export { Theme, Classes, ClassNames } from './../interfaces';
1112

@@ -22,16 +23,16 @@ function isThemeWithVariant(theme: Theme | ThemeWithVariant): theme is ThemeWith
2223
return theme && theme.hasOwnProperty('variant');
2324
}
2425

25-
function isThemeWithVariants(theme: Theme | ThemeWithVariants): theme is ThemeWithVariants {
26+
function isThemeWithVariants(theme: Theme | ThemeWithVariants | ThemeWithVariant): theme is ThemeWithVariants {
2627
return theme && theme.hasOwnProperty('variants');
2728
}
2829

2930
function isVariantModule(variant: string | Variant): variant is Variant {
3031
return typeof variant !== 'string';
3132
}
3233

33-
function registerThemeInjector(theme: Theme | ThemeWithVariant | undefined, themeRegistry: Registry): Injector {
34-
const themeInjector = new Injector(theme);
34+
function registerThemeInjector(theme: Theme | ThemeWithVariant | undefined, themeRegistry: Registry): ThemeInjector {
35+
const themeInjector = new ThemeInjector(theme);
3536
themeRegistry.defineInjector(INJECTED_THEME_KEY, (invalidator) => {
3637
themeInjector.setInvalidator(invalidator);
3738
return () => themeInjector;
@@ -90,16 +91,15 @@ export const theme = factory(
9091
});
9192

9293
function set(theme: Theme): void;
93-
function set<T extends ThemeWithVariants>(theme: T, variant?: keyof T['variants']): void;
94-
function set<T extends ThemeWithVariants>(theme: Theme | T, variant?: keyof T['variants']): void {
95-
const currentTheme = injector.get<Injector<Theme | ThemeWithVariant | undefined>>(INJECTED_THEME_KEY);
96-
94+
function set<T extends ThemeWithVariants>(theme: T, variant?: keyof T['variants'] | Variant): void;
95+
function set<T extends ThemeWithVariants>(theme: Theme | T, variant?: keyof T['variants'] | Variant): void {
96+
const currentTheme = injector.get<ThemeInjector>(INJECTED_THEME_KEY);
9797
if (currentTheme) {
9898
if (isThemeWithVariants(theme)) {
99-
theme = { theme: theme.theme, variant: theme.variants[`${variant || 'default'}`] };
99+
currentTheme.set(theme, variant);
100+
} else {
101+
currentTheme.set(theme);
100102
}
101-
102-
currentTheme.set(theme);
103103
}
104104
}
105105

src/core/mixins/Themed.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { inject } from './../decorators/inject';
1414
import { WidgetBase } from './../WidgetBase';
1515
import { handleDecorator } from './../decorators/handleDecorator';
1616
import { diffProperty } from './../decorators/diffProperty';
17+
import { ThemeInjector } from '../ThemeInjector';
1718

1819
export { Theme, Classes, ClassNames } from './../interfaces';
1920

@@ -37,7 +38,7 @@ function isThemeVariant(theme: Theme | ThemeWithVariant): theme is ThemeWithVari
3738
return theme.hasOwnProperty('variant');
3839
}
3940

40-
function isThemeVariantConfig(theme: Theme | ThemeWithVariants): theme is ThemeWithVariants {
41+
function isThemeVariantConfig(theme: Theme | ThemeWithVariants | ThemeWithVariant): theme is ThemeWithVariants {
4142
return theme.hasOwnProperty('variants');
4243
}
4344

@@ -92,8 +93,11 @@ function createThemeClassesLookup(classes: ClassNames[]): ClassNames {
9293
*
9394
* @returns the theme injector used to set the theme
9495
*/
95-
export function registerThemeInjector(theme: Theme | ThemeWithVariant, themeRegistry: Registry): Injector {
96-
const themeInjector = new Injector(theme);
96+
export function registerThemeInjector(
97+
theme: Theme | ThemeWithVariants | ThemeWithVariant,
98+
themeRegistry: Registry
99+
): ThemeInjector {
100+
const themeInjector = new ThemeInjector(theme);
97101
themeRegistry.defineInjector(INJECTED_THEME_KEY, (invalidator) => {
98102
themeInjector.setInvalidator(invalidator);
99103
return () => themeInjector;

tests/core/unit/mixins/Themed.ts

+69-7
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ registerSuite('ThemedMixin', {
357357
variants: {
358358
'theme variant can be injected via a registry'() {
359359
const themeWithVariant = {
360-
css: {
360+
theme: {
361361
'test-key': {
362362
root: 'themed-root'
363363
}
@@ -387,7 +387,7 @@ registerSuite('ThemedMixin', {
387387
};
388388

389389
const themeWithVariant = {
390-
css: {
390+
theme: {
391391
'test-key': {
392392
root: 'variant-themed-root'
393393
}
@@ -413,9 +413,71 @@ registerSuite('ThemedMixin', {
413413
renderResult = themedInstance.__render__() as VNode;
414414
assert.deepEqual(renderResult.properties.classes, 'variant-root');
415415
},
416+
'should use default theme when set via theme context'() {
417+
const themeWithVariants = {
418+
theme: {
419+
'test-key': {
420+
root: 'variant-themed-root'
421+
}
422+
},
423+
variants: {
424+
default: {
425+
root: 'variant-root'
426+
},
427+
red: {
428+
root: 'red-root'
429+
}
430+
}
431+
};
432+
433+
registerThemeInjector(themeWithVariants, testRegistry);
434+
class InjectedTheme extends TestWidget {
435+
render() {
436+
return v('div', { classes: this.variant() });
437+
}
438+
}
439+
const themedInstance = new InjectedTheme();
440+
themedInstance.registry.base = testRegistry;
441+
themedInstance.__setProperties__({});
442+
let renderResult = themedInstance.__render__() as VNode;
443+
assert.deepEqual(renderResult.properties.classes, 'variant-root');
444+
},
445+
'should use be able to choose variant via theme context'() {
446+
const themeWithVariants = {
447+
theme: {
448+
'test-key': {
449+
root: 'variant-themed-root'
450+
}
451+
},
452+
variants: {
453+
default: {
454+
root: 'variant-root'
455+
},
456+
red: {
457+
root: 'red-root'
458+
}
459+
}
460+
};
461+
462+
const themeInjectorContext = registerThemeInjector(themeWithVariants, testRegistry);
463+
class InjectedTheme extends TestWidget {
464+
render() {
465+
return v('div', { classes: this.variant() });
466+
}
467+
}
468+
const themedInstance = new InjectedTheme();
469+
themedInstance.registry.base = testRegistry;
470+
themedInstance.__setProperties__({});
471+
let renderResult = themedInstance.__render__() as VNode;
472+
assert.deepEqual(renderResult.properties.classes, 'variant-root');
473+
themeInjectorContext.set(themeWithVariants, 'red');
474+
themedInstance.__setProperties__({});
475+
renderResult = themedInstance.__render__() as VNode;
476+
assert.deepEqual(renderResult.properties.classes, 'red-root');
477+
},
416478
'theme variant can be set at the widget level'() {
417479
const themeWithVariant = {
418-
css: {
480+
theme: {
419481
'test-key': {
420482
root: 'themed-root'
421483
}
@@ -440,7 +502,7 @@ registerSuite('ThemedMixin', {
440502
},
441503
'theme property overrides injected property'() {
442504
const themeWithVariant = {
443-
css: {
505+
theme: {
444506
'test-key': {
445507
root: 'themed-root'
446508
}
@@ -451,7 +513,7 @@ registerSuite('ThemedMixin', {
451513
};
452514

453515
const secondThemeWithVariant = {
454-
css: {
516+
theme: {
455517
'test-key': {
456518
root: 'themed-root'
457519
}
@@ -478,8 +540,8 @@ registerSuite('ThemedMixin', {
478540
},
479541
'can inject theme config with variants'() {
480542
const themeWithVariantConfig = {
481-
css: {
482-
css: {
543+
theme: {
544+
theme: {
483545
'test-key': {
484546
root: 'themed-root'
485547
}

0 commit comments

Comments
 (0)