UNPKG

trellis

Version:

Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications

152 lines (149 loc) 4.04 kB
import { fuzzyRanges, fuzzyScore } from "./chunk-EEQNVCWD.js"; // src/combobox/core/index.ts function defaultFilter(query, items) { const scored = []; for (const item of items) { const score = fuzzyScore(query, item.label); if (score > 0) scored.push({ item, score }); } scored.sort((a, b) => b.score - a.score); return scored.map(({ item }) => item); } function createComboboxCore(config) { const { items = [], onSelect, closeOnSelect = true, wrap = true, filter = defaultFilter, value = null, resetQueryOnClose = true } = config; const baseState = { open: false, query: "", items, loading: false, selectedId: value }; let state = deriveState({ ...baseState, results: items, activeIndex: 0 }); const subscribers = /* @__PURE__ */ new Set(); const notify = () => subscribers.forEach((fn) => fn()); function deriveState(partial) { const results = filter(partial.query, partial.items); let activeIndex = partial.activeIndex; if (results.length === 0) { activeIndex = -1; } else if (activeIndex < 0) { activeIndex = 0; } else if (activeIndex >= results.length) { activeIndex = wrap ? 0 : results.length - 1; } const selectedItem = partial.items.find((i) => i.id === partial.selectedId) ?? null; const highlight = results.map((item) => fuzzyRanges(partial.query, item.label)); return { ...partial, results, activeIndex, selectedItem, empty: partial.open && !partial.loading && results.length === 0, highlight }; } const actions = { open: () => { state = deriveState({ ...state, open: true }); notify(); }, close: () => { state = deriveState({ ...state, open: false, query: resetQueryOnClose ? "" : state.query, activeIndex: -1 }); notify(); }, toggle: () => { state = deriveState({ ...state, open: !state.open, query: !state.open ? "" : state.query, activeIndex: !state.open ? -1 : state.activeIndex }); notify(); }, setQuery: (query) => { state = deriveState({ ...state, query, activeIndex: 0 }); notify(); }, move: (delta) => { if (state.results.length === 0) return; const next = state.activeIndex + delta; let activeIndex; if (wrap) { activeIndex = (next % state.results.length + state.results.length) % state.results.length; } else { activeIndex = Math.min(Math.max(next, 0), state.results.length - 1); } state = deriveState({ ...state, activeIndex }); notify(); }, setActiveIndex: (index) => { state = deriveState({ ...state, activeIndex: index }); notify(); }, select: (id) => { const item = id !== void 0 ? state.items.find((i) => i.id === id) : state.results[state.activeIndex]; if (!item || item.disabled) return null; if (onSelect) onSelect(item); state = deriveState({ ...state, selectedId: item.id, open: closeOnSelect ? false : state.open, query: closeOnSelect && resetQueryOnClose ? "" : state.query, activeIndex: -1 }); notify(); return item; }, clear: () => { state = deriveState({ ...state, selectedId: null, query: "" }); notify(); }, setLoading: (loading) => { state = deriveState({ ...state, loading }); notify(); }, setItems: (nextItems) => { state = deriveState({ ...state, items: nextItems, activeIndex: 0, loading: false }); notify(); }, setValue: (id) => { state = deriveState({ ...state, selectedId: id }); notify(); } }; const core = { get state() { return state; }, actions, subscribe: (listener) => { subscribers.add(listener); return () => subscribers.delete(listener); } }; return core; } export { createComboboxCore };