Skip to content

chore: Unreleased bugs with advanced section #4882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Feb 18, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const getCssText = (
return;
}
result.push(`/* ${comment} */`);
result.push(generateStyleMap({ style: mergeStyles(style) }));
result.push(generateStyleMap(mergeStyles(style)));
};

add("Style Sources", sourceStyles);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { lexer } from "css-tree";
import { forwardRef, useRef, useState, type KeyboardEvent } from "react";
import { matchSorter } from "match-sorter";
import {
Expand All @@ -24,11 +23,14 @@ import {
} from "@webstudio-is/css-data";
import {
cssWideKeywords,
generateStyleMap,
hyphenateProperty,
toValue,
type StyleProperty,
} from "@webstudio-is/css-engine";
import { deleteProperty, setProperty } from "../../shared/use-style-data";
import { composeEventHandlers } from "~/shared/event-utils";
import { parseStyleInput } from "./parse-style-input";

type SearchItem = { property: string; label: string; value?: string };

Expand Down Expand Up @@ -88,25 +90,24 @@ const matchOrSuggestToCreate = (
// Limit the array to 100 elements
matched.length = Math.min(matched.length, 100);

const property = search.trim();
if (
property.startsWith("--") &&
lexer.match("<custom-ident>", property).matched
) {
matched.unshift({
property,
label: `Create "${property}"`,
});
}
// When there is no match we suggest to create a custom property.
if (
matched.length === 0 &&
lexer.match("<custom-ident>", `--${property}`).matched
) {
matched.unshift({
property: `--${property}`,
label: `--${property}: unset;`,
});
if (matched.length === 0) {
const parsedStyles = parseStyleInput(search);
// When parsedStyles is more than one, user entered a shorthand.
// We will suggest to insert their shorthand first.
if (parsedStyles.length > 1) {
matched.push({
property: search,
label: `Create "${search}"`,
});
}
// Now we will suggest to insert each longhand separately.
for (const style of parsedStyles) {
matched.push({
property: style.property,
value: toValue(style.value),
label: `Create "${generateStyleMap(new Map([[style.property, style.value]]))}"`,
});
}
}

return matched;
Expand All @@ -122,7 +123,7 @@ const matchOrSuggestToCreate = (
* paste css declarations
*
*/
export const AddStylesInput = forwardRef<
export const AddStyleInput = forwardRef<
HTMLInputElement,
{
onClose: () => void;
Expand All @@ -147,7 +148,11 @@ export const AddStylesInput = forwardRef<
onChange: (value) => setItem({ property: value ?? "", label: value ?? "" }),
onItemSelect: (item) => {
clear();
onSubmit(`${item.property}: ${item.value ?? "unset"}`);
// When there is no value, it is either a property or a declaration(s)
if (item.value === undefined) {
return onSubmit(item.property);
}
onSubmit(`${item.property}: ${item.value}`);
},
onItemHighlight: (item) => {
const previousHighlightedItem = highlightedItemRef.current;
Expand Down Expand Up @@ -207,6 +212,17 @@ export const AddStylesInput = forwardRef<
handleDelete,
]);

const handleBlur = composeEventHandlers([
inputProps.onBlur,
() => {
// When user clicks on a combobox item, input will receive blur event,
// but we don't want that to be handled upstream because input may get hidden without click getting handled.
if (combobox.isOpen === false) {
onBlur();
}
},
]);

return (
<ComboboxRoot open={combobox.isOpen}>
<div {...combobox.getComboboxProps()}>
Expand All @@ -215,10 +231,7 @@ export const AddStylesInput = forwardRef<
{...inputProps}
autoFocus
onFocus={onFocus}
onBlur={(event) => {
inputProps.onBlur(event);
onBlur();
}}
onBlur={handleBlur}
inputRef={forwardedRef}
onKeyDown={handleKeyDown}
placeholder="Add styles"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
theme,
Tooltip,
} from "@webstudio-is/design-system";
import { parseCss, propertyDescriptions } from "@webstudio-is/css-data";
import { propertyDescriptions } from "@webstudio-is/css-data";
import {
hyphenateProperty,
toValue,
Expand Down Expand Up @@ -55,7 +55,8 @@ import { useClientSupports } from "~/shared/client-supports";
import { CopyPasteMenu, propertyContainerAttribute } from "./copy-paste-menu";
import { $advancedStyles } from "./stores";
import { $settings } from "~/builder/shared/client-settings";
import { AddStylesInput } from "./add-styles-input";
import { AddStyleInput } from "./add-style-input";
import { parseStyleInput } from "./parse-style-input";

// Only here to keep the same section module interface
export const properties = [];
Expand Down Expand Up @@ -97,13 +98,8 @@ const AdvancedStyleSection = (props: {
);
};

const insertStyles = (text: string) => {
let parsedStyles = parseCss(`selector{${text}}`);
if (parsedStyles.length === 0) {
// Try a single property without a value.
parsedStyles = parseCss(`selector{${text}: unset}`);
}

const insertStyles = (css: string) => {
const parsedStyles = parseStyleInput(css);
if (parsedStyles.length === 0) {
return [];
}
Expand Down Expand Up @@ -386,9 +382,10 @@ export const Section = () => {
setRecentProperties(
Array.from(new Set([...recentProperties, ...insertedProperties]))
);
return styles;
};

const handleShowAddStylesInput = () => {
const handleShowAddStyleInput = () => {
setIsAdding(true);
// User can click twice on the add button, so we need to focus the input on the second click after autoFocus isn't working.
addPropertyInputRef.current?.focus();
Expand Down Expand Up @@ -434,7 +431,7 @@ export const Section = () => {
<AdvancedStyleSection
label="Advanced"
properties={advancedProperties}
onAdd={handleShowAddStylesInput}
onAdd={handleShowAddStyleInput}
>
<Box css={{ paddingInline: theme.panel.paddingInline }}>
<SearchField
Expand All @@ -460,7 +457,7 @@ export const Section = () => {
autoFocus={isLast}
onChangeComplete={(event) => {
if (event.type === "enter") {
handleShowAddStylesInput();
handleShowAddStyleInput();
}
}}
onReset={() => {
Expand All @@ -482,15 +479,17 @@ export const Section = () => {
{ overflow: "hidden", height: 0 }
}
>
<AddStylesInput
<AddStyleInput
onSubmit={(cssText: string) => {
setIsAdding(false);
handleInsertStyles(cssText);
const styles = handleInsertStyles(cssText);
if (styles.length > 0) {
setIsAdding(false);
}
}}
onClose={handleAbortAddStyles}
onFocus={() => {
if (isAdding === false) {
handleShowAddStylesInput();
handleShowAddStyleInput();
}
}}
onBlur={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const CopyPasteMenu = ({
}
}

const css = generateStyleMap({ style: mergeStyles(currentStyleMap) });
const css = generateStyleMap(mergeStyles(currentStyleMap));
navigator.clipboard.writeText(css);
};

Expand All @@ -59,7 +59,7 @@ export const CopyPasteMenu = ({
return;
}
const style = new Map([[property, value]]);
const css = generateStyleMap({ style });
const css = generateStyleMap(style);
navigator.clipboard.writeText(css);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { describe, test, expect } from "vitest";
import { parseStyleInput } from "./parse-style-input";

describe("parseStyleInput", () => {
test("parses custom property", () => {
const result = parseStyleInput("--custom-color");
expect(result).toEqual([
{
selector: "selector",
property: "--custom-color",
value: { type: "unset", value: "" },
},
]);
});

test("parses regular property", () => {
const result = parseStyleInput("color");
expect(result).toEqual([
{
selector: "selector",
property: "color",
value: { type: "unset", value: "" },
},
]);
});

test("trims whitespace", () => {
const result = parseStyleInput(" color ");
expect(result).toEqual([
{
selector: "selector",
property: "color",
value: { type: "unset", value: "" },
},
]);
});

test("handles unparsable regular property", () => {
const result = parseStyleInput("notapro perty");
expect(result).toEqual([]);
});

test("converts unknown property to custom property assuming user forgot to add --", () => {
const result = parseStyleInput("notaproperty");
expect(result).toEqual([
{
selector: "selector",
property: "--notaproperty",
value: { type: "unset", value: "" },
},
]);
});

test("parses single property-value pair", () => {
const result = parseStyleInput("color: red");
expect(result).toEqual([
{
selector: "selector",
property: "color",
value: { type: "keyword", value: "red" },
},
]);
});

test("parses multiple property-value pairs", () => {
const result = parseStyleInput("color: red; display: block");
expect(result).toEqual([
{
selector: "selector",
property: "color",
value: { type: "keyword", value: "red" },
},
{
selector: "selector",
property: "display",
value: { type: "keyword", value: "block" },
},
]);
});

test("parses multiple property-value pairs, one is invalid", () => {
const result = parseStyleInput("color: red; somethinginvalid: block");
expect(result).toEqual([
{
selector: "selector",
property: "color",
value: { type: "keyword", value: "red" },
},
{
selector: "selector",
property: "--somethinginvalid",
value: { type: "unparsed", value: "block" },
},
]);
});

test("parses custom property with value", () => {
const result = parseStyleInput("--custom-color: red");
expect(result).toEqual([
{
selector: "selector",
property: "--custom-color",
value: { type: "unparsed", value: "red" },
},
]);
});

test("handles malformed style block", () => {
const result = parseStyleInput("color: red; invalid;");
expect(result).toEqual([
{
selector: "selector",
property: "color",
value: { type: "keyword", value: "red" },
},
]);
});
});
Loading
Loading