import { ReadonlySignal } from '@preact/signals-core'; import { B as Binding } from './bindings-CYwoJpQb.js'; /** * JSX intrinsic-element types — kerf's per-tag attribute contracts. * * Replaces the previous `[elemName: string]: Record` * catch-all that allowed any tag and any prop. Now: known tags get focused * attribute interfaces (typos fail to compile); unknown tags require * declaration merging to opt in. * * Coverage is intentionally focused, not exhaustive. The most common ~30 * HTML elements + the SVG primitives that make up `toElement`'s fragment * set are typed in detail. Rare attributes can be added in follow-ups, or * extended on a per-project basis via declaration merging into the * `kerfjs/jsx-runtime` JSX namespace (KF-100): * * import type { KerfBaseAttrs, KerfCustomElement } from 'kerfjs/jsx-runtime'; * * declare module 'kerfjs/jsx-runtime' { * namespace JSX { * interface IntrinsicElements { * 'my-element': KerfCustomElement & { foo?: string }; * } * } * } * * `IntrinsicElements` in `jsx-runtime` is an **interface** that extends the * one defined here, which is what makes the merge above work — type aliases * (the previous shape) couldn't be merged. * * Every attribute value is `AttrValue` — string / number / boolean / null / * undefined / `SafeHtml`. Event-handler props (`onClick` etc.) are * deliberately omitted: kerf renders to strings, so inline handlers do * nothing. Use `delegate()` / `delegateCapture()` instead. * * --- * * ## Provenance — where these types come from * * Attribute names, value sets, and per-element membership are taken from the * **WHATWG HTML Living Standard** (and **SVG 2** for the SVG interfaces), with * MDN used only as a readable index into them. They are NOT derived from * `@types/react`, `lib.dom.d.ts`, or any other framework's table — those model * a *property* surface (`HTMLElement.draggable: boolean`), and kerf emits * *content attributes* into an HTML string, which is a different contract in * exactly the places that bite (see the enumerated-attribute rule below). * * Coverage is deliberately focused rather than exhaustive: the ~100 most-used * elements and their commonly-authored attributes. A missing attribute is a * gap to fill, not a statement that it's invalid — extend via declaration * merging (above) until it lands here. * * ## The rule that governs every value type * * `boolean` in an attribute type means **HTML boolean attribute** — one whose * *presence* is the whole signal. `foo={true}` renders ` foo` and `foo={false}` * renders nothing, so only attributes with those exact semantics may accept a * boolean. * * HTML's **enumerated** attributes look boolean but are not: they take the * literal *strings* `"true"` / `"false"`, and their missing-value default is a * third state. Typing one as `boolean` produces markup that silently means the * opposite of what was written: * * - `draggable={true}` → `
` → empty value is invalid for * `draggable`, so the element falls to the **auto** state — which for a * `
` means **not draggable**. The attribute that was supposed to turn * dragging on turns nothing on. * - `draggable={false}` → attribute omitted → **auto** again, and auto for * `` / `` is *draggable*. The disable never happens either. * - `spellCheck={false}` / `contentEditable={false}` → omitted → the * **inherit** default, not the false state. (Their `true` direction happens * to work: the empty string is a spec keyword for the true state on those * two, unlike `draggable`.) * * So these are typed as string literal unions and reject `boolean` outright. * Six attributes have this shape; they do NOT all share one type, because the * keywords differ — `EnumeratedBool` (`"true"`/`"false"`) covers `draggable`, * `spellcheck` and `writingsuggestions`, `ContentEditableValue` adds * `plaintext-only`, and `translate` (`"yes"`/`"no"`) and `autocorrect` * (`"on"`/`"off"`) spell their keywords differently again. Grep `EnumeratedBool` * for the largest group. The fix is at the type * level rather than in the runtime on purpose: translating `{true}` → * `="true"` would require the renderer to carry a list of every enumerated * attribute in HTML, and any attribute *missing* from that list would silently * regress to precisely this bug. A per-attribute type keeps the knowledge where * the spec knowledge already lives and costs nothing at runtime. The tradeoff * is a compile error on `draggable={true}` — which is the point. * * One residual hole this cannot close: a signal-valued attribute * (`draggable={sig}`) is `ReadonlySignal`, so a boolean inside it is * invisible to the type system. Put the string in the signal: `signal('true')`. * * A third shape sits between the two and accepts `boolean | string` * legitimately: **presence-or-value** attributes, where the presence carries * the meaning and a value refines it. `download` is the clearest case — bare, * it means "download this and let the server name the file"; with a value, the * value is the filename; absent, it's ordinary navigation. `capture` on * `` is the same shape (bare = the default capture device, * `user`/`environment` pick one). * * The test that separates this shape from the enumerated one: ask what * `{true}` and `{false}` each render, then what those markups MEAN. Here all * three states are real and distinct, so both forms are typed. For `draggable` * they collapse onto the same `auto` state, which is why `boolean` is rejected * there. * * ## Deliberate deviations from the spec * * Each of these is a knowing departure, kept because removing it would cost * more than it buys: * * - **Lowercase aliases** (`class`, `for`, `tabindex`, `autofocus`, * `spellcheck`, `contenteditable`, `autocomplete`) sit alongside the * camelCase forms. Both spellings are accepted because the migration docs * tell incoming developers to write the real HTML name. * - **`contentEditable="inherit"`** is accepted but is *not* a spec keyword. * It lands on the inherit state only via the invalid-value default. Kept * for React parity; omitting the attribute is the spec-correct way to * inherit. * - **`tabindex` / `autofocus` / `capture`** accept a widened value set * (string ints, plain `boolean`) matching what the parser actually honors. * - **`cellPadding` / `cellSpacing`** are obsolete presentational attributes, * marked `@deprecated` rather than removed so legacy markup still compiles. * - **`xlink:*`** attributes are deprecated in SVG 2 but still typed — real * documents and icon sprites still carry them. * - **`data-morph-skip` / `-skip-children` / `-preserve`** are kerf's own * `data-*` attributes, valid HTML by the `data-*` rule. * - **``** is Open Graph vocabulary, not an attribute in the * HTML standard. Typed anyway because it is universal in real documents — * every social-preview `` carries `og:*` meta tags. * * Attributes that are *not* typed because they do nothing when rendered as * markup: `value` / `defaultValue` on `` — which is what kerf's morph reconciles. */ interface HTMLTextareaAttrs extends KerfBaseAttrs { name?: AttrLike; placeholder?: AttrLike; rows?: AttrLike; cols?: AttrLike; required?: AttrLike; disabled?: AttrLike; readOnly?: AttrLike; maxLength?: AttrLike; minLength?: AttrLike; wrap?: AttrLike<'hard' | 'soft' | 'off'>; /** Submits the field's text direction alongside its value, under this name. */ dirName?: AttrLike; autoComplete?: AttrLike; /** KF-183 — lowercase HTML form accepted alongside `autoComplete`. */ autocomplete?: AttrLike; form?: AttrLike; } interface HTMLTableAttrs extends KerfBaseAttrs { /** @deprecated Obsolete presentational attribute — use CSS `padding` on the cells. Typed so legacy markup still compiles. */ cellPadding?: AttrLike; /** @deprecated Obsolete presentational attribute — use CSS `border-spacing`. Typed so legacy markup still compiles. */ cellSpacing?: AttrLike; } interface HTMLTableCellAttrs extends KerfBaseAttrs { colSpan?: AttrLike; rowSpan?: AttrLike; headers?: AttrLike; scope?: AttrLike<'row' | 'col' | 'rowgroup' | 'colgroup'>; abbr?: AttrLike; } interface HTMLColAttrs extends KerfBaseAttrs { span?: AttrLike; } interface HTMLMetaAttrs extends KerfBaseAttrs { name?: AttrLike; content?: AttrLike; charSet?: AttrLike; httpEquiv?: AttrLike; media?: AttrLike; /** * Open Graph (`og:title` etc.) — NOT in the HTML standard, typed because it * is universal in real documents. See the deviations list in this file's * header. */ property?: AttrLike; } interface HTMLLinkAttrs extends KerfBaseAttrs { href?: AttrLike; rel?: AttrLike; type?: AttrLike; media?: AttrLike; sizes?: AttrLike; hrefLang?: AttrLike; as?: AttrLike; crossOrigin?: AttrLike; integrity?: AttrLike; referrerPolicy?: AttrLike; fetchPriority?: FetchPriority; /** A genuine HTML boolean attribute on ``: the stylesheet is not applied (and for a stylesheet link, not fetched) until it's removed. */ disabled?: AttrLike; /** For `rel="preload" as="image"`: the srcset the preload should match. */ imageSrcSet?: AttrLike; imageSizes?: AttrLike; blocking?: BlockingToken; } interface HTMLScriptAttrs extends KerfBaseAttrs { src?: AttrLike; type?: AttrLike; async?: AttrLike; defer?: AttrLike; noModule?: AttrLike; integrity?: AttrLike; crossOrigin?: AttrLike; referrerPolicy?: AttrLike; blocking?: BlockingToken; fetchPriority?: FetchPriority; } /** No `scoped`: the proposal was removed from the HTML standard and never shipped in any engine. */ interface HTMLStyleAttrs extends KerfBaseAttrs { type?: AttrLike; media?: AttrLike; blocking?: BlockingToken; } interface HTMLIframeAttrs extends KerfBaseAttrs { src?: AttrLike; srcDoc?: AttrLike; name?: AttrLike; sandbox?: AttrLike; allow?: AttrLike; allowFullScreen?: AttrLike; width?: AttrLike; height?: AttrLike; loading?: LoadingBehavior; referrerPolicy?: AttrLike; } interface HTMLMediaAttrs extends KerfBaseAttrs { src?: AttrLike; controls?: AttrLike; autoPlay?: AttrLike; loop?: AttrLike; muted?: AttrLike; preload?: AttrLike<'auto' | 'metadata' | 'none' | ''>; crossOrigin?: AttrLike; } interface HTMLVideoAttrs extends HTMLMediaAttrs { poster?: AttrLike; width?: AttrLike; height?: AttrLike; playsInline?: AttrLike; } interface HTMLSourceAttrs extends KerfBaseAttrs { src?: AttrLike; type?: AttrLike; srcSet?: AttrLike; sizes?: AttrLike; media?: AttrLike; /** Valid when the parent is ``: intrinsic dimensions for the candidate image, so layout is stable before selection. */ width?: AttrLike; height?: AttrLike; } interface HTMLTrackAttrs extends KerfBaseAttrs { src?: AttrLike; kind?: AttrLike<'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata'>; srcLang?: AttrLike; label?: AttrLike; default?: AttrLike; } interface HTMLDetailsAttrs extends KerfBaseAttrs { open?: AttrLike; } interface HTMLDialogAttrs extends KerfBaseAttrs { open?: AttrLike; } interface HTMLOlAttrs extends KerfBaseAttrs { reversed?: AttrLike; start?: AttrLike; type?: AttrLike<'1' | 'a' | 'A' | 'i' | 'I'>; } interface HTMLLiAttrs extends KerfBaseAttrs { value?: AttrLike; } interface HTMLProgressAttrs extends KerfBaseAttrs { value?: AttrLike; max?: AttrLike; } interface HTMLMeterAttrs extends KerfBaseAttrs { value?: AttrLike; min?: AttrLike; max?: AttrLike; low?: AttrLike; high?: AttrLike; optimum?: AttrLike; } interface HTMLCanvasAttrs extends KerfBaseAttrs { width?: AttrLike; height?: AttrLike; } interface HTMLBaseAttrs extends KerfBaseAttrs { href?: AttrLike; target?: AttrLike; } interface HTMLBlockquoteAttrs extends KerfBaseAttrs { cite?: AttrLike; } interface HTMLQAttrs extends KerfBaseAttrs { cite?: AttrLike; } /** * SVG attribute set — focused on the elements `toElement`'s SVG path supports * (the `SVG_FRAGMENT_TAGS` set in `src/toElement.ts`). Presentation attrs are * shared via `SVGPresentationAttrs`. */ interface SVGPresentationAttrs { fill?: AttrLike; fillOpacity?: AttrLike; fillRule?: AttrLike<'nonzero' | 'evenodd' | 'inherit'>; stroke?: AttrLike; strokeWidth?: AttrLike; strokeOpacity?: AttrLike; strokeLinecap?: AttrLike<'butt' | 'round' | 'square' | 'inherit'>; strokeLinejoin?: AttrLike<'miter' | 'round' | 'bevel' | 'inherit'>; strokeDasharray?: AttrLike; strokeDashoffset?: AttrLike; strokeMiterlimit?: AttrLike; opacity?: AttrLike; vectorEffect?: AttrLike; clipPath?: AttrLike; clipRule?: AttrLike; mask?: AttrLike; filter?: AttrLike; pointerEvents?: AttrLike; shapeRendering?: AttrLike; paintOrder?: AttrLike; color?: AttrLike; display?: AttrLike; visibility?: AttrLike; } interface SVGCommonAttrs extends DataAriaAttrs, SVGPresentationAttrs { id?: AttrLike; className?: AttrLike; /** KF-191 — lowercase HTML form accepted alongside `className`. */ class?: AttrLike; style?: AttrLike; transform?: AttrLike; tabIndex?: AttrLike; /** KF-191 — lowercase HTML form accepted alongside `tabIndex` (string-valued per the HTML/SVG spec). */ tabindex?: AttrLike; role?: AttrLike; xmlns?: AttrLike; xmlnsXlink?: AttrLike; children?: unknown; } interface SVGSvgAttrs extends SVGCommonAttrs { width?: AttrLike; height?: AttrLike; viewBox?: AttrLike; preserveAspectRatio?: AttrLike; x?: AttrLike; y?: AttrLike; } interface SVGPathAttrs extends SVGCommonAttrs { d?: AttrLike; pathLength?: AttrLike; } interface SVGCircleAttrs extends SVGCommonAttrs { cx?: AttrLike; cy?: AttrLike; r?: AttrLike; } interface SVGRectAttrs extends SVGCommonAttrs { x?: AttrLike; y?: AttrLike; width?: AttrLike; height?: AttrLike; rx?: AttrLike; ry?: AttrLike; } interface SVGLineAttrs extends SVGCommonAttrs { x1?: AttrLike; y1?: AttrLike; x2?: AttrLike; y2?: AttrLike; } interface SVGEllipseAttrs extends SVGCommonAttrs { cx?: AttrLike; cy?: AttrLike; rx?: AttrLike; ry?: AttrLike; } interface SVGPolyAttrs extends SVGCommonAttrs { points?: AttrLike; } interface SVGTextAttrs extends SVGCommonAttrs { x?: AttrLike; y?: AttrLike; dx?: AttrLike; dy?: AttrLike; textAnchor?: AttrLike<'start' | 'middle' | 'end' | 'inherit'>; dominantBaseline?: AttrLike; fontFamily?: AttrLike; fontSize?: AttrLike; fontStyle?: AttrLike; fontWeight?: AttrLike; letterSpacing?: AttrLike; } interface SVGUseAttrs extends SVGCommonAttrs { xlinkHref?: AttrLike; href?: AttrLike; x?: AttrLike; y?: AttrLike; width?: AttrLike; height?: AttrLike; } interface SVGImageAttrs extends SVGCommonAttrs { href?: AttrLike; xlinkHref?: AttrLike; x?: AttrLike; y?: AttrLike; width?: AttrLike; height?: AttrLike; preserveAspectRatio?: AttrLike; } interface SVGForeignObjectAttrs extends SVGCommonAttrs { x?: AttrLike; y?: AttrLike; width?: AttrLike; height?: AttrLike; } /** * Loose attribute set for custom elements / web components. Use via * declaration merging into the `kerfjs/jsx-runtime` JSX namespace if your * project uses tags not enumerated below: * * import type { KerfCustomElement } from 'kerfjs/jsx-runtime'; * * declare module 'kerfjs/jsx-runtime' { * namespace JSX { * interface IntrinsicElements { * 'my-component': KerfCustomElement & { foo?: string }; * } * } * } * * `KerfCustomElement` is re-exported from `kerfjs/jsx-runtime` (KF-100) so * apps don't need to reach into the internal `kerfjs/jsx-types` path. */ interface KerfCustomElement extends KerfBaseAttrs { [k: string]: AttrValue | unknown; } /** * Built-in tag table. Renamed from `IntrinsicElements` (KF-123) so the type * name in `dist/jsx-runtime.d.ts` cannot shadow the namespace's own * `IntrinsicElements` after tsup/tsc strips import aliases — the previous * name produced `interface IntrinsicElements extends IntrinsicElements {}` * in the emitted .d.ts, which self-resolves to an empty interface and * breaks every `` in consumer .tsx with TS2339. */ interface KerfBuiltinIntrinsicElements { html: KerfBaseAttrs; head: KerfBaseAttrs; body: KerfBaseAttrs; div: KerfBaseAttrs; span: KerfBaseAttrs; section: KerfBaseAttrs; article: KerfBaseAttrs; header: KerfBaseAttrs; footer: KerfBaseAttrs; main: KerfBaseAttrs; nav: KerfBaseAttrs; aside: KerfBaseAttrs; h1: KerfBaseAttrs; h2: KerfBaseAttrs; h3: KerfBaseAttrs; h4: KerfBaseAttrs; h5: KerfBaseAttrs; h6: KerfBaseAttrs; p: KerfBaseAttrs; hr: KerfBaseAttrs; br: KerfBaseAttrs; pre: KerfBaseAttrs; blockquote: HTMLBlockquoteAttrs; q: HTMLQAttrs; ol: HTMLOlAttrs; ul: KerfBaseAttrs; li: HTMLLiAttrs; dl: KerfBaseAttrs; dt: KerfBaseAttrs; dd: KerfBaseAttrs; figure: KerfBaseAttrs; figcaption: KerfBaseAttrs; a: HTMLAnchorAttrs; em: KerfBaseAttrs; strong: KerfBaseAttrs; small: KerfBaseAttrs; s: KerfBaseAttrs; cite: KerfBaseAttrs; code: KerfBaseAttrs; kbd: KerfBaseAttrs; samp: KerfBaseAttrs; var: KerfBaseAttrs; sub: KerfBaseAttrs; sup: KerfBaseAttrs; i: KerfBaseAttrs; b: KerfBaseAttrs; u: KerfBaseAttrs; mark: KerfBaseAttrs; abbr: KerfBaseAttrs; time: KerfBaseAttrs & { dateTime?: AttrLike; }; img: HTMLImgAttrs; picture: KerfBaseAttrs; source: HTMLSourceAttrs; track: HTMLTrackAttrs; iframe: HTMLIframeAttrs; embed: KerfBaseAttrs & { src?: AttrLike; type?: AttrLike; width?: AttrLike; height?: AttrLike; }; object: KerfBaseAttrs & { data?: AttrLike; type?: AttrLike; name?: AttrLike; width?: AttrLike; height?: AttrLike; }; audio: HTMLMediaAttrs; video: HTMLVideoAttrs; canvas: HTMLCanvasAttrs; area: HTMLAreaAttrs; map: KerfBaseAttrs & { name?: AttrLike; }; form: HTMLFormAttrs; input: HTMLInputAttrs; button: HTMLButtonAttrs; select: HTMLSelectAttrs; optgroup: HTMLOptgroupAttrs; option: HTMLOptionAttrs; textarea: HTMLTextareaAttrs; label: HTMLLabelAttrs; fieldset: KerfBaseAttrs & { name?: AttrLike; form?: AttrLike; disabled?: AttrLike; }; legend: KerfBaseAttrs; datalist: KerfBaseAttrs; output: KerfBaseAttrs & { name?: AttrLike; form?: AttrLike; htmlFor?: AttrLike; for?: AttrLike; }; progress: HTMLProgressAttrs; meter: HTMLMeterAttrs; table: HTMLTableAttrs; caption: KerfBaseAttrs; colgroup: HTMLColAttrs; col: HTMLColAttrs; thead: KerfBaseAttrs; tbody: KerfBaseAttrs; tfoot: KerfBaseAttrs; tr: KerfBaseAttrs; td: HTMLTableCellAttrs; th: HTMLTableCellAttrs; meta: HTMLMetaAttrs; link: HTMLLinkAttrs; script: HTMLScriptAttrs; style: HTMLStyleAttrs; base: HTMLBaseAttrs; title: KerfBaseAttrs; details: HTMLDetailsAttrs; summary: KerfBaseAttrs; dialog: HTMLDialogAttrs; template: KerfBaseAttrs; slot: KerfBaseAttrs & { name?: AttrLike; }; svg: SVGSvgAttrs; g: SVGCommonAttrs; defs: SVGCommonAttrs; symbol: SVGCommonAttrs; use: SVGUseAttrs; path: SVGPathAttrs; circle: SVGCircleAttrs; rect: SVGRectAttrs; line: SVGLineAttrs; ellipse: SVGEllipseAttrs; polygon: SVGPolyAttrs; polyline: SVGPolyAttrs; text: SVGTextAttrs; tspan: SVGTextAttrs; image: SVGImageAttrs; foreignObject: SVGForeignObjectAttrs; clipPath: SVGCommonAttrs; mask: SVGCommonAttrs; pattern: SVGCommonAttrs; filter: SVGCommonAttrs; marker: SVGCommonAttrs; linearGradient: SVGCommonAttrs; radialGradient: SVGCommonAttrs; stop: SVGCommonAttrs & { offset?: AttrLike; stopColor?: AttrLike; stopOpacity?: AttrLike; }; } /** * `Segment` — kerf's structured render output. * * The JSX runtime emits a `SafeHtml` wrapping a `Segment`. Most renders * produce a single static segment (just an HTML string), which behaves * exactly like a string for backward compatibility. When the tree * contains a list (`each()`) or a parent whose children include a list, * the runtime emits a structured segment that `mount()` can dispatch * on — running its native keyed reconciler for the list parts and * leaving the static surrounds to the general-purpose diff. * * Why have a structured form at all: the perf bottleneck for huge * keyed lists isn't the per-row JSX work (which `each()` already * memoizes). It's that flattening every render's whole tree to one * big HTML string forces a full `innerHTML` parse and a tree walk * over rows we know are unchanged. The segment shape lets mount() * skip both for the list parts. */ type Segment = StaticSegment | ListSegment | MixedSegment; interface StaticSegment { kind: 'static'; html: string; } interface ListItem { /** * The row's object identity. Used by the reconciler to match new items * against live DOM nodes across renders. Unchanged ref → reuse the * existing live node; replaced ref → build a fresh node. */ ref: object; /** * KF-294: the row's fine-grained binding specs (signals in row attrs/text). * Undefined for granular-path rows (which snapshot in this spike). The * snapshot reconciler wires these to the fresh row node and disposes them * when the row is removed. */ bindings?: Binding[]; /** * Optional cache-invalidation key that captures external state affecting * this row's render (e.g. selection class). Different cacheKey on the * same `ref` triggers a cache miss for that row. `undefined` when the * user didn't pass a `key` callback to `each()`. */ cacheKey: unknown; html: string; } interface ListSegment { kind: 'list'; id: string; items: ListItem[]; /** * Optional granular patches (KF-92). When present, the list reconciler * applies these directly to the existing binding instead of doing a * full classify+reconcile pass. Emitted by `each()` when bound to an * `arraySignal`. Mutually exclusive with the `items` snapshot in the * sense that the snapshot is treated as informational/fall-back when * patches are present. */ patches?: ArrayPatchInternal[]; /** * KF-388: the identity of the data this list renders — the `arraySignal` * instance, or `undefined` for a plain array. * * A list's `id` is its call-order index, so a render that changes how many * `each()` calls precede this one hands this segment a DIFFERENT list's * binding. Patches are only meaningful against the binding they were queued * for, so the reconciler compares this against the binding's recorded source * before trusting the patch queue. It is an identity check, not a value * check — the instance is never read. */ source?: object; } /** * Internal patch shape used inside list segments. Mirrors `ArrayPatch` * from `array-signal.ts` but typed against `object` so the segment layer * doesn't need to be generic. `update` / `insert` patches carry the row's * pre-rendered HTML — `each()` renders them at JSX-evaluation time inside a * try/catch so a throwing render falls back to the snapshot path (KF-99) * instead of leaving the signal and DOM divergent. */ type ArrayPatchInternal = { type: 'update'; index: number; item: object; html: string; bindings?: Binding[]; } | { type: 'insert'; index: number; item: object; html: string; bindings?: Binding[]; } | { type: 'remove'; index: number; } | { type: 'move'; from: number; to: number; } | { type: 'replace'; items: readonly object[]; }; interface MixedSegment { kind: 'mixed'; parts: Segment[]; } /** * kerf JSX runtime. * * JSX renders to `SafeHtml`, which wraps both: * - `__html`: the flattened HTML string (what `toString()` returns; what * legacy/SSR consumers care about) * - `__segment`: a structured representation that distinguishes "static * html", "keyed list", and "mixed" content. * * Most renders are pure-static and the segment is just `{kind:'static',html}`. * When the tree contains a list (via `each()`) or a parent whose children * include a non-static segment, the runtime threads that structure up so * `mount()` can dispatch on it — running its native keyed reconciler for * the list parts and leaving the static surrounds to the general-purpose * diff. * * Configure in your `tsconfig.json`: * * "jsx": "react-jsx", * "jsxImportSource": "kerfjs" * * Then write JSX as you normally would — kerf provides the `jsx` / * `jsxs` / `jsxDEV` / `Fragment` exports the JSX transform looks for. */ declare const SAFE_HTML_BRAND: unique symbol; declare class SafeHtml { readonly __html: string; readonly __segment: Segment; readonly [SAFE_HTML_BRAND]: true; constructor(input: string | Segment); toString(): string; } /** * Type guard for `SafeHtml`. Prefer this over `instanceof SafeHtml` — it works * across module copies (e.g. when the consumer's bundler loads kerf's barrel * and JSX-runtime entries as independent modules). * * Security note (KF-321): this is a duck-check on the global `Symbol.for` brand, * so same-realm code *can* forge a "trusted" value — `{ [SAFE_HTML_BRAND]: true, * __html: '' }` passes and bypasses escaping + the URL screen. * This is intentional and not a vulnerability: minting the brand requires a * Symbol key, which no data channel (JSON.parse, form/query/localStorage, * structuredClone, JSON-based prototype-pollution) can produce — those all yield * string keys. The only way to forge it is to run JS that writes the symbol, and * such code can equally `import { raw }`. Forgery therefore grants no capability * an attacker with code execution lacks — the same posture as React's * `$$typeof: Symbol.for('react.element')`. The global `Symbol.for` (vs a * module-private symbol) is a deliberate cross-bundle-recognition tradeoff (see * the `SAFE_HTML_BRAND` note above); a private symbol would additionally block * same-realm forgery, but only closes a non-threat at the cost of that * recognition, so it's kept global by design. */ declare function isSafeHtml(value: unknown): value is SafeHtml; /** * Inject a pre-escaped HTML string verbatim, bypassing kerf's auto-escaping. * * **Reach for this rarely.** kerf escapes automatically everywhere else, so a lot * of `raw()` in a codebase is usually a sign the wrong tool is being used — the * common cases have a safer, first-class answer: * - Interpolating dynamic text/attributes? Plain JSX (`

{value}

`, * `class={sig}`) already escapes it; you don't need `raw()`. * - Composing markup? Build a {@link SafeHtml} the normal way — a JSX expression, * the `html` tagged template (`kerfjs/html`), `each()`, or a component function * that returns JSX. Those all produce trusted `SafeHtml` without hand-writing * an HTML string. * - A genuinely trusted, pre-escaped **dynamic** value (server output, config, a * hard-coded string)? That's the one legitimate use. The * `kerfjs/no-raw-with-dynamic-arg` lint rule flags a `raw()` whose argument is * not a literal — because unsanitized user input is the canonical XSS mistake — * so acknowledge it with a `// eslint-disable-next-line * kerfjs/no-raw-with-dynamic-arg` at that call. That explicit override is the * single sanctioned way to say "I've reviewed this; it's trusted," and it * leaves a searchable audit trail. * * `raw()` is NOT a sanitizer — it does no escaping. For user-controlled input, * sanitize first (`raw(DOMPurify.sanitize(marked(userMarkdown)))`) or, better, * render it through escaping JSX instead. */ declare function raw(html: string): SafeHtml; /** * Internal: build a `SafeHtml` representing a list segment. Used by * `each()` so the JSX runtime is the sole owner of `SafeHtml` construction. */ declare function listSafeHtml(id: string, items: ListSegment['items'], source?: object): SafeHtml; /** * Internal: build a `SafeHtml` representing a granular list segment with * patches (KF-92). The reconciler applies the patches to the existing * binding directly, skipping the per-item iteration that the snapshot * `listSafeHtml` requires. `items` is included for fall-through paths * (toString during SSR, fall-back when the binding doesn't exist yet). * * Patch HTML is rendered upstream (in `each()`) inside a try/catch — see * KF-99 — so by the time we get here every `update` / `insert` patch * already carries a `html` string, and the reconciler does no further * row rendering. */ declare function granularListSafeHtml(id: string, items: ListSegment['items'], patches: NonNullable, source?: object): SafeHtml; type Child = SafeHtml | string | number | boolean | null | undefined | ReadonlySignal; type Children = Child | Children[]; interface Props { children?: Children; [key: string]: unknown; } /** * Reject an attribute NAME that kerf must never route into HTML or the DOM. * Shared by BOTH attribute paths so they enforce one contract (KF-306, KF-322): * * - the static string path (`renderAttr`) — a name is emitted verbatim into * the open tag, and `on*` values become live inline handlers once parsed; * - the fine-grained bound path (`jsx()` signal branch → `bindAttr` → * `setBoundAttr` → `el.setAttribute(name, …)`) — `setAttribute('onclick', …)` * installs a LIVE handler in the browser, an XSS vector that would otherwise * bypass the static guard entirely. * * Two rejections: * 1. `on*` (any case) — kerf's model is event delegation, never inline * handlers. `isFn` tailors the message: function values get the * delegate() migration pointer; string/signal/other values get the * XSS-aware message. * 2. a malformed name (spread of untrusted keys, `
`) that could * break out of the open tag. The bound path can't actually inject markup * this way — `setAttribute` throws `InvalidCharacterError` rather than * parsing — but the same guard runs on both paths for one consistent * contract. Validated post-alias; every `ATTR_ALIASES` value is itself a * valid name, so aliasing is unaffected. */ declare function assertEmittableAttrName(key: string, name: string, isFn: boolean): void; declare function jsx(tag: string | ((props: Props) => SafeHtml), props: Props): SafeHtml; declare function Fragment({ children }: { children?: Children; }): SafeHtml; declare namespace JSX { type Element = SafeHtml; interface ElementChildrenAttribute { children: unknown; } interface IntrinsicElements extends KerfBuiltinIntrinsicElements { } } /** * Internal coordination exports for the `kerfjs/html` tagged-template * front-end (`src/html.ts`). Underscore-prefixed and deliberately NOT in the * main barrel — the contract is that `html\`\`` routes through the exact same * value semantics as JSX (text holes: `_toSegment`; attribute holes: * `_assertEmittableAttrName` + `_renderAttrVerbatim`), so the two authoring * paths cannot drift. `_renderAttrVerbatim` skips the camelCase * `ATTR_ALIASES` table: template authors write real HTML attribute names * (`class`, not `className`). */ declare function _toSegment(child: unknown): Segment; declare function _renderAttrVerbatim(name: string, value: unknown): string; export { type AttrLike, type AttrValue, type DataAriaAttrs, Fragment, JSX, type KerfBaseAttrs, type KerfCustomElement, SafeHtml, assertEmittableAttrName as _assertEmittableAttrName, _renderAttrVerbatim, _toSegment, granularListSafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw };