Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useState } from "react";
import { computed } from "nanostores";
import { useStore } from "@nanostores/react";
import { matchSorter } from "match-sorter";
import { ariaAttributes } from "@webstudio-is/html-data";
import {
type Instance,
type Props,
Expand All @@ -17,7 +16,11 @@ import {
Box,
Grid,
} from "@webstudio-is/design-system";
import { isAttributeNameSafe } from "@webstudio-is/react-sdk";
import {
isAttributeNameSafe,
reactPropsToStandardAttributes,
standardAttributesToReactProps,
} from "@webstudio-is/react-sdk";
import {
$propValuesByInstanceSelector,
$propsIndex,
Expand All @@ -34,11 +37,9 @@ import { $selectedInstance, $selectedInstanceKey } from "~/shared/awareness";
import { renderControl } from "../controls/combined";
import { usePropsLogic, type PropAndMeta } from "./use-props-logic";
import { AnimationSection } from "./animation/animation-section";
import {
$instanceTags,
$matchingBreakpoints,
} from "../../style-panel/shared/model";
import { $matchingBreakpoints } from "../../style-panel/shared/model";
import { matchMediaBreakpoints } from "./match-media-breakpoints";
import { $selectedInstancePropsMetas } from "../shared";

type Item = {
name: string;
Expand Down Expand Up @@ -80,7 +81,11 @@ const renderProperty = (
instanceId,
meta,
prop,
computedValue: propValues.get(propName) ?? meta.defaultValue,
computedValue:
propValues.get(propName) ??
// support legacy html props with react names
propValues.get(standardAttributesToReactProps[propName]) ??
meta.defaultValue,
propName,
onChange: (propValue) => {
logic.handleChange({ prop, propName }, propValue);
Expand All @@ -99,33 +104,31 @@ const renderProperty = (
const forbiddenProperties = new Set(["style", "class", "className"]);

const $availableProps = computed(
[$selectedInstance, $props, $registeredComponentPropsMetas, $instanceTags],
(instance, props, propsMetas, instanceTags) => {
[
$selectedInstance,
$props,
$registeredComponentPropsMetas,
$selectedInstancePropsMetas,
],
(instance, props, componentPropsMetas, instancePropsMetas) => {
const availableProps = new Map<Item["name"], Item>();
if (instance === undefined) {
return [];
}
// add component props
const metas = propsMetas.get(instance.component);
for (const [name, propMeta] of Object.entries(metas?.props ?? {})) {
const { label, description } = propMeta;
for (const [name, { label, description }] of instancePropsMetas) {
availableProps.set(name, { name, label, description });
}
// add aria attributes only for components with tags
const tag = instanceTags.get(instance.id);
if (tag) {
for (const { name, description } of ariaAttributes) {
availableProps.set(name, { name, description });
}
if (instance === undefined) {
return [];
}
const propsMetas = componentPropsMetas.get(instance.component);
// remove initial props
for (const name of metas?.initialProps ?? []) {
for (const name of propsMetas?.initialProps ?? []) {
availableProps.delete(name);
availableProps.delete(reactPropsToStandardAttributes[name]);
}
// remove defined props
for (const prop of props.values()) {
if (prop.instanceId === instance.id) {
availableProps.delete(prop.name);
availableProps.delete(reactPropsToStandardAttributes[prop.name]);
}
}
return Array.from(availableProps.values());
Expand Down Expand Up @@ -336,10 +339,9 @@ export const PropsSectionContainer = ({
},
});

const hasMetaProps = Object.keys(logic.meta.props).length !== 0;

if (hasMetaProps === false) {
return null;
const propsMetas = useStore($selectedInstancePropsMetas);
if (propsMetas.size === 0) {
return;
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { computed } from "nanostores";
import { useStore } from "@nanostores/react";
import type { PropMeta, Instance, Prop } from "@webstudio-is/sdk";
import { descendantComponent } from "@webstudio-is/sdk";
import { showAttribute, textContentAttribute } from "@webstudio-is/react-sdk";
import {
reactPropsToStandardAttributes,
showAttribute,
standardAttributesToReactProps,
textContentAttribute,
} from "@webstudio-is/react-sdk";
import {
$instances,
$isContentMode,
Expand All @@ -13,8 +18,11 @@ import {
} from "~/shared/nano-states";
import { isRichText } from "~/shared/content-model";
import { $selectedInstancePath } from "~/shared/awareness";
import { showAttributeMeta, type PropValue } from "../shared";
import { ariaAttributes } from "@webstudio-is/html-data";
import {
$selectedInstancePropsMetas,
showAttributeMeta,
type PropValue,
} from "../shared";

type PropOrName = { prop?: Prop; propName: string };

Expand Down Expand Up @@ -154,42 +162,6 @@ const $canHaveTextContent = computed(
});
}
);
type Attribute = (typeof ariaAttributes)[number];

const attributeToMeta = (attribute: Attribute): PropMeta => {
if (attribute.type === "string") {
return {
type: "string",
control: "text",
required: false,
};
}
if (attribute.type === "select") {
const options = attribute.options ?? [];
return {
type: "string",
control: options.length > 3 ? "select" : "radio",
required: false,
options,
};
}
if (attribute.type === "number") {
return {
type: "number",
control: "number",
required: false,
};
}
if (attribute.type === "boolean") {
return {
type: "boolean",
control: "boolean",
required: false,
};
}
attribute.type satisfies never;
throw Error("impossible case");
};

/** usePropsLogic expects that key={instanceId} is used on the ancestor component */
export const usePropsLogic = ({
Expand Down Expand Up @@ -219,27 +191,17 @@ export const usePropsLogic = ({
return propsWhiteList.includes(propName);
};

const meta = useStore($registeredComponentPropsMetas).get(
instance.component
) ?? {
props: {},
initialProps: [],
};

const savedProps = props;

// we will delete items from these maps as we categorize the props
const unprocessedSaved = new Map(savedProps.map((prop) => [prop.name, prop]));

const metas = new Map<Prop["name"], PropMeta>();
for (const attribute of ariaAttributes) {
metas.set(attribute.name, attributeToMeta(attribute));
}
for (const [name, propMeta] of Object.entries(meta.props)) {
metas.set(name, propMeta);
}
const propsMetas = useStore($selectedInstancePropsMetas);

const initialPropsNames = new Set(meta.initialProps ?? []);
const componentPropsMeta = useStore($registeredComponentPropsMetas).get(
instance.component
);
const initialPropsNames = new Set(componentPropsMeta?.initialProps);

const systemProps: PropAndMeta[] = [];
// descendant component is not actually rendered
Expand Down Expand Up @@ -278,9 +240,13 @@ export const usePropsLogic = ({
}

const initialProps: PropAndMeta[] = [];
for (const name of initialPropsNames) {
const saved = getAndDelete<Prop>(unprocessedSaved, name);
const propMeta = metas.get(name);
for (let name of initialPropsNames) {
let propMeta = propsMetas.get(name);
// className -> class
if (propsMetas.has(reactPropsToStandardAttributes[name])) {
name = reactPropsToStandardAttributes[name];
propMeta = propsMetas.get(name);
}

if (propMeta === undefined) {
console.error(
Expand All @@ -289,7 +255,16 @@ export const usePropsLogic = ({
continue;
}

let prop = saved;
let prop =
getAndDelete<Prop>(unprocessedSaved, name) ??
// support legacy html props stored with react names
getAndDelete<Prop>(
unprocessedSaved,
standardAttributesToReactProps[name]
);
if (prop) {
prop = { ...prop, name };
}

// For initial props, if prop is not saved, we want to show default value if available.
//
Expand All @@ -312,13 +287,20 @@ export const usePropsLogic = ({
}

const addedProps: PropAndMeta[] = [];
for (const prop of Array.from(unprocessedSaved.values()).reverse()) {
for (let prop of Array.from(unprocessedSaved.values()).reverse()) {
// ignore parameter props
if (prop.type === "parameter") {
continue;
}

const propMeta = metas.get(prop.name) ?? getDefaultMetaForType("string");
let name = prop.name;
let propMeta = propsMetas.get(name);
// support legacy html props stored with react names
if (propsMetas.has(reactPropsToStandardAttributes[name])) {
name = reactPropsToStandardAttributes[name];
propMeta = propsMetas.get(name);
}
prop = { ...prop, name };
propMeta ??= getDefaultMetaForType("string");

addedProps.push({
prop,
Expand All @@ -329,7 +311,8 @@ export const usePropsLogic = ({

const handleAdd = (propName: string) => {
// In case of custom property/attribute we get a string.
const propMeta = metas.get(propName) ?? getDefaultMetaForType("string");
const propMeta =
propsMetas.get(propName) ?? getDefaultMetaForType("string");
const prop = getStartingProp(instance.id, propMeta, propName);
if (prop) {
updateProp(prop);
Expand Down Expand Up @@ -358,7 +341,6 @@ export const usePropsLogic = ({
handleAdd,
handleChange,
handleChangeByPropName,
meta,
/** Similar to Initial, but displayed as a separate group in UI etc.
* Currentrly used only for the ID prop. */
systemProps: systemProps.filter(({ propName }) => isPropVisible(propName)),
Expand Down
Loading