@wordpress/block-library
Version:
Block library for the WordPress editor.
293 lines (292 loc) • 10 kB
JavaScript
// packages/block-library/src/navigation-link/shared/controls.js
import {
__experimentalToolsPanel as ToolsPanel,
__experimentalToolsPanelItem as ToolsPanelItem,
__experimentalInputControl as InputControl,
Button,
CheckboxControl,
TextControl,
TextareaControl
} from "@wordpress/components";
import { __, sprintf } from "@wordpress/i18n";
import { useRef, useEffect, useState } from "@wordpress/element";
import { useInstanceId } from "@wordpress/compose";
import { safeDecodeURI } from "@wordpress/url";
import { __unstableStripHTML as stripHTML } from "@wordpress/dom";
import { linkOff as unlinkIcon } from "@wordpress/icons";
import { useDispatch } from "@wordpress/data";
import { store as blockEditorStore } from "@wordpress/block-editor";
import { useToolsPanelDropdownMenuProps } from "../../utils/hooks";
import { updateAttributes } from "./update-attributes";
import { useEntityBinding } from "./use-entity-binding";
import { jsx, jsxs } from "react/jsx-runtime";
function getEntityTypeName(type, kind) {
if (kind === "post-type") {
switch (type) {
case "post":
return __("post");
case "page":
return __("page");
default:
return type || __("post");
}
}
if (kind === "taxonomy") {
switch (type) {
case "category":
return __("category");
case "tag":
return __("tag");
default:
return type || __("term");
}
}
return type || __("item");
}
function Controls({ attributes, setAttributes, clientId }) {
const { label, url, description, rel, opensInNewTab } = attributes;
const lastURLRef = useRef(url);
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
const urlInputRef = useRef();
const shouldFocusURLInputRef = useRef(false);
const inputId = useInstanceId(Controls, "link-input");
const helpTextId = `${inputId}__help`;
const [inputValue, setInputValue] = useState(url);
useEffect(() => {
setInputValue(url);
lastURLRef.current = url;
}, [url]);
const { hasUrlBinding, isBoundEntityAvailable, clearBinding } = useEntityBinding({
clientId,
attributes
});
const { updateBlockAttributes } = useDispatch(blockEditorStore);
const unsyncBoundLink = () => {
clearBinding();
updateBlockAttributes(clientId, {
url: lastURLRef.current,
// set the lastURLRef as the new editable value so we avoid bugs from empty link states
id: void 0
});
};
useEffect(() => {
if (!hasUrlBinding && shouldFocusURLInputRef.current) {
urlInputRef.current?.select();
}
shouldFocusURLInputRef.current = false;
}, [hasUrlBinding]);
return /* @__PURE__ */ jsxs(
ToolsPanel,
{
label: __("Settings"),
resetAll: () => {
setAttributes({
label: "",
url: "",
description: "",
rel: "",
opensInNewTab: false
});
},
dropdownMenuProps,
children: [
/* @__PURE__ */ jsx(
ToolsPanelItem,
{
hasValue: () => !!label,
label: __("Text"),
onDeselect: () => setAttributes({ label: "" }),
isShownByDefault: true,
children: /* @__PURE__ */ jsx(
TextControl,
{
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
label: __("Text"),
value: label ? stripHTML(label) : "",
onChange: (labelValue) => {
setAttributes({ label: labelValue });
},
autoComplete: "off"
}
)
}
),
/* @__PURE__ */ jsx(
ToolsPanelItem,
{
hasValue: () => !!url,
label: __("Link"),
onDeselect: () => setAttributes({ url: "" }),
isShownByDefault: true,
children: /* @__PURE__ */ jsx(
InputControl,
{
ref: urlInputRef,
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
id: inputId,
label: __("Link"),
value: (() => {
if (hasUrlBinding && !isBoundEntityAvailable) {
return "";
}
return inputValue ? safeDecodeURI(inputValue) : "";
})(),
autoComplete: "off",
type: "url",
disabled: hasUrlBinding,
"aria-invalid": hasUrlBinding && !isBoundEntityAvailable ? "true" : void 0,
"aria-describedby": helpTextId,
className: hasUrlBinding && !isBoundEntityAvailable ? "navigation-link-control__input-with-error-suffix" : void 0,
onChange: (newValue) => {
if (isBoundEntityAvailable) {
return;
}
setInputValue(newValue);
},
onFocus: () => {
if (isBoundEntityAvailable) {
return;
}
lastURLRef.current = url;
},
onBlur: () => {
if (isBoundEntityAvailable) {
return;
}
const finalValue = !inputValue ? lastURLRef.current : inputValue;
setInputValue(finalValue);
updateAttributes({ url: finalValue }, setAttributes, {
...attributes,
url: lastURLRef.current
});
},
help: hasUrlBinding && !isBoundEntityAvailable ? /* @__PURE__ */ jsx(
MissingEntityHelp,
{
id: helpTextId,
type: attributes.type,
kind: attributes.kind
}
) : isBoundEntityAvailable && /* @__PURE__ */ jsx(
BindingHelpText,
{
type: attributes.type,
kind: attributes.kind
}
),
suffix: hasUrlBinding && /* @__PURE__ */ jsx(
Button,
{
icon: unlinkIcon,
onClick: () => {
unsyncBoundLink();
shouldFocusURLInputRef.current = true;
},
"aria-describedby": helpTextId,
showTooltip: true,
label: __("Unsync and edit"),
__next40pxDefaultSize: true,
className: hasUrlBinding && !isBoundEntityAvailable ? "navigation-link-control__error-suffix-button" : void 0
}
)
}
)
}
),
/* @__PURE__ */ jsx(
ToolsPanelItem,
{
hasValue: () => !!opensInNewTab,
label: __("Open in new tab"),
onDeselect: () => setAttributes({ opensInNewTab: false }),
isShownByDefault: true,
children: /* @__PURE__ */ jsx(
CheckboxControl,
{
__nextHasNoMarginBottom: true,
label: __("Open in new tab"),
checked: opensInNewTab,
onChange: (value) => setAttributes({ opensInNewTab: value })
}
)
}
),
/* @__PURE__ */ jsx(
ToolsPanelItem,
{
hasValue: () => !!description,
label: __("Description"),
onDeselect: () => setAttributes({ description: "" }),
isShownByDefault: true,
children: /* @__PURE__ */ jsx(
TextareaControl,
{
__nextHasNoMarginBottom: true,
label: __("Description"),
value: description || "",
onChange: (descriptionValue) => {
setAttributes({ description: descriptionValue });
},
help: __(
"The description will be displayed in the menu if the current theme supports it."
)
}
)
}
),
/* @__PURE__ */ jsx(
ToolsPanelItem,
{
hasValue: () => !!rel,
label: __("Rel attribute"),
onDeselect: () => setAttributes({ rel: "" }),
isShownByDefault: true,
children: /* @__PURE__ */ jsx(
TextControl,
{
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
label: __("Rel attribute"),
value: rel || "",
onChange: (relValue) => {
setAttributes({ rel: relValue });
},
autoComplete: "off",
help: __(
"The relationship of the linked URL as space-separated link types."
)
}
)
}
)
]
}
);
}
function BindingHelpText({ type, kind }) {
const entityType = getEntityTypeName(type, kind);
return sprintf(
/* translators: %s is the entity type (e.g., "page", "post", "category") */
__("Synced with the selected %s."),
entityType
);
}
function MissingEntityHelpText({ type, kind }) {
const entityType = getEntityTypeName(type, kind);
return sprintf(
/* translators: %s is the entity type (e.g., "page", "post", "category") */
__("Synced %s is missing. Please update or remove this link."),
entityType
);
}
function MissingEntityHelp({ id, type, kind }) {
return /* @__PURE__ */ jsx("span", { id, className: "navigation-link-control__error-text", children: /* @__PURE__ */ jsx(MissingEntityHelpText, { type, kind }) });
}
export {
BindingHelpText,
Controls,
MissingEntityHelpText
};
//# sourceMappingURL=controls.js.map