Skip to content

Commit 06872a5

Browse files
committed
Merge branch 'main' into fix-wf-paste
2 parents cbb31ba + 9fd5ce0 commit 06872a5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+818
-492
lines changed

apps/builder/app/builder/builder.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
} from "~/shared/nano-states";
3333
import { $settings, type Settings } from "./shared/client-settings";
3434
import { builderUrl, getCanvasUrl } from "~/shared/router-utils";
35-
import { useCopyPaste } from "~/shared/copy-paste";
3635
import { BlockingAlerts } from "./features/blocking-alerts";
3736
import { useSyncPageUrl } from "~/shared/pages";
3837
import { useMount, useUnmount } from "~/shared/hook-utils/use-mount";
@@ -58,6 +57,7 @@ import { updateWebstudioData } from "~/shared/instance-utils";
5857
import { migrateWebstudioDataMutable } from "~/shared/webstudio-data-migrator";
5958
import { Loading, LoadingBackground } from "./shared/loading";
6059
import { mergeRefs } from "@react-aria/utils";
60+
import { initCopyPaste } from "~/shared/copy-paste";
6161

6262
registerContainers();
6363

@@ -285,9 +285,7 @@ export const Builder = ({
285285
const isCloneDialogOpen = useStore($isCloneDialogOpen);
286286
const isPreviewMode = useStore($isPreviewMode);
287287
const { onRef: onRefReadCanvas, onTransitionEnd } = useReadCanvasRect();
288-
// We need to initialize this in both canvas and builder,
289-
// because the events will fire in either one, depending on where the focus is
290-
useCopyPaste();
288+
291289
useSetWindowTitle();
292290

293291
const iframeRefCallback = mergeRefs((element: HTMLIFrameElement | null) => {
@@ -299,14 +297,24 @@ export const Builder = ({
299297
const [loadingState, setLoadingState] = useState(() => $loadingState.get());
300298

301299
useEffect(() => {
300+
const abortController = new AbortController();
301+
// We need to initialize this in both canvas and builder,
302+
// because the events will fire in either one, depending on where the focus is
303+
// @todo we need to forward the events from canvas to builder and avoid importing this
304+
// in both places
305+
initCopyPaste(abortController);
306+
302307
const unsubscribe = $loadingState.subscribe((loadingState) => {
303308
setLoadingState(loadingState);
304309
// We need to stop updating it once it's ready in case in the future it changes again.
305310
if (loadingState.state === "ready") {
306311
unsubscribe();
307312
}
308313
});
309-
return unsubscribe;
314+
return () => {
315+
unsubscribe();
316+
abortController.abort();
317+
};
310318
}, []);
311319

312320
const canvasUrl = getCanvasUrl();

apps/builder/app/builder/features/inspector/inspector.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,17 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
102102
const meta = metas.get(selectedInstance.component);
103103
const documentType = selectedPage?.meta.documentType ?? "html";
104104

105-
const availablePanels = {
106-
style: documentType === "html" && (meta?.stylable ?? true),
107-
// @todo hide root component settings until
108-
// global data sources are implemented
109-
settings: selectedInstance.component !== rootComponent,
110-
};
105+
type PanelName = "style" | "settings";
106+
107+
const availablePanels = new Set<PanelName>();
108+
if (documentType === "html" && (meta?.stylable ?? true)) {
109+
availablePanels.add("style");
110+
}
111+
// @todo hide root component settings until
112+
// global data sources are implemented
113+
if (selectedInstance.component !== rootComponent) {
114+
availablePanels.add("settings");
115+
}
111116

112117
return (
113118
<EnhancedTooltipProvider
@@ -120,18 +125,18 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
120125
<PanelTabs
121126
ref={tabsRef}
122127
value={
123-
availablePanels[activeInspectorPanel]
128+
availablePanels.has(activeInspectorPanel)
124129
? activeInspectorPanel
125-
: Object.keys(availablePanels)[0]
130+
: Array.from(availablePanels)[0]
126131
}
127132
onValueChange={(panel) => {
128-
$activeInspectorPanel.set(panel as keyof typeof availablePanels);
133+
$activeInspectorPanel.set(panel as PanelName);
129134
}}
130135
asChild
131136
>
132137
<Flex direction="column">
133138
<PanelTabsList>
134-
{availablePanels.style && (
139+
{availablePanels.has("style") && (
135140
<Tooltip
136141
variant="wrapped"
137142
content={
@@ -146,7 +151,7 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
146151
</div>
147152
</Tooltip>
148153
)}
149-
{availablePanels.settings && (
154+
{availablePanels.has("settings") && (
150155
<Tooltip
151156
variant="wrapped"
152157
content={

apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,8 @@ export const PropsSection = (props: PropsSectionProps) => {
181181
}}
182182
/>
183183
)}
184-
{logic.addedProps.map((item, index) =>
185-
renderProperty(props, item, {
186-
deletable: true,
187-
autoFocus: index === 0,
188-
})
184+
{logic.addedProps.map((item) =>
185+
renderProperty(props, item, { deletable: true })
189186
)}
190187
{logic.initialProps.map((item) => renderProperty(props, item))}
191188
</Flex>

apps/builder/app/builder/features/style-panel/controls/font-weight/font-weight-control.tsx

Lines changed: 46 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,81 @@
1-
import { Select } from "@webstudio-is/design-system";
2-
import { type FontWeight, fontWeights } from "@webstudio-is/fonts";
1+
import { Select, Text } from "@webstudio-is/design-system";
2+
import {
3+
type FontWeight,
4+
fontWeightNames,
5+
fontWeights,
6+
} from "@webstudio-is/fonts";
37
import { toValue } from "@webstudio-is/css-engine";
4-
import { useMemo } from "react";
5-
import { useAssets } from "~/builder/shared/assets";
6-
import { isSupportedFontWeight } from "./is-supported-font-weight";
8+
import { useEffect } from "react";
79
import { useComputedStyles } from "../../shared/model";
810
import { setProperty } from "../../shared/use-style-data";
11+
import { canvasApi } from "~/shared/canvas-api";
12+
import { useStore } from "@nanostores/react";
13+
import { $detectedFontsWeights } from "~/shared/nano-states";
914

10-
type FontWeightItem = {
11-
label: string;
12-
weight: FontWeight;
13-
};
14-
15-
const allFontWeights: Array<FontWeightItem> = (
16-
Object.keys(fontWeights) as Array<FontWeight>
17-
).map((weight) => ({
18-
label: `${weight} - ${fontWeights[weight].label}`,
19-
weight,
20-
}));
21-
22-
// All font weights for the current family
23-
const useAvailableFontWeights = (
24-
currentFamily: string
25-
): Array<FontWeightItem> => {
26-
const { assetContainers } = useAssets("font");
27-
28-
// Find all font weights that are available for the current font family.
29-
return useMemo(() => {
30-
const found = allFontWeights.filter((option) => {
31-
return assetContainers.find((assetContainer) => {
32-
if (
33-
assetContainer.status !== "uploaded" ||
34-
assetContainer.asset.type !== "font"
35-
) {
36-
return false;
37-
}
38-
return isSupportedFontWeight(
39-
assetContainer.asset,
40-
option.weight,
41-
currentFamily
42-
);
43-
});
44-
});
45-
return found.length === 0 ? allFontWeights : found;
46-
}, [currentFamily, assetContainers]);
47-
};
48-
49-
const useLabels = (
50-
availableFontWeights: Array<FontWeightItem>,
51-
currentWeight: string
52-
) => {
53-
// support named aliases
54-
if (currentWeight === "normal") {
55-
currentWeight = "400";
56-
}
57-
if (currentWeight === "bold") {
58-
currentWeight = "700";
59-
}
60-
const labels = useMemo(
61-
() => availableFontWeights.map((option) => option.label),
62-
[availableFontWeights]
63-
);
64-
65-
const selectedLabel = useMemo(() => {
66-
const selectedOption =
67-
availableFontWeights.find((option) => option.weight === currentWeight) ??
68-
// In case available weights a custom font supports does not include the current weight,
69-
// we show the first available weight.
70-
availableFontWeights[0];
71-
return selectedOption?.label;
72-
}, [currentWeight, availableFontWeights]);
15+
const allFontWeights = Object.keys(fontWeights) as Array<FontWeight>;
7316

74-
return { labels, selectedLabel };
75-
};
17+
const labels = new Map(
18+
allFontWeights.map((weight) => [
19+
weight,
20+
`${weight} - ${fontWeights[weight as FontWeight].label}`,
21+
])
22+
);
7623

7724
export const FontWeightControl = () => {
7825
// We need the font family to determine which font weights are available
7926
const [fontWeight, fontFamily] = useComputedStyles([
8027
"fontWeight",
8128
"fontFamily",
8229
]);
30+
const detectedFontsWeights = useStore($detectedFontsWeights);
31+
const fontFamilyCss = toValue(fontFamily.usedValue);
32+
const fontWeightCss = toValue(fontWeight.cascadedValue);
33+
const supportedFontWeights = detectedFontsWeights.get(fontFamilyCss) ?? [];
8334

84-
const availableFontWeights = useAvailableFontWeights(
85-
toValue(fontFamily.cascadedValue, (styleValue) => {
86-
// override default fallback with rendering only first font family
87-
if (styleValue.type === "fontFamily") {
88-
return {
89-
type: "fontFamily",
90-
value: [styleValue.value[0]],
91-
};
92-
}
93-
})
94-
);
95-
const { labels, selectedLabel } = useLabels(
96-
availableFontWeights,
97-
toValue(fontWeight.cascadedValue)
98-
);
35+
useEffect(() => {
36+
canvasApi.detectSupportedFontWeights(fontFamilyCss);
37+
}, [fontFamilyCss]);
38+
39+
const selectedWeight =
40+
fontWeightNames.get(fontWeightCss) ?? (fontWeightCss as FontWeight);
9941

10042
const setValue = setProperty("fontWeight");
10143

102-
const setFontWeight = (label: string, options?: { isEphemeral: boolean }) => {
103-
const selected = availableFontWeights.find(
104-
(option) => option.label === label
105-
);
106-
if (selected) {
107-
setValue({ type: "keyword", value: selected.weight }, options);
108-
}
44+
const setFontWeight = (
45+
nextWeight: FontWeight,
46+
options?: { isEphemeral: boolean }
47+
) => {
48+
setValue({ type: "keyword", value: nextWeight }, options);
10949
};
11050

11151
return (
11252
<Select
11353
// show empty field instead of radix placeholder
11454
// like css value input does
11555
placeholder=""
116-
options={labels}
56+
options={allFontWeights}
57+
getLabel={(weight: FontWeight) => {
58+
return (
59+
<Text
60+
color={supportedFontWeights.includes(weight) ? "main" : "subtle"}
61+
>
62+
{labels.get(weight)}
63+
</Text>
64+
);
65+
}}
11766
// We use a weight as a value, because there are only 9 weights and they are unique.
118-
value={selectedLabel}
67+
value={selectedWeight}
11968
onChange={setFontWeight}
120-
onItemHighlight={(label) => {
69+
onItemHighlight={(nextWeight) => {
12170
// Remove preview when mouse leaves the item.
122-
if (label === undefined) {
71+
if (nextWeight === undefined) {
12372
if (fontWeight !== undefined) {
12473
setValue(fontWeight.cascadedValue, { isEphemeral: true });
12574
}
12675
return;
12776
}
12877
// Preview on mouse enter or focus.
129-
setFontWeight(label, { isEphemeral: true });
78+
setFontWeight(nextWeight, { isEphemeral: true });
13079
}}
13180
onOpenChange={(isOpen) => {
13281
// Remove ephemeral changes when closing the menu.

apps/builder/app/builder/features/style-panel/controls/font-weight/is-supported-font-weight.test.ts

Lines changed: 0 additions & 45 deletions
This file was deleted.

apps/builder/app/builder/features/style-panel/controls/font-weight/is-supported-font-weight.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ const AdvancedPropertyValue = ({
256256
styleValue.value.startsWith("--")
257257
) {
258258
setProperty(property)(
259-
{ type: "var", value: styleValue.value.slice(2), fallbacks: [] },
259+
{ type: "var", value: styleValue.value.slice(2) },
260260
{ ...options, listed: true }
261261
);
262262
} else {

apps/builder/app/builder/features/style-panel/shared/css-value-input/parse-intermediate-or-invalid-value.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,6 @@ export const parseIntermediateOrInvalidValue = (
2020
value = value.slice(0, -1);
2121
}
2222

23-
if (property.startsWith("--")) {
24-
return {
25-
type: "unparsed",
26-
value,
27-
};
28-
}
29-
3023
// When user enters a number, we don't know if its a valid unit value,
3124
// so we are going to parse it with a unit and if its not invalid - we take it.
3225
const ast = parse(value, { context: "value" });

0 commit comments

Comments
 (0)