UNPKG

@wordpress/block-editor

Version:
419 lines (418 loc) 14.1 kB
// packages/block-editor/src/components/link-control/index.js import clsx from "clsx"; import { Button, Spinner, Notice, TextControl, __experimentalHStack as HStack, __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper } from "@wordpress/components"; import { __, sprintf } from "@wordpress/i18n"; import { useRef, useState, useEffect, useMemo } from "@wordpress/element"; import { useInstanceId } from "@wordpress/compose"; import { focus } from "@wordpress/dom"; import { ENTER } from "@wordpress/keycodes"; import { isShallowEqualObjects } from "@wordpress/is-shallow-equal"; import { useSelect, useDispatch } from "@wordpress/data"; import { store as preferencesStore } from "@wordpress/preferences"; import { keyboardReturn, linkOff } from "@wordpress/icons"; import deprecated from "@wordpress/deprecated"; import LinkControlSettingsDrawer from "./settings-drawer"; import LinkControlSearchInput from "./search-input"; import LinkPreview from "./link-preview"; import LinkSettings from "./settings"; import useCreatePage from "./use-create-page"; import useInternalValue from "./use-internal-value"; import { ViewerFill } from "./viewer-slot"; import { DEFAULT_LINK_SETTINGS } from "./constants"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; var noop = () => { }; var PREFERENCE_SCOPE = "core/block-editor"; var PREFERENCE_KEY = "linkControlSettingsDrawer"; function LinkControl({ searchInputPlaceholder, value, settings = DEFAULT_LINK_SETTINGS, onChange = noop, onRemove, onCancel, noDirectEntry = false, showSuggestions = true, showInitialSuggestions, forceIsEditingLink, createSuggestion, withCreateSuggestion, inputValue: propInputValue = "", suggestionsQuery = {}, noURLSuggestion = false, createSuggestionButtonText, hasRichPreviews = false, hasTextControl = false, renderControlBottom = null, handleEntities = false }) { if (withCreateSuggestion === void 0 && createSuggestion) { withCreateSuggestion = true; } const [settingsOpen, setSettingsOpen] = useState(false); const { advancedSettingsPreference } = useSelect((select) => { const prefsStore = select(preferencesStore); return { advancedSettingsPreference: prefsStore.get(PREFERENCE_SCOPE, PREFERENCE_KEY) ?? false }; }, []); const { set: setPreference } = useDispatch(preferencesStore); const setSettingsOpenWithPreference = (prefVal) => { if (setPreference) { setPreference(PREFERENCE_SCOPE, PREFERENCE_KEY, prefVal); } setSettingsOpen(prefVal); }; const isSettingsOpen = advancedSettingsPreference || settingsOpen; const isMountingRef = useRef(true); const wrapperNode = useRef(); const textInputRef = useRef(); const searchInputRef = useRef(); const entityUrlFallbackRef = useRef(); const settingsKeys = settings.map(({ id }) => id); const [ internalControlValue, setInternalControlValue, setInternalURLInputValue, setInternalTextInputValue, createSetInternalSettingValueHandler ] = useInternalValue(value); const isEntity = handleEntities && !!internalControlValue?.id; const baseId = useInstanceId(LinkControl, "link-control"); const helpTextId = isEntity ? `${baseId}__help` : null; const valueHasChanges = value && !isShallowEqualObjects(internalControlValue, value); const [isEditingLink, setIsEditingLink] = useState( forceIsEditingLink !== void 0 ? forceIsEditingLink : !value || !value.url ); const { createPage, isCreatingPage, errorMessage } = useCreatePage(createSuggestion); useEffect(() => { if (forceIsEditingLink === void 0) { return; } setIsEditingLink(forceIsEditingLink); }, [forceIsEditingLink]); useEffect(() => { if (isMountingRef.current) { return; } const nextFocusTarget = focus.focusable.find(wrapperNode.current)[0] || wrapperNode.current; nextFocusTarget.focus(); }, [isEditingLink, isCreatingPage]); useEffect(() => { isMountingRef.current = false; return () => { isMountingRef.current = true; }; }, []); const hasLinkValue = value?.url?.trim()?.length > 0; const stopEditing = () => { setIsEditingLink(false); }; const handleSelectSuggestion = (updatedValue) => { if (updatedValue?.kind === "taxonomy" && updatedValue?.url) { entityUrlFallbackRef.current = updatedValue.url; } const nonSettingsChanges = Object.keys(updatedValue).reduce( (acc, key) => { if (!settingsKeys.includes(key)) { acc[key] = updatedValue[key]; } return acc; }, {} ); onChange({ ...internalControlValue, ...nonSettingsChanges, // As title is not a setting, it must be manually applied // in such a way as to preserve the users changes over // any "title" value provided by the "suggestion". title: internalControlValue?.title || updatedValue?.title }); stopEditing(); }; const handleSubmit = () => { if (valueHasChanges) { onChange({ ...value, ...internalControlValue, url: currentUrlInputValue }); } stopEditing(); }; const handleSubmitWithEnter = (event) => { const { keyCode } = event; if (keyCode === ENTER && !currentInputIsEmpty) { event.preventDefault(); handleSubmit(); } }; const resetInternalValues = () => { setInternalControlValue(value); }; const handleCancel = (event) => { event.preventDefault(); event.stopPropagation(); resetInternalValues(); if (hasLinkValue) { stopEditing(); } else { onRemove?.(); } onCancel?.(); }; const [shouldFocusSearchInput, setShouldFocusSearchInput] = useState(false); const handleUnlink = () => { const { id, kind, type, ...restValue } = internalControlValue; setInternalControlValue({ ...restValue, id: void 0, kind: void 0, type: void 0, url: void 0 }); setShouldFocusSearchInput(true); }; useEffect(() => { if (shouldFocusSearchInput) { searchInputRef.current?.focus(); setShouldFocusSearchInput(false); } }, [shouldFocusSearchInput]); const currentUrlInputValue = propInputValue || internalControlValue?.url || ""; const currentInputIsEmpty = !currentUrlInputValue?.trim()?.length; const shownUnlinkControl = onRemove && value && !isEditingLink && !isCreatingPage; const showActions = isEditingLink && hasLinkValue; const showTextControl = hasLinkValue && hasTextControl; const isEditing = (isEditingLink || !value) && !isCreatingPage; const isDisabled = !valueHasChanges || currentInputIsEmpty; const showSettings = !!settings?.length && isEditingLink && hasLinkValue; const previewValue = useMemo(() => { if (value?.kind === "taxonomy" && !value?.url && entityUrlFallbackRef.current) { return { ...value, url: entityUrlFallbackRef.current }; } return value; }, [value]); return /* @__PURE__ */ jsxs( "div", { tabIndex: -1, ref: wrapperNode, className: "block-editor-link-control", children: [ isCreatingPage && /* @__PURE__ */ jsxs("div", { className: "block-editor-link-control__loading", children: [ /* @__PURE__ */ jsx(Spinner, {}), " ", __("Creating"), "\u2026" ] }), isEditing && /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsxs( "div", { className: clsx({ "block-editor-link-control__search-input-wrapper": true, "has-text-control": showTextControl, "has-actions": showActions }), children: [ showTextControl && /* @__PURE__ */ jsx( TextControl, { __nextHasNoMarginBottom: true, ref: textInputRef, className: "block-editor-link-control__field block-editor-link-control__text-content", label: __("Text"), value: internalControlValue?.title, onChange: setInternalTextInputValue, onKeyDown: handleSubmitWithEnter, __next40pxDefaultSize: true } ), /* @__PURE__ */ jsx( LinkControlSearchInput, { ref: searchInputRef, currentLink: value, className: "block-editor-link-control__field block-editor-link-control__search-input", placeholder: searchInputPlaceholder, value: currentUrlInputValue, withCreateSuggestion, onCreateSuggestion: createPage, onChange: setInternalURLInputValue, onSelect: handleSelectSuggestion, showInitialSuggestions, allowDirectEntry: !noDirectEntry, showSuggestions, suggestionsQuery, withURLSuggestion: !noURLSuggestion, createSuggestionButtonText, hideLabelFromVision: !showTextControl, isEntity, suffix: /* @__PURE__ */ jsx( SearchSuffixControl, { isEntity, showActions, isDisabled, onUnlink: handleUnlink, onSubmit: handleSubmit, helpTextId } ) } ), isEntity && helpTextId && /* @__PURE__ */ jsx( "p", { id: helpTextId, className: "block-editor-link-control__help", children: sprintf( /* translators: %s: entity type (e.g., page, post) */ __("Synced with the selected %s."), internalControlValue?.type || "item" ) } ) ] } ), errorMessage && /* @__PURE__ */ jsx( Notice, { className: "block-editor-link-control__search-error", status: "error", isDismissible: false, children: errorMessage } ) ] }), value && !isEditingLink && !isCreatingPage && /* @__PURE__ */ jsx( LinkPreview, { value: previewValue, onEditClick: () => setIsEditingLink(true), hasRichPreviews, hasUnlinkControl: shownUnlinkControl, onRemove: () => { onRemove(); setIsEditingLink(true); } }, previewValue?.url ), showSettings && /* @__PURE__ */ jsx("div", { className: "block-editor-link-control__tools", children: !currentInputIsEmpty && /* @__PURE__ */ jsx( LinkControlSettingsDrawer, { settingsOpen: isSettingsOpen, setSettingsOpen: setSettingsOpenWithPreference, children: /* @__PURE__ */ jsx( LinkSettings, { value: internalControlValue, settings, onChange: createSetInternalSettingValueHandler( settingsKeys ) } ) } ) }), showActions && /* @__PURE__ */ jsxs( HStack, { justify: "right", className: "block-editor-link-control__search-actions", children: [ /* @__PURE__ */ jsx( Button, { __next40pxDefaultSize: true, variant: "tertiary", onClick: handleCancel, children: __("Cancel") } ), /* @__PURE__ */ jsx( Button, { __next40pxDefaultSize: true, variant: "primary", onClick: isDisabled ? noop : handleSubmit, className: "block-editor-link-control__search-submit", "aria-disabled": isDisabled, children: __("Apply") } ) ] } ), !isCreatingPage && renderControlBottom && renderControlBottom() ] } ); } function SearchSuffixControl({ isEntity, showActions, isDisabled, onUnlink, onSubmit, helpTextId }) { if (isEntity) { return /* @__PURE__ */ jsx( Button, { icon: linkOff, onClick: onUnlink, "aria-describedby": helpTextId, showTooltip: true, label: __("Unsync and edit"), __next40pxDefaultSize: true } ); } if (showActions) { return void 0; } return /* @__PURE__ */ jsx(InputControlSuffixWrapper, { variant: "control", children: /* @__PURE__ */ jsx( Button, { onClick: isDisabled ? noop : onSubmit, label: __("Submit"), icon: keyboardReturn, className: "block-editor-link-control__search-submit", "aria-disabled": isDisabled, size: "small" } ) }); } LinkControl.ViewerFill = ViewerFill; LinkControl.DEFAULT_LINK_SETTINGS = DEFAULT_LINK_SETTINGS; var DeprecatedExperimentalLinkControl = (props) => { deprecated("wp.blockEditor.__experimentalLinkControl", { since: "6.8", alternative: "wp.blockEditor.LinkControl" }); return /* @__PURE__ */ jsx(LinkControl, { ...props }); }; DeprecatedExperimentalLinkControl.ViewerFill = LinkControl.ViewerFill; DeprecatedExperimentalLinkControl.DEFAULT_LINK_SETTINGS = LinkControl.DEFAULT_LINK_SETTINGS; var link_control_default = LinkControl; export { DeprecatedExperimentalLinkControl, link_control_default as default }; //# sourceMappingURL=index.js.map