@wordpress/block-editor
Version:
419 lines (418 loc) • 14.1 kB
JavaScript
// 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