UNPKG

@wordpress/block-library

Version:
311 lines (310 loc) 10.5 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.mjs"; import { Controls, LinkUI, updateAttributes, useEntityBinding, useIsInvalidLink, InvalidDraftDisplay, useEnableLinkStatusValidation, useIsDraggingWithin, selectLabelText } from "../navigation-link/shared/index.mjs"; import { getColors, getNavigationChildBlockProps } from "../navigation/edit/utils.mjs"; import { DEFAULT_BLOCK } from "../navigation/constants.mjs"; import { getSubmenuVisibility } from "../navigation/utils/get-submenu-visibility.mjs"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; var ALLOWED_BLOCKS = [ "core/navigation-link", "core/navigation-submenu", "core/page-list" ]; function NavigationSubmenuEdit({ attributes, isSelected, setAttributes, mergeBlocks, onReplace, context, clientId }) { const { label, url, description, kind, type, id } = attributes; const { showSubmenuIcon, maxNestingLevel } = context; const blockEditingMode = useBlockEditingMode(); const submenuVisibility = getSubmenuVisibility(context); const openSubmenusOnClick = blockEditingMode !== "default" ? true : submenuVisibility === "click"; 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 validateLinkStatus = useEnableLinkStatusValidation(clientId); const prevHasChildren = usePrevious(hasChildren); const [isInvalid, isDraft] = useIsInvalidLink( kind, type, id, validateLinkStatus ); 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(ref); } } }, [url]); 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, "open-always": submenuVisibility === "always" }), 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: [ !isInvalid && !isDraft && /* @__PURE__ */ jsxs(Fragment, { 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 }) ] }), (isInvalid || isDraft) && /* @__PURE__ */ jsx( InvalidDraftDisplay, { label, isInvalid, isDraft, className: "wp-block-navigation-item__label" } ), !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.mjs.map