@wordpress/block-editor
Version:
398 lines (397 loc) • 12.9 kB
JavaScript
// packages/block-editor/src/components/content-only-controls/index.js
import {
store as blocksStore,
privateApis as blocksPrivateApis
} from "@wordpress/blocks";
import {
__experimentalHStack as HStack,
Icon,
Navigator
} from "@wordpress/components";
import { useDispatch, useSelect } from "@wordpress/data";
import { __ } from "@wordpress/i18n";
import { arrowLeft, arrowRight } from "@wordpress/icons";
import { DataForm } from "@wordpress/dataviews";
import { useState, useMemo } from "@wordpress/element";
import { unlock } from "../../lock-unlock";
import { store as blockEditorStore } from "../../store";
import BlockIcon from "../block-icon";
import useBlockDisplayTitle from "../block-title/use-block-display-title";
import useBlockDisplayInformation from "../use-block-display-information";
import FieldsDropdownMenu from "./fields-dropdown-menu";
import RichText from "./rich-text";
import Media from "./media";
import Link from "./link";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var { fieldsKey, formKey } = unlock(blocksPrivateApis);
var CONTROLS = {
richtext: RichText,
media: Media,
link: Link
};
function createConfiguredControl(config) {
const { control, ...controlConfig } = config;
const ControlComponent = CONTROLS[control];
if (!ControlComponent) {
throw new Error(`Control type "${control}" not found`);
}
return function ConfiguredControl(props) {
return /* @__PURE__ */ jsx(ControlComponent, { ...props, config: controlConfig });
};
}
function normalizeMediaValue(value, fieldDef) {
const defaults = {
id: null,
url: "",
caption: "",
alt: "",
type: "image",
poster: "",
featuredImage: false,
link: ""
};
const result = {};
if (fieldDef?.mapping) {
Object.keys(fieldDef.mapping).forEach((key) => {
result[key] = value?.[key] ?? defaults[key] ?? "";
});
return result;
}
Object.keys(defaults).forEach((key) => {
result[key] = value?.[key] ?? defaults[key];
});
return result;
}
function denormalizeMediaValue(value, fieldDef) {
if (!fieldDef.mapping) {
return value;
}
const result = {};
Object.entries(fieldDef.mapping).forEach(([key]) => {
if (key in value) {
result[key] = value[key];
}
});
return result;
}
function normalizeLinkValue(value, fieldDef) {
const defaults = {
url: "",
rel: "",
linkTarget: "",
destination: ""
};
const result = {};
if (fieldDef?.mapping) {
Object.keys(fieldDef.mapping).forEach((key) => {
result[key] = value?.[key] ?? defaults[key] ?? "";
});
return result;
}
Object.keys(defaults).forEach((key) => {
result[key] = value?.[key] ?? defaults[key];
});
return result;
}
function denormalizeLinkValue(value, fieldDef) {
if (!fieldDef.mapping) {
return value;
}
const result = {};
Object.entries(fieldDef.mapping).forEach(([key]) => {
if (key in value) {
result[key] = value[key];
}
});
return result;
}
function BlockFields({ clientId }) {
const { attributes, blockType } = useSelect(
(select) => {
const { getBlockAttributes, getBlockName } = select(blockEditorStore);
const { getBlockType } = select(blocksStore);
const blockName = getBlockName(clientId);
return {
attributes: getBlockAttributes(clientId),
blockType: getBlockType(blockName)
};
},
[clientId]
);
const { updateBlockAttributes } = useDispatch(blockEditorStore);
const blockTitle = useBlockDisplayTitle({
clientId,
context: "list-view"
});
const blockInformation = useBlockDisplayInformation(clientId);
const blockTypeFields = blockType?.[fieldsKey];
const [form, setForm] = useState(() => {
return blockType?.[formKey];
});
const dataFormFields = useMemo(() => {
if (!blockTypeFields?.length) {
return [];
}
return blockTypeFields.map((fieldDef) => {
const ControlComponent = CONTROLS[fieldDef.type];
const defaultValues = {};
if (fieldDef.mapping && blockType?.attributes) {
Object.entries(fieldDef.mapping).forEach(
([key, attrKey]) => {
defaultValues[key] = blockType.attributes[attrKey]?.defaultValue ?? void 0;
}
);
}
const field = {
id: fieldDef.id,
label: fieldDef.label,
type: fieldDef.type,
// Use the field's type; DataForm will use built-in or custom Edit
config: { ...fieldDef.args, defaultValues },
hideLabelFromVision: fieldDef.id === "content",
// getValue and setValue handle the mapping to block attributes
getValue: ({ item }) => {
if (fieldDef.mapping) {
const mappedValue = {};
Object.entries(fieldDef.mapping).forEach(
([key, attrKey]) => {
mappedValue[key] = item[attrKey];
}
);
if (fieldDef.type === "media") {
return normalizeMediaValue(mappedValue, fieldDef);
}
if (fieldDef.type === "link") {
return normalizeLinkValue(mappedValue, fieldDef);
}
return mappedValue;
}
return item[fieldDef.id];
},
setValue: ({ item, value }) => {
if (fieldDef.mapping) {
let denormalizedValue = value;
if (fieldDef.type === "media") {
denormalizedValue = denormalizeMediaValue(
value,
fieldDef
);
} else if (fieldDef.type === "link") {
denormalizedValue = denormalizeLinkValue(
value,
fieldDef
);
}
const updates = {};
Object.entries(fieldDef.mapping).forEach(
([key, attrKey]) => {
if (key in denormalizedValue) {
updates[attrKey] = denormalizedValue[key];
} else {
updates[attrKey] = item[attrKey];
}
}
);
return updates;
}
return { [fieldDef.id]: value };
}
};
if (ControlComponent) {
field.Edit = createConfiguredControl({
control: fieldDef.type,
clientId,
fieldDef
});
}
return field;
});
}, [blockTypeFields, blockType?.attributes, clientId]);
const handleToggleField = (fieldId) => {
setForm((prev) => {
if (prev.fields?.includes(fieldId)) {
return {
...prev,
fields: prev.fields.filter((id) => id !== fieldId)
};
}
return {
...prev,
fields: [...prev.fields || [], fieldId]
};
});
};
if (!blockTypeFields?.length) {
return null;
}
return /* @__PURE__ */ jsxs("div", { className: "block-editor-content-only-controls__fields-container", children: [
/* @__PURE__ */ jsx("div", { className: "block-editor-content-only-controls__fields-header", children: /* @__PURE__ */ jsxs(HStack, { spacing: 1, justify: "space-between", expanded: true, children: [
/* @__PURE__ */ jsxs(HStack, { spacing: 1, justify: "flex-start", children: [
/* @__PURE__ */ jsx(BlockIcon, { icon: blockInformation?.icon }),
/* @__PURE__ */ jsx("div", { children: blockTitle })
] }),
/* @__PURE__ */ jsx(
FieldsDropdownMenu,
{
fields: dataFormFields,
visibleFields: form.fields,
onToggleField: handleToggleField
}
)
] }) }),
/* @__PURE__ */ jsx(
DataForm,
{
data: attributes,
fields: dataFormFields,
form,
onChange: (changes) => {
updateBlockAttributes(clientId, changes);
}
}
)
] });
}
function DrillDownButton({ clientId }) {
const blockTitle = useBlockDisplayTitle({
clientId,
context: "list-view"
});
const blockInformation = useBlockDisplayInformation(clientId);
return /* @__PURE__ */ jsx("div", { className: "block-editor-content-only-controls__button-panel", children: /* @__PURE__ */ jsx(
Navigator.Button,
{
path: `/${clientId}`,
className: "block-editor-content-only-controls__drill-down-button",
children: /* @__PURE__ */ jsxs(HStack, { expanded: true, justify: "space-between", children: [
/* @__PURE__ */ jsxs(HStack, { justify: "flex-start", spacing: 1, children: [
/* @__PURE__ */ jsx(BlockIcon, { icon: blockInformation?.icon }),
/* @__PURE__ */ jsx("div", { children: blockTitle })
] }),
/* @__PURE__ */ jsx(Icon, { icon: arrowRight })
] })
}
) });
}
function ContentOnlyControlsScreen({
rootClientId,
contentClientIds,
parentClientIds,
isNested
}) {
const isRootContentBlock = useSelect(
(select) => {
const { getBlockName } = select(blockEditorStore);
const blockName = getBlockName(rootClientId);
const { hasContentRoleAttribute } = unlock(select(blocksStore));
return hasContentRoleAttribute(blockName);
},
[rootClientId]
);
if (!isRootContentBlock && !contentClientIds.length) {
return null;
}
return /* @__PURE__ */ jsxs(Fragment, { children: [
isNested && /* @__PURE__ */ jsx("div", { className: "block-editor-content-only-controls__button-panel", children: /* @__PURE__ */ jsx(Navigator.BackButton, { className: "block-editor-content-only-controls__back-button", children: /* @__PURE__ */ jsxs(HStack, { expanded: true, spacing: 1, justify: "flex-start", children: [
/* @__PURE__ */ jsx(Icon, { icon: arrowLeft }),
/* @__PURE__ */ jsx("div", { children: __("Back") })
] }) }) }),
isRootContentBlock && /* @__PURE__ */ jsx(BlockFields, { clientId: rootClientId }),
contentClientIds.map((clientId) => {
if (parentClientIds?.[clientId]) {
return /* @__PURE__ */ jsx(
DrillDownButton,
{
clientId
},
clientId
);
}
return /* @__PURE__ */ jsx(BlockFields, { clientId }, clientId);
})
] });
}
function ContentOnlyControls({ rootClientId }) {
const { updatedRootClientId, nestedContentClientIds, contentClientIds } = useSelect(
(select) => {
const { getClientIdsOfDescendants, getBlockEditingMode } = select(blockEditorStore);
const _nestedContentClientIds = {};
const _contentClientIds = [];
let allNestedClientIds = [];
const allContentClientIds = getClientIdsOfDescendants(
rootClientId
).filter(
(clientId) => getBlockEditingMode(clientId) === "contentOnly"
);
for (const clientId of allContentClientIds) {
const childClientIds = getClientIdsOfDescendants(
clientId
).filter(
(childClientId) => getBlockEditingMode(childClientId) === "contentOnly"
);
if (childClientIds.length > 1 && !allNestedClientIds.includes(clientId)) {
_nestedContentClientIds[clientId] = childClientIds;
allNestedClientIds = [
allNestedClientIds,
...childClientIds
];
}
if (!allNestedClientIds.includes(clientId)) {
_contentClientIds.push(clientId);
}
}
if (_contentClientIds.length === 1 && Object.keys(_nestedContentClientIds).length === 1) {
const onlyParentClientId = Object.keys(
_nestedContentClientIds
)[0];
return {
updatedRootClientId: onlyParentClientId,
contentClientIds: _nestedContentClientIds[onlyParentClientId],
nestedContentClientIds: {}
};
}
return {
nestedContentClientIds: _nestedContentClientIds,
contentClientIds: _contentClientIds
};
},
[rootClientId]
);
return /* @__PURE__ */ jsxs(Navigator, { initialPath: "/", children: [
/* @__PURE__ */ jsx(
Navigator.Screen,
{
path: "/",
className: "block-editor-content-only-controls__screen",
children: /* @__PURE__ */ jsx(
ContentOnlyControlsScreen,
{
rootClientId: updatedRootClientId ?? rootClientId,
contentClientIds,
parentClientIds: nestedContentClientIds
}
)
}
),
Object.keys(nestedContentClientIds).map((clientId) => /* @__PURE__ */ jsx(
Navigator.Screen,
{
path: `/${clientId}`,
className: "block-editor-content-only-controls__screen",
children: /* @__PURE__ */ jsx(
ContentOnlyControlsScreen,
{
isNested: true,
rootClientId: clientId,
contentClientIds: nestedContentClientIds[clientId]
}
)
},
clientId
))
] });
}
export {
ContentOnlyControls as default
};
//# sourceMappingURL=index.js.map