All files / src/autocomplete util.js

40.24% Statements 33/82
31.81% Branches 14/44
72.72% Functions 8/11
49.23% Lines 32/65

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114  2x 2x 2x 8x 8x 8x   4x 2x 2x         5x 5x 5x     5x 5x       5x 5x                             1x 1x 1x 1x 1x         1x   1x 1x 1x 1x 1x 1x 1x 1x       1x                     2x                                                                              
export function groupData (items) {
	const nogroup = [];
	const _groups = {};
	items.forEach(item => {
		Iif (!item.group) return nogroup.push(item);
		_groups[item.group] = _groups[item.group] || { name: item.group, items: [] };
		_groups[item.group].items.push(item);
	});
	const groups = Object.values(_groups).filter(g => !!g.items.length);
	Iif (nogroup.length) groups.unshift({ items: nogroup });
	return groups;
}
 
 
export function highlight (listEl) {
	requestAnimationFrame(() => {
		const selectedEl = listEl.querySelector('.selected');
		Iif (!selectedEl) return;
 
		// going up
		let top = selectedEl.offsetTop;
		Iif (listEl.scrollTop > top) listEl.scrollTo({ top });
 
		// going down
		else {
			top = selectedEl.offsetTop + selectedEl.offsetHeight - listEl.offsetHeight;
			Iif (listEl.scrollTop < top) listEl.scrollTo({ top });
		}
	});
}
 
 
// quick and instant recalc to minimise visual flyover of the dropdown across the screen
export function quickPositionRecalc (listEl, inputEl) {
	const inputBox = inputEl.getBoundingClientRect();
	listEl.style.top = (inputBox.top + inputBox.height + 3) + 'px';
	listEl.style.left = inputBox.left + 'px';
}
 
 
export function recalculateListPosition (listEl, inputEl, elevated) {
	Iif (elevated) quickPositionRecalc(listEl, inputEl);
	requestAnimationFrame(() => {
		Iif (!listEl || !listEl.style) return;
		const inputBox = inputEl.getBoundingClientRect();
		Iif (elevated) {
			listEl.style.top = (inputBox.top + inputBox.height + 3) + 'px';
			listEl.style.left = inputBox.left + 'px';
		}
		else {
			listEl.style.top = (inputBox.height + 3) + 'px';
		}
		listEl.style.minWidth = inputBox.width + 'px';
		listEl.style.height = 'auto';
		const listBox = listEl.getBoundingClientRect();
		const listT = listBox.top;
		const listH = listBox.height;
		const winH = window.innerHeight;
		let maxH = 0;
		Iif (listT + listH + 10 > winH) {
			maxH = Math.max(winH - listT - 10, 100);
			listEl.style.height = maxH + 'px';
		}
		Iif (listT + maxH + 10 > winH) {
			listEl.style.height = listBox.height + 'px';
			if (elevated) listEl.style.top = (inputBox.top - listBox.height - 3) + 'px';
			else listEl.style.top = (-listBox.height - 3) + 'px';
		}
	});
}
 
 
 
export function deepCopy (o) {
	return structuredClone(o);
}
 
 
export function fuzzy (hay = '', s = '') {
	if (s.length === 0) return true;
	if (hay.length === 0) return false;
	if (s.length > hay.length) return false;
	if (s === hay) return true;
	hay = hay.toLowerCase();
	s = s.toLowerCase();
	let n = -1;
	for (const l of s) if (!~(n = hay.indexOf(l, n + 1))) return false;
	return true;
}
 
 
export function emphasize (str, q) {
	if (!q) return str;
	str = '' + str;
	let idx = 0;
	const low = str.toLowerCase();
 
	// string includes the whole query block
	if (low.includes(q)) return str.replace(new RegExp(`(${q})`, 'ig'), '<b>$1</b>');
 
	// string includes the scattered query
	const stra = str.split('');
	q = q.toLowerCase();
	for (const l of q) {
		idx = low.indexOf(l, idx);
		const letter = stra[idx];
		if (letter) {
			stra.splice(idx, 1, `<b>${letter}</b>`);
			idx += 1;
		}
	}
	return stra.join('');
}