Skip to content
Closed
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
13 changes: 11 additions & 2 deletions src/browser/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { ThemePreset, Override, validateOverride } from '../shared/theme';
import { createOverrideDeclarations } from '../shared/declaration';
import { createOverrideDeclarations, createFullThemeDeclarations } from '../shared/declaration';
import { getNonce, createStyleNode, appendStyleNode } from './dom';
import { createMultiThemeCustomizer } from '../shared/declaration/customizer';
import { getContexts, getThemeFromPreset } from '../shared/theme/validate';

export interface GenerateThemeStylesheetParams {
override: Override;
preset: ThemePreset;
Expand Down Expand Up @@ -52,6 +51,16 @@ export function applyTheme(params: ApplyThemeParams): ApplyThemeResult {
};
}

export interface GenerateFullThemeStylesheetParams {
preset: ThemePreset;
baseThemeId?: string;
}

export function generateFullThemeStylesheet({ preset, baseThemeId }: GenerateFullThemeStylesheetParams): string {
const theme = getThemeFromPreset(preset, baseThemeId);
return createFullThemeDeclarations(theme, preset.propertiesMap);
}

export {
Theme,
Override,
Expand Down
107 changes: 46 additions & 61 deletions src/shared/declaration/__tests__/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`renderDeclarations > does not render unnecessary declarations 1`] = `
":global body{
"@layer cloudscape-base-theme {
:global body{
--fontFamilyBase-css:"Helvetica Neue", Arial, sans-serif;
}
}"
`;

exports[`renderDeclarations > includes secondary theme 1`] = `
"body{
"@layer cloudscape-base-theme {
body{
--fontFamilyBase-css:"Helvetica Neue", Arial, sans-serif;
--fontFamilyBody-css:var(--fontFamilyBase-css);
--black-css:black;
Expand All @@ -27,51 +30,44 @@ exports[`renderDeclarations > includes secondary theme 1`] = `
.compact{
--scaledSize-css:var(--small-css);
}
@media not print {.dark{
--shadow-css:var(--black-css);
--boxShadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}
.disabled-motion{
--appear-css:0;
}
.navigation{
--shadow-css:var(--black-css);
--boxShadow-css:purple;
--buttonShadow-css:var(--shadow-css);
--lineShadow-css:var(--buttonShadow-css);
}
@media not print {.dark .navigation{
--shadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}
@media not print {.dark.navigation{
--shadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}
.secondary-theme{
--black-css:purple;
--brown-css:black;
}
.secondary-theme .navigation{
--boxShadow-css:var(--shadow-css);
}
.secondary-theme .navigation,
.navigation.secondary-theme{
--boxShadow-css:var(--shadow-css);
}
@media not print {.dark.secondary-theme .navigation{
@media not print {
.dark{
--shadow-css:var(--black-css);
--boxShadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}
.dark .navigation,
.dark.navigation{
--shadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}
.dark.secondary-theme .navigation{
--shadow-css:var(--grey-css);
--boxShadow-css:var(--brown-css);
}}
@media not print {.dark.navigation.secondary-theme{
}
.dark.navigation.secondary-theme{
--shadow-css:var(--grey-css);
--boxShadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}"
}
}
}"
`;

exports[`renderDeclarations > renders declarations for theme with :root selector and context 1`] = `
":global body{
"@layer cloudscape-base-theme {
:global body{
--fontFamilyBase-css:"Helvetica Neue", Arial, sans-serif;
--fontFamilyBody-css:var(--fontFamilyBase-css);
--black-css:black;
Expand All @@ -91,32 +87,27 @@ exports[`renderDeclarations > renders declarations for theme with :root selector
:global .compact{
--scaledSize-css:var(--small-css);
}
@media not print {:global .dark{
--shadow-css:var(--black-css);
--boxShadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}
:global .disabled-motion{
--appear-css:0;
}
:global .navigation{
@media not print {
:global .dark{
--shadow-css:var(--black-css);
--boxShadow-css:purple;
--buttonShadow-css:var(--shadow-css);
--lineShadow-css:var(--buttonShadow-css);
}
@media not print {:global .dark .navigation{
--shadow-css:var(--brown-css);
--boxShadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}
@media not print {:global .dark.navigation{
}
:global .dark .navigation,
:global .dark.navigation{
--shadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}"
}
}
}"
`;

exports[`renderDeclarations > renders declarations for theme with non :root selector 1`] = `
".secondary-theme{
"@layer cloudscape-base-theme {
.secondary-theme{
--fontFamilyBase-css:"Helvetica Neue", Arial, sans-serif;
--fontFamilyBody-css:var(--fontFamilyBase-css);
--black-css:purple;
Expand All @@ -136,34 +127,28 @@ exports[`renderDeclarations > renders declarations for theme with non :root sele
.compact.secondary-theme{
--scaledSize-css:var(--small-css);
}
@media not print {.dark.secondary-theme{
--shadow-css:var(--black-css);
--boxShadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}
.disabled-motion.secondary-theme{
--appear-css:0;
}
.secondary-theme .navigation{
--shadow-css:var(--black-css);
--buttonShadow-css:var(--shadow-css);
--boxShadow-css:var(--shadow-css);
--lineShadow-css:var(--buttonShadow-css);
}
.secondary-theme .navigation,
.navigation.secondary-theme{
--shadow-css:var(--black-css);
--buttonShadow-css:var(--shadow-css);
--boxShadow-css:var(--shadow-css);
--lineShadow-css:var(--buttonShadow-css);
}
@media not print {.dark.secondary-theme .navigation{
--shadow-css:var(--grey-css);
@media not print {
.dark.secondary-theme{
--shadow-css:var(--black-css);
--boxShadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}
@media not print {.dark.navigation.secondary-theme{
}
.dark.secondary-theme .navigation,
.dark.navigation.secondary-theme{
--shadow-css:var(--grey-css);
--boxShadow-css:var(--brown-css);
--lineShadow-css:var(--boxShadow-css);
}}"
}
}
}"
`;
24 changes: 20 additions & 4 deletions src/shared/declaration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { RuleCreator } from './rule';
import { SingleThemeCreator } from './single';
import { MultiThemeCreator } from './multi';
import { Selector } from './selector';
import { UsedPropertyRegistry } from './registry';
import { MinimalTransformer } from './transformer';
import { AllPropertyRegistry, UsedPropertyRegistry } from './registry';
import { MinimalTransformer, SelectorMergeOptimizer } from './transformer';
import Stylesheet from './stylesheet';
import { cloneDeep, values } from '../utils';

function createMinimalTheme(base: Theme, override: Override): Theme {
Expand Down Expand Up @@ -66,6 +67,10 @@ function addMissingTokensToTheme(theme: Theme, tokens: string[], sourceTheme: Th
});
}

function optimize(stylesheet: Stylesheet): string {
return new SelectorMergeOptimizer().transform(new MinimalTransformer().transform(stylesheet)).toString();
}

export function createOverrideDeclarations(
base: Theme,
override: Override,
Expand All @@ -86,7 +91,7 @@ export function createOverrideDeclarations(
new UsedPropertyRegistry(propertiesMap, usedTokens),
);
const stylesheet = new SingleThemeCreator(minimalTheme, ruleCreator, base, propertiesMap).create();
return new MinimalTransformer().transform(stylesheet).toString();
return optimize(stylesheet);
}

export function createBuildDeclarations(
Expand All @@ -105,5 +110,16 @@ export function createBuildDeclarations(
new UsedPropertyRegistry(propertiesMap, usedTokens),
);
const stylesheet = new MultiThemeCreator(themes, ruleCreator, propertiesMap).create();
return new MinimalTransformer().transform(stylesheet).toString();
return `@layer cloudscape-base-theme {\n${optimize(stylesheet)}\n}`;
}

export function createFullThemeDeclarations(
theme: Theme,
propertiesMap: PropertiesMap,
selectorCustomizer: SelectorCustomizer = (s) => s,
): string {
const ruleCreator = new RuleCreator(new Selector(selectorCustomizer), new AllPropertyRegistry(propertiesMap));
const stylesheet = new SingleThemeCreator(theme, ruleCreator).create();
const css = optimize(stylesheet);
return `@layer cloudscape-base-theme, cloudscape-theme;\n@layer cloudscape-theme {\n${css}\n}`;
}
31 changes: 26 additions & 5 deletions src/shared/declaration/stylesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,26 @@ export default class Stylesheet {
* @returns CSS
*/
toString(): string {
return asValuesArray(this.rulesMap)
.map((rule) => rule.toString())
.join('\n');
const rules = asValuesArray(this.rulesMap);
const mediaGroups = new Map<string, Rule[]>();
const result: string[] = [];

rules.forEach((rule) => {
if (!rule.media) {
result.push(rule.toString());
return;
}
const group = mediaGroups.get(rule.media) ?? [];
if (group.length === 0) mediaGroups.set(rule.media, group);
group.push(rule);
});

mediaGroups.forEach((group, media) => {
const inner = group.map((r) => r.toRuleString()).join('\n');
result.push(`@media ${media} {\n${inner}\n}`);
});

return result.join('\n');
}
}

Expand Down Expand Up @@ -82,14 +99,18 @@ export class Rule {
}

toString(): string {
const lines = asValuesArray(this.declarationsMap).map((decl) => decl.toString());
const rule = `${this.selector}{\n\t${lines.join('\n\t')}\n}`;
const rule = this.toRuleString();
if (this.media) {
return `@media ${this.media} {${rule}}`;
}
return rule;
}

toRuleString(): string {
const lines = asValuesArray(this.declarationsMap).map((decl) => decl.toString());
return `${this.selector}{\n\t${lines.join('\n\t')}\n}`;
}

isModeRule(): boolean {
return !!this.media;
}
Expand Down
26 changes: 25 additions & 1 deletion src/shared/declaration/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { entries } from '../utils';
import type Stylesheet from './stylesheet';
import { Declaration } from './stylesheet';
import { Declaration, Rule } from './stylesheet';
import { getFirstSelector, isGlobalSelector } from '../styles/selector';
import { getReferencedVar } from './utils';

Expand Down Expand Up @@ -97,6 +97,30 @@ export class MinimalTransformer implements Transformer {
}
}

export class SelectorMergeOptimizer implements Transformer {
transform(stylesheet: Stylesheet): Stylesheet {
const rules = stylesheet.getAllRules();
const byDeclarations = new Map<string, Rule[]>();
rules.forEach((rule) => {
const key = `${rule.media ?? ''}|${rule
.getAllDeclarations()
.map((d) => d.toString())
.join('')}`;
const group = byDeclarations.get(key) ?? [];
group.push(rule);
byDeclarations.set(key, group);
});

byDeclarations.forEach((group) => {
if (group.length < 2) return;
group[0].selector = group.map((r) => r.selector).join(',\n');
group.slice(1).forEach((r) => stylesheet.removeRule(r));
});

return stylesheet;
}
}

function difference<T>(mapA: Record<string, T>, mapB: Record<string, T>): Record<string, T> {
const diff: Record<string, T> = {};

Expand Down
Loading