svelte-multiselect
Version:
Svelte multi-select component
92 lines (91 loc) • 3.35 kB
JavaScript
/**
* Get the label key from an option object or the option itself if it's a string or number
* @param opt Option
* @returns Label
*/
export const get_label = (opt) => {
if (opt instanceof Object) {
if (opt.label === undefined) {
console.error(`MultiSelect option ${JSON.stringify(opt)} is an object but has no label key`);
}
return opt.label;
}
return `${opt}`;
};
/**
* This function is used extract CSS strings from a {selected, option} style object to be used in the style attribute of the option.
* If the style is a string, it will be returned as is
* @param option Option
* @param key
* @returns CSS string
*/
export function get_style(option, key = null) {
let css_str = ``;
if (![`selected`, `option`, null].includes(key)) {
console.error(`MultiSelect: Invalid key=${key} for get_style`);
}
if (typeof option == `object` && option.style) {
if (typeof option.style == `string`) {
css_str = option.style;
}
if (typeof option.style == `object`) {
if (key && key in option.style)
return option.style[key] ?? ``;
else {
console.error(`Invalid style object for option=${JSON.stringify(option)}`);
}
}
}
// ensure css_str ends with a semicolon
if (css_str.trim() && !css_str.trim().endsWith(`;`))
css_str += `;`;
return css_str;
}
/**
* Highlights text nodes that matching the string query
* @param element Parent element
* @param query Search query
* @param noMatchingOptionsMsg Text for empty node
*/
export function highlight_matching_nodes(element, query, noMatchingOptionsMsg) {
if (typeof CSS == `undefined` || !CSS.highlights || !query)
return; // abort if CSS highlight API not supported
// clear previous ranges from HighlightRegistry
CSS.highlights.clear();
const tree_walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, {
acceptNode: (node) => {
// don't highlight text in the "no matching options" message
if (node?.textContent === noMatchingOptionsMsg)
return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
},
});
const text_nodes = [];
let current_node = tree_walker.nextNode();
while (current_node) {
text_nodes.push(current_node);
current_node = tree_walker.nextNode();
}
// iterate over all text nodes and find matches
const ranges = text_nodes.map((el) => {
const text = el.textContent?.toLowerCase();
const indices = [];
let start_pos = 0;
while (text && start_pos < text.length) {
const index = text.indexOf(query, start_pos);
if (index === -1)
break;
indices.push(index);
start_pos = index + query.length;
}
// create range object for each str found in the text node
return indices.map((index) => {
const range = new Range();
range.setStart(el, index);
range.setEnd(el, index + query.length);
return range;
});
});
// create Highlight object from ranges and add to registry
CSS.highlights.set(`sms-search-matches`, new Highlight(...ranges.flat()));
}