Skip to content

Commit ed41c42

Browse files
authored
feat: Improved preload variable handling (#1723)
- Preload now always applies default preload values before applying cached ones. This ensures that cached preload values don't prevent new defaults from being applied if / when we add new variables to the preload list - Default preload variables can now be passed in to ThemeUtils + ThemeProvider. This will allow DHE to specify additional variables if needed resolves #1695 and part of #1679
1 parent 6894d96 commit ed41c42

5 files changed

Lines changed: 126 additions & 42 deletions

File tree

packages/code-studio/src/styleguide/SpectrumComparison.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
RadioItem,
2525
Select,
2626
} from '@deephaven/components';
27+
import { EMPTY_FUNCTION } from '@deephaven/utils';
2728
import {
2829
SAMPLE_SECTION_E2E_IGNORE,
2930
SPECTRUM_COMPARISON_SAMPLES_ID,
@@ -73,15 +74,21 @@ export function SpectrumComparison(): JSX.Element {
7374

7475
{buttons.map(([level, variant]) => (
7576
<Fragment key={level}>
76-
<BootstrapButtonOld kind={level}>Button</BootstrapButtonOld>
77+
<BootstrapButtonOld onClick={EMPTY_FUNCTION} kind={level}>
78+
Button
79+
</BootstrapButtonOld>
7780

7881
<Button variant={variant} style="fill">
7982
Button
8083
</Button>
8184
</Fragment>
8285
))}
8386

84-
<BootstrapButtonOld kind="primary" disabled>
87+
<BootstrapButtonOld
88+
onClick={EMPTY_FUNCTION}
89+
kind="primary"
90+
disabled
91+
>
8592
Disabled
8693
</BootstrapButtonOld>
8794
<Button variant="accent" style="fill" isDisabled>
@@ -98,14 +105,20 @@ export function SpectrumComparison(): JSX.Element {
98105

99106
{buttons.map(([level, variant]) => (
100107
<Fragment key={level}>
101-
<BootstrapButtonOld kind={level}>{level}</BootstrapButtonOld>
108+
<BootstrapButtonOld onClick={EMPTY_FUNCTION} kind={level}>
109+
{level}
110+
</BootstrapButtonOld>
102111
<Button variant={variant} style="outline">
103112
{variant}
104113
</Button>
105114
</Fragment>
106115
))}
107116

108-
<BootstrapButtonOld kind="secondary" disabled>
117+
<BootstrapButtonOld
118+
onClick={EMPTY_FUNCTION}
119+
kind="secondary"
120+
disabled
121+
>
109122
Disabled
110123
</BootstrapButtonOld>
111124
<Button variant="primary" style="outline" isDisabled>
@@ -121,10 +134,12 @@ export function SpectrumComparison(): JSX.Element {
121134
<label>Bootstrap</label>
122135
<label>Spectrum</label>
123136

124-
<BootstrapButtonOld kind="inline">Inline</BootstrapButtonOld>
137+
<BootstrapButtonOld onClick={EMPTY_FUNCTION} kind="inline">
138+
Inline
139+
</BootstrapButtonOld>
125140
<ActionButton>Action</ActionButton>
126141

127-
<BootstrapButtonOld kind="inline" disabled>
142+
<BootstrapButtonOld onClick={EMPTY_FUNCTION} kind="inline" disabled>
128143
Disabled
129144
</BootstrapButtonOld>
130145
<ActionButton isDisabled>Disabled</ActionButton>

packages/components/src/theme/ThemeModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const DEFAULT_LIGHT_THEME_KEY = 'default-light' satisfies BaseThemeKey;
4343

4444
// Hex versions of some of the default dark theme color palette needed for
4545
// preload defaults.
46-
const DEFAULT_DARK_THEME_PALETTE = {
46+
export const DEFAULT_DARK_THEME_PALETTE = {
4747
blue: {
4848
500: '#2f5bc0',
4949
400: '#254ba4',

packages/components/src/theme/ThemeProvider.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { createContext, ReactNode, useEffect, useMemo, useState } from 'react';
22
import Log from '@deephaven/log';
3-
import { DEFAULT_DARK_THEME_KEY, ThemeData } from './ThemeModel';
3+
import {
4+
DEFAULT_DARK_THEME_KEY,
5+
DEFAULT_PRELOAD_DATA_VARIABLES,
6+
ThemeData,
7+
} from './ThemeModel';
48
import {
59
calculatePreloadStyleContent,
610
getActiveThemes,
@@ -30,11 +34,13 @@ export interface ThemeProviderProps {
3034
* tell the provider to activate the base themes.
3135
*/
3236
themes: ThemeData[] | null;
37+
defaultPreloadValues?: Record<string, string>;
3338
children: ReactNode;
3439
}
3540

3641
export function ThemeProvider({
3742
themes: customThemes,
43+
defaultPreloadValues = DEFAULT_PRELOAD_DATA_VARIABLES,
3844
children,
3945
}: ThemeProviderProps): JSX.Element | null {
4046
const baseThemes = useMemo(() => getDefaultBaseThemes(), []);
@@ -71,9 +77,10 @@ export function ThemeProvider({
7177

7278
// Override fill color for certain inline SVGs (the originals are provided
7379
// by theme-svg.scss)
74-
overrideSVGFillColors();
80+
overrideSVGFillColors(defaultPreloadValues);
7581

76-
const preloadStyleContent = calculatePreloadStyleContent();
82+
const preloadStyleContent =
83+
calculatePreloadStyleContent(defaultPreloadValues);
7784

7885
log.debug2('updateThemePreloadData:', {
7986
active: activeThemes.map(theme => theme.themeKey),
@@ -87,7 +94,7 @@ export function ThemeProvider({
8794
preloadStyleContent,
8895
});
8996
},
90-
[activeThemes, selectedThemeKey, customThemes]
97+
[activeThemes, selectedThemeKey, customThemes, defaultPreloadValues]
9198
);
9299

93100
useEffect(() => {

packages/components/src/theme/ThemeUtils.test.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ describe('calculatePreloadStyleContent', () => {
5959
}
6060

6161
it('should set defaults if css variables are not defined', () => {
62-
expect(calculatePreloadStyleContent()).toEqual(
63-
expectedContent(DEFAULT_PRELOAD_DATA_VARIABLES)
64-
);
62+
expect(
63+
calculatePreloadStyleContent(DEFAULT_PRELOAD_DATA_VARIABLES)
64+
).toEqual(expectedContent(DEFAULT_PRELOAD_DATA_VARIABLES));
6565
});
6666

6767
it('should resolve css variables', () => {
@@ -71,7 +71,9 @@ describe('calculatePreloadStyleContent', () => {
7171
);
7272
document.body.style.setProperty('--dh-color-bg', 'orange');
7373

74-
expect(calculatePreloadStyleContent()).toEqual(
74+
expect(
75+
calculatePreloadStyleContent(DEFAULT_PRELOAD_DATA_VARIABLES)
76+
).toEqual(
7577
expectedContent({
7678
...DEFAULT_PRELOAD_DATA_VARIABLES,
7779
'--dh-color-loading-spinner-primary': 'pink',
@@ -96,7 +98,10 @@ describe.each([document.body, document.createElement('div')])(
9698
it('should return empty string if property does not exist and no default value exists', () => {
9799
asMock(computedStyle.getPropertyValue).mockReturnValue('');
98100

99-
const resolver = createCssVariableResolver(targetElement);
101+
const resolver = createCssVariableResolver(
102+
targetElement,
103+
DEFAULT_PRELOAD_DATA_VARIABLES
104+
);
100105

101106
expect(getComputedStyle).toHaveBeenCalledWith(targetElement);
102107

@@ -113,7 +118,10 @@ describe.each([document.body, document.createElement('div')])(
113118
(key, value) => {
114119
asMock(computedStyle.getPropertyValue).mockReturnValue('');
115120

116-
const resolver = createCssVariableResolver(targetElement);
121+
const resolver = createCssVariableResolver(
122+
targetElement,
123+
DEFAULT_PRELOAD_DATA_VARIABLES
124+
);
117125

118126
expect(getComputedStyle).toHaveBeenCalledWith(targetElement);
119127

@@ -126,7 +134,10 @@ describe.each([document.body, document.createElement('div')])(
126134
name => `resolved:${name}`
127135
);
128136

129-
const resolver = createCssVariableResolver(targetElement);
137+
const resolver = createCssVariableResolver(
138+
targetElement,
139+
DEFAULT_PRELOAD_DATA_VARIABLES
140+
);
130141

131142
expect(getComputedStyle).toHaveBeenCalledWith(targetElement);
132143

@@ -387,7 +398,7 @@ describe('overrideSVGFillColors', () => {
387398
: 'red'
388399
);
389400

390-
overrideSVGFillColors();
401+
overrideSVGFillColors(DEFAULT_PRELOAD_DATA_VARIABLES);
391402

392403
expect(getComputedStyle).toHaveBeenCalledWith(document.body);
393404
expect(document.body.style.removeProperty).toHaveBeenCalledWith(key);
@@ -418,12 +429,22 @@ describe('preloadTheme', () => {
418429

419430
preloadTheme();
420431

421-
const styleEl = document.querySelector('style');
432+
const [styleElDefaults, styleElPrevious] =
433+
document.querySelectorAll('style');
422434

423-
expect(styleEl).not.toBeNull();
424-
expect(styleEl?.innerHTML).toEqual(
425-
preloadData?.preloadStyleContent ?? calculatePreloadStyleContent()
435+
expect(styleElDefaults).not.toBeNull();
436+
expect(styleElDefaults?.innerHTML).toEqual(
437+
calculatePreloadStyleContent(DEFAULT_PRELOAD_DATA_VARIABLES)
426438
);
439+
440+
if (preloadData?.preloadStyleContent == null) {
441+
expect(styleElPrevious).toBeUndefined();
442+
} else {
443+
expect(styleElPrevious).toBeDefined();
444+
expect(styleElPrevious?.innerHTML).toEqual(
445+
preloadData?.preloadStyleContent
446+
);
447+
}
427448
});
428449
});
429450

packages/components/src/theme/ThemeUtils.ts

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,19 @@ export type VarExpressionResolver = (varExpression: string) => string;
3131
* happens before themes are fully loaded so that we can style things like the
3232
* loading spinner and background color which are shown to the user early on in
3333
* the app lifecycle.
34+
* @defaultPreloadValues Default values to use if a preload variable is not set.
3435
*/
35-
export function calculatePreloadStyleContent(): CssVariableStyleContent {
36-
const resolveVar = createCssVariableResolver(document.body);
36+
export function calculatePreloadStyleContent(
37+
defaultPreloadValues: Record<string, string>
38+
): CssVariableStyleContent {
39+
const resolveVar = createCssVariableResolver(
40+
document.body,
41+
defaultPreloadValues
42+
);
3743

3844
// Calculate the current preload variables. If the variable is not set, use
3945
// the default value.
40-
const pairs = Object.keys(DEFAULT_PRELOAD_DATA_VARIABLES).map(
46+
const pairs = Object.keys(defaultPreloadValues).map(
4147
key => `${key}:${resolveVar(key as ThemePreloadColorVariable)}`
4248
);
4349

@@ -47,12 +53,14 @@ export function calculatePreloadStyleContent(): CssVariableStyleContent {
4753
/**
4854
* Create a resolver function for calculating the value of a css variable based
4955
* on a given element's computed style. If the variable resolves to '', we check
50-
* DEFAULT_PRELOAD_DATA_VARIABLES for a default value, and if one does not exist,
56+
* `defaultValues` for a default value, and if one does not exist,
5157
* return ''.
5258
* @param el Element to resolve css variables against
59+
* @param defaultValues Default values to use if a variable is not set.
5360
*/
5461
export function createCssVariableResolver(
55-
el: Element
62+
el: Element,
63+
defaultValues: Record<string, string>
5664
): (varName: ThemeCssVariableName) => string {
5765
const computedStyle = getComputedStyle(el);
5866

@@ -67,12 +75,25 @@ export function createCssVariableResolver(
6775
return value;
6876
}
6977

70-
return (
71-
DEFAULT_PRELOAD_DATA_VARIABLES[varName as ThemePreloadColorVariable] ?? ''
72-
);
78+
return defaultValues[varName as ThemePreloadColorVariable] ?? '';
7379
};
7480
}
7581

82+
/**
83+
* Create a style tag containing preload css variables and add to the head.
84+
* @param id The id of the style tag
85+
* @param preloadStyleContent The css variable content to add to the style tag
86+
*/
87+
export function createPreloadStyleElement(
88+
id: `theme-preload-${string}`,
89+
preloadStyleContent: CssVariableStyleContent
90+
): void {
91+
const style = document.createElement('style');
92+
style.id = id;
93+
style.innerHTML = preloadStyleContent;
94+
document.head.appendChild(style);
95+
}
96+
7697
/**
7798
* Extracts all css variable expressions from the given record and returns
7899
* a set of unique expressions.
@@ -378,18 +399,35 @@ export function getThemeKey(pluginName: string, themeName: string): string {
378399

379400
/**
380401
* Preload minimal theme variables from the cache.
402+
* @defaultPreloadValues Optional default values to use if a preload variable is not set.
381403
*/
382-
export function preloadTheme(): void {
383-
const preloadStyleContent =
384-
getThemePreloadData()?.preloadStyleContent ??
385-
calculatePreloadStyleContent();
404+
export function preloadTheme(
405+
defaultPreloadValues: Record<string, string> = DEFAULT_PRELOAD_DATA_VARIABLES
406+
): void {
407+
const previousPreloadStyleContent =
408+
getThemePreloadData()?.preloadStyleContent;
409+
410+
const defaultPreloadStyleContent =
411+
calculatePreloadStyleContent(defaultPreloadValues);
412+
413+
log.debug('Preloading theme content:', {
414+
defaultPreloadStyleContent,
415+
previousPreloadStyleContent,
416+
});
386417

387-
log.debug('Preloading theme content:', `'${preloadStyleContent}'`);
418+
createPreloadStyleElement(
419+
'theme-preload-defaults',
420+
defaultPreloadStyleContent
421+
);
388422

389-
const style = document.createElement('style');
390-
style.id = 'theme-preload';
391-
style.innerHTML = preloadStyleContent;
392-
document.head.appendChild(style);
423+
// Any preload variables that were saved by last theme load should override
424+
// the defaults
425+
if (previousPreloadStyleContent != null) {
426+
createPreloadStyleElement(
427+
'theme-preload-previous',
428+
previousPreloadStyleContent
429+
);
430+
}
393431
}
394432

395433
/**
@@ -406,9 +444,12 @@ export function preloadTheme(): void {
406444
* just change the background color instead of relying on this util, but this
407445
* is not always possible. e.g. <select> elements don't support pseudo elements,
408446
* so there's not a good way to set icons via masks.
447+
* @param defaultValues Default values to use if a variable is not set.
409448
*/
410-
export function overrideSVGFillColors(): void {
411-
const resolveVar = createCssVariableResolver(document.body);
449+
export function overrideSVGFillColors(
450+
defaultValues: Record<string, string>
451+
): void {
452+
const resolveVar = createCssVariableResolver(document.body, defaultValues);
412453

413454
Object.entries(SVG_ICON_MANUAL_COLOR_MAP).forEach(([key, value]) => {
414455
// Clear any previous override so that our variables get resolved against the

0 commit comments

Comments
 (0)