{"version":3,"file":"index.mjs","names":[],"sources":["../../forge-jsx-components/src/utils/jsxComponent.ts","../../forge-jsx-components/src/runtime/jsx-runtime.ts"],"sourcesContent":["import { component } from '@ministryofjustice/hmpps-forge/core/components'\nimport type {\n  BlockDefinition,\n  ComponentOptions,\n  ForgeComponent,\n  ResolvedPropsOf,\n} from '@ministryofjustice/hmpps-forge/core/components'\n\nimport type { RawHtml } from '../runtime/jsx-runtime'\n\n/**\n * Defines a component whose render is written in JSX - `component()` with the output\n * pinned to the JSX runtime's `RawHtml`, stringified at the boundary so the registry\n * entry produces the same plain HTML strings as every other component.\n *\n * No renderer is involved: JSX compiles to direct string building, so unlike\n * `nunjucksComponent` there is no environment to inject.\n *\n * @experimental Part of the experimental JSX component API - may change or be removed\n * in a minor release.\n *\n * @example\n * ```tsx\n * export interface MyBadge extends BlockDefinition {\n *   text: ResolvableString\n * }\n *\n * export const MyBadge = jsxComponent<MyBadge>('myBadge', {\n *   render: props => <strong class=\"moj-badge\">{props.text}</strong>,\n * })\n * ```\n */\nexport function jsxComponent<TBlock extends BlockDefinition>(\n  variant: string,\n  options: ComponentOptions<TBlock, RawHtml, undefined>,\n): ForgeComponent<TBlock, string> {\n  // TBlock is still generic here, so the conditional options type is unresolved - read\n  // the render through a minimal shape, as component() itself does with its options.\n  const { render } = options as { render: (props: ResolvedPropsOf<TBlock>, renderer: undefined) => RawHtml }\n\n  const stringOptions = {\n    ...options,\n    render: (props: ResolvedPropsOf<TBlock>) => String(render(props, undefined)),\n  } as unknown as ComponentOptions<TBlock, string, undefined>\n\n  return component<TBlock, string, undefined>(variant, stringOptions)\n}\n","/**\n * The forge JSX runtime - compiles JSX straight to escaped HTML strings, with no\n * framework underneath.\n *\n * TypeScript's automatic JSX transform (`\"jsx\": \"react-jsx\"` with `\"jsxImportSource\"`\n * pointing at this package) rewrites `<div class=\"x\">{y}</div>` into calls to the\n * `jsx`/`jsxs` functions in this module, so this file's name and export names are a\n * compiler contract, not a style choice.\n *\n * @experimental Part of the experimental JSX component API - may change or be removed\n * in a minor release.\n */\nimport type { IntrinsicElementAttributes } from './types/intrinsicElements.type'\n\n/**\n * Elements with no closing tag, per the HTML spec - rendered as `<tag>` with\n * children ignored.\n */\nconst VOID_ELEMENTS = new Set([\n  'area',\n  'base',\n  'br',\n  'col',\n  'embed',\n  'hr',\n  'img',\n  'input',\n  'link',\n  'meta',\n  'source',\n  'track',\n  'wbr',\n])\n\nconst HTML_ENTITY_MAP: Record<string, string> = {\n  '<': '&lt;',\n  '>': '&gt;',\n  '&': '&amp;',\n  '\"': '&quot;',\n  \"'\": '&#39;',\n}\n\nconst escapeHtmlEntities = (value: string): string => value.replace(/[<>&\"']/g, char => HTML_ENTITY_MAP[char])\n\n/**\n * Identifies `RawHtml` across bundle copies of this module - `Symbol.for` keys into the\n * global symbol registry, so the check holds even when two entrypoints each bundle\n * their own copy of the class.\n */\nconst RAW_HTML_BRAND = Symbol.for('forge.jsx.rawHtml')\n\n/**\n * A string of HTML that is already safe to embed - the serializer includes it verbatim\n * instead of escaping it. Every JSX expression evaluates to one of these.\n *\n * @experimental Part of the experimental JSX component API - may change or be removed\n * in a minor release.\n */\nexport class RawHtml {\n  readonly [RAW_HTML_BRAND] = true\n\n  constructor(readonly html: string) {}\n\n  toString(): string {\n    return this.html\n  }\n}\n\nconst isRawHtml = (value: unknown): value is RawHtml =>\n  typeof value === 'object' && value !== null && RAW_HTML_BRAND in value\n\n/**\n * Marks trusted markup as safe to embed without escaping - the HTML of an\n * already-rendered child block, for instance. Everything not wrapped in `raw()`\n * is escaped.\n *\n * @experimental Part of the experimental JSX component API - may change or be removed\n * in a minor release.\n *\n * @example\n * ```tsx\n * <div class=\"card__body\">{raw(renderedChildBlock.html)}</div>\n * ```\n */\nexport const raw = (html: string): RawHtml => new RawHtml(html)\n\n/**\n * Anything a JSX expression can nest inside an element. Strings and numbers are\n * escaped, `RawHtml` is embedded verbatim, and `null`/`undefined`/booleans render\n * as nothing (so `{condition && <p>...</p>}` works).\n */\nexport type JsxChild = string | number | boolean | null | undefined | RawHtml | JsxChild[]\n\n/**\n * The props a JSX element receives: attributes plus the nested children. The\n * automatic transform passes children inside props rather than as extra arguments.\n */\nexport interface JsxProps {\n  children?: JsxChild\n  [attribute: string]: unknown\n}\n\ntype FunctionComponent = (props: JsxProps) => RawHtml\n\nconst serializeChildren = (child: JsxChild): string => {\n  if (child === null || child === undefined || typeof child === 'boolean') {\n    return ''\n  }\n\n  if (Array.isArray(child)) {\n    return child.map(serializeChildren).join('')\n  }\n\n  if (isRawHtml(child)) {\n    return child.html\n  }\n\n  return escapeHtmlEntities(String(child))\n}\n\nconst serializeAttributes = (props: JsxProps): string =>\n  Object.entries(props)\n    .filter(([name, value]) => name !== 'children' && value !== undefined && value !== null && value !== false)\n    .map(([name, value]) =>\n      value === true\n        ? ` ${escapeHtmlEntities(name)}`\n        : ` ${escapeHtmlEntities(name)}=\"${escapeHtmlEntities(String(value))}\"`,\n    )\n    .join('')\n\n/**\n * The automatic JSX transform's element factory - `<div class=\"x\">{y}</div>` compiles\n * to `jsx('div', { class: 'x', children: y })`. Not called directly from user code.\n *\n * The transform passes JSX `key` attributes as a third argument; keys are meaningless\n * when rendering to a string, so the extra argument is ignored.\n *\n * @experimental Part of the experimental JSX component API - may change or be removed\n * in a minor release.\n */\nexport const jsx = (tag: string | FunctionComponent, props: JsxProps): RawHtml => {\n  if (typeof tag === 'function') {\n    return tag(props)\n  }\n\n  const attributes = serializeAttributes(props)\n\n  if (VOID_ELEMENTS.has(tag)) {\n    return new RawHtml(`<${tag}${attributes}>`)\n  }\n\n  return new RawHtml(`<${tag}${attributes}>${serializeChildren(props.children)}</${tag}>`)\n}\n\n/**\n * The transform calls `jsxs` instead of `jsx` when an element has multiple static\n * children - the distinction only matters to frameworks that key children, so both\n * share one implementation here.\n *\n * @experimental Part of the experimental JSX component API - may change or be removed\n * in a minor release.\n */\nexport const jsxs = jsx\n\n/**\n * Renders fragment children (`<>...</>`) with no wrapping element.\n *\n * @experimental Part of the experimental JSX component API - may change or be removed\n * in a minor release.\n */\nexport const Fragment = (props: JsxProps): RawHtml => new RawHtml(serializeChildren(props.children))\n\n/**\n * Development-mode entry point - dev transforms (Vite, esbuild with jsxDev,\n * TypeScript's \"react-jsxdev\") import `<jsxImportSource>/jsx-dev-runtime` and call\n * `jsxDEV` instead of `jsx`/`jsxs`. Its extra arguments (key, static-children flag,\n * source location) only matter to frameworks that diff and re-render, so it is the\n * production implementation under the dev name - the package's `jsx-dev-runtime`\n * subpath resolves to this same module.\n *\n * @experimental Part of the experimental JSX component API - may change or be removed\n * in a minor release.\n */\nexport const jsxDEV = jsx\n\n/**\n * The types TypeScript reads from the `jsxImportSource` module to type-check JSX\n * expressions: what an expression evaluates to, which tags exist with which\n * attributes, and which prop carries nested children.\n *\n * The namespace, its name and its member names are all part of the compiler's JSX\n * contract, hence the lint exemptions.\n */\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport declare namespace JSX {\n  type Element = RawHtml\n\n  // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n  interface IntrinsicElements extends IntrinsicElementAttributes {}\n\n  interface ElementChildrenAttribute {\n    children: unknown\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,aACd,SACA,SACgC;CAGhC,MAAM,EAAE,WAAW;CAEnB,MAAM,gBAAgB;EACpB,GAAG;EACH,SAAS,UAAmC,OAAO,OAAO,OAAO,KAAA,CAAS,CAAC;CAC7E;CAEA,OAAO,UAAqC,SAAS,aAAa;AACpE;;;;;;;;ACGA,MAAM,iBAAiB,OAAO,IAAI,mBAAmB;;;;;;;;AASrD,IAAa,UAAb,MAAqB;CAGE;CAFrB,CAAU,kBAAkB;CAE5B,YAAY,MAAuB;EAAd,KAAA,OAAA;CAAe;CAEpC,WAAmB;EACjB,OAAO,KAAK;CACd;AACF;;;;;;;;;;;;;;AAkBA,MAAa,OAAO,SAA0B,IAAI,QAAQ,IAAI"}