vue-typeahead3
Version:
A super lightweight typeahead / autocompletion component for Vue.js 3. It's written in TypeScript using Vue.js 3 Composition API.
162 lines (161 loc) • 6.32 kB
JavaScript
import { defineComponent, ref, toRefs, computed, watch, openBlock, createElementBlock, createElementVNode, withDirectives, normalizeClass, withKeys, vModelText, Fragment, renderList, toDisplayString, createTextVNode, unref, createCommentVNode, pushScopeId, popScopeId } from "vue";
var Typeahead_vue_vue_type_style_index_0_scoped_true_lang = "";
var _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _withScopeId = (n) => (pushScopeId("data-v-9737757a"), n = n(), popScopeId(), n);
const _hoisted_1 = { class: "typeahead-container" };
const _hoisted_2 = { class: "search-container" };
const _hoisted_3 = ["placeholder"];
const _hoisted_4 = {
key: 0,
class: "results-container"
};
const _hoisted_5 = { class: "results" };
const _hoisted_6 = ["onClick", "onMouseover"];
const _hoisted_7 = { class: "category" };
const _hoisted_8 = { key: 1 };
const _hoisted_9 = {
key: 1,
class: "results-container"
};
const _hoisted_10 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ createElementVNode("ul", { class: "results" }, [
/* @__PURE__ */ createElementVNode("li", null, "No results found!")
], -1));
const _hoisted_11 = [
_hoisted_10
];
const _sfc_main = defineComponent({
__name: "Typeahead",
props: {
modelValue: {},
suggestions: {},
placeholder: { default: "Type to search..." },
searchKey: { default: "value" },
categoryKey: { default: "category" },
valueKey: { default: "value" },
maxResults: { default: 5 }
},
emits: ["update:modelValue"],
setup(__props, { expose: __expose, emit: __emit }) {
const props = __props;
const emit = __emit;
const searchTerm = ref("");
const selected = ref(false);
const focused = ref(-1);
const { valueKey, suggestions, searchKey, modelValue } = toRefs(props);
const results = computed(
() => {
var _a;
if (!((_a = searchTerm == null ? void 0 : searchTerm.value) == null ? void 0 : _a.length) || (selected == null ? void 0 : selected.value))
return [];
return suggestions.value.filter(
(entry) => entry[searchKey.value].toLowerCase().includes(searchTerm.value.toLowerCase())
);
}
);
const select = (result) => {
searchTerm.value = result[valueKey.value];
selected.value = true;
return emit("update:modelValue", result);
};
__expose({ select });
const handleArrow = (dir) => {
if (dir < 0) {
if (focused.value > 0) {
focused.value--;
}
} else if (dir > 0 && focused.value < results.value.length - 1) {
focused.value++;
}
};
const handleEsc = () => {
searchTerm.value = "";
selected.value = false;
focused.value = -1;
return emit("update:modelValue", void 0);
};
const handleClear = () => {
if (!searchTerm.value) {
handleEsc();
}
};
const handleSelect = () => {
const result = results.value[focused.value];
select(result);
};
const focus = (index) => {
focused.value = index;
};
const isFocused = (index) => {
return index === focused.value;
};
watch(modelValue, (newVal, oldVal) => {
if (newVal === oldVal) {
return;
}
if (newVal && newVal[props.valueKey] !== (searchTerm == null ? void 0 : searchTerm.value)) {
select(newVal);
}
if (!newVal && searchTerm.value && newVal[props.valueKey] !== searchTerm.value) {
handleClear();
}
});
watch(searchTerm, (newVal, oldVal) => {
if (newVal !== oldVal && (!newVal || newVal.length === 0)) {
selected.value = false;
focused.value = -1;
return emit("update:modelValue", void 0);
}
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", _hoisted_1, [
createElementVNode("div", _hoisted_2, [
withDirectives(createElementVNode("input", {
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchTerm.value = $event),
type: "search",
placeholder: props.placeholder,
class: normalizeClass({ "has-results": results.value.length }),
onSearch: handleClear,
onKeyup: [
_cache[1] || (_cache[1] = withKeys(($event) => handleArrow(-1), ["up"])),
_cache[2] || (_cache[2] = withKeys(($event) => handleArrow(1), ["down"])),
withKeys(handleEsc, ["esc"]),
withKeys(handleSelect, ["enter"])
]
}, null, 42, _hoisted_3), [
[vModelText, searchTerm.value]
])
]),
results.value.length && !selected.value ? (openBlock(), createElementBlock("div", _hoisted_4, [
createElementVNode("ul", _hoisted_5, [
(openBlock(true), createElementBlock(Fragment, null, renderList(results.value, (result, index) => {
return openBlock(), createElementBlock(Fragment, { key: index }, [
index < _ctx.maxResults ? (openBlock(), createElementBlock("li", {
key: 0,
class: normalizeClass({ focused: isFocused(index) }),
onClick: ($event) => select(result),
onMouseover: ($event) => focus(index),
onMouseleave: _cache[3] || (_cache[3] = ($event) => focus(-1))
}, [
createElementVNode("h5", _hoisted_7, toDisplayString(result[_ctx.categoryKey]), 1),
createTextVNode(" " + toDisplayString(result[unref(valueKey)]), 1)
], 42, _hoisted_6)) : createCommentVNode("", true),
results.value.length === 0 ? (openBlock(), createElementBlock("li", _hoisted_8, "Test")) : createCommentVNode("", true)
], 64);
}), 128))
])
])) : searchTerm.value && !results.value.length && !selected.value ? (openBlock(), createElementBlock("div", _hoisted_9, _hoisted_11)) : createCommentVNode("", true)
]);
};
}
});
var Typeahead = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9737757a"]]);
Typeahead.install = function(app) {
app.component(Typeahead.name, Typeahead);
};
export { Typeahead as default };