/**
* `unsafeHtml()` — opt-in sanitizer bypass marker for {@link storyHtml}.
*
* @module bquery/storybook
*/
/** @internal */
export const UNSAFE_HTML_PLACEHOLDER_PREFIX = '\u0001BQ_UNSAFE_HTML_';
/** @internal */
export const UNSAFE_HTML_PLACEHOLDER_SUFFIX = '_END\u0001';
/** @internal */
const UNSAFE_HTML_BRAND: unique symbol = Symbol('bquery.storybook.unsafeHtml');
/**
* Marker returned by {@link unsafeHtml}. Identifies an interpolated story
* value that should bypass the {@link storyHtml} sanitizer.
*/
export interface UnsafeHtmlMarker {
/** @internal */
readonly [UNSAFE_HTML_BRAND]: true;
/** The raw HTML that will be re-inserted after sanitization. */
readonly value: string;
}
let warnedOnce = false;
/** @internal */
export const createUnsafeHtmlMarker = (value: string): UnsafeHtmlMarker => ({
[UNSAFE_HTML_BRAND]: true,
value,
});
const emitDevWarning = (): void => {
if (warnedOnce) return;
warnedOnce = true;
if (typeof console === 'undefined' || typeof console.warn !== 'function') return;
console.warn(
'[bquery/storybook] unsafeHtml() bypasses sanitization for the wrapped value. ' +
'Only use it with trusted, author-controlled markup.'
);
};
/**
* Wraps an HTML fragment so {@link storyHtml} re-inserts it verbatim after
* sanitizing the surrounding template. The wrapped value is **not** sanitized.
*
* @remarks
* Mirrors `lit-html`'s `unsafeHTML` directive. Only use with trusted,
* author-controlled markup — never with user-supplied input.
*
* @param value - The HTML fragment to insert verbatim
* @returns An opaque marker recognised by {@link storyHtml}
*
* @example
* ```ts
* import { storyHtml, unsafeHtml } from '@bquery/bquery/storybook';
*
* const trustedSvg = '';
* storyHtml`${unsafeHtml(trustedSvg)}`;
* ```
*/
export const unsafeHtml = (value: string): UnsafeHtmlMarker => {
emitDevWarning();
return createUnsafeHtmlMarker(value);
};
/**
* Type guard for {@link UnsafeHtmlMarker}.
* @internal
*/
export const isUnsafeHtmlMarker = (value: unknown): value is UnsafeHtmlMarker => {
if (value === null || typeof value !== 'object') return false;
return (value as { [UNSAFE_HTML_BRAND]?: true })[UNSAFE_HTML_BRAND] === true;
};