Skip to content

Commit 646c5b3

Browse files
authored
fix: normalize html attributes only when not defined by component (#5215)
Proper fix for #5211 Now props meta are available in component generator and can be used to figure if prop is defined by component or comes from html attributes. Added same logic in canvas and component generator. Also got rid from no longer necessary registeredComponentPropsMetas store since all props are described by component meta.
1 parent 0eea205 commit 646c5b3

File tree

10 files changed

+81
-112
lines changed

10 files changed

+81
-112
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,10 @@ const checkProp = (options = defaultOptions, label?: string): PropMeta => ({
199199

200200
registerComponentLibrary({
201201
components: {},
202-
metas: {},
203202
templates: {},
204-
propsMetas: {
203+
metas: {
205204
Box: {
205+
icon: "",
206206
props: {
207207
initialText: textProp("", "multi\nline"),
208208
initialShortText: shortTextProp(),

apps/builder/app/builder/features/settings-panel/shared.tsx

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {
3939
import {
4040
$dataSourceVariables,
4141
$dataSources,
42-
$registeredComponentPropsMetas,
42+
$registeredComponentMetas,
4343
$variableValuesByInstanceSelector,
4444
} from "~/shared/nano-states";
4545
import type { BindingVariant } from "~/builder/shared/binding-popover";
@@ -455,62 +455,60 @@ const attributeToMeta = (attribute: Attribute): PropMeta => {
455455
};
456456

457457
export const $selectedInstancePropsMetas = computed(
458-
[$selectedInstance, $registeredComponentPropsMetas, $instanceTags],
459-
(instance, componentPropsMetas, instanceTags): Map<string, PropMeta> => {
458+
[$selectedInstance, $registeredComponentMetas, $instanceTags],
459+
(instance, metas, instanceTags): Map<string, PropMeta> => {
460460
if (instance === undefined) {
461461
return new Map();
462462
}
463-
const propsMetas = componentPropsMetas.get(instance.component)?.props ?? {};
463+
const meta = metas.get(instance.component);
464464
const tag = instanceTags.get(instance.id);
465-
const metas = new Map<Prop["name"], PropMeta>();
465+
const propsMetas = new Map<Prop["name"], PropMeta>();
466466
// add html attributes only when instance has tag
467467
if (tag) {
468468
for (const attribute of [...ariaAttributes].reverse()) {
469-
metas.set(attribute.name, attributeToMeta(attribute));
469+
propsMetas.set(attribute.name, attributeToMeta(attribute));
470470
}
471471
if (attributesByTag["*"]) {
472472
for (const attribute of [...attributesByTag["*"]].reverse()) {
473-
metas.set(attribute.name, attributeToMeta(attribute));
473+
propsMetas.set(attribute.name, attributeToMeta(attribute));
474474
}
475475
}
476476
if (attributesByTag[tag]) {
477477
for (const attribute of [...attributesByTag[tag]].reverse()) {
478-
metas.set(attribute.name, attributeToMeta(attribute));
478+
propsMetas.set(attribute.name, attributeToMeta(attribute));
479479
}
480480
}
481481
}
482-
for (const [name, propMeta] of Object.entries(propsMetas).reverse()) {
482+
for (const [name, propMeta] of Object.entries(
483+
meta?.props ?? {}
484+
).reverse()) {
483485
// when component property has the same name as html attribute in react
484486
// override to deduplicate similar properties
485487
// for example component can have own "className" and html has "class"
486488
const htmlName = reactPropsToStandardAttributes[name];
487489
if (htmlName) {
488-
metas.delete(htmlName);
490+
propsMetas.delete(htmlName);
489491
}
490-
metas.set(name, propMeta);
492+
propsMetas.set(name, propMeta);
491493
}
492-
metas.set(showAttribute, showAttributeMeta);
494+
propsMetas.set(showAttribute, showAttributeMeta);
493495
// ui should render in the following order
494496
// 1. system properties
495497
// 2. component properties
496498
// 3. specific tag attributes
497499
// 4. global html attributes
498500
// 5. aria attributes
499-
return new Map(Array.from(metas.entries()).reverse());
501+
return new Map(Array.from(propsMetas.entries()).reverse());
500502
}
501503
);
502504

503505
export const $selectedInstanceInitialPropNames = computed(
504-
[
505-
$selectedInstance,
506-
$registeredComponentPropsMetas,
507-
$selectedInstancePropsMetas,
508-
],
509-
(selectedInstance, componentPropsMetas, instancePropsMetas) => {
506+
[$selectedInstance, $registeredComponentMetas, $selectedInstancePropsMetas],
507+
(selectedInstance, metas, instancePropsMetas) => {
510508
const initialPropNames = new Set<string>();
511509
if (selectedInstance) {
512510
const initialProps =
513-
componentPropsMetas.get(selectedInstance.component)?.initialProps ?? [];
511+
metas.get(selectedInstance.component)?.initialProps ?? [];
514512
for (const propName of initialProps) {
515513
// className -> class
516514
if (instancePropsMetas.has(reactPropsToStandardAttributes[propName])) {

apps/builder/app/canvas/canvas.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useMemo, useEffect, useState, useLayoutEffect, useRef } from "react";
22
import { ErrorBoundary, type FallbackProps } from "react-error-boundary";
33
import { useStore } from "@nanostores/react";
4-
import { type Instances, coreMetas, corePropsMetas } from "@webstudio-is/sdk";
4+
import { type Instances, coreMetas } from "@webstudio-is/sdk";
55
import { coreTemplates } from "@webstudio-is/sdk/core-templates";
66
import type { Components } from "@webstudio-is/react-sdk";
77
import { wsImageLoader } from "@webstudio-is/image";
@@ -240,13 +240,11 @@ export const Canvas = () => {
240240
registerComponentLibrary({
241241
components: {},
242242
metas: coreMetas,
243-
propsMetas: corePropsMetas,
244243
templates: coreTemplates,
245244
});
246245
registerComponentLibrary({
247246
components: baseComponents,
248247
metas: baseComponentMetas,
249-
propsMetas: {},
250248
hooks: baseComponentHooks,
251249
templates: baseComponentTemplates,
252250
});
@@ -257,22 +255,19 @@ export const Canvas = () => {
257255
Body,
258256
},
259257
metas: {},
260-
propsMetas: {},
261258
templates: {},
262259
});
263260
registerComponentLibrary({
264261
namespace: "@webstudio-is/sdk-components-react-radix",
265262
components: radixComponents,
266263
metas: radixComponentMetas,
267-
propsMetas: {},
268264
hooks: radixComponentHooks,
269265
templates: radixTemplates,
270266
});
271267
registerComponentLibrary({
272268
namespace: "@webstudio-is/sdk-components-animation",
273269
components: animationComponents,
274270
metas: animationComponentMetas,
275-
propsMetas: {},
276271
hooks: animationComponentHooks,
277272
templates: animationTemplates,
278273
});

apps/builder/app/canvas/features/webstudio-component/webstudio-component.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -301,22 +301,22 @@ const useInstanceProps = (instanceSelector: InstanceSelector) => {
301301
if (tag !== undefined) {
302302
instancePropsObject[tagProperty] = tag;
303303
}
304-
const hasTags =
305-
Object.keys(metas.get(instance?.component ?? "")?.presetStyle ?? {})
306-
.length > 0;
304+
const meta = metas.get(instance?.component ?? "");
305+
const hasTags = Object.keys(meta?.presetStyle ?? {}).length > 0;
307306
const index = indexesWithinAncestors.get(instanceId);
308307
if (index !== undefined) {
309308
instancePropsObject[indexProperty] = index.toString();
310309
}
311310
const instanceProps = propValuesByInstanceSelector.get(instanceKey);
312311
if (instanceProps) {
313312
for (const [name, value] of instanceProps) {
314-
if (hasTags) {
315-
const reactName = standardAttributesToReactProps[name] ?? name;
316-
instancePropsObject[reactName] = value;
317-
} else {
318-
instancePropsObject[name] = value;
313+
let propName = name;
314+
// convert html attribute only when component has tags
315+
// and does not specify own property with this name
316+
if (hasTags && !meta?.props?.[propName]) {
317+
propName = standardAttributesToReactProps[propName] ?? propName;
319318
}
319+
instancePropsObject[propName] = value;
320320
}
321321
}
322322
return instancePropsObject;

apps/builder/app/shared/nano-states/components.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
getIndexesWithinAncestors,
1313
type Instance,
1414
type WsComponentMeta,
15-
type WsComponentPropsMeta,
1615
} from "@webstudio-is/sdk";
1716
import type { InstanceSelector } from "../tree-utils";
1817
import { $memoryProps, $props } from "./misc";
@@ -176,15 +175,10 @@ export const $registeredTemplates = atom(
176175
new Map<string, GeneratedTemplateMeta>()
177176
);
178177

179-
export const $registeredComponentPropsMetas = atom(
180-
new Map<string, WsComponentPropsMeta>()
181-
);
182-
183178
export const registerComponentLibrary = ({
184179
namespace,
185180
components,
186181
metas,
187-
propsMetas,
188182
hooks,
189183
templates,
190184
}: {
@@ -193,7 +187,6 @@ export const registerComponentLibrary = ({
193187
// eslint-disable-next-line @typescript-eslint/no-explicit-any
194188
components: Record<Instance["component"], ExoticComponent<any>>;
195189
metas: Record<Instance["component"], WsComponentMeta>;
196-
propsMetas: Record<Instance["component"], WsComponentPropsMeta>;
197190
hooks?: Hook[];
198191
templates: Record<Instance["component"], TemplateMeta>;
199192
}) => {
@@ -206,22 +199,10 @@ export const registerComponentLibrary = ({
206199
}
207200
$registeredComponents.set(nextComponents);
208201

209-
const prevPropsMetas = $registeredComponentPropsMetas.get();
210-
const nextPropsMetas = new Map(prevPropsMetas);
211-
for (const [componentName, propsMeta] of Object.entries(propsMetas)) {
212-
nextPropsMetas.set(`${prefix}${componentName}`, propsMeta);
213-
}
214-
215202
const prevMetas = $registeredComponentMetas.get();
216203
const nextMetas = new Map(prevMetas);
217204
for (const [componentName, meta] of Object.entries(metas)) {
218205
nextMetas.set(`${prefix}${componentName}`, meta);
219-
if (meta.initialProps || meta.props) {
220-
nextPropsMetas.set(`${prefix}${componentName}`, {
221-
initialProps: meta.initialProps,
222-
props: meta.props ?? {},
223-
});
224-
}
225206
}
226207
$registeredComponentMetas.set(nextMetas);
227208

@@ -241,6 +222,4 @@ export const registerComponentLibrary = ({
241222
const nextHooks = [...prevHooks, ...hooks];
242223
$registeredComponentHooks.set(nextHooks);
243224
}
244-
245-
$registeredComponentPropsMetas.set(nextPropsMetas);
246225
};

apps/builder/app/shared/sync/sync-stores.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import {
4343
$blockChildOutline,
4444
$textToolbar,
4545
$registeredComponentMetas,
46-
$registeredComponentPropsMetas,
4746
$registeredTemplates,
4847
$modifierKeys,
4948
} from "~/shared/nano-states";
@@ -147,10 +146,6 @@ export const createObjectPool = () => {
147146
"registeredComponentMetas",
148147
$registeredComponentMetas
149148
),
150-
new NanostoresSyncObject(
151-
"registeredComponentPropsMetas",
152-
$registeredComponentPropsMetas
153-
),
154149
new NanostoresSyncObject("registeredTemplates", $registeredTemplates),
155150
new NanostoresSyncObject("canvasScrollbarWidth", $canvasScrollbarSize),
156151
new NanostoresSyncObject("systemDataByPage", $systemDataByPage),

packages/react-sdk/src/component-generator.test.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { expect, test } from "vitest";
33
import stripIndent from "strip-indent";
44
import {
55
createScope,
6+
elementComponent,
67
ROOT_INSTANCE_ID,
78
SYSTEM_VARIABLE_ID,
89
WsComponentMeta,
@@ -1386,7 +1387,9 @@ test("convert attributes to react compatible when render ws:element", () => {
13861387
name: "Page",
13871388
rootInstanceId: "bodyId",
13881389
parameters: [],
1389-
metas: new Map(),
1390+
metas: new Map([
1391+
[elementComponent, { icon: "", presetStyle: { div: [] } }],
1392+
]),
13901393
...renderData(
13911394
<$.Body ws:id="bodyId">
13921395
<ws.element
@@ -1444,6 +1447,46 @@ test("convert attributes to react compatible when render components with tags",
14441447
);
14451448
});
14461449

1450+
test("ignore props similar to standard attributes when react components defines them", () => {
1451+
expect(
1452+
generateWebstudioComponent({
1453+
classesMap: new Map(),
1454+
scope: createScope(),
1455+
name: "Page",
1456+
rootInstanceId: "bodyId",
1457+
parameters: [],
1458+
metas: new Map([
1459+
[
1460+
"Vimeo",
1461+
{
1462+
icon: "",
1463+
presetStyle: { div: [] },
1464+
props: {
1465+
autoplay: { type: "boolean", control: "boolean", required: true },
1466+
},
1467+
},
1468+
],
1469+
]),
1470+
...renderData(
1471+
<$.Body ws:id="bodyId">
1472+
<$.Vimeo autoplay={true}></$.Vimeo>
1473+
</$.Body>
1474+
),
1475+
})
1476+
).toEqual(
1477+
validateJSX(
1478+
clear(`
1479+
const Page = () => {
1480+
return <Body>
1481+
<Vimeo
1482+
autoplay={true} />
1483+
</Body>
1484+
}
1485+
`)
1486+
)
1487+
);
1488+
});
1489+
14471490
test("ignore props similar to standard attributes on react components without tags", () => {
14481491
expect(
14491492
generateWebstudioComponent({

packages/react-sdk/src/component-generator.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ export const generateJsxElement = ({
170170
return "";
171171
}
172172

173-
const hasTags =
174-
Object.keys(metas.get(instance.component)?.presetStyle ?? {}).length > 0;
173+
const meta = metas.get(instance.component);
174+
const hasTags = Object.keys(meta?.presetStyle ?? {}).length > 0;
175175

176176
let generatedProps = "";
177177

@@ -204,7 +204,9 @@ export const generateJsxElement = ({
204204
continue;
205205
}
206206
let name = prop.name;
207-
if (instance.component === elementComponent || hasTags) {
207+
// convert html attribute only when component has tags
208+
// and does not specify own property with this name
209+
if (hasTags && !meta?.props?.[prop.name]) {
208210
name = standardAttributesToReactProps[prop.name] ?? prop.name;
209211
}
210212

0 commit comments

Comments
 (0)