refactor: migrate css preview to new style engine (#4353)

Preview now managed by new style engine.

![Screenshot 2024-10-29 at 12 05
25](https://github.com/user-attachments/assets/3017247e-2fc6-4572-bb19-8309067a570d)
This commit is contained in:
Bogdan Chadkin authored and GitHub committed 2024-11-04 10:36:53 +04:00
1 parent 4fb2338ae8
commit 5fbda47b41
6 files changed
+158 -118

No files matched your search

@@ -1,14 +1,11 @@
import { computed } from "nanostores";
import { useStore } from "@nanostores/react";
import {
ScrollArea,
css,
textVariants,
theme,
} from "@webstudio-is/design-system";
// @todo feature should not import another feature
import {
type StyleInfo,
useStyleInfo,
} from "~/builder/features/style-panel/shared/style-info";
import {
generateStyleMap,
hyphenateProperty,
@@ -16,9 +13,10 @@ import {
} from "@webstudio-is/css-engine";
import type { StyleMap, StyleProperty } from "@webstudio-is/css-engine";
import { CollapsibleSection } from "~/builder/shared/collapsible-section";
import { useMemo } from "react";
import { captureError } from "@webstudio-is/error-utils";
import { highlightCss } from "~/builder/shared/code-highlight";
import type { ComputedStyleDecl } from "~/shared/style-object-model";
import { $selectedInstanceSelector } from "~/shared/nano-states";
import { $definedComputedStyles } from "~/builder/features/style-panel/shared/model";
const preStyle = css(textVariants.mono, {
margin: 0,
@@ -27,43 +25,40 @@ const preStyle = css(textVariants.mono, {
cursor: "text",
});
// - Compiles a CSS string from the style info
// - Compiles a CSS string from the style engine
// - Groups by category and separates categories with comments
const getCssText = (currentStyle: StyleInfo) => {
const getCssText = (
computedStyles: ComputedStyleDecl[],
instanceId: string
) => {
const sourceStyles: StyleMap = new Map();
const inheritedStyles: StyleMap = new Map();
const cascadedStyles: StyleMap = new Map();
const presetStyles: StyleMap = new Map();
// Aggregate styles by category so we can group them when rendering.
let property: StyleProperty;
for (property in currentStyle) {
const value = currentStyle[property];
property = hyphenateProperty(property) as StyleProperty;
if (value === undefined) {
continue;
for (const styleDecl of computedStyles) {
const property = hyphenateProperty(styleDecl.property) as StyleProperty;
let group;
if (
styleDecl.source.name === "local" ||
styleDecl.source.name === "overwritten"
) {
group = sourceStyles;
}
if (value.local ?? value.nextSource?.value ?? value.previousSource?.value) {
sourceStyles.set(property, value.value);
continue;
if (styleDecl.source.name === "remote") {
group = cascadedStyles;
}
if (value.preset) {
presetStyles.set(property, value.value);
continue;
if (styleDecl.source.name === "preset") {
group = presetStyles;
}
if (value.cascaded?.value) {
cascadedStyles.set(property, value.value);
continue;
if (group) {
if (styleDecl.source.instanceId === instanceId) {
group.set(property, styleDecl.cascadedValue);
} else {
inheritedStyles.set(property, styleDecl.cascadedValue);
}
}
if (value.inherited?.value) {
inheritedStyles.set(property, value.value);
continue;
}
if (value.value) {
// Doesn't need handling
continue;
}
captureError(new Error("Unknown style source"), value);
}
const result: Array<string> = [];
@@ -84,25 +79,23 @@ const getCssText = (currentStyle: StyleInfo) => {
return result.join("\n");
};
const useHighlightedCss = () => {
const currentStyle = useStyleInfo();
return useMemo(() => {
if (Object.keys(currentStyle).length === 0) {
const $highlightedCss = computed(
[$selectedInstanceSelector, $definedComputedStyles],
(instanceSelector, computedStyles) => {
if (instanceSelector === undefined) {
return;
}
const cssText = getCssText(currentStyle);
const [instanceId] = instanceSelector;
const cssText = getCssText(computedStyles, instanceId);
return highlightCss(cssText);
}, [currentStyle]);
};
}
);
export const CssPreview = () => {
const code = useHighlightedCss();
const code = useStore($highlightedCss);
if (code === undefined) {
return null;
}
return (
<CollapsibleSection label="CSS Preview" fullWidth>
<ScrollArea css={{ padding: theme.panel.padding }}>
@@ -162,7 +162,7 @@ export const PropsSection = (props: PropsSectionProps) => {
return (
<>
<Row css={{ py: theme.spacing[3] }}>
<Row css={{ py: theme.panel.paddingBlock }}>
{logic.systemProps.map((item) => renderProperty(props, item))}
</Row>
@@ -67,6 +67,8 @@ import { PropertyInfo } from "../../property-label";
import { sections } from "../sections";
import { ColorPopover } from "../../shared/color-picker";
import {
$instances,
$registeredComponentMetas,
$selectedInstanceSelector,
$styles,
$styleSourceSelections,
@@ -377,11 +379,20 @@ const initialProperties = new Set<StyleProperty>([
const $advancedProperties = computed(
[
$selectedInstanceSelector,
$instances,
$registeredComponentMetas,
$styleSourceSelections,
$matchingBreakpoints,
$styles,
],
(instanceSelector, styleSourceSelections, matchingBreakpoints, styles) => {
(
instanceSelector,
instances,
metas,
styleSourceSelections,
matchingBreakpoints,
styles
) => {
if (instanceSelector === undefined) {
return [];
}
@@ -393,6 +404,8 @@ const $advancedProperties = computed(
instanceSelector;
const definedStyles = getDefinedStyles({
instanceSelector: instanceAndRootSelector,
instances,
metas,
matchingBreakpoints,
styleSourceSelections,
styles,
@@ -10,6 +10,7 @@ import {
type VarValue,
} from "@webstudio-is/css-engine";
import {
Instances,
ROOT_INSTANCE_ID,
Styles,
StyleSourceSelections,
@@ -17,7 +18,7 @@ import {
type Instance,
type StyleDecl,
} from "@webstudio-is/sdk";
import { rootComponent } from "@webstudio-is/react-sdk";
import { rootComponent, WsComponentMeta } from "@webstudio-is/react-sdk";
import {
$breakpoints,
$instances,
@@ -96,20 +97,36 @@ export const $matchingBreakpoints = computed(
export const getDefinedStyles = ({
instanceSelector,
instances,
metas,
matchingBreakpoints: matchingBreakpointsArray,
styleSourceSelections,
styles,
}: {
instanceSelector: InstanceSelector;
instances: Instances;
metas: Map<string, WsComponentMeta>;
matchingBreakpoints: Breakpoint["id"][];
styleSourceSelections: StyleSourceSelections;
styles: Styles;
}) => {
const definedStyles = new Set<StyleDecl>();
const definedStyles = new Set<{
property: StyleProperty;
listed?: boolean;
}>();
const inheritedStyleSources = new Set();
const instanceStyleSources = new Set();
const matchingBreakpoints = new Set(matchingBreakpointsArray);
for (const instanceId of instanceSelector) {
const instance = instances.get(instanceId);
const meta = instance?.component
? metas.get(instance.component)
: undefined;
for (const presetStyles of Object.values(meta?.presetStyle ?? {})) {
for (const styleDecl of presetStyles) {
definedStyles.add(styleDecl);
}
}
const styleSources = styleSourceSelections.get(instanceId)?.values;
if (styleSources) {
for (const styleSourceId of styleSources) {
@@ -143,23 +160,43 @@ export const getDefinedStyles = ({
return definedStyles;
};
const $instanceAndRootSelector = computed(
$selectedInstanceSelector,
(instanceSelector) => {
if (instanceSelector === undefined) {
return;
}
if (instanceSelector[0] === ROOT_INSTANCE_ID) {
return instanceSelector;
}
return [...instanceSelector, ROOT_INSTANCE_ID];
}
);
export const $definedStyles = computed(
[
$selectedInstanceSelector,
$instanceAndRootSelector,
$instances,
$registeredComponentMetas,
$styleSourceSelections,
$matchingBreakpoints,
$styles,
],
(instanceSelector, styleSourceSelections, matchingBreakpoints, styles) => {
(
instanceSelector,
instances,
metas,
styleSourceSelections,
matchingBreakpoints,
styles
) => {
if (instanceSelector === undefined) {
return new Set<StyleDecl>();
}
const instanceAndRootSelector =
instanceSelector[0] === ROOT_INSTANCE_ID
? instanceSelector
: [...instanceSelector, ROOT_INSTANCE_ID];
return getDefinedStyles({
instanceSelector: instanceAndRootSelector,
instanceSelector,
instances,
metas,
matchingBreakpoints,
styleSourceSelections,
styles,
@@ -198,20 +235,7 @@ const $model = computed(
}
);
const $instanceAndRootSelector = computed(
$selectedInstanceSelector,
(instanceSelector) => {
if (instanceSelector === undefined) {
return;
}
if (instanceSelector[0] === ROOT_INSTANCE_ID) {
return instanceSelector;
}
return [...instanceSelector, ROOT_INSTANCE_ID];
}
);
export const $availableVariables = computed(
export const $definedComputedStyles = computed(
[
$definedStyles,
$model,
@@ -219,25 +243,39 @@ export const $availableVariables = computed(
$selectedOrLastStyleSourceSelector,
],
(definedStyles, model, instanceSelector, styleSourceSelector) => {
const availableVariables = new Map<string, VarValue>();
const computedStyles = new Map<string, ComputedStyleDecl>();
for (const { property } of definedStyles) {
if (property.startsWith("--")) {
const { computedValue } = getComputedStyleDecl({
model,
instanceSelector,
styleSourceId: styleSourceSelector?.styleSourceId,
state: styleSourceSelector?.state,
property,
});
// deduplicate by property name
availableVariables.set(property, {
// deduplicate by property name
if (computedStyles.has(property)) {
continue;
}
const computedStyleDecl = getComputedStyleDecl({
model,
instanceSelector,
styleSourceId: styleSourceSelector?.styleSourceId,
state: styleSourceSelector?.state,
property,
});
computedStyles.set(property, computedStyleDecl);
}
return Array.from(computedStyles.values());
}
);
export const $availableVariables = computed(
$definedComputedStyles,
(computedStyles) => {
const availableVariables: VarValue[] = [];
for (const styleDecl of computedStyles) {
if (styleDecl.property.startsWith("--")) {
availableVariables.push({
type: "var",
value: property.slice(2),
fallback: toVarFallback(computedValue),
value: styleDecl.property.slice(2),
fallback: toVarFallback(styleDecl.computedValue),
});
}
}
return Array.from(availableVariables.values());
return availableVariables;
}
);
+2 -3
View File
@@ -18,7 +18,7 @@
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@codemirror/autocomplete": "^6.18.1",
"@codemirror/commands": "^6.7.0",
"@codemirror/commands": "^6.7.1",
"@codemirror/lang-css": "^6.3.0",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.2",
@@ -36,7 +36,7 @@
"@lexical/react": "^0.16.0",
"@lexical/selection": "^0.16.0",
"@lexical/utils": "^0.16.0",
"@lezer/common": "^1.2.2",
"@lezer/common": "^1.2.3",
"@lezer/css": "^1.1.9",
"@lezer/highlight": "^1.2.1",
"@nanostores/react": "^0.7.1",
@@ -60,7 +60,6 @@
"@webstudio-is/dashboard": "workspace:*",
"@webstudio-is/design-system": "workspace:*",
"@webstudio-is/domain": "workspace:*",
"@webstudio-is/error-utils": "workspace:*",
"@webstudio-is/feature-flags": "workspace:*",
"@webstudio-is/fonts": "workspace:*",
"@webstudio-is/http-client": "workspace:*",
+29 -32
View File
@@ -126,10 +126,10 @@ importers:
version: 1.3.1
'@codemirror/autocomplete':
specifier: ^6.18.1
version: 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2)
version: 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3)
'@codemirror/commands':
specifier: ^6.7.0
version: 6.7.0
specifier: ^6.7.1
version: 6.7.1
'@codemirror/lang-css':
specifier: ^6.3.0
version: 6.3.0(@codemirror/view@6.34.1)
@@ -182,8 +182,8 @@ importers:
specifier: ^0.16.0
version: 0.16.0
'@lezer/common':
specifier: ^1.2.2
version: 1.2.2
specifier: ^1.2.3
version: 1.2.3
'@lezer/css':
specifier: ^1.1.9
version: 1.1.9
@@ -253,9 +253,6 @@ importers:
'@webstudio-is/domain':
specifier: workspace:*
version: link:../../packages/domain
'@webstudio-is/error-utils':
specifier: workspace:*
version: link:../../packages/error-utils
'@webstudio-is/feature-flags':
specifier: workspace:*
version: link:../../packages/feature-flags
@@ -2682,8 +2679,8 @@ packages:
'@codemirror/view': ^6.0.0
'@lezer/common': ^1.0.0
'@codemirror/commands@6.7.0':
resolution: {integrity: sha512-+cduIZ2KbesDhbykV02K25A5xIVrquSPz4UxxYBemRlAT2aW8dhwUgLDwej7q/RJUHKk4nALYcR1puecDvbdqw==}
'@codemirror/commands@6.7.1':
resolution: {integrity: sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==}
'@codemirror/lang-css@6.3.0':
resolution: {integrity: sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA==}
@@ -3831,8 +3828,8 @@ packages:
peerDependencies:
yjs: '>=13.5.22'
'@lezer/common@1.2.2':
resolution: {integrity: sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw==}
'@lezer/common@1.2.3':
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
'@lezer/css@1.1.9':
resolution: {integrity: sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==}
@@ -10302,67 +10299,67 @@ snapshots:
'@cloudflare/workers-types@4.20240701.0': {}
'@codemirror/autocomplete@6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2)':
'@codemirror/autocomplete@6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3)':
dependencies:
'@codemirror/language': 6.10.3
'@codemirror/state': 6.4.1
'@codemirror/view': 6.34.1
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@codemirror/commands@6.7.0':
'@codemirror/commands@6.7.1':
dependencies:
'@codemirror/language': 6.10.3
'@codemirror/state': 6.4.1
'@codemirror/view': 6.34.1
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@codemirror/lang-css@6.3.0(@codemirror/view@6.34.1)':
dependencies:
'@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2)
'@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3)
'@codemirror/language': 6.10.3
'@codemirror/state': 6.4.1
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/css': 1.1.9
transitivePeerDependencies:
- '@codemirror/view'
'@codemirror/lang-html@6.4.9':
dependencies:
'@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2)
'@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3)
'@codemirror/lang-css': 6.3.0(@codemirror/view@6.34.1)
'@codemirror/lang-javascript': 6.2.2
'@codemirror/language': 6.10.3
'@codemirror/state': 6.4.1
'@codemirror/view': 6.34.1
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/css': 1.1.9
'@lezer/html': 1.3.10
'@codemirror/lang-javascript@6.2.2':
dependencies:
'@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2)
'@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3)
'@codemirror/language': 6.10.3
'@codemirror/lint': 6.8.2
'@codemirror/state': 6.4.1
'@codemirror/view': 6.34.1
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/javascript': 1.4.19
'@codemirror/lang-markdown@6.3.0':
dependencies:
'@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2)
'@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3)
'@codemirror/lang-html': 6.4.9
'@codemirror/language': 6.10.3
'@codemirror/state': 6.4.1
'@codemirror/view': 6.34.1
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/markdown': 1.3.1
'@codemirror/language@6.10.3':
dependencies:
'@codemirror/state': 6.4.1
'@codemirror/view': 6.34.1
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
style-mod: 4.1.2
@@ -11265,37 +11262,37 @@ snapshots:
lexical: 0.16.0
yjs: 13.5.52
'@lezer/common@1.2.2': {}
'@lezer/common@1.2.3': {}
'@lezer/css@1.1.9':
dependencies:
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@lezer/highlight@1.2.1':
dependencies:
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/html@1.3.10':
dependencies:
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@lezer/javascript@1.4.19':
dependencies:
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@lezer/lr@1.4.2':
dependencies:
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/markdown@1.3.1':
dependencies:
'@lezer/common': 1.2.2
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@mdx-js/mdx@2.3.0':