Jake Vanderwerf
2026-09-06 eef16f5a0f5af0aea313e411534643678ba4bea1
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
      />
   );