import { n as __exportAll } from "./chunk-B-1-B7_t.js";
import { c as ComboboxAnchor_default, i as ComboboxInput_default, l as Combobox_default, n as ComboboxItemIndicator_default, o as ComboboxEmpty_default, r as ComboboxItem_default, s as ComboboxContent_default } from "./combobox-D0Svg8GU.js";
import { t as FieldFrame_default } from "./field-frame-DhCOWcMP.js";
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, normalizeClass, onBeforeUnmount, openBlock, ref, renderList, shallowRef, toDisplayString, unref, useModel, watch, withCtx } from "vue";
import { ChevronsUpDown, Loader2, Search } from "@lucide/vue";
//#region src/composables/useAsyncSearch.ts
/**
* Shared search/debounce/in-flight-token plumbing for async option
* compositions (AsyncSelectPicker, …).
*
*   const { query, results, isLoading, onInput, dispose } = useAsyncSearch({
*     onSearch: (q) => fetch(`/api/users?q=${q}`).then(r => r.json()),
*     debounceMs: 250,
*     minSearchLength: 1,
*     initialOptions: [],
*   })
*
* The composable is intentionally HTTP-agnostic — `onSearch` is just a
* Promise-returning callback supplied by the consumer.
*/
function useAsyncSearch(options) {
	const query = ref("");
	const results = shallowRef(options.initialOptions ? [...options.initialOptions] : []);
	const isLoading = ref(false);
	let token = 0;
	let timer = null;
	const minLen = options.minSearchLength ?? 1;
	const debounceMs = options.debounceMs ?? 250;
	function clearTimer() {
		if (timer !== null) {
			clearTimeout(timer);
			timer = null;
		}
	}
	function runSearch(q) {
		const fn = options.onSearch;
		const myToken = ++token;
		if (!fn || q.length < minLen) {
			isLoading.value = false;
			results.value = options.initialOptions ? [...options.initialOptions] : [];
			return;
		}
		isLoading.value = true;
		fn(q).then((next) => {
			if (myToken !== token) return;
			results.value = Array.isArray(next) ? next : [];
			isLoading.value = false;
		}).catch(() => {
			if (myToken !== token) return;
			results.value = [];
			isLoading.value = false;
		});
	}
	function trigger(next) {
		query.value = next;
		clearTimer();
		if (debounceMs <= 0) {
			runSearch(next);
			return;
		}
		timer = setTimeout(() => {
			timer = null;
			runSearch(next);
		}, debounceMs);
	}
	function dispose() {
		clearTimer();
		token++;
		isLoading.value = false;
	}
	onBeforeUnmount(dispose);
	return {
		query,
		results,
		isLoading,
		trigger,
		dispose
	};
}
//#endregion
//#region src/components/compositions/elements/picker/AsyncSelectPicker.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1 = {
	key: 0,
	class: "truncate text-sm",
	"data-testid": "async-picker-label"
};
var _hoisted_2 = {
	key: 1,
	class: "text-sm text-muted-foreground"
};
var _hoisted_3 = { class: "flex items-center border-b px-2 py-1.5" };
var _hoisted_4 = {
	key: 0,
	class: "px-2 py-2 text-sm text-muted-foreground",
	"data-testid": "async-picker-loading-text"
};
var AsyncSelectPicker_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "AsyncSelectPicker",
	props: /* @__PURE__ */ mergeModels({
		modelValue: {},
		id: {},
		name: {},
		label: {},
		hideLabel: { type: Boolean },
		placeholder: {},
		required: { type: Boolean },
		readonly: { type: Boolean },
		disabled: { type: Boolean },
		helpText: {},
		errors: { default: () => [] },
		onSearch: {},
		initialOptions: { default: () => [] },
		minSearchLength: { default: 1 },
		debounceMs: { default: 250 },
		loadingText: { default: "Loading..." },
		emptyText: { default: "No results" }
	}, {
		"modelValue": {},
		"modelModifiers": {}
	}),
	emits: ["update:modelValue"],
	setup(__props, { expose: __expose }) {
		/**
		* Props-driven async select picker composition. but presented as a button-style
		* picker — the trigger reads as a single selected value with a chevron,
		* and the Combobox panel appears beneath it.
		*
		*   <AsyncSelectPicker
		*     v-model="userId"
		*     label="User"
		*     :on-search="(q) => fetch(`/users?q=${q}`).then(r => r.json())"
		*   />
		*/
		const props = __props;
		const model = useModel(__props, "modelValue");
		function normaliseOption(opt) {
			if (typeof opt === "string") return {
				label: opt,
				value: opt,
				disabled: false
			};
			return {
				label: opt.label,
				value: String(opt.value),
				disabled: Boolean(opt.disabled)
			};
		}
		const { query, results, isLoading, trigger } = useAsyncSearch({
			onSearch: props.onSearch,
			initialOptions: props.initialOptions,
			minSearchLength: props.minSearchLength,
			debounceMs: props.debounceMs
		});
		watch(() => props.initialOptions, (next) => {
			if (query.value.length < props.minSearchLength) results.value = next ? [...next] : [];
		});
		const normalisedResults = computed(() => (results.value ?? []).map(normaliseOption));
		const normalisedAll = computed(() => {
			const initial = (props.initialOptions ?? []).map(normaliseOption);
			const fromResults = normalisedResults.value;
			const merged = /* @__PURE__ */ new Map();
			for (const o of initial) merged.set(o.value, o);
			for (const o of fromResults) merged.set(o.value, o);
			return Array.from(merged.values());
		});
		const currentValue = computed(() => {
			if (model.value === void 0 || model.value === null) return;
			return String(model.value);
		});
		const selectedLabel = computed(() => {
			if (currentValue.value === void 0) return "";
			const match = normalisedAll.value.find((o) => o.value === currentValue.value);
			return match ? match.label : currentValue.value;
		});
		function onInput(event) {
			const next = event.target.value;
			trigger(next);
		}
		function onUpdate(next) {
			if (Array.isArray(next)) {
				model.value = next[0] === void 0 ? null : next[0];
				return;
			}
			model.value = next === void 0 ? null : next;
		}
		function onOpenChange(open) {
			if (open && query.value === "") results.value = props.initialOptions ? [...props.initialOptions] : [];
		}
		__expose({ trigger });
		return (_ctx, _cache) => {
			return openBlock(), createBlock(unref(FieldFrame_default), {
				label: __props.label,
				required: __props.required,
				"hide-label": __props.hideLabel,
				for: __props.id,
				"help-text": __props.helpText,
				errors: __props.errors
			}, {
				default: withCtx(() => [createVNode(unref(Combobox_default), {
					"model-value": currentValue.value,
					disabled: __props.disabled || __props.readonly,
					"ignore-filter": "",
					"onUpdate:modelValue": onUpdate,
					"onUpdate:open": onOpenChange
				}, {
					default: withCtx(() => [createVNode(unref(ComboboxAnchor_default), {
						"data-element-name": __props.name ?? void 0,
						"data-testid": "async-picker-anchor",
						class: normalizeClass(["justify-between text-left", __props.errors.length ? "border-destructive ring-destructive" : ""])
					}, {
						default: withCtx(() => [selectedLabel.value ? (openBlock(), createElementBlock("span", _hoisted_1, toDisplayString(selectedLabel.value), 1)) : (openBlock(), createElementBlock("span", _hoisted_2, toDisplayString(__props.placeholder ?? "Select…"), 1)), createVNode(unref(ChevronsUpDown), { class: "ml-2 h-4 w-4 shrink-0 opacity-50" })]),
						_: 1
					}, 8, ["data-element-name", "class"]), createVNode(unref(ComboboxContent_default), null, {
						default: withCtx(() => [
							createElementVNode("div", _hoisted_3, [
								createVNode(unref(Search), { class: "mr-2 h-4 w-4 shrink-0 opacity-50" }),
								createVNode(unref(ComboboxInput_default), {
									id: __props.id,
									"model-value": unref(query),
									placeholder: __props.placeholder ?? "Search…",
									class: "h-8",
									"data-testid": "async-picker-input",
									onInput
								}, null, 8, [
									"id",
									"model-value",
									"placeholder"
								]),
								unref(isLoading) ? (openBlock(), createBlock(unref(Loader2), {
									key: 0,
									"data-testid": "async-picker-loader",
									class: "ml-2 h-4 w-4 shrink-0 animate-spin text-muted-foreground"
								})) : createCommentVNode("", true)
							]),
							unref(isLoading) ? (openBlock(), createElementBlock("div", _hoisted_4, toDisplayString(__props.loadingText), 1)) : normalisedResults.value.length === 0 ? (openBlock(), createBlock(unref(ComboboxEmpty_default), { key: 1 }, {
								default: withCtx(() => [createTextVNode(toDisplayString(__props.emptyText), 1)]),
								_: 1
							})) : createCommentVNode("", true),
							(openBlock(true), createElementBlock(Fragment, null, renderList(normalisedResults.value, (opt) => {
								return openBlock(), createBlock(unref(ComboboxItem_default), {
									key: opt.value,
									value: opt.value,
									disabled: opt.disabled
								}, {
									default: withCtx(() => [createVNode(unref(ComboboxItemIndicator_default)), createTextVNode(" " + toDisplayString(opt.label), 1)]),
									_: 2
								}, 1032, ["value", "disabled"]);
							}), 128))
						]),
						_: 1
					})]),
					_: 1
				}, 8, ["model-value", "disabled"])]),
				_: 1
			}, 8, [
				"label",
				"required",
				"hide-label",
				"for",
				"help-text",
				"errors"
			]);
		};
	}
});
//#endregion
//#region src/components/compositions/elements/picker/AsyncSelectPicker.vue
var AsyncSelectPicker_exports = /* @__PURE__ */ __exportAll({ default: () => AsyncSelectPicker_default });
var AsyncSelectPicker_default = AsyncSelectPicker_vue_vue_type_script_setup_true_lang_default;
//#endregion
export { AsyncSelectPicker_exports as n, AsyncSelectPicker_default as t };

//# sourceMappingURL=AsyncSelectPicker-CyYYUzQ4.js.map