From 56a9a1ccf764ff7a6af8f8a2292cb07443cb4aa7 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 28 May 2026 18:19:57 +0000
Subject: [PATCH] =New Gitbit setpu

---
 assets/js/concise/TaxonomySelector.js |  472 +++++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 298 insertions(+), 174 deletions(-)

diff --git a/assets/js/concise/TaxonomySelector.js b/assets/js/concise/TaxonomySelector.js
index 5244f02..90f6076 100644
--- a/assets/js/concise/TaxonomySelector.js
+++ b/assets/js/concise/TaxonomySelector.js
@@ -14,6 +14,7 @@
 
 		this.activeField = null;
 		this.isInitializing = true;
+		this.lazyInit = false;
 		this.messageText = {}
 		this.init();
 	}
@@ -21,6 +22,7 @@
 	init() {
 		this.initStore();
 		this.initElements();
+		this.defineTemplates();
 		this.initModal();
 		this.scanExistingFields();
 		this.initListeners();
@@ -42,7 +44,7 @@
 				indexes: [
 					{name: 'taxonomy', keyPath: 'taxonomy'},
 					{name: 'parent', keyPath: 'parent'},
-					{name: 'slug', keyPath: 'slug', unique: true},
+					{name: 'slug', keyPath: 'slug'},
 					{name: 'count', keyPath: 'count'},
 				],
 				endpoint: 'terms',
@@ -62,6 +64,88 @@
 		this.store.subscribe(this.handleStoreEvent.bind(this));
 	}
 
+	defineTemplates() {
+		const T = window.jvbTemplates;
+		const terms = this;
+
+		T.define('emptyState');
+		T.define('selectedTerm', {
+			refs: {
+				name: '.item-name',
+				btn: 'button',
+			},
+			setup({el, refs, manyRefs, data}) {
+				el.dataset.id = data.id;
+				el.dataset.taxonomy = data.taxonomy;
+				if (refs.name) refs.name.textContent = data.path;
+				if (refs.button) refs.button.title = `Remove ${data.name}`;
+			}
+		});
+		T.define('termListItem', {
+			refs: {
+				checkbox: 'input',
+				label: 'label',
+				name: 'span, .term-name'
+			},
+			setup({el, refs, manyRefs, data}) {
+				el.dataset.id = data.id;
+
+				let field = terms.currentField();
+				let isSelected = terms.selectedTerms.get(terms.activeField).has(data.id);
+				let limitReached = field.limit > 0 && terms.selectedTerms.get(terms.activeField).size >= field.limit;
+
+				if (refs.checkbox) {
+					refs.checkbox.dataset.id = data.id;
+					refs.checkbox.id = `${field.id}-${data.id}`;
+					refs.checkbox.name = `${field.id}-${field.taxonomy}-select`;
+					refs.checkbox.value = data.id;
+					refs.checkbox.disabled = !isSelected && limitReached;
+					refs.checkbox.checked = isSelected;
+				}
+				if (refs.label) {
+					refs.label.htmlFor = `${field.id}-${data.id}`;
+					refs.label.title = data.path??data.name;
+					refs.label.dataset.path = data.path;
+				}
+				if (refs.name) {
+					refs.name.textContent = data.show ? data.path : data.name;
+				}
+
+				if (data.hasChildren) {
+					let temp = {
+						plural: field.plural,
+						name: data.name
+					};
+					const toggle = window.jvbTemplates.create('termChildrenToggle', temp);
+					el.append(toggle);
+				}
+			}
+		});
+
+		T.define('termChildrenToggle', {
+			setup({el, refs, manyRefs, data}) {
+				el.ariaLabel = `View ${data.plural} nested under ${data.name}`;
+			}
+		});
+
+		T.define('termBreadcrumb', {
+			setup({el, refs, manyRefs, data}) {
+				el.dataset.id = data.id;
+				el.textContent = data.name;
+				el.title = data.name;
+			}
+		});
+
+		T.define('autocompleteItem', {
+			setup({el, refs, manyRefs, data}) {
+				el.dataset.id = data.id;
+				el.textContent = data.path||data.name;
+				el.title = `Select ${data.name}`;
+			}
+		});
+
+
+	}
 	/******************************************************************
 	 ELEMENTS
 	 ******************************************************************/
