All files / lib tooltips.js

0% Statements 0/84
0% Branches 0/1
0% Functions 0/1
0% Lines 0/84

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                                                                                                                                                                                         
import tippy from 'sveltejs-tippy';
import xss from 'xss';
import { displayPattern } from './KeyboardHint.svelte';
import { omit } from './utils';
 
/**
 * @typedef {object} TooltipParameters
 * @property {string} text - The text to display in the tooltip.
 * @property {string} [keyboard] - The keyboard shortcut to display in the tooltip.
 * @property {number} [delay] - The delay before showing the tooltip.
 * @property {`${'top'|'right'|'bottom'|'left'}${''|`-${'start'|'end'}`}`} [placement] - The placement of the tooltip .
 */
 
/** @param {string | [string, number] | TooltipParameters | undefined} parameters */
function props(parameters) {
	const base = { arrow: svgArrow };
	let content = '';
	let delay = 50;
	if (!parameters) {
		return {
			...base,
			content: '',
			delay: [delay, 0]
		};
	}
 
	if (typeof parameters === 'string') content = parameters;
	else if (Array.isArray(parameters)) [content, delay] = parameters;
	else {
		return {
			...base,
			...omit(parameters, 'text', 'keyboard'),
			delay: [delay, 0],
			allowHTML: true,
			content:
				xss(parameters.text, {
					allowList: {
						kbd: ['class']
					}
				}) +
				// XXX: needs :global styling from KeyboardHint.svelte
				(parameters.keyboard
					? ' <kbd class=hint>' +
						displayPattern(parameters.keyboard)
							.entries()
							.map(([i, part]) =>
								i % 2 === 0
									? `<kbd>${part}</kbd>`
									: `<span class=separator>${part}</span>`
							)
							.toArray()
							.join('') +
						'</kbd>'
					: '')
		};
	}
 
	return { ...base, content, delay: [delay, 0] };
}
 
/**
 * Create a tooltip
 * @param {HTMLElement & {_tippy?: {destroy: () => void; setProps: (props: unknown) => void}}} node
 * @param {string | [string, number] | TooltipParameters | undefined} parameters text or [text, delay] or tippy.js options
 */
export function tooltip(node, parameters) {
	const properties = props(parameters);
	tippy(node, properties);
 
	if (properties.content.length <= 0) {
		delete node.dataset.tooltipContent;
		node._tippy?.destroy();
	}
 
	return {
		/** @param {string | [string, number] | TooltipParameters | undefined} parameters */
		update(parameters) {
			const properties = props(parameters);
			node.dataset.tooltipContent = properties.content;
			if (!node._tippy) tippy(node, properties);
			if (properties.content.length <= 0) node._tippy?.destroy();
			node._tippy?.setProps(properties);
		},
		destroy() {
			delete node.dataset.tooltipContent;
			node._tippy?.destroy();
		}
	};
}
 
const svgArrow =
	'<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><path d="M0 6s1.796-.013 4.67-3.615C5.851.9 6.93.006 8 0c1.07-.006 2.148.887 3.343 2.385C14.233 6.005 16 6 16 6H0z"></svg>';