@wordpress/block-library
Version:
Block library for the WordPress editor.
327 lines (326 loc) • 11 kB
JavaScript
// packages/block-library/src/navigation-submenu/edit.js
import clsx from "clsx";
import { useSelect, useDispatch } from "@wordpress/data";
import { ToolbarButton, ToolbarGroup } from "@wordpress/components";
import { displayShortcut, isKeyboardEvent } from "@wordpress/keycodes";
import { __ } from "@wordpress/i18n";
import {
BlockControls,
InnerBlocks,
useInnerBlocksProps,
InspectorControls,
RichText,
useBlockProps,
useBlockEditingMode,
store as blockEditorStore,
getColorClassName
} from "@wordpress/block-editor";
import { isURL, prependHTTP } from "@wordpress/url";
import { useState, useEffect, useRef } from "@wordpress/element";
import { link as linkIcon, removeSubmenu } from "@wordpress/icons";
import { speak } from "@wordpress/a11y";
import { createBlock } from "@wordpress/blocks";
import { useMergeRefs, usePrevious } from "@wordpress/compose";
import { ItemSubmenuIcon } from "./icons";
import {
Controls,
LinkUI,
updateAttributes,
useEntityBinding
} from "../navigation-link/shared";
import {
getColors,
getNavigationChildBlockProps
} from "../navigation/edit/utils";
import { DEFAULT_BLOCK } from "../navigation/constants";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var ALLOWED_BLOCKS = [
"core/navigation-link",
"core/navigation-submenu",
"core/page-list"
];
var useIsDraggingWithin = (elementRef) => {
const [isDraggingWithin, setIsDraggingWithin] = useState(false);
useEffect(() => {
const { ownerDocument } = elementRef.current;
function handleDragStart(event) {
handleDragEnter(event);
}
function handleDragEnd() {
setIsDraggingWithin(false);
}
function handleDragEnter(event) {
if (elementRef.current.contains(event.target)) {
setIsDraggingWithin(true);
} else {
setIsDraggingWithin(false);
}
}
ownerDocument.addEventListener("dragstart", handleDragStart);
ownerDocument.addEventListener("dragend", handleDragEnd);
ownerDocument.addEventListener("dragenter", handleDragEnter);
return () => {
ownerDocument.removeEventListener("dragstart", handleDragStart);
ownerDocument.removeEventListener("dragend", handleDragEnd);
ownerDocument.removeEventListener("dragenter", handleDragEnter);
};
}, []);
return isDraggingWithin;
};
function NavigationSubmenuEdit({
attributes,
isSelected,
setAttributes,
mergeBlocks,
onReplace,
context,
clientId
}) {
const { label, url, description } = attributes;
const {
showSubmenuIcon,
maxNestingLevel,
openSubmenusOnClick: contextOpenSubmenusOnClick
} = context;
const blockEditingMode = useBlockEditingMode();
const openSubmenusOnClick = blockEditingMode !== "default" ? true : contextOpenSubmenusOnClick;
const { clearBinding, createBinding } = useEntityBinding({
clientId,
attributes
});
const { __unstableMarkNextChangeAsNotPersistent, replaceBlock } = useDispatch(blockEditorStore);
const [isLinkOpen, setIsLinkOpen] = useState(false);
const [popoverAnchor, setPopoverAnchor] = useState(null);
const listItemRef = useRef(null);
const isDraggingWithin = useIsDraggingWithin(listItemRef);
const itemLabelPlaceholder = __("Add text\u2026");
const ref = useRef();
const {
parentCount,
isParentOfSelectedBlock,
isImmediateParentOfSelectedBlock,
hasChildren,
selectedBlockHasChildren,
onlyDescendantIsEmptyLink
} = useSelect(
(select) => {
const {
hasSelectedInnerBlock,
getSelectedBlockClientId,
getBlockParentsByBlockName,
getBlock,
getBlockCount,
getBlockOrder
} = select(blockEditorStore);
let _onlyDescendantIsEmptyLink;
const selectedBlockId = getSelectedBlockClientId();
const selectedBlockChildren = getBlockOrder(selectedBlockId);
if (selectedBlockChildren?.length === 1) {
const singleBlock = getBlock(selectedBlockChildren[0]);
_onlyDescendantIsEmptyLink = singleBlock?.name === "core/navigation-link" && !singleBlock?.attributes?.label;
}
return {
parentCount: getBlockParentsByBlockName(
clientId,
"core/navigation-submenu"
).length,
isParentOfSelectedBlock: hasSelectedInnerBlock(
clientId,
true
),
isImmediateParentOfSelectedBlock: hasSelectedInnerBlock(
clientId,
false
),
hasChildren: !!getBlockCount(clientId),
selectedBlockHasChildren: !!selectedBlockChildren?.length,
onlyDescendantIsEmptyLink: _onlyDescendantIsEmptyLink
};
},
[clientId]
);
const prevHasChildren = usePrevious(hasChildren);
useEffect(() => {
if (!openSubmenusOnClick && !url) {
setIsLinkOpen(true);
}
}, []);
useEffect(() => {
if (!isSelected) {
setIsLinkOpen(false);
}
}, [isSelected]);
useEffect(() => {
if (isLinkOpen && url) {
if (isURL(prependHTTP(label)) && /^.+\.[a-z]+/.test(label)) {
selectLabelText();
}
}
}, [url]);
function selectLabelText() {
ref.current.focus();
const { ownerDocument } = ref.current;
const { defaultView } = ownerDocument;
const selection = defaultView.getSelection();
const range = ownerDocument.createRange();
range.selectNodeContents(ref.current);
selection.removeAllRanges();
selection.addRange(range);
}
const {
textColor,
customTextColor,
backgroundColor,
customBackgroundColor
} = getColors(context, parentCount > 0);
function onKeyDown(event) {
if (isKeyboardEvent.primary(event, "k")) {
event.preventDefault();
event.stopPropagation();
setIsLinkOpen(true);
}
}
const blockProps = useBlockProps({
ref: useMergeRefs([setPopoverAnchor, listItemRef]),
className: clsx("wp-block-navigation-item", {
"is-editing": isSelected || isParentOfSelectedBlock,
"is-dragging-within": isDraggingWithin,
"has-link": !!url,
"has-child": hasChildren,
"has-text-color": !!textColor || !!customTextColor,
[getColorClassName("color", textColor)]: !!textColor,
"has-background": !!backgroundColor || customBackgroundColor,
[getColorClassName("background-color", backgroundColor)]: !!backgroundColor,
"open-on-click": openSubmenusOnClick
}),
style: {
color: !textColor && customTextColor,
backgroundColor: !backgroundColor && customBackgroundColor
},
onKeyDown
});
const innerBlocksColors = getColors(context, true);
const allowedBlocks = parentCount >= maxNestingLevel ? ALLOWED_BLOCKS.filter(
(blockName) => blockName !== "core/navigation-submenu"
) : ALLOWED_BLOCKS;
const navigationChildBlockProps = getNavigationChildBlockProps(innerBlocksColors);
const innerBlocksProps = useInnerBlocksProps(navigationChildBlockProps, {
allowedBlocks,
defaultBlock: DEFAULT_BLOCK,
directInsert: true,
// Ensure block toolbar is not too far removed from item
// being edited.
// see: https://github.com/WordPress/gutenberg/pull/34615.
__experimentalCaptureToolbars: true,
renderAppender: isSelected || isImmediateParentOfSelectedBlock && !selectedBlockHasChildren || // Show the appender while dragging to allow inserting element between item and the appender.
hasChildren ? InnerBlocks.ButtonBlockAppender : false
});
const ParentElement = openSubmenusOnClick ? "button" : "a";
function transformToLink() {
const newLinkBlock = createBlock("core/navigation-link", attributes);
replaceBlock(clientId, newLinkBlock);
}
useEffect(() => {
if (!hasChildren && prevHasChildren) {
__unstableMarkNextChangeAsNotPersistent();
transformToLink();
}
}, [hasChildren, prevHasChildren]);
const canConvertToLink = !selectedBlockHasChildren || onlyDescendantIsEmptyLink;
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(BlockControls, { children: /* @__PURE__ */ jsxs(ToolbarGroup, { children: [
!openSubmenusOnClick && /* @__PURE__ */ jsx(
ToolbarButton,
{
name: "link",
icon: linkIcon,
title: __("Link"),
shortcut: displayShortcut.primary("k"),
onClick: () => {
setIsLinkOpen(true);
}
}
),
/* @__PURE__ */ jsx(
ToolbarButton,
{
name: "revert",
icon: removeSubmenu,
title: __("Convert to Link"),
onClick: transformToLink,
className: "wp-block-navigation__submenu__revert",
disabled: !canConvertToLink
}
)
] }) }),
/* @__PURE__ */ jsx(InspectorControls, { children: /* @__PURE__ */ jsx(
Controls,
{
attributes,
setAttributes,
clientId
}
) }),
/* @__PURE__ */ jsxs("div", { ...blockProps, children: [
/* @__PURE__ */ jsxs(ParentElement, { className: "wp-block-navigation-item__content", children: [
/* @__PURE__ */ jsx(
RichText,
{
ref,
identifier: "label",
className: "wp-block-navigation-item__label",
value: label,
onChange: (labelValue) => setAttributes({ label: labelValue }),
onMerge: mergeBlocks,
onReplace,
"aria-label": __("Navigation link text"),
placeholder: itemLabelPlaceholder,
withoutInteractiveFormatting: true,
onClick: () => {
if (!openSubmenusOnClick && !url) {
setIsLinkOpen(true);
}
}
}
),
description && /* @__PURE__ */ jsx("span", { className: "wp-block-navigation-item__description", children: description }),
!openSubmenusOnClick && isLinkOpen && /* @__PURE__ */ jsx(
LinkUI,
{
clientId,
link: attributes,
onClose: () => {
setIsLinkOpen(false);
},
anchor: popoverAnchor,
onRemove: () => {
setAttributes({ url: "" });
speak(__("Link removed."), "assertive");
},
onChange: (updatedValue) => {
const {
isEntityLink,
attributes: updatedAttributes
} = updateAttributes(
updatedValue,
setAttributes,
attributes
);
if (isEntityLink) {
createBinding(updatedAttributes);
} else {
clearBinding();
}
}
}
)
] }),
(showSubmenuIcon || openSubmenusOnClick) && /* @__PURE__ */ jsx("span", { className: "wp-block-navigation__submenu-icon", children: /* @__PURE__ */ jsx(ItemSubmenuIcon, {}) }),
/* @__PURE__ */ jsx("div", { ...innerBlocksProps })
] })
] });
}
export {
NavigationSubmenuEdit as default
};
//# sourceMappingURL=edit.js.map