Skip to content
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

build: Release 05-02-2025 #4837

Closed
wants to merge 7 commits into from
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
32 changes: 28 additions & 4 deletions apps/builder/app/builder/features/navigator/navigator-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,12 @@ const canDrag = (instance: Instance, instanceSelector: InstanceSelector) => {
if (instanceSelector.length === 1) {
return false;
}

// Do not drag if the instance name is being edited
if ($editingItemSelector.get()?.join(",") === instanceSelector.join(",")) {
return false;
}

if ($isContentMode.get()) {
const parentId = instanceSelector[1];
const parentInstance = $instances.get().get(parentId);
Expand Down Expand Up @@ -542,6 +548,20 @@ export const NavigatorTree = () => {
}
}, [selectedInstanceSelector]);

const selectInstanceAndClearSelection = (
instanceSelector: undefined | Instance["id"][],
event: React.MouseEvent | React.FocusEvent
) => {
if (event.currentTarget.querySelector("[contenteditable]") === null) {
// Allow text selection and edits inside current TreeNode
// Outside if text is selected, it needs to be unselected before selecting the instance.
// Otherwise user will cmd+c the text instead of copying the instance.
window.getSelection()?.removeAllRanges();
}

selectInstance(instanceSelector);
};

return (
<ScrollArea
direction="both"
Expand All @@ -558,8 +578,10 @@ export const NavigatorTree = () => {
level={0}
isSelected={selectedKey === ROOT_INSTANCE_ID}
buttonProps={{
onClick: () => selectInstance([ROOT_INSTANCE_ID]),
onFocus: () => selectInstance([ROOT_INSTANCE_ID]),
onClick: (event) =>
selectInstanceAndClearSelection([ROOT_INSTANCE_ID], event),
onFocus: (event) =>
selectInstanceAndClearSelection([ROOT_INSTANCE_ID], event),
}}
action={
<Tooltip
Expand Down Expand Up @@ -664,8 +686,10 @@ export const NavigatorTree = () => {
$blockChildOutline.set(undefined);
},
onMouseLeave: () => $hoveredInstanceSelector.set(undefined),
onClick: () => selectInstance(item.selector),
onFocus: () => selectInstance(item.selector),
onClick: (event) =>
selectInstanceAndClearSelection(item.selector, event),
onFocus: (event) =>
selectInstanceAndClearSelection(item.selector, event),
onKeyDown: (event) => {
if (event.key === "Enter") {
emitCommand("editInstanceText");
Expand Down
14 changes: 11 additions & 3 deletions apps/builder/app/builder/features/pages/pages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useEffect, useRef } from "react";
import { useStore } from "@nanostores/react";
import {
Tooltip,
Box,
Button,
SmallIconButton,
TreeNode,
Expand All @@ -13,6 +12,7 @@ import {
TreeSortableItem,
type TreeDropTarget,
toast,
ScrollArea,
} from "@webstudio-is/design-system";
import {
ChevronRightIcon,
Expand Down Expand Up @@ -313,7 +313,15 @@ const PagesTree = ({
}

return (
<Box css={{ overflowY: "auto", flexBasis: 0, flexGrow: 1 }}>
<ScrollArea
direction="both"
css={{
width: "100%",
overflow: "hidden",
flexBasis: 0,
flexGrow: 1,
}}
>
<TreeRoot>
{flatPagesTree.map((item, index) => {
const handleExpand = (isExpanded: boolean, all: boolean) => {
Expand Down Expand Up @@ -435,7 +443,7 @@ const PagesTree = ({
);
})}
</TreeRoot>
</Box>
</ScrollArea>
);
};

Expand Down
19 changes: 3 additions & 16 deletions apps/builder/app/shared/copy-paste/init-copy-paste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,10 @@ const validateClipboardEvent = (event: ClipboardEvent) => {
// Allows native selection of text in the Builder panels, such as CSS Preview.
if (event.type === "copy") {
const isInBuilderContext = window.self === window.top;
const selection = window.getSelection();

if (isInBuilderContext) {
// Note on event.target:
//
// The spec (https://w3c.github.io/clipboard-apis/#to-fire-a-clipboard-event)
// says that if the context is not editable, the target should be the focused node.
//
// But in practice it seems that the target is based
// on where the cursor is, rather than which element has focus.
// For example, if a <button> has focus, the target is the <body> element.
// If some text is selected, the target is a wrapping element of the text.
// (at least in Chrome).

// We are using the behavior described above: if some text is selected, the target is usually (at least in the cases we need) not the body.
if (event.target !== window.document.body) {
return false;
}
if (isInBuilderContext && selection && selection.isCollapsed === false) {
return false;
}
}

Expand Down
4 changes: 2 additions & 2 deletions apps/builder/app/shared/data-variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export const findUnsetVariableNames = ({

for (const dataSource of dataSources.values()) {
if (
instanceIds.has(dataSource.scopeInstanceId) &&
instanceIds.has(dataSource.scopeInstanceId ?? "") &&
dataSource.type === "resource"
) {
resourceIds.add(dataSource.resourceId);
Expand Down Expand Up @@ -359,7 +359,7 @@ export const restoreTreeVariablesMutable = ({

for (const dataSource of dataSources.values()) {
if (
instanceIds.has(dataSource.scopeInstanceId) &&
instanceIds.has(dataSource.scopeInstanceId ?? "") &&
dataSource.type === "resource"
) {
resourceIds.add(dataSource.resourceId);
Expand Down
8 changes: 4 additions & 4 deletions apps/builder/app/shared/instance-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ export const extractWebstudioFragment = (
const fragmentResourceIds = new Set<Resource["id"]>();
const unsetNameById = new Map<DataSource["id"], DataSource["name"]>();
for (const dataSource of dataSources.values()) {
if (fragmentInstanceIds.has(dataSource.scopeInstanceId)) {
if (fragmentInstanceIds.has(dataSource.scopeInstanceId ?? "")) {
fragmentDataSources.set(dataSource.id, dataSource);
if (dataSource.type === "resource") {
fragmentResourceIds.add(dataSource.resourceId);
Expand Down Expand Up @@ -892,7 +892,7 @@ export const insertWebstudioFragmentCopy = ({
const usedResourceIds = new Set<Resource["id"]>();
for (const dataSource of fragment.dataSources) {
// insert only data sources within portal content
if (instanceIds.has(dataSource.scopeInstanceId)) {
if (instanceIds.has(dataSource.scopeInstanceId ?? "")) {
dataSources.set(dataSource.id, dataSource);
if (dataSource.type === "resource") {
usedResourceIds.add(dataSource.resourceId);
Expand Down Expand Up @@ -984,13 +984,13 @@ export const insertWebstudioFragmentCopy = ({
for (let dataSource of fragment.dataSources) {
const { scopeInstanceId } = dataSource;
// insert only data sources within portal content
if (fragmentInstanceIds.has(scopeInstanceId)) {
if (fragmentInstanceIds.has(scopeInstanceId ?? "")) {
const newDataSourceId = nanoid();
newDataSourceIds.set(dataSource.id, newDataSourceId);
dataSource = structuredClone(unwrap(dataSource));
dataSource.id = newDataSourceId;
dataSource.scopeInstanceId =
newInstanceIds.get(scopeInstanceId) ?? scopeInstanceId;
newInstanceIds.get(scopeInstanceId ?? "") ?? scopeInstanceId;
if (dataSource.type === "resource") {
const newResourceId = nanoid();
newResourceIds.set(dataSource.resourceId, newResourceId);
Expand Down
2 changes: 1 addition & 1 deletion packages/feature-flags/src/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export const staticExport = false;
export const contentEditableMode = false;
export const command = false;
export const headSlotComponent = false;
export const stylePanelModes = false;
export const stylePanelModes = true;
11 changes: 8 additions & 3 deletions packages/sdk/src/schema/data-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,25 @@ export const DataSource = z.union([
z.object({
type: z.literal("variable"),
id: DataSourceId,
scopeInstanceId: z.string(),
// The instance should always be specified for variables,
// however, there was a bug in the embed template
// which produced variables without an instance
// and these variables will fail validation
// if we make it required
scopeInstanceId: z.string().optional(),
name: z.string(),
value: DataSourceVariableValue,
}),
z.object({
type: z.literal("parameter"),
id: DataSourceId,
scopeInstanceId: z.string(),
scopeInstanceId: z.string().optional(),
name: z.string(),
}),
z.object({
type: z.literal("resource"),
id: DataSourceId,
scopeInstanceId: z.string(),
scopeInstanceId: z.string().optional(),
name: z.string(),
resourceId: z.string(),
}),
Expand Down