diff --git a/apps/builder/app/builder/features/settings-panel/variables-section.tsx b/apps/builder/app/builder/features/settings-panel/variables-section.tsx index 7ac61f4bf0b1..00e284e518e5 100644 --- a/apps/builder/app/builder/features/settings-panel/variables-section.tsx +++ b/apps/builder/app/builder/features/settings-panel/variables-section.tsx @@ -48,25 +48,30 @@ import { import { $selectedInstance, $selectedInstanceKey, + $selectedInstancePath, $selectedPage, } from "~/shared/awareness"; /** * find variables defined specifically on this selected instance */ -const $instanceVariables = computed( - [$selectedInstance, $dataSources], - (instance, dataSources) => { - const matchedVariables: DataSource[] = []; - if (instance === undefined) { - return matchedVariables; +const $availableVariables = computed( + [$selectedInstancePath, $dataSources], + (instancePath, dataSources) => { + if (instancePath === undefined) { + return []; } - for (const dataSource of dataSources.values()) { - if (instance.id === dataSource.scopeInstanceId) { - matchedVariables.push(dataSource); + const availableVariables = new Map(); + // order from ancestor to descendant + // so descendants can override ancestor variables + for (const { instance } of instancePath.slice().reverse()) { + for (const dataSource of dataSources.values()) { + if (dataSource.scopeInstanceId === instance.id) { + availableVariables.set(dataSource.name, dataSource); + } } } - return matchedVariables; + return Array.from(availableVariables.values()); } ); @@ -181,19 +186,19 @@ const EmptyVariables = () => { const VariablesItem = ({ variable, + source, index, value, usageCount, }: { variable: DataSource; + source: "local" | "remote"; index: number; value: unknown; usageCount: number; }) => { - const label = - value === undefined - ? variable.name - : `${variable.name}: ${formatValuePreview(value)}`; + const labelValue = + value === undefined ? "" : `: ${formatValuePreview(value)}`; const [inspectDialogOpen, setInspectDialogOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false); return ( @@ -201,7 +206,13 @@ const VariablesItem = ({ {label}} + label={ + + + {labelValue} + + } + disabled={source === "remote"} data-state={isMenuOpen ? "open" : undefined} buttons={ <> @@ -234,13 +245,15 @@ const VariablesItem = ({ setInspectDialogOpen(true)}> Inspect - 0} - onSelect={() => deleteVariable(variable.id)} - > - Delete {usageCount > 0 && `(${usageCount} bindings)`} - + {source === "local" && ( + 0} + onSelect={() => deleteVariable(variable.id)} + > + Delete {usageCount > 0 && `(${usageCount} bindings)`} + + )} @@ -252,7 +265,8 @@ const VariablesItem = ({ }; const VariablesList = () => { - const availableVariables = useStore($instanceVariables); + const instance = useStore($selectedInstance); + const availableVariables = useStore($availableVariables); const variableValues = useStore($instanceVariableValues); const usedVariables = useStore($usedVariables); @@ -262,18 +276,18 @@ const VariablesList = () => { return ( - {availableVariables.map((variable, index) => { - const value = variableValues.get(variable.id); - return ( - - ); - })} + {availableVariables.map((variable, index) => ( + + ))} ); }; diff --git a/apps/builder/app/builder/shared/binding-popover.tsx b/apps/builder/app/builder/shared/binding-popover.tsx index 50396662886f..c68b5868f0f8 100644 --- a/apps/builder/app/builder/shared/binding-popover.tsx +++ b/apps/builder/app/builder/shared/binding-popover.tsx @@ -8,6 +8,7 @@ import { createContext, type ReactNode, } from "react"; +import { useStore } from "@nanostores/react"; import { DotIcon, InfoCircleIcon, @@ -47,7 +48,6 @@ import { $isDesignMode, computeExpression, } from "~/shared/nano-states"; -import { useStore } from "@nanostores/react"; export const evaluateExpressionWithinScope = ( expression: string, diff --git a/apps/builder/app/shared/instance-utils.ts b/apps/builder/app/shared/instance-utils.ts index c99d1bb514e7..dc2c153de4f0 100644 --- a/apps/builder/app/shared/instance-utils.ts +++ b/apps/builder/app/shared/instance-utils.ts @@ -1087,14 +1087,16 @@ export const insertWebstudioFragmentCopy = ({ dataSources.set(newDataSourceId, { ...dataSource, id: newDataSourceId, - scopeInstanceId: newInstanceIds.get(scopeInstanceId), + scopeInstanceId: + newInstanceIds.get(scopeInstanceId) ?? scopeInstanceId, resourceId: newResourceId, }); } else { dataSources.set(newDataSourceId, { ...dataSource, id: newDataSourceId, - scopeInstanceId: newInstanceIds.get(scopeInstanceId), + scopeInstanceId: + newInstanceIds.get(scopeInstanceId) ?? scopeInstanceId, }); } } diff --git a/apps/builder/app/shared/nano-states/props.test.ts b/apps/builder/app/shared/nano-states/props.test.ts index 1ea43050287c..debc382a3d2f 100644 --- a/apps/builder/app/shared/nano-states/props.test.ts +++ b/apps/builder/app/shared/nano-states/props.test.ts @@ -93,12 +93,14 @@ test("compute expression prop values", () => { toMap([ { id: "var1", + scopeInstanceId: "box", type: "variable", name: "", value: { type: "number", value: 1 }, }, { id: "var2", + scopeInstanceId: "box", type: "variable", name: "", value: { type: "string", value: "Hello" }, @@ -162,6 +164,7 @@ test("generate action prop callbacks", () => { toMap([ { id: "var", + scopeInstanceId: "box", type: "variable", name: "", value: { type: "number", value: 1 }, @@ -292,6 +295,7 @@ test("compute expression from collection items", () => { toMap([ { id: "itemId", + scopeInstanceId: "list", type: "parameter", name: "item", }, @@ -362,6 +366,7 @@ test("access parameter value from variables values", () => { toMap([ { id: "parameterId", + scopeInstanceId: "body", type: "parameter", name: "paramName", }, @@ -400,6 +405,7 @@ test("compute props bound to resource variables", () => { toMap([ { id: "resourceVariableId", + scopeInstanceId: "body", type: "resource", name: "paramName", resourceId: "resourceId", diff --git a/packages/design-system/src/components/css-value-list-item.tsx b/packages/design-system/src/components/css-value-list-item.tsx index 54728f82ca1c..e77cb787b311 100644 --- a/packages/design-system/src/components/css-value-list-item.tsx +++ b/packages/design-system/src/components/css-value-list-item.tsx @@ -180,7 +180,7 @@ export const CssValueListItem = forwardRef( {...listItemAttributes} {...rest} hidden={hidden} - disabled={hidden === true} + disabled={hidden === true || rest.disabled} > diff --git a/packages/sdk/src/schema/data-sources.ts b/packages/sdk/src/schema/data-sources.ts index 8ad76ba47023..003a8a82ceaf 100644 --- a/packages/sdk/src/schema/data-sources.ts +++ b/packages/sdk/src/schema/data-sources.ts @@ -30,20 +30,20 @@ export const DataSource = z.union([ z.object({ type: z.literal("variable"), id: DataSourceId, - scopeInstanceId: z.optional(z.string()), + scopeInstanceId: z.string(), name: z.string(), value: DataSourceVariableValue, }), z.object({ type: z.literal("parameter"), id: DataSourceId, - scopeInstanceId: z.optional(z.string()), + scopeInstanceId: z.string(), name: z.string(), }), z.object({ type: z.literal("resource"), id: DataSourceId, - scopeInstanceId: z.optional(z.string()), + scopeInstanceId: z.string(), name: z.string(), resourceId: z.string(), }),