From eef16f5a0f5af0aea313e411534643678ba4bea1 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 06 Sep 2026 20:58:21 +0000
Subject: [PATCH] =Polishing up the gutenberg sidebar for custom meta. Seems to be working more or less. Added integration with OpenStreetMaps for location fields. Still to add: row ordering for repeaters, Maybe a better view for the selector field (terms, posts, etc), ensure fields save correctly and repopulate.

---
 src/editor-fields/fields/selector-field.js |   64 +++++++++++++++++++++++++++-----
 1 files changed, 54 insertions(+), 10 deletions(-)

diff --git a/src/editor-fields/fields/selector-field.js b/src/editor-fields/fields/selector-field.js
index 88d32ec..3e4cad2 100644
--- a/src/editor-fields/fields/selector-field.js
+++ b/src/editor-fields/fields/selector-field.js
@@ -17,16 +17,26 @@
 	return (search) => addQueryArgs('/wp/v2/search', { search, type: 'post', subtype: field.post_type, per_page: 20 });
 };
 
+const createPayload = (field, subtype, name) =>
+	subtype === 'post' ? { title: name, status: 'publish', postType: field.post_type } : { name };
+
 const labelOf = (item, subtype) => (subtype === 'user' ? item.name : item.title?.rendered ?? item.title ?? item.name);
 
 const SelectorField = ({ field, value, onChange }) => {
 	const subtype = resolveSubtype(field);
+	const canCreate = subtype === 'taxonomy';
+	console.log(field);
 	const buildPath = searchPath(field);
-	const ids = Array.isArray(value) ? value : value ? [value] : [];
+	const ids = Array.isArray(value) ? value : value ? value.split(',') : [];
 
 	const [tokens, setTokens] = useState([]);   // display strings
 	const [idByLabel, setIdByLabel] = useState({});
 	const [options, setOptions] = useState({}); // label -> id, from last search
+	const [search, setSearch] = useState('');
+	const [creating, setCreating] = useState(false);
+
+	const createLabel = search ? `Not found. Create "${search}"?` : null;
+
 
 	// Resolve current IDs to labels on mount / value change
 	useEffect(() => {
@@ -54,7 +64,9 @@
 	}, [JSON.stringify(ids)]);
 
 	const onInputChange = useCallback((search) => {
-		if (!search) return;
+		setSearch(search);
+		if (!search) { setOptions({}); return;}
+
 		apiFetch({ path: buildPath(search) })
 			.then((results) => {
 				const map = {};
@@ -64,13 +76,7 @@
 			.catch(() => {});
 	}, [buildPath, subtype]);
 
-	const onChangeTokens = (newTokens) => {
-		setTokens(newTokens);
-		const newIds = newTokens
-			.map((label) => idByLabel[label] ?? options[label])
-			.filter((id) => id !== undefined);
-		setIdByLabel((prev) => ({ ...prev, ...options }));
-
+	const commitIds = (newIds) => {
 		const isSingle = field.max === 1;
 		if (isSingle) {
 			onChange(newIds[newIds.length - 1] ?? 0);
@@ -79,15 +85,53 @@
 		}
 	};
 
+	const onChangeTokens = (newTokens) => {
+		const pendingCreate = newTokens.find((t) => t === createLabel);
+
+		if (pendingCreate && canCreate && !creating) {
+			setCreating(true);
+			apiFetch({ path: `/wp/v2/${field.taxonomy}`, method: 'POST', data: createPayload(field, subtype, search) })
+				.then((item) => {
+					const label = labelOf(item, subtype);
+					setIdByLabel((prev) => ({ ...prev, [label]: item.id }));
+					setTokens((prev) => {
+						const finalTokens = prev.filter((t) => t !== createLabel).concat(label);
+						const newIds = finalTokens.map((l) => idByLabel[l] ?? options[l] ?? (l === label ? item.id : undefined))
+							.filter((id) => id !== undefined);
+						commitIds(newIds);
+						return finalTokens;
+					});
+				})
+				.catch(() => {})
+				.finally(() => setCreating(false));
+			return;
+		}
+
+		const resolved = newTokens.filter(
+			(label) => idByLabel[label] !== undefined || options[label] !== undefined
+		);
+		const newIds = resolved.map((label) => idByLabel[label] ?? options[label]);
+		setTokens(resolved);
+		setIdByLabel((prev) => ({ ...prev, ...options }));
+		commitIds(newIds);
+	};
+
+	const noMatch = search && !Object.keys(options).some((l) => l.toLowerCase() === search.toLowerCase());
+	const suggestions = field.max === 1 && tokens.length
+		? []
+		: [...Object.keys(options), ...(canCreate && noMatch ? [createLabel] : [])];
+
+
 	return (
 		<FormTokenField
 			label={field.label}
 			help={field.description}
 			value={tokens}
-			suggestions={field.max === 1 && tokens.length ? [] : Object.keys(options)}
+			suggestions={suggestions}
 			onChange={onChangeTokens}
 			onInputChange={onInputChange}
 			maxLength={field.max || undefined}
+			id={field.name}
 			__experimentalExpandOnFocus
 		/>
 	);

--
Gitblit v1.10.0