import { r as registerInstance, h, H as Host } from './index-DBTJNfo7.js';
import { m as markdownToHTML } from './markdown-parser-DJi1w622.js';
import { g as globalConfig } from './config-Dnt5w_Bp.js';
import { d as defaultSchema } from './index-CJ0GYrWG.js';
import './_commonjsHelpers-BFTU3MAI.js';

class ImageIntersectionObserver {
    /**
     * @param containerElement - The element containing images to observe.
     */
    constructor(containerElement) {
        this.handleIntersection = (entries) => {
            for (const entry of entries) {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    const dataSrc = img.dataset.src;
                    if (dataSrc) {
                        img.setAttribute('src', dataSrc);
                        delete img.dataset.src;
                    }
                    this.observer.unobserve(img);
                }
            }
        };
        this.observer = new IntersectionObserver(this.handleIntersection);
        const images = containerElement.querySelectorAll('img');
        for (const img of images) {
            this.observer.observe(img);
        }
    }
    disconnect() {
        this.observer.disconnect();
    }
}

var _a;
/**
 * Map of URL-bearing property names to their allowed protocols, sourced
 * from rehype-sanitize's defaultSchema. This means protocol updates from
 * Dependabot bumps to rehype-sanitize automatically apply here too —
 * no custom blocklist to maintain.
 *
 * We can't use rehype-sanitize directly for URL sanitization because it
 * operates on HTML attributes, not on values inside JSON strings. By the
 * time rehype-sanitize runs, `link` is just a raw JSON string attribute —
 * the `href` only becomes visible after JSON parsing in hydration.
 * So we replicate rehype-sanitize's protocol validation logic here,
 * using its own protocol list as the source of truth.
 *
 * The map covers all URL-bearing property names that rehype-sanitize
 * defines protocols for: `href`, `src`, `cite`, and `longDesc`.
 *
 * @internal
 */
const SAFE_PROTOCOLS_BY_PROPERTY = new Map(Object.entries((_a = defaultSchema.protocols) !== null && _a !== void 0 ? _a : {}).map(([prop, protocols]) => [
    prop,
    new Set(protocols),
]));

/**
 * After innerHTML is set on a container, custom elements receive all
 * attribute values as strings. This function walks whitelisted custom
 * elements and parses any attribute values that look like JSON objects
 * or arrays, setting them as JS properties instead.
 *
 * This enables markdown content to include custom elements with complex
 * props, e.g.:
 * ```html
 * <limel-chip text="GitHub" link='{"href":"https://github.com","target":"_blank"}'></limel-chip>
 * ```
 *
 * @param container - The root element to search within.
 * @param whitelist - The list of whitelisted custom element definitions.
 */
function hydrateCustomElements(container, whitelist) {
    if (!container || !(whitelist === null || whitelist === void 0 ? void 0 : whitelist.length)) {
        return;
    }
    for (const definition of whitelist) {
        const elements = container.querySelectorAll(definition.tagName);
        for (const element of elements) {
            hydrateElement(element, definition.attributes);
        }
    }
}
function hydrateElement(element, attributes) {
    for (const attrName of attributes) {
        const value = element.getAttribute(attrName);
        if (!value) {
            continue;
        }
        const parsed = tryParseJson(value);
        if (parsed !== undefined) {
            const sanitized = sanitizeUrls(parsed);
            // Set the JS property (camelCase) instead of the attribute
            const propName = attributeToPropName(attrName);
            element[propName] = sanitized;
        }
    }
}
function tryParseJson(value) {
    const trimmed = value.trim();
    if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
        (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
        try {
            return JSON.parse(trimmed);
        }
        catch (_a) {
            // The sanitizer may HTML-encode quotes inside attribute values.
            // Try decoding common HTML entities before giving up.
            try {
                const decoded = trimmed
                    .replaceAll('&#x22;', '"')
                    .replaceAll('&#34;', '"')
                    .replaceAll('&quot;', '"')
                    .replaceAll('&#x27;', "'")
                    .replaceAll('&#39;', "'")
                    .replaceAll('&apos;', "'")
                    .replaceAll('&amp;', '&');
                return JSON.parse(decoded);
            }
            catch (_b) {
                return;
            }
        }
    }
}
/**
 * Check whether a URL string uses a safe protocol for the given property.
 * Relative URLs, hash links, and protocol-relative URLs are always allowed.
 * @param value - The URL string to check.
 * @param allowedProtocols - The set of allowed protocols for this property.
 */
function isSafeUrl(value, allowedProtocols) {
    const trimmed = value.trim();
    const colonIndex = trimmed.indexOf(':');
    // No colon, or colon appears after ?, #, or / → relative URL, always safe
    if (colonIndex === -1 || /[?#/]/.test(trimmed.slice(0, colonIndex))) {
        return true;
    }
    const protocol = trimmed.slice(0, colonIndex).toLowerCase();
    return allowedProtocols.has(protocol);
}
/**
 * Recursively sanitize URL-bearing properties in a parsed JSON value.
 * Uses the same protocol allowlists as rehype-sanitize to block dangerous
 * schemes (e.g. `javascript:`, `data:`) while allowing safe ones.
 * Covers all URL properties that rehype-sanitize defines protocols for:
 * `href`, `src`, `cite`, and `longDesc`.
 * Unsafe URLs are removed to prevent script injection.
 * @param value
 */
function sanitizeUrls(value) {
    if (value === null || typeof value !== 'object') {
        return value;
    }
    if (Array.isArray(value)) {
        return value.map(sanitizeUrls);
    }
    const result = Object.assign({}, value);
    for (const key of Object.keys(result)) {
        const allowedProtocols = SAFE_PROTOCOLS_BY_PROPERTY.get(key);
        if (allowedProtocols &&
            typeof result[key] === 'string' &&
            !isSafeUrl(result[key], allowedProtocols)) {
            console.warn(`limel-markdown: Removed unsafe URL from "${key}" during sanitization.`);
            delete result[key];
        }
        else if (typeof result[key] === 'object' && result[key] !== null) {
            result[key] = sanitizeUrls(result[key]);
        }
    }
    return result;
}
/**
 * Convert a kebab-case attribute name to a camelCase property name.
 * e.g. "menu-items" → "menuItems"
 * @param attrName
 */
function attributeToPropName(attrName) {
    return attrName.replaceAll(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}

/**
 * Default whitelist of lime-elements components that are safe to render
 * inside `limel-markdown`.
 *
 * These components are self-contained and require no complex setup.
 * URL-bearing properties (e.g. `link.href` on `limel-chip`) are
 * automatically sanitized during hydration to prevent injection attacks.
 *
 * Consumers can extend this list via the `whitelist` prop or
 * `limel-config` global config.
 *
 * @internal
 */
const DEFAULT_MARKDOWN_WHITELIST = [
    {
        tagName: 'limel-chip',
        attributes: [
            'text',
            'icon',
            'link',
            'badge',
            'disabled',
            'readonly',
            'selected',
            'type',
            'size',
        ],
    },
    {
        tagName: 'limel-icon',
        attributes: ['name', 'size', 'badge'],
    },
    {
        tagName: 'limel-badge',
        attributes: ['label'],
    },
    {
        tagName: 'limel-callout',
        attributes: ['heading', 'icon', 'type'],
    },
    {
        tagName: 'limel-linear-progress',
        attributes: ['value', 'indeterminate'],
    },
    {
        tagName: 'limel-circular-progress',
        attributes: [
            'value',
            'max-value',
            'prefix',
            'suffix',
            'size',
            'display-percentage-colors',
        ],
    },
    {
        tagName: 'limel-spinner',
        attributes: ['size'],
    },
    {
        tagName: 'limel-info-tile',
        attributes: ['value', 'icon', 'label', 'prefix', 'suffix', 'badge'],
    },
];

const markdownCss = () => `@charset "UTF-8";code{font-family:ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace;font-size:var(--limel-theme-default-small-font-size);letter-spacing:-0.0125rem;color:rgb(var(--contrast-1300));-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;display:inline-block;border-radius:0.25rem;padding:0.03125rem 0.25rem;background-color:rgb(var(--contrast-600))}pre>code{display:block;margin:0.5rem 0;padding:0.5rem 0.75rem;overflow:auto;white-space:pre-wrap}h1{font-size:1.5rem}h2{font-size:1.25rem}h3{font-size:1.125rem}h4{font-size:1rem}h5{font-size:var(--limel-theme-default-font-size)}h6{font-size:0.75rem}h1,h2{margin-top:0.5rem;margin-bottom:0.5rem;letter-spacing:-0.03125rem;font-weight:500}h3,h4{margin-top:0.75rem;margin-bottom:0.25rem;font-weight:600}h5,h6{margin-top:0.5rem;margin-bottom:0.125rem;font-weight:600}h1,h2,h3,h4,h5,h6{word-break:break-word;hyphens:auto;-webkit-hyphens:auto}:not([contenteditable=true]) h1,:not([contenteditable=true]) h2,:not([contenteditable=true]) h3,:not([contenteditable=true]) h4,:not([contenteditable=true]) h5,:not([contenteditable=true]) h6{text-wrap:balance}[contenteditable=true] h1,[contenteditable=true] h2,[contenteditable=true] h3,[contenteditable=true] h4,[contenteditable=true] h5,[contenteditable=true] h6{text-wrap:initial}:host(limel-markdown.truncate-paragraphs) p{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}p,li{font-size:var(--limel-theme-default-font-size);word-break:break-word}a{word-break:break-all}p{margin-top:0;margin-bottom:0.5rem}p:only-child{margin-bottom:0}a{transition:color 0.2s ease;color:var(--markdown-hyperlink-color, rgb(var(--color-blue-dark)));text-decoration:none}a:hover{color:var(--markdown-hyperlink-color--hovered, rgb(var(--color-blue-default)))}hr{margin:1.75rem 0 2rem 0;border-width:0;border-top:1px solid rgb(var(--contrast-500))}ul{list-style:none}ul li{position:relative;margin-left:0.75rem}ul li:before{content:"";position:absolute;left:-0.5rem;top:0.5rem;width:0.25rem;height:0.25rem;border-radius:50%;background-color:rgb(var(--contrast-700));display:block}ol{margin-top:0.25rem;padding-left:1rem}ul{margin-top:0.25rem;padding-left:0}ul ul,ul ol,ol ol,ol ul{margin-left:0}li{margin-bottom:0.25rem}:host(limel-markdown:not(.no-table-styles)) table{table-layout:auto;min-width:100%;border-collapse:collapse;border-spacing:0;background:transparent;margin:0.75rem 0}:host(limel-markdown:not(.no-table-styles)) tbody{border:1px solid rgb(var(--contrast-400));border-radius:0.25rem}:host(limel-markdown:not(.no-table-styles)) th,:host(limel-markdown:not(.no-table-styles)) td{text-align:left;vertical-align:top;transition:background-color 0.2s ease;font-size:var(--limel-theme-default-font-size)}:host(limel-markdown:not(.no-table-styles)) td{padding:0.5rem 0.375rem 0.75rem 0.375rem}:host(limel-markdown:not(.no-table-styles)) tr th{background-color:rgb(var(--contrast-400));padding:0.25rem 0.375rem;font-weight:normal}:host(limel-markdown:not(.no-table-styles)) tr th:only-child{text-align:center}:host(limel-markdown:not(.no-table-styles)) tbody tr:nth-child(odd) td{background-color:rgb(var(--contrast-200))}:host(limel-markdown:not(.no-table-styles)) tbody tr:hover td{background-color:rgb(var(--contrast-300))}table{display:block;box-sizing:border-box;overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}blockquote{position:relative;max-width:100%;margin:0.75rem 0;padding:0.5rem;border-left:0.25rem solid rgb(var(--contrast-500));background-color:rgb(var(--contrast-200))}blockquote:before,blockquote:after{position:absolute;line-height:0;font-size:2rem;opacity:0.4}blockquote:before{content:"“";left:-0.5rem;top:0.5rem}blockquote:after{content:"”";right:-0.25rem;bottom:-0.25rem}blockquote blockquote{padding-top:0;padding-right:0;padding-bottom:0;padding-left:0.25rem;border-color:rgb(var(--contrast-700));border-left-width:1px}blockquote blockquote:before,blockquote blockquote:after{display:none}blockquote:has(>blockquote){padding-left:0.25rem;padding-bottom:0}dl{display:grid;grid-template-columns:1fr 2fr;grid-template-rows:1fr;margin-bottom:2rem;border:1px solid rgb(var(--contrast-400));border-radius:0.375rem;background-color:rgb(var(--contrast-200))}dl dt,dl dd{padding:0.375rem 0.5rem;font-size:var(--limel-theme-default-font-size);margin:0}dl dt:nth-of-type(even),dl dd:nth-of-type(even){background-color:rgb(var(--contrast-300))}dl dt:first-child{border-top-left-radius:0.375rem}dl dt:last-child{border-bottom-left-radius:0.375rem}dl dd:first-child{border-top-right-radius:0.375rem}dl dd:last-child{border-bottom-right-radius:0.375rem}img{max-width:100%;border-radius:0.25rem}kbd{font-family:ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace;font-weight:600;color:rgb(var(--contrast-1100));background-color:rgb(var(--contrast-200));white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:normal;padding:0.125rem 0.5rem;margin:0 0.25rem;box-shadow:var(--button-shadow-normal), 0 0.03125rem 0.21875rem 0 rgba(var(--contrast-100), 0.5) inset;border-radius:0.125rem;border-style:solid;border-color:rgba(var(--contrast-600), 0.8);border-width:0 1px 0.125rem 1px}:host(limel-markdown.adjust-for-table-cell) img{max-height:1.25rem;vertical-align:middle}:host(limel-markdown.adjust-for-table-cell) p{display:inline}:host(limel-markdown.adjust-for-table-cell) h1,:host(limel-markdown.adjust-for-table-cell) h2,:host(limel-markdown.adjust-for-table-cell) h3,:host(limel-markdown.adjust-for-table-cell) h4,:host(limel-markdown.adjust-for-table-cell) h5,:host(limel-markdown.adjust-for-table-cell) h6{display:inline-block;vertical-align:bottom;font-size:var(--limel-theme-default-font-size);margin:0 0.25rem 0 0;letter-spacing:normal;font-weight:500}:host(limel-markdown.adjust-for-table-cell) h1:before,:host(limel-markdown.adjust-for-table-cell) h2:before,:host(limel-markdown.adjust-for-table-cell) h3:before,:host(limel-markdown.adjust-for-table-cell) h4:before,:host(limel-markdown.adjust-for-table-cell) h5:before,:host(limel-markdown.adjust-for-table-cell) h6:before{opacity:0.6;vertical-align:middle;font-size:0.5rem;border-radius:0.25rem 0 0 0.25rem;padding:0.25rem;padding-right:2rem;margin-right:-1.75rem;background:linear-gradient(to right, rgb(var(--contrast-800), 0.6), rgb(var(--contrast-800), 0))}:host(limel-markdown.adjust-for-table-cell) h1:before{content:"H1"}:host(limel-markdown.adjust-for-table-cell) h2:before{content:"H2"}:host(limel-markdown.adjust-for-table-cell) h3:before{content:"H3"}:host(limel-markdown.adjust-for-table-cell) h4:before{content:"H4"}:host(limel-markdown.adjust-for-table-cell) h5:before{content:"H5"}:host(limel-markdown.adjust-for-table-cell) h6:before{content:"H6"}:host(limel-markdown.adjust-for-table-cell) pre{margin:0}:host(limel-markdown.adjust-for-table-cell) pre>code{padding:0.125rem;margin:0}:host(limel-markdown.adjust-for-table-cell) dl{margin:0}:host(limel-markdown.adjust-for-table-cell) dl dt,:host(limel-markdown.adjust-for-table-cell) dl dd{padding:0.00625rem 0.125rem}*,*::before,*::after{box-sizing:border-box}* :where(:not(img,video,svg,canvas,iframe)),*::before :where(:not(img,video,svg,canvas,iframe)),*::after :where(:not(img,video,svg,canvas,iframe)){min-width:0;min-height:0}hr{border-top:1px solid rgb(var(--contrast-700))}.MsoNormal{margin:0}:host(limel-markdown.reset-img-height) #markdown img{height:auto}`;

const Markdown = class {
    constructor(hostRef) {
        registerInstance(this, hostRef);
        /**
         * The input text. Treated as GitHub Flavored Markdown, with the addition
         * that any included HTML will be parsed and rendered as HTML, rather than
         * as text.
         */
        this.value = '';
        /**
         * Additional whitelisted custom elements to render inside markdown.
         *
         * A built-in set of lime-elements components (such as `limel-chip`,
         * `limel-icon`, `limel-badge`, `limel-callout`, etc.) is always
         * allowed by default. Any entries provided here are **merged** with
         * those defaults — if both define the same `tagName`, their
         * attributes are combined.
         *
         * Can also be set via `limel-config`. Setting this property will
         * override the global config.
         *
         * JSON attribute values that contain URL-bearing properties
         * (`href`, `src`, `cite`, `longDesc`) are automatically sanitized
         * using the same protocol allowlists as rehype-sanitize. URLs with
         * dangerous schemes (e.g. `javascript:`, `data:`) are removed
         * (with a console warning).
         *
         * @alpha
         */
        this.whitelist = globalConfig.markdownWhitelist;
        /**
         * Enable lazy loading for images
         */
        this.lazyLoadImages = false;
        /**
         * Set to `false` to preserve empty paragraphs before rendering.
         * Empty paragraphs are paragraphs that do not contain
         * any meaningful content (text, images, etc.), or only contain
         * whitespace (`<br />` or `&nbsp;`).
         */
        this.removeEmptyParagraphs = true;
        this.imageIntersectionObserver = null;
    }
    async textChanged() {
        try {
            this.cleanupImageIntersectionObserver();
            // The whitelist merge and default import live here (not in
            // markdown-parser.ts) because this component orchestrates both
            // the parser and hydration, which both need the combined list.
            if (!this.cachedCombinedWhitelist ||
                this.whitelist !== this.cachedConsumerWhitelist) {
                this.cachedConsumerWhitelist = this.whitelist;
                this.cachedCombinedWhitelist = mergeWhitelists(DEFAULT_MARKDOWN_WHITELIST, this.whitelist);
            }
            const combinedWhitelist = this.cachedCombinedWhitelist;
            const html = await markdownToHTML(this.value, {
                forceHardLineBreaks: true,
                whitelist: combinedWhitelist,
                lazyLoadImages: this.lazyLoadImages,
                removeEmptyParagraphs: this.removeEmptyParagraphs,
            });
            this.rootElement.innerHTML = html;
            // Hydration parses JSON attribute values (e.g. link='{"href":"..."}')
            // into JS properties. URL sanitization happens here because
            // rehype-sanitize can't inspect values inside JSON strings.
            hydrateCustomElements(this.rootElement, combinedWhitelist);
            this.setupImageIntersectionObserver();
        }
        catch (error) {
            console.error(error);
        }
    }
    handleWhitelistChange() {
        return this.textChanged();
    }
    handleRemoveEmptyParagraphsChange() {
        return this.textChanged();
    }
    async componentDidLoad() {
        this.textChanged();
    }
    disconnectedCallback() {
        this.cleanupImageIntersectionObserver();
    }
    render() {
        return (h(Host, { key: '61ca61192c4d1141494ced2b431770cccb623581' }, h("div", { key: 'b0ebadbc5b2ee426df9f1a0c77d88cf162a43bcc', id: "markdown", ref: (el) => (this.rootElement = el) })));
    }
    setupImageIntersectionObserver() {
        if (this.lazyLoadImages) {
            this.imageIntersectionObserver = new ImageIntersectionObserver(this.rootElement);
        }
    }
    cleanupImageIntersectionObserver() {
        if (this.imageIntersectionObserver) {
            this.imageIntersectionObserver.disconnect();
            this.imageIntersectionObserver = null;
        }
    }
    static get watchers() { return {
        "value": [{
                "textChanged": 0
            }],
        "whitelist": [{
                "handleWhitelistChange": 0
            }],
        "removeEmptyParagraphs": [{
                "handleRemoveEmptyParagraphsChange": 0
            }]
    }; }
};
/**
 * Merge the default whitelist with a consumer-provided one.
 * If both define the same tagName, their attributes are combined.
 * @param defaults
 * @param consumer
 */
function mergeWhitelists(defaults, consumer) {
    if (!(consumer === null || consumer === void 0 ? void 0 : consumer.length)) {
        return defaults.map((def) => (Object.assign(Object.assign({}, def), { attributes: [...def.attributes] })));
    }
    const merged = new Map();
    for (const def of [...defaults, ...consumer]) {
        const existing = merged.get(def.tagName);
        if (existing) {
            for (const attr of def.attributes) {
                existing.add(attr);
            }
        }
        else {
            merged.set(def.tagName, new Set(def.attributes));
        }
    }
    return [...merged.entries()].map(([tagName, attrs]) => ({
        tagName,
        attributes: [...attrs],
    }));
}
Markdown.style = markdownCss();

export { Markdown as limel_markdown };