@@ -100,7 +184,7 @@
 			},
 			favourites: '.favourite-terms',
 			field: {
-				toggle: 'button.taxonomy-toggle, [data-filter="taxonomy"]',
+				toggle: 'button.selector-toggle, [data-filter="taxonomy"]',
 				value: 'input[type="hidden"]',
 				selected: '.selected-items',
 				dropdown: {
@@ -148,29 +232,20 @@
 	}
 
 	handleClick(e) {
+		if (!this.container.contains(e.target) && !e.target.closest('[data-type="selector"], [data-field-type="selector"]')) {
+			return;
+		}
 		const fieldId = this.getFieldId(e.target) || this.activeField;
 		const field = this.fields.get(fieldId);
 		if (!fieldId || !field) return;
 
-		const autoComplete = window.targetCheck(e, '.item.autocomplete');
-
-		if (autoComplete) {
-			let termId = parseInt(autoComplete.dataset.id);
-			this.addSelected(termId, fieldId);
-			this.scheduleHideDropdown(fieldId);
-			if (field.ui.search) {
-				field.ui.search.value = '';
+		if (this.creator) {
+			let button = window.targetCheck(e, this.selectors.create.button);
+			if (button) {
+				this.maybeCreateTerm(e).then(()=>{});
 			}
 		}
 
-		const toggleButton = window.targetCheck(e, this.selectors.field.toggle);
-
-		if (toggleButton) {
-			e.preventDefault();
-			this.openModal(fieldId);
-			return;
-		}
-
 		const removeButton = window.targetCheck(e, '.remove-term');
 		if (removeButton) {
 			const termId = removeButton.closest('[data-id]').dataset.id??false;
@@ -180,6 +255,27 @@
 			return;
 		}
 
+		const autocomplete = window.targetCheck(e, '.item.autocomplete');
+
+		if (autocomplete) {
+			let termId = parseInt(autocomplete.dataset.id);
+			this.addSelected(termId, fieldId);
+			this.scheduleHideDropdown(fieldId, 6000);
+			if (field.ui.search) {
+				field.ui.search.value = '';
+			}
+			return;
+		}
+
+		const toggleButton = window.targetCheck(e, this.selectors.field.toggle);
+
+		if (toggleButton) {
+			e.preventDefault();
+			this.openModal(fieldId);
+			return;
+		}
+
+
 		if (e.target.matches('.modal-close')) {
 			this.updateFieldValue(fieldId);
 			this.modal?.handleClose();
@@ -207,12 +303,14 @@
 		if (pathLevel) {
 			const termId = parseInt(pathLevel.dataset.id)??0;
 			this.navigateTo(termId);
+			return;
 		}
 
 		const dropdown = window.targetCheck(e, this.selectors.field.dropdown);
 		if (dropdown) {
 			// reset the timer for hiding the dropdown
 			this.scheduleHideDropdown(fieldId);
+			return;
 		}
 
 		const clearSearch = window.targetCheck(e, this.selectors.search.clear);
@@ -230,17 +328,9 @@
 				this.ui.search.input.value = '';
 			}
 		}
-
-		if (this.creator) {
-			let button = window.targetCheck(e, this.selectors.create.button);
-			if (button) {
-				this.maybeCreateTerm(e).then(()=>{});
-			}
-		}
-
 	}
 	handleChange(e) {
-		if (!this.container.contains(e.target)) {
+		if (!this.container.contains(e.target) && !e.target.closest('[data-type="selector"], [data-field-type="selector"]')) {
 			return;
 		}
 		if (!['checkbox', 'button'].includes(e.target.type)) return;
@@ -257,6 +347,9 @@
 	}
 	//For search in modal or field autocomplete
 	handleInput(e) {
+		if (!this.container.contains(e.target) && !e.target.closest('[data-type="selector"], [data-field-type="selector"]')) {
+			return;
+		}
 		let fieldId = this.getFieldId(e.target)??this.activeField;
 		if (!fieldId) return;
 		const field = this.fields.get(fieldId);
@@ -272,7 +365,7 @@
 		}
 
 		let query = e.target.value.trim();
-		this.setMessage(true, `Searching for "${query}" in ${field.plural??'items'}`);
+		this.setMessage(field,true, `Searching for "${query}" in ${field.plural??'items'}`);
 		window.debouncer.schedule(
 			`${fieldId}-search`,
 			async () => {
@@ -297,7 +390,7 @@
 			return;
 		}
 		this.activeField = fieldId;
-		this.setMessage(true, `Loading ${field.plural}...`);
+		this.setMessage(field,true, `Loading ${field.plural}...`);
 		this.resetFilters({taxonomy: field.taxonomy});
 	}
 
@@ -315,9 +408,13 @@
 	}
 
 	handleFocus(e) {
+		if (!this.container.contains(e.target) && !e.target.closest('[data-type="selector"], [data-field-type="selector"]')) {
+			return;
+		}
 		const fieldId = this.getFieldId(e.target);
+		if (!fieldId) return;
 		const field = this.fields.get(fieldId);
-		if (!fieldId || !field) return;
+		if (!field) return;
 		if (!field.hasAutocomplete && !field.hasSearch) return;
 
 		window.debouncer.cancel(`${fieldId}-search-results`);
@@ -329,16 +426,22 @@
 
 	//Hide autocomplete dropdown on blur
 	handleBlur(e) {
+		if (!this.container.contains(e.target) && !e.target.closest('[data-type="selector"], [data-field-type="selector"]')) {
+			return;
+		}
 		const fieldId = this.getFieldId(e.target);
+		if (!fieldId) return;
 		const field = this.fields.get(fieldId);
-		if (!fieldId || ! field) return;
+		if (!field) return;
 		if (!field.hasAutocomplete || this.container.open) return;
+		if (e.target.closest('.remove-item')) return;
+
 		if (e.relatedTarget && field.ui.dropdown.wrapper?.contains(e.relatedTarget)) return;
 
 		this.scheduleHideDropdown(fieldId);
 	}
 
-	scheduleHideDropdown(fieldId){
+	scheduleHideDropdown(fieldId, delay = 1500){
 		const field = this.fields.get(fieldId);
 		if (!field) return;
 
@@ -352,7 +455,7 @@
 					field.ui.dropdown.wrapper.hidden = true;
 				}
 			},
-			1500
+			delay
 		);
 	}
 
@@ -455,6 +558,10 @@
 	closeModal() {
 		const field = this.fields.get(this.activeField);
 		if (!field) return;
+
+
+		this.updateFieldValue(this.activeField);
+
 		this.observer.unobserve(this.ui.terms.sentinel);
 		window.removeChildren(this.ui.terms.list);
 
@@ -522,24 +629,26 @@
 		if (!field) return;
 		if (this.ui.selected.querySelector(`[data-id="${termId}"]`)) return;
 
-		const item = window.getTemplate('selectedTerm');
-		if (!item) return;
+		this.ui.selected.append(this.getSelectedTermUI(term));
+	}
 
-		item.dataset.id = termId;
-		item.dataset.taxonomy = field.taxonomy;
-		item.querySelector('.item-name').textContent = term.path;
-		item.querySelector('button').title = `Remove ${term.name}`;
-
-		this.ui.selected.append(item);
+	getSelectedTermUI(term, showPath = true) {
+		return window.jvbTemplates.create('selectedTerm', term);
 	}
 	/******************************************************************
 	 FIELDS
 	 ******************************************************************/
 	scanExistingFields(container = document.body) {
-		container.querySelectorAll('[data-type="selector"]').forEach(
+		container.querySelectorAll('[data-type="selector"], [data-field-type="selector"]').forEach(
 			selector => {
 				try {
-					this.registerField(selector);
+					if (selector.dataset.lazy) {
+						this.lazyInit = true;
+					} else {
+						// Register field if not already registered
+						// registerField will check if already registered and return early if so
+						this.registerField(selector);
+					}
 				} catch (error) {
 					this.error.log(error, {
 						component: 'TaxonomySelector',
@@ -549,12 +658,41 @@
 				}
 			}
 		);
+		if (this.lazyInit) {
+			this.initObserver(container);
+		}
+	}
+
+	unregisterFields(container) {
+		container.querySelectorAll('[data-type="selector"],[data-field-type="selector"]').forEach(
+			selector=> {
+				this.fields.delete(selector.dataset.fieldId);
+			}
+		);
+	}
+	initObserver(container){
+		this.lazyObserver = new IntersectionObserver((entries) => {
+			entries.forEach(entry => {
+				if (entry.isIntersecting && entry.target.dataset.lazy) {
+					delete entry.target.dataset.lazy;
+					this.registerField(entry.target);
+					this.lazyObserver.unobserve(entry.target);
+				}
+			});
+		}, {rootMargin: '50px'});
+
+		container.querySelectorAll('[data-type="selector"][data-lazy], [data-field-type="selector"][data-lazy]').forEach(field => {
+			this.lazyObserver.observe(field);
+		});
 	}
 
 	registerField(element, options = {}) {
+		if (element.dataset.fieldId && this.fields.has(element.dataset.fieldId)) {
+			return element.dataset.fieldId; // Already registered
+		}
+
 		let input = element.querySelector('input[type="hidden"]');
 		if (!input && !Object.hasOwn(element.dataset, 'filter')) {
-			console.warn('TaxonomySelector: No hidden input found for field', element);
 			return;
 		}
 
@@ -566,7 +704,7 @@
 
 		let selectors = this.selectors.field;
 		const isFilter = Object.hasOwn(element.dataset,'filter') && element.dataset.filter === 'taxonomy';
-		let button = (isFilter) ? element : element.querySelector('button.taxonomy-toggle');
+		let button = (isFilter) ? element : element.querySelector('button.selector-toggle');
 
 		if (Object.keys(options).length === 0){
 			if (!button) return;
@@ -578,7 +716,6 @@
 				autocomplete: Object.hasOwn(button.dataset, 'autocomplete'),
 				creatable: Object.hasOwn(button.dataset, 'creatable')
 			};
-			if (Object.keys(options).length === 0) return;
 		} else if (Object.hasOwn(options, 'toggle')) {
 			button = document.querySelector(options.toggle);
 			selectors.toggle = options.toggle;
@@ -629,7 +766,18 @@
 		if (this.isInitializing) {
 			this.batchFetch.add(config.taxonomy);
 		}
-		this.updateFieldUI(fieldId);
+
+		if (element.offsetParent !== null) {
+			this.updateFieldUI(fieldId);
+		} else {
+			// Defer until visible
+			requestIdleCallback(() => {
+				if (element.offsetParent !== null) {
+					this.updateFieldUI(fieldId);
+				}
+			}, {timeout: 2000});
+
+		}
 
 		return fieldId;
 	}
@@ -695,7 +843,10 @@
 		const field = this.fields.get(fieldId);
 		if (!field) return;
 		let selected = Array.from(this.selectedTerms.get(fieldId));
-		field.ui.value.value = selected.join(',');
+		if (field.ui.value) {
+			field.ui.value.value = selected.join(',')??'';
+			field.ui.value.dispatchEvent(new Event('change', { bubbles: true }));
+		}
 	}
 
 	checkLimits(fieldId) {
@@ -728,9 +879,9 @@
 
 	updateFieldsForTaxonomy(taxonomy) {
 		let fields = Array.from(this.fields.values())
-			.filter(field => !field.checked && field.taxonomy === taxonomy);
+			.filter(field => field.taxonomy === taxonomy);
 		const hasItems = Array.from(this.store.data.values())
-			.some(term=>term.taxonomy === taxonomy);
+			.some(term => term && term.taxonomy === taxonomy);
 
 		fields.forEach(field => {
 			if (!field.toggle) return;
@@ -750,16 +901,17 @@
 			if (this.store.filters.page??1 === 1) {
 				window.removeChildren(this.ui.terms.list);
 			}
-			this.setMessage(true, this.store.filters.search === ''
+			this.setMessage(field,true, this.store.filters.search === ''
 				? `No matching ${field.plural}.`
 				: `No ${field.plural} found.`,
 				false);
 			if (this.ui.terms.sentinel) {
 				this.observer.unobserve(this.ui.terms.sentinel);
 			}
+			return;
 		}
 
-		this.setCreateButton(true);
+		this.setCreateButton(field,true);
 
 		if (this.ui.terms.sentinel) {
 			if (this.store.lastResponse?.has_more) {
@@ -772,112 +924,49 @@
 		const currentParent = this.store.filters.parent??0;
 		this.ui.nav.back.hidden = currentParent === 0;
 
-		const fragment = document.createDocumentFragment();
-		terms.forEach(term => {
-			const element = this.createTermElement({
-				show: showPath,
-				... term
-			});
-			if (element) {
-				fragment.append(element);
-			}
-		});
+		window.chunkIt(
+			terms,
+			(term) => this.createTermElement({show:showPath, ... term}),
+			(fragment) => this.ui.terms.list.append(fragment),
+			10
+		).then(()=>{});
 
 		if (terms.length > 0) {
-			this.setMessage(false);
+			this.setMessage(field,false);
 		}
-
-		this.ui.terms.list.append(fragment);
 	}
 	createTermElement(term) {
 		if (!term || !term.name) return null;
-
-		const item = window.getTemplate('termListItem');
-		item.dataset.id = term.id;
-
-		const isSelected = this.selectedTerms.get(this.activeField).has(term.id);
-		let [
-			checkbox,
-			label,
-			nameSpan
-		] = [
-			item.querySelector('input'),
-			item.querySelector('label'),
-			item.querySelector('span, .term-name')
-		];
-
-		let field = this.currentField();
-		let limitReached = field.limit > 0 && this.selectedTerms.get(this.activeField).size >= field.limit;
-		if (checkbox && label && nameSpan) {
-			[
-				checkbox.dataset.id,
-				checkbox.id,
-				checkbox.name,
-				checkbox.value,
-				checkbox.disabled,
-				checkbox.checked,
-				label.htmlFor,
-				label.title,
-				label.dataset.path,
-				nameSpan.textContent
-			] = [
-				term.id,
-				`${field.element.id}-${term.id}`,
-				`${field.element.id}-${field.taxonomy}-select`,
-				term.id,
-				!isSelected && limitReached,
-				isSelected,
-				`${field.element.id}-${term.id}`,
-				term.path??term.name,
-				term.path,
-				term.show ? term.path : term.name
-			];
-			if (term.hasChildren) {
-				const toggle = window.getTemplate('termChildrenToggle');
-				if (toggle) {
-					toggle.ariaLabel = `View ${field.plural} nested under ${term.name}`;
-					item.append(toggle);
-				}
-			}
-		}
-
-		return item;
+		return window.jvbTemplates.create('termListItem', term);
 	}
 
 	showAutocompleteTerms() {
 		const field = this.currentField();
-		const terms = this.currentTerms();
-		if (!field) return;
-
+		if (!field || !field.hasAutocomplete || !field.ui.dropdown?.list) return;
 		const dropdown = field.ui.dropdown.list;
-		if (!dropdown) return;
+		const terms = this.currentTerms();
 
 		window.removeChildren(dropdown);
 		if (terms.length === 0) {
-			this.setMessage(true, `No ${field.plural} found.`, false);
+			this.setMessage(field,true, `No ${field.plural} found.`, false);
 		} else {
-			terms.forEach(term => {
-				const item = this.createAutocompleteTerm(term);
-				if (item) {
-					dropdown.append(item);
-				}
-			});
-			this.setMessage(false);
+			window.chunkIt(
+				terms,
+				(term) => this.createAutocompleteTerm(term),
+				(fragment) => dropdown.append(fragment)
+			).then(()=>{});
+
+			this.setMessage(field,false);
 		}
-		this.setCreateButton(true);
+		this.setCreateButton(field,true);
 
 		if (field.ui.dropdown.wrapper) {
 			field.ui.dropdown.wrapper.hidden = false;
 		}
 	}
+
 	createAutocompleteTerm(term) {
-		const item = window.getTemplate('autocompleteItem');
-		if (!item) return;
-
-		item.dataset.id = term.id;
-		item.textContent = term.path || term.name;
-
-		return item;
+		return window.jvbTemplates.create('autocompleteItem', term);
 	}
 	/******************************************************************
 	 UI
@@ -886,16 +975,12 @@
 		const term = this.store.get(termId);
 		const field = this.fields.get(fieldId);
 		if (!term || !field) return;
+
 		//if the term already exists in the selected items, bail early
 		if (field.ui.selected && field.ui.selected.querySelector(`[data-id="${termId}"]`)) return;
 
-		const item = window.getTemplate('selectedTerm');
-		if (!item) return;
 
-		item.dataset.id = termId;
-		item.dataset.taxonomy = field.taxonomy;
-		item.querySelector('.item-name').textContent = term.path;
-		item.querySelector('button').title = `Remove ${term.name}`;
+		let item = this.getSelectedTermUI(term);
 
 		if (field.ui.selected) {
 			field.ui.selected.append(item);
@@ -926,13 +1011,7 @@
 			// Add new breadcrumb
 			const term = this.store.get(termId);
 			if (!term) return;
-
-			const crumb = window.getTemplate('termBreadcrumb');
-			if (!crumb) return;
-
-			crumb.dataset.id = termId;
-			crumb.textContent = term.name;
-			crumb.title = term.name;
+			const crumb = window.jvbTemplates.create('termBreadcrumb', term);
 
 			nav.append(crumb);
 		}
@@ -955,6 +1034,13 @@
 	/******************************************************************
 	 UTILITY
 	 ******************************************************************/
+	checkRendered(collection, term) {
+		if (!collection) return;
+		if (!Object.hasOwn(collection, term.taxonomy)) {
+			collection[term.taxonomy] = new Map();
+		}
+		return collection[term.taxonomy].has(term.id);
+	}
 	currentField() {
 		return this.fields.get(this.activeField)??false;
 	}
@@ -1000,13 +1086,11 @@
 			handlers[event]?.(data);
 		} catch (error) {
 			console.error(`Error handling store event "${event}":`, error);
-			this.setMessage(true, 'An error occurred loading data', false);
 		}
 	}
 	handleDataLoaded() {
 		const taxonomy = this.store.filters.taxonomy;
 
-		// Always update fields for loaded taxonomies (handles both single and batch)
 		if (taxonomy) {
 			const taxonomies = taxonomy.split(',').map(t => t.trim());
 			taxonomies.forEach(tax => this.updateFieldsForTaxonomy(tax));
@@ -1020,11 +1104,9 @@
 			this.showResults(true);
 			return;
 		}
-		this.setMessage(false);
 	}
 
 	showResults(isAutoComplete = false) {
-		this.setMessage(false);
 		const terms = this.store.getFiltered();
 		const filters = this.store.filters;
 		const isSearch = filters.search && filters.search.length > 0;
@@ -1034,7 +1116,10 @@
 			filters
 		});
 
-
+		if (!this.activeField && isAutoComplete) {
+			return;
+		}
+		this.setMessage(this.currentField(), false);
 		if (isAutoComplete) {
 			this.showAutocompleteTerms();
 		} else {
@@ -1050,19 +1135,37 @@
 
 	handleFetchError(error) {
 		const field = this.currentField();
-		const message = field
-			? `Failed to load ${field.plural}`
-			: 'Failed to load data';
+		this.setMessage(field, true, 'Something went wrong.', false);
 
-		this.setMessage(true, message, false);
+		const conf = this.container.open || field?.isFilter ? this.ui : field?.ui;
+		const p = conf?.message?.message;
+
+		if (p && !p.querySelector('.clear-cache-btn')) {
+			const btn = document.createElement('button');
+			btn.className = 'clear-cache-btn';
+			btn.type = 'button';
+			btn.textContent = 'Clear cache and try again';
+			btn.addEventListener('click', async () => {
+				btn.remove();
+				this.store.clearCache();
+				if (this.activeField && field) {
+					await this.store.setFilters({
+						taxonomy: field.taxonomy,
+						page: 1,
+						search: '',
+						parent: 0
+					});
+				}
+			});
+			p.appendChild(btn);
+		}
+
 		console.error('Store fetch error:', error);
 	}
 	async batchFetchTaxonomies() {
 		if (this.batchFetch.size === 0) return;
-
 		const taxonomies = Array.from(this.batchFetch);
 		this.batchFetch.clear();
-
 		try {
 			await this.store.setFilters({
 				taxonomy: taxonomies.join(','),
@@ -1087,14 +1190,14 @@
 	/**************************************************
 	 LOADING
 	**************************************************/
-	setCreateButton(show = true) {
-		const field = this.currentField();
-		if (!field || !field.canCreate || !this.creator) return;
+	setCreateButton(field, show = true) {
+		if (!field.canCreate || !this.creator) return;
 
 		const conf = (this.container.open) ? this.ui : field.ui;
 		if (!conf.create?.button || !conf.create?.span) return;
 
 		const createButton = conf.create.button;
+		createButton.hidden = !show;
 		const buttonSpan = conf.create.span;
 		const input = (this.container.open) ? conf.search.input : conf.search;
 		if (!input) return;
@@ -1114,46 +1217,66 @@
 		if (!field) return;
 
 		window.debouncer.cancel(`${field.id}-search-results`);
+
 		let data = {
 			taxonomy: field.taxonomy,
 			parent: this.store.filters.parent??0
 		}
-		//If it's autocomplete or the selector's search input, we just need the name
+
 		if (!this.container.open || this.ui.search.input.value !== '') {
 			data.name = (this.container.open) ? this.ui.search.input.value : field.ui.search.value;
 		} else {
-			//Otherwise, we've created it from the details element
 			data.parent = this.creator.ui.parent.value??data.parent;
 			data.name = this.creator.ui.name.value??false;
 		}
+
 		if (data.parent !== undefined && data.name) {
-			this.setMessage(true, `Creating "${data.name}"...`);
-			this.setCreateButton(false);
+			this.setMessage(field,true, `Creating "${data.name}"...`);
+			this.setCreateButton(field,false);
+
 			if (this.container.open) {
 				window.removeChildren(this.ui.terms.list);
 			} else {
 				field.ui.search.disabled = true;
-				window.removeChildren(field.ui.dropdown.list);
 				if (field.ui.dropdown.wrapper) {
 					field.ui.dropdown.wrapper.hidden = false;
 				}
 			}
+
 			let term = await this.creator.handleTermCreation(data);
+
 			if (term) {
+				// Stop any typeLoop animation and show success message WITHOUT typeLoop
+				this.setMessage(field,true, `"${term.name}" created!`, false);
+
 				this.addSelected(term.id, field.id);
+				this.updateFieldValue(field.id);
+				// For autocomplete, show the newly created term in dropdown
+				if (!this.container.open && field.ui.dropdown.list) {
+					window.removeChildren(field.ui.dropdown.list);
+					const termElement = this.createAutocompleteTerm(term);
+					if (termElement) {
+						termElement.classList.add('newly-created');
+						field.ui.dropdown.list.append(termElement);
+					}
+				}
+				this.scheduleHideDropdown(field.id, 300);
+				this.setMessage(field,false);
+			} else {
+				// Creation failed - hide immediately
+				this.setMessage(field,false);
+				if (!this.container.open && field.ui.dropdown.wrapper) {
+					field.ui.dropdown.wrapper.hidden = true;
+				}
 			}
+
 			if (!this.container.open) {
 				field.ui.search.disabled = false;
 				field.ui.search.value = '';
 			}
-			this.scheduleHideDropdown(field.id);
-			this.setMessage(false);
 		}
 	}
-	setMessage(show = true, message = '', type = true) {
-		const field = this.currentField();
-		if (!field) return;
-
+	setMessage(field, show = true, message = '', type = true) {
 		const conf = this.container.open||field.isFilter ? this.ui : (field.isFilter ? null : field.ui);
 		if (!conf?.message?.message) return;
 
@@ -1222,6 +1345,7 @@
 			this.observer?.unobserve(this.ui.terms.sentinel);
 		}
 		this.observer?.disconnect();
+		this.lazyObserver?.disconnect();
 
 		// Remove event listeners
 		document.removeEventListener('click', this.clickHandler);

--
Gitblit v1.10.0