UNPKG

@wordpress/block-library

Version:
327 lines (326 loc) 11 kB
// 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