import { customElement, property } from 'lit/decorators.js'; import { html } from 'lit'; import { requestInclude } from './request.js'; import { WaIncludeErrorEvent } from '../../events/include-error.js'; import { WaLoadEvent } from '../../events/load.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './include.styles.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import type { CSSResultGroup } from 'lit'; /** * @summary Includes give you the power to embed external HTML files into the page. * @documentation https://backers.webawesome.com/docs/components/include * @status stable * @since 2.0 * * @event wa-load - Emitted when the included file is loaded. * @event {{ status: number }} wa-error - Emitted when the included file fails to load due to an error. */ @customElement('wa-include') export default class WaInclude extends WebAwesomeElement { static styles: CSSResultGroup = [componentStyles, styles]; /** * The location of the HTML file to include. Be sure you trust the content you are including as it will be executed as * code and can result in XSS attacks. */ @property() src: string; /** The fetch mode to use. */ @property() mode: 'cors' | 'no-cors' | 'same-origin' = 'cors'; /** * Allows included scripts to be executed. Be sure you trust the content you are including as it will be executed as * code and can result in XSS attacks. */ @property({ attribute: 'allow-scripts', type: Boolean }) allowScripts = false; private executeScript(script: HTMLScriptElement) { // Create a copy of the script and swap it out so the browser executes it const newScript = document.createElement('script'); [...script.attributes].forEach(attr => newScript.setAttribute(attr.name, attr.value)); newScript.textContent = script.textContent; script.parentNode!.replaceChild(newScript, script); } @watch('src') async handleSrcChange() { try { const src = this.src; const file = await requestInclude(src, this.mode); // If the src changed since the request started do nothing, otherwise we risk overwriting a subsequent response if (src !== this.src) { return; } if (!file.ok) { this.dispatchEvent(new WaIncludeErrorEvent({ status: file.status })); return; } this.innerHTML = file.html; if (this.allowScripts) { [...this.querySelectorAll('script')].forEach(script => this.executeScript(script)); } this.dispatchEvent(new WaLoadEvent()); } catch { this.dispatchEvent(new WaIncludeErrorEvent({ status: -1 })); } } render() { return html``; } } declare global { interface HTMLElementTagNameMap { 'wa-include': WaInclude; } }