@wordpress/block-library
Version:
Block library for the WordPress editor.
418 lines (417 loc) • 12.7 kB
JavaScript
// packages/block-library/src/button/edit.js
import clsx from "clsx";
import { __, sprintf } from "@wordpress/i18n";
import {
useEffect,
useState,
useRef,
useMemo,
createInterpolateElement
} from "@wordpress/element";
import {
TextControl,
ToolbarButton,
Popover,
ExternalLink
} from "@wordpress/components";
import {
BlockControls,
InspectorControls,
RichText,
useBlockProps,
LinkControl,
__experimentalUseBorderProps as useBorderProps,
__experimentalUseColorProps as useColorProps,
__experimentalGetSpacingClassesAndStyles as useSpacingProps,
__experimentalGetShadowClassesAndStyles as useShadowProps,
__experimentalGetDimensionsClassesAndStyles as useDimensionsProps,
__experimentalGetElementClassName,
store as blockEditorStore,
useBlockEditingMode,
getTypographyClassesAndStyles as useTypographyProps,
useSettings,
privateApis as blockEditorPrivateApis
} from "@wordpress/block-editor";
import { displayShortcut, isKeyboardEvent, ENTER } from "@wordpress/keycodes";
import { link, linkOff } from "@wordpress/icons";
import {
createBlock,
cloneBlock,
getDefaultBlockName,
getBlockBindingsSource
} from "@wordpress/blocks";
import { useMergeRefs, useRefEffect } from "@wordpress/compose";
import { useSelect, useDispatch } from "@wordpress/data";
import { NEW_TAB_TARGET, NOFOLLOW_REL } from "./constants.mjs";
import { getUpdatedLinkAttributes } from "./get-updated-link-attributes.mjs";
import removeAnchorTag from "../utils/remove-anchor-tag.mjs";
import { unlock } from "../lock-unlock.mjs";
import useDeprecatedTextAlign from "../utils/deprecated-text-align-attributes.mjs";
import { getWidthClasses, isPercentageWidth } from "./utils.mjs";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var { HTMLElementControl } = unlock(blockEditorPrivateApis);
var LINK_SETTINGS = [
...LinkControl.DEFAULT_LINK_SETTINGS,
{
id: "nofollow",
title: __("Mark as nofollow")
}
];
function useEnter(props) {
const { replaceBlocks, selectionChange } = useDispatch(blockEditorStore);
const { getBlock, getBlockRootClientId, getBlockIndex } = useSelect(blockEditorStore);
const propsRef = useRef(props);
propsRef.current = props;
return useRefEffect((element) => {
function onKeyDown(event) {
if (event.defaultPrevented || event.keyCode !== ENTER) {
return;
}
const { content, clientId } = propsRef.current;
if (content.length) {
return;
}
event.preventDefault();
const topParentListBlock = getBlock(
getBlockRootClientId(clientId)
);
const blockIndex = getBlockIndex(clientId);
const head = cloneBlock({
...topParentListBlock,
innerBlocks: topParentListBlock.innerBlocks.slice(
0,
blockIndex
)
});
const middle = createBlock(getDefaultBlockName());
const after = topParentListBlock.innerBlocks.slice(
blockIndex + 1
);
const tail = after.length ? [
cloneBlock({
...topParentListBlock,
innerBlocks: after
})
] : [];
replaceBlocks(
topParentListBlock.clientId,
[head, middle, ...tail],
1
);
selectionChange(middle.clientId);
}
element.addEventListener("keydown", onKeyDown);
return () => {
element.removeEventListener("keydown", onKeyDown);
};
}, []);
}
function ButtonEdit(props) {
const {
attributes,
setAttributes,
className,
isSelected,
onReplace,
mergeBlocks,
clientId,
context
} = props;
const {
tagName,
linkTarget,
placeholder,
rel,
style,
text,
url,
metadata
} = attributes;
const width = style?.dimensions?.width;
useDeprecatedTextAlign(props);
const TagName = tagName || "a";
function onKeyDown(event) {
if (isKeyboardEvent.primary(event, "k")) {
startEditing(event);
} else if (isKeyboardEvent.primaryShift(event, "k")) {
unlink();
richTextRef.current?.focus();
}
}
const [popoverAnchor, setPopoverAnchor] = useState(null);
const borderProps = useBorderProps(attributes);
const colorProps = useColorProps(attributes);
const spacingProps = useSpacingProps(attributes);
const shadowProps = useShadowProps(attributes);
const dimensionsProps = useDimensionsProps(attributes);
const ref = useRef();
const richTextRef = useRef();
const blockProps = useBlockProps({
ref: useMergeRefs([setPopoverAnchor, ref]),
onKeyDown
});
const blockEditingMode = useBlockEditingMode();
const [isEditingURL, setIsEditingURL] = useState(false);
const isURLSet = !!url;
const opensInNewTab = linkTarget === NEW_TAB_TARGET;
const nofollow = !!rel?.includes(NOFOLLOW_REL);
const isLinkTag = "a" === TagName;
const {
createPageEntity,
userCanCreatePages,
lockUrlControls = false
} = useSelect(
(select) => {
if (!isSelected) {
return {};
}
const _settings = select(blockEditorStore).getSettings();
const blockBindingsSource = getBlockBindingsSource(
metadata?.bindings?.url?.source
);
return {
createPageEntity: _settings.__experimentalCreatePageEntity,
userCanCreatePages: _settings.__experimentalUserCanCreatePages,
lockUrlControls: !!metadata?.bindings?.url && !blockBindingsSource?.canUserEditValue?.({
select,
context,
args: metadata?.bindings?.url?.args
})
};
},
[context, isSelected, metadata?.bindings?.url]
);
async function handleCreate(pageTitle) {
const page = await createPageEntity({
title: pageTitle,
status: "draft"
});
return {
id: page.id,
type: page.type,
title: page.title.rendered,
url: page.link,
kind: "post-type"
};
}
function createButtonText(searchTerm) {
return createInterpolateElement(
sprintf(
/* translators: %s: search term. */
__("Create page: <mark>%s</mark>"),
searchTerm
),
{ mark: /* @__PURE__ */ jsx("mark", {}) }
);
}
function startEditing(event) {
event.preventDefault();
setIsEditingURL(true);
}
function unlink() {
setAttributes({
url: void 0,
linkTarget: void 0,
rel: void 0
});
setIsEditingURL(false);
}
useEffect(() => {
if (!isSelected) {
setIsEditingURL(false);
}
}, [isSelected]);
const linkValue = useMemo(
() => ({ url, opensInNewTab, nofollow }),
[url, opensInNewTab, nofollow]
);
const useEnterRef = useEnter({ content: text, clientId });
const mergedRef = useMergeRefs([useEnterRef, richTextRef]);
const [fluidTypographySettings, layout, dimensionSizes] = useSettings(
"typography.fluid",
"layout",
"dimensions.dimensionSizes"
);
const dimensionPresets = useMemo(() => {
if (!dimensionSizes) {
return [];
}
return [
...dimensionSizes?.custom ?? [],
...dimensionSizes?.theme ?? [],
...dimensionSizes?.default ?? []
];
}, [dimensionSizes]);
const typographyProps = useTypographyProps(attributes, {
typography: {
fluid: fluidTypographySettings
},
layout: {
wideSize: layout?.wideSize
}
});
const resolvedWidth = useMemo(() => {
if (!width) {
return void 0;
}
const presetPrefix = "var:preset|dimension|";
if (width.startsWith(presetPrefix)) {
const slug = width.slice(presetPrefix.length);
const preset = dimensionPresets?.find((p) => p.slug === slug);
return preset?.size ?? width;
}
return width;
}, [width, dimensionPresets]);
const hasNonContentControls = blockEditingMode === "default";
const hasBlockControls = hasNonContentControls || isLinkTag && !lockUrlControls;
const classes = clsx(
blockProps.className,
getWidthClasses(resolvedWidth)
);
const widthStyle = useMemo(() => {
if (!width) {
return {};
}
if (isPercentageWidth(resolvedWidth)) {
return {
"--wp--block-button--width": parseFloat(resolvedWidth)
};
}
return dimensionsProps.style;
}, [width, resolvedWidth, dimensionsProps.style]);
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
"div",
{
...blockProps,
className: classes,
style: { ...blockProps.style, ...widthStyle },
children: /* @__PURE__ */ jsx(
RichText,
{
ref: mergedRef,
"aria-label": __("Button text"),
placeholder: placeholder || __("Add text\u2026"),
value: text,
onChange: (value) => setAttributes({
text: removeAnchorTag(value)
}),
withoutInteractiveFormatting: true,
className: clsx(
className,
"wp-block-button__link",
colorProps.className,
borderProps.className,
typographyProps.className,
{
// For backwards compatibility add style that isn't
// provided via block support.
"no-border-radius": style?.border?.radius === 0,
[`has-custom-font-size`]: blockProps.style.fontSize
},
__experimentalGetElementClassName("button")
),
style: {
...borderProps.style,
...colorProps.style,
...spacingProps.style,
...shadowProps.style,
...typographyProps.style,
writingMode: void 0
},
onReplace,
onMerge: mergeBlocks,
identifier: "text"
}
)
}
),
hasBlockControls && /* @__PURE__ */ jsx(BlockControls, { group: "block", children: isLinkTag && !lockUrlControls && /* @__PURE__ */ jsx(
ToolbarButton,
{
name: "link",
icon: !isURLSet ? link : linkOff,
title: !isURLSet ? __("Link") : __("Unlink"),
shortcut: !isURLSet ? displayShortcut.primary("k") : displayShortcut.primaryShift("k"),
onClick: !isURLSet ? startEditing : unlink,
isActive: isURLSet
}
) }),
isLinkTag && isSelected && (isEditingURL || isURLSet) && !lockUrlControls && /* @__PURE__ */ jsx(
Popover,
{
placement: "bottom",
onClose: () => {
setIsEditingURL(false);
richTextRef.current?.focus();
},
anchor: popoverAnchor,
focusOnMount: isEditingURL ? "firstElement" : false,
__unstableSlotName: "__unstable-block-tools-after",
shift: true,
children: /* @__PURE__ */ jsx(
LinkControl,
{
value: linkValue,
onChange: ({
url: newURL,
opensInNewTab: newOpensInNewTab,
nofollow: newNofollow
}) => setAttributes(
getUpdatedLinkAttributes({
rel,
url: newURL,
opensInNewTab: newOpensInNewTab,
nofollow: newNofollow
})
),
onRemove: () => {
unlink();
richTextRef.current?.focus();
},
forceIsEditingLink: isEditingURL,
settings: LINK_SETTINGS,
createSuggestion: createPageEntity && handleCreate,
withCreateSuggestion: userCanCreatePages,
createSuggestionButtonText: createButtonText
}
)
}
),
/* @__PURE__ */ jsxs(InspectorControls, { group: "advanced", children: [
/* @__PURE__ */ jsx(
HTMLElementControl,
{
tagName,
onChange: (value) => setAttributes({ tagName: value }),
options: [
{ label: __("Default (<a>)"), value: "a" },
{ label: "<button>", value: "button" }
]
}
),
isLinkTag && /* @__PURE__ */ jsx(
TextControl,
{
__next40pxDefaultSize: true,
label: __("Link relation"),
help: createInterpolateElement(
__(
"The <a>Link Relation</a> attribute defines the relationship between a linked resource and the current document."
),
{
a: /* @__PURE__ */ jsx(ExternalLink, { href: "https://developer.mozilla.org/docs/Web/HTML/Attributes/rel" })
}
),
value: rel || "",
onChange: (newRel) => setAttributes({ rel: newRel })
}
)
] })
] });
}
var edit_default = ButtonEdit;
export {
edit_default as default
};
//# sourceMappingURL=edit.mjs.map