(null);
const { data: metaKeys = [], isLoading: isLoadingKeys } = useQuery({
queryKey: ["metaKeys", postId],
queryFn: () => fetchAvailableMetaKeys(postId),
staleTime: Infinity,
});
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
fieldInputRef.current &&
!fieldInputRef.current.contains(event.target as Node)
) {
setIsSuggestionsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () =>
document.removeEventListener("mousedown", handleClickOutside);
}, []);
const suggestions = useMemo(() => {
const query = keyValue.toLowerCase();
return {
postFields: postFieldKeys.filter(
item =>
item.key.toLowerCase().includes(query) ||
(item.label && item.label.toLowerCase().includes(query))
),
metaKeys: metaKeys.filter(
item =>
item.key.toLowerCase().includes(query) ||
(item.label && item.label.toLowerCase().includes(query))
),
};
}, [keyValue, metaKeys]);
return {
fieldInputRef,
isSuggestionsOpen,
setIsSuggestionsOpen,
suggestions,
hasSuggestions:
suggestions.postFields.length > 0 ||
suggestions.metaKeys.length > 0,
isLoadingKeys,
};
}
function createBindingSource(
keyValue: string,
customPostId: string,
fallback: string
): BindingSource {
const binding: BindingSource = { key: keyValue };
if (customPostId) {
const parsedId = parseInt(customPostId, 10);
if (!isNaN(parsedId) && parsedId > 0) {
binding.postId = parsedId;
}
}
if (fallback) {
binding.fallback = fallback;
}
return binding;
}
function NewBindingForm({
unboundAttributes,
onApply,
onCancel,
postId,
}: NewBindingFormProps) {
const [selectedPath, setSelectedPath] = useState("");
const [keyValue, setKeyValue] = useState("");
const [customPostId, setCustomPostId] = useState("");
const [fallback, setFallback] = useState("");
const metaPostId = customPostId
? parseInt(customPostId, 10) || undefined
: postId;
const {
fieldInputRef,
isSuggestionsOpen,
setIsSuggestionsOpen,
suggestions,
hasSuggestions,
isLoadingKeys,
} = useBindingFormState({
keyValue,
postId: metaPostId,
});
return (
);
}
interface AttributeBindingProps {
attribute: BindableAttribute;
binding: BindingSource;
bindableAttributes: BindableAttribute[];
boundPaths: string[];
onBindingChange: (path: string, binding: BindingSource) => void;
onRemove: () => void;
postId?: number;
}
function AttributeBinding({
attribute,
binding,
bindableAttributes,
boundPaths,
onBindingChange,
onRemove,
postId,
}: AttributeBindingProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [selectedPath, setSelectedPath] = useState(attribute.path);
const [keyValue, setKeyValue] = useState(binding.key);
const [customPostId, setCustomPostId] = useState(
binding.postId?.toString() ?? ""
);
const [fallback, setFallback] = useState(binding.fallback ?? "");
const metaPostId = customPostId
? parseInt(customPostId, 10) || undefined
: postId;
const {
fieldInputRef,
isSuggestionsOpen,
setIsSuggestionsOpen,
suggestions,
hasSuggestions,
isLoadingKeys,
} = useBindingFormState({
keyValue,
postId: metaPostId,
});
const hasUnsavedChanges =
selectedPath !== attribute.path ||
keyValue !== binding.key ||
customPostId !== (binding.postId?.toString() ?? "") ||
fallback !== (binding.fallback ?? "");
const { data: previewValue, refresh } = useDynamicData(binding, postId);
const summary = customPostId
? `${binding.key} (post #${customPostId})`
: binding.key;
return (
{isExpanded && (
)}
);
}
export default DynamicDataPanel;