45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
const downloads = new WeakMap<HTMLElement, () => void>();
|
|
|
|
/** Keep a direct user-gesture fallback until replaced or the editor is removed. */
|
|
export function offerBuilderDownload(
|
|
host: HTMLElement,
|
|
content: string,
|
|
type: string,
|
|
filename: string,
|
|
): void {
|
|
const outlet = host.querySelector<HTMLElement>("[data-builder-download]");
|
|
if (!outlet) throw new Error("Download outlet is unavailable");
|
|
const url = URL.createObjectURL(new Blob([content], { type }));
|
|
const link = host.ownerDocument.createElement("a");
|
|
link.href = url;
|
|
link.download = filename;
|
|
link.textContent = `Download prepared file: ${filename}`;
|
|
let disposed = false;
|
|
const observer = new MutationObserver(() => {
|
|
if (!host.isConnected) dispose();
|
|
});
|
|
const dispose = () => {
|
|
if (disposed) return;
|
|
disposed = true;
|
|
observer.disconnect();
|
|
URL.revokeObjectURL(url);
|
|
link.remove();
|
|
if (downloads.get(host) === dispose) downloads.delete(host);
|
|
};
|
|
try {
|
|
downloads.get(host)?.();
|
|
outlet.replaceChildren(link);
|
|
observer.observe(host.ownerDocument.documentElement, { childList: true, subtree: true });
|
|
downloads.set(host, dispose);
|
|
} catch (error) {
|
|
dispose();
|
|
throw error;
|
|
}
|
|
// A blocked automatic click must not destroy the direct-click fallback.
|
|
try {
|
|
link.click();
|
|
} catch {
|
|
/* The visible link remains available. */
|
|
}
|
|
}
|