UNPKG

@wordpress/block-library

Version:
418 lines (417 loc) 12.7 kB
// 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