import { css, html } from "lit"; import { BaseElement } from "../../core/base-element.ts"; import { CommonIframeSandboxElement as _, IPC, } from "@commontools/iframe-sandbox"; /** * CTIframe - An iframe to execute arbitrary scripts * * See `@commontools/iframe-sandbox` for security details. * * @element ct-iframe * * @attr {string} src - String representation of HTML content to load within an iframe * @attr {object} context - Cell context * * @event {CustomEvent} load - The iframe was successfully loaded * @event {CustomEvent} fix - Dispatched when user clicks "Fix" on an error modal * * @example * */ export class CTIframe extends BaseElement { static override properties = { src: { type: String }, context: { type: Object }, _errorDetails: { state: true }, }; declare src: string; // HACK: The UI framework already translates the top level cell into updated // properties, but we want to only have to deal with one type of listening, so // we'll add a an extra level of indirection with the "context" property. declare context: object | null; declare _errorDetails: IPC.GuestError | null; constructor() { super(); this.src = ""; this.context = null; this._errorDetails = null; } static override styles = [ BaseElement.baseStyles, css` :host { display: block; width: 100%; height: 100%; } .error-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; } .error-content { background-color: var( --ct-theme-color-surface, var(--ct-color-white, #ffffff) ); padding: var(--ct-theme-spacing-loose, 1.25rem); border-radius: var( --ct-theme-border-radius, var(--ct-border-radius-md, 0.375rem) ); max-width: 80%; max-height: 80%; overflow: auto; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .error-content h2 { margin: 0 0 1rem; color: var(--ct-theme-color-text, var(--ct-color-gray-900, #111827)); } .error-content p { margin: 0.5rem 0; color: var(--ct-theme-color-text, var(--ct-color-gray-900, #111827)); } .error-content pre { background-color: var( --ct-theme-color-surface-hover, var(--ct-color-gray-100, #f3f4f6) ); padding: 1rem; border-radius: var( --ct-theme-border-radius, var(--ct-border-radius-sm, 0.25rem) ); overflow: auto; font-family: monospace; } .error-actions { margin-top: 1.25rem; display: flex; justify-content: flex-end; gap: 0.75rem; } .error-actions button { padding: 0.5rem 1rem; border-radius: var( --ct-theme-border-radius, var(--ct-border-radius-md, 0.375rem) ); border: 1px solid var(--ct-theme-color-border, var(--ct-color-gray-300, #d1d5db)); background-color: var( --ct-theme-color-surface, var(--ct-color-white, #ffffff) ); color: var(--ct-theme-color-text, var(--ct-color-gray-900, #111827)); cursor: pointer; font-size: 0.875rem; font-weight: 500; transition: all 0.2s ease; } .error-actions button:hover { background-color: var( --ct-theme-color-surface-hover, var(--ct-color-gray-100, #f3f4f6) ); } .error-actions button:first-child { background-color: var( --ct-theme-color-primary, var(--ct-color-primary, #3b82f6) ); color: var( --ct-theme-color-primary-foreground, var(--ct-color-white, #ffffff) ); border-color: var( --ct-theme-color-primary, var(--ct-color-primary, #3b82f6) ); } .error-actions button:first-child:hover { opacity: 0.9; } `, ]; private onLoad() { this.emit("load"); } private onError(e: CustomEvent) { this._errorDetails = e.detail; } private dismissError() { this._errorDetails = null; } private fixError() { this.emit("fix", this._errorDetails); this._errorDetails = null; } override render() { return html` ${this._errorDetails ? html`

Error

Description: ${this._errorDetails .description}

Source: ${this._errorDetails.source}

Line: ${this._errorDetails.lineno}

Column: ${this._errorDetails.colno}

${this._errorDetails.stacktrace}
` : ""} `; } } globalThis.customElements.define("ct-iframe", CTIframe);