mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-04 02:58:23 +09:00
# Description This updates the navigator to use Figma-style subtree highlighting instead of relying on vertical depth lines, which makes deeply nested selections easier to follow. The selected node keeps the primary selected background, its descendant subtree gets a lighter background, and the navigator hides depth bars to reduce visual noise. Related to ([#5206](https://github.com/webstudio-is/webstudio/issues/5206)) ## Steps for reproduction 1. Open the Builder and create or expand a deeply nested navigator tree. 2. Select a node that has multiple nested descendants. 3. Expect the selected node to have the active background, its descendant subtree to have a lighter background, and the navigator to no longer rely on vertical guide lines for orientation. https://github.com/user-attachments/assets/c7be9884-43e2-46f1-bb58-68bcf0bad48c ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [x] made a self-review - [x] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [x] tested locally and on preview environment (preview dev login: 0000) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [x] added tests - [ ] if any new env variables are added, added them to `.env` file
132 lines
3.4 KiB
TypeScript
132 lines
3.4 KiB
TypeScript
import { test, expect } from "vitest";
|
|
import type {
|
|
Instance,
|
|
StyleDecl,
|
|
Styles,
|
|
StyleSource,
|
|
StyleSourceSelection,
|
|
} from "@webstudio-is/sdk";
|
|
import { getStyleDeclKey } from "@webstudio-is/sdk";
|
|
import {
|
|
cloneStyles,
|
|
findLocalStyleSourcesWithinInstances,
|
|
isDescendantOrSelf,
|
|
} from "./tree-utils";
|
|
|
|
const createStyleSource = (
|
|
type: StyleSource["type"],
|
|
id: StyleSource["id"]
|
|
): StyleSource => {
|
|
if (type === "token") {
|
|
return {
|
|
type,
|
|
id,
|
|
name: id,
|
|
};
|
|
}
|
|
return {
|
|
type,
|
|
id,
|
|
};
|
|
};
|
|
|
|
const createStyleSourceSelection = (
|
|
instanceId: Instance["id"],
|
|
values: StyleSource["id"][]
|
|
): StyleSourceSelection => {
|
|
return {
|
|
instanceId,
|
|
values,
|
|
};
|
|
};
|
|
|
|
const createStyleDecl = (
|
|
styleSourceId: string,
|
|
breakpointId: string,
|
|
value?: string
|
|
): StyleDecl => {
|
|
return {
|
|
styleSourceId,
|
|
breakpointId,
|
|
property: "width",
|
|
value: {
|
|
type: "keyword",
|
|
value: value ?? "value",
|
|
},
|
|
};
|
|
};
|
|
|
|
const createStyleDeclPair = (
|
|
styleSourceId: string,
|
|
breakpointId: string,
|
|
state?: string,
|
|
value?: string
|
|
) => {
|
|
return [
|
|
getStyleDeclKey({
|
|
styleSourceId,
|
|
breakpointId,
|
|
state,
|
|
property: "width",
|
|
}),
|
|
createStyleDecl(styleSourceId, breakpointId, value),
|
|
] as const;
|
|
};
|
|
|
|
test("clone styles with appled new style source ids", () => {
|
|
const styles: Styles = new Map([
|
|
createStyleDeclPair("styleSource1", "bp1"),
|
|
createStyleDeclPair("styleSource2", "bp2"),
|
|
createStyleDeclPair("styleSource1", "bp3"),
|
|
createStyleDeclPair("styleSource3", "bp4"),
|
|
createStyleDeclPair("styleSource1", "bp5"),
|
|
createStyleDeclPair("styleSource3", "bp6"),
|
|
]);
|
|
const clonedStyleSourceIds = new Map<StyleSource["id"], StyleSource["id"]>();
|
|
clonedStyleSourceIds.set("styleSource2", "newStyleSource2");
|
|
clonedStyleSourceIds.set("styleSource3", "newStyleSource3");
|
|
expect(cloneStyles(styles, clonedStyleSourceIds)).toEqual([
|
|
createStyleDecl("newStyleSource2", "bp2"),
|
|
createStyleDecl("newStyleSource3", "bp4"),
|
|
createStyleDecl("newStyleSource3", "bp6"),
|
|
]);
|
|
});
|
|
|
|
test("find local style sources within instances", () => {
|
|
const instanceIds = new Set(["instance2", "instance4"]);
|
|
const styleSources = [
|
|
createStyleSource("local", "local1"),
|
|
createStyleSource("local", "local2"),
|
|
createStyleSource("token", "token3"),
|
|
createStyleSource("local", "local4"),
|
|
createStyleSource("token", "token5"),
|
|
createStyleSource("local", "local6"),
|
|
];
|
|
const styleSourceSelections = [
|
|
createStyleSourceSelection("instance1", ["local1"]),
|
|
createStyleSourceSelection("instance2", ["local2"]),
|
|
createStyleSourceSelection("instance3", ["token3"]),
|
|
createStyleSourceSelection("instance4", ["local4", "token5"]),
|
|
createStyleSourceSelection("instance5", ["local6"]),
|
|
];
|
|
expect(
|
|
findLocalStyleSourcesWithinInstances(
|
|
styleSources,
|
|
styleSourceSelections,
|
|
instanceIds
|
|
)
|
|
).toEqual(new Set(["local2", "local4"]));
|
|
});
|
|
|
|
test("is descendant or self", () => {
|
|
expect(isDescendantOrSelf(["1", "2", "3"], ["1", "2", "3"])).toBe(true);
|
|
expect(isDescendantOrSelf(["0", "1", "2", "3"], ["1", "2", "3"])).toBe(true);
|
|
expect(isDescendantOrSelf(["1", "2", "3"], ["0", "1", "2", "3"])).toBe(false);
|
|
expect(
|
|
isDescendantOrSelf(
|
|
["item-child", "collection:entry-1", "collection", "body", "page-root"],
|
|
["collection", "body", "page-root"]
|
|
)
|
|
).toBe(true);
|
|
});
|