@wordpress/block-library
Version:
Block library for the WordPress editor.
448 lines (447 loc) • 13.6 kB
JavaScript
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import clsx from "clsx";
import { NEW_TAB_TARGET, NOFOLLOW_REL } from "./constants";
import { getUpdatedLinkAttributes } from "./get-updated-link-attributes";
import removeAnchorTag from "../utils/remove-anchor-tag";
import { useToolsPanelDropdownMenuProps } from "../utils/hooks";
import { unlock } from "../lock-unlock";
import { __, sprintf } from "@wordpress/i18n";
import {
useEffect,
useState,
useRef,
useMemo,
createInterpolateElement
} from "@wordpress/element";
import {
TextControl,
ToolbarButton,
Popover,
ExternalLink,
__experimentalToolsPanel as ToolsPanel,
__experimentalToolsPanelItem as ToolsPanelItem,
__experimentalToggleGroupControl as ToggleGroupControl,
__experimentalToggleGroupControlOption as ToggleGroupControlOption
} from "@wordpress/components";
import {
AlignmentControl,
BlockControls,
InspectorControls,
RichText,
useBlockProps,
LinkControl,
__experimentalUseBorderProps as useBorderProps,
__experimentalUseColorProps as useColorProps,
__experimentalGetSpacingClassesAndStyles as useSpacingProps,
__experimentalGetShadowClassesAndStyles as useShadowProps,
__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";
const { HTMLElementControl } = unlock(blockEditorPrivateApis);
const 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 WidthPanel({ selectedWidth, setAttributes }) {
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
return /* @__PURE__ */ jsx(
ToolsPanel,
{
label: __("Settings"),
resetAll: () => setAttributes({ width: void 0 }),
dropdownMenuProps,
children: /* @__PURE__ */ jsx(
ToolsPanelItem,
{
label: __("Width"),
isShownByDefault: true,
hasValue: () => !!selectedWidth,
onDeselect: () => setAttributes({ width: void 0 }),
__nextHasNoMarginBottom: true,
children: /* @__PURE__ */ jsx(
ToggleGroupControl,
{
label: __("Width"),
value: selectedWidth,
onChange: (newWidth) => setAttributes({ width: newWidth }),
isBlock: true,
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true,
children: [25, 50, 75, 100].map((widthValue) => {
return /* @__PURE__ */ jsx(
ToggleGroupControlOption,
{
value: widthValue,
label: sprintf(
/* translators: %d: Percentage value. */
__("%d%%"),
widthValue
)
},
widthValue
);
})
}
)
}
)
}
);
}
function ButtonEdit(props) {
const {
attributes,
setAttributes,
className,
isSelected,
onReplace,
mergeBlocks,
clientId,
context
} = props;
const {
tagName,
textAlign,
linkTarget,
placeholder,
rel,
style,
text,
url,
width,
metadata
} = attributes;
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 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] = useSettings(
"typography.fluid",
"layout"
);
const typographyProps = useTypographyProps(attributes, {
typography: {
fluid: fluidTypographySettings
},
layout: {
wideSize: layout?.wideSize
}
});
const hasNonContentControls = blockEditingMode === "default";
const hasBlockControls = hasNonContentControls || isLinkTag && !lockUrlControls;
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
"div",
{
...blockProps,
className: clsx(blockProps.className, {
[`has-custom-width wp-block-button__width-${width}`]: width
}),
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,
{
[`has-text-align-${textAlign}`]: textAlign,
// 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__ */ jsxs(BlockControls, { group: "block", children: [
hasNonContentControls && /* @__PURE__ */ jsx(
AlignmentControl,
{
value: textAlign,
onChange: (nextAlign) => {
setAttributes({ textAlign: nextAlign });
}
}
),
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__ */ jsx(InspectorControls, { children: /* @__PURE__ */ jsx(
WidthPanel,
{
selectedWidth: width,
setAttributes
}
) }),
/* @__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,
__nextHasNoMarginBottom: 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.js.map