From 9e8f75876ef431bf22492846270ed381918b4cde Mon Sep 17 00:00:00 2001 From: "Grok (via xAI)" Date: Thu, 15 Oct 2026 04:34:54 +0000 Subject: [PATCH] fix: format README and isolate document shell - Prettier-format README so format:check passes. - Document shell (document, body, shell options, openTag, mergeShell) lives under src/templates/document and is published as the @llazyemail/template-runtime-display/document subpath. - Email shell imports from the document entry instead of owning merge logic. --- README.md | 16 ++++++++-------- package.json | 10 ++++++++++ src/index.ts | 6 ++---- src/templates/document/index.ts | 18 ++++++++++++++++++ src/templates/document/mergeShell.ts | 18 ++++++++++++++++++ src/templates/email/shell.ts | 20 ++------------------ src/templates/email/types.ts | 2 +- tsup.config.ts | 1 + 8 files changed, 60 insertions(+), 31 deletions(-) create mode 100644 src/templates/document/index.ts create mode 100644 src/templates/document/mergeShell.ts diff --git a/README.md b/README.md index 711fce0..91b98fe 100644 --- a/README.md +++ b/README.md @@ -100,20 +100,20 @@ renderTemplate(page, { title: 'Hello' }, { slots: { extra: '

More

' } }); The email preset calls these. They stay public so a part, or a caller, can use them directly. -| Function | Role | -| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| `head({ title?, preview?, styles?, extraHead? })` | `` with inbox meta, ``, optional preheader, styles, extra HTML | -| `main({ heading?, bodyText?, ctaLabel?, ctaUrl?, innerHtml? })` | `<main>` with an `<h1>`, a paragraph, and an optional button, or raw `innerHtml` | -| `footer({ companyName?, unsubscribeUrl? })` | `<footer>` with the company name and an optional unsubscribe link | -| `body({ preheaderHtml?, mainHtml?, footerHtml?, attributes? })` | `<body>` around an optional preheader, main, and footer. Optional raw attributes. | -| `content({ content? })` | A `<div>` around raw HTML. The email shell does not call it | +| Function | Role | +| ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| `head({ title?, preview?, styles?, extraHead? })` | `<head>` with inbox meta, `<title>`, optional preheader, styles, extra HTML | +| `main({ heading?, bodyText?, ctaLabel?, ctaUrl?, innerHtml? })` | `<main>` with an `<h1>`, a paragraph, and an optional button, or raw `innerHtml` | +| `footer({ companyName?, unsubscribeUrl? })` | `<footer>` with the company name and an optional unsubscribe link | +| `body({ preheaderHtml?, mainHtml?, footerHtml?, attributes? })` | `<body>` around an optional preheader, main, and footer. Optional raw attributes. | +| `content({ content? })` | A `<div>` around raw HTML. The email shell does not call it | | `document({ headHtml?, mainHtml?, footerHtml?, doctype?, lang?, htmlAttributes?, bodyAttributes? })` | Doctype, `<html>`, head, and body. Defaults to HTML5 + `lang="en"`. Attributes accept a raw string or a structured map. | Omitted strings render empty. The button is present only when both `ctaLabel` and `ctaUrl` are set. The unsubscribe link is present only when `unsubscribeUrl` is set. `bodyText`, `content`, and slot strings may contain inline HTML. Callers own that markup. `escapeHtml` is exported for text and attribute positions; the shell does not call it. -Subpath exports: `@llazyemail/template-runtime-display/escape`, `/head`, `/body`, and `/main`. +Subpath exports: `@llazyemail/template-runtime-display/escape`, `/head`, `/body`, `/main`, and `/document`. ## Scripts diff --git a/package.json b/package.json index 32decc6..95c31e4 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,16 @@ "types": "./dist/main.d.cts", "default": "./dist/main.cjs" } + }, + "./document": { + "import": { + "types": "./dist/document.d.ts", + "default": "./dist/document.js" + }, + "require": { + "types": "./dist/document.d.cts", + "default": "./dist/document.cjs" + } } }, "files": [ diff --git a/src/index.ts b/src/index.ts index f0bd0f5..5e1d032 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,7 +42,5 @@ export type { BodyProps } from './templates/body/body'; export { content } from './templates/content/content'; export type { ContentProps } from './templates/content/content'; -export { document } from './templates/document/document'; -export type { DocumentProps, DocumentShellOptions } from './templates/document/document'; -export { openTag, serializeAttributes } from './utils/openTag'; -export type { TagAttributes } from './utils/openTag'; +export { document, mergeShell, openTag, serializeAttributes } from './templates/document'; +export type { DocumentProps, DocumentShellOptions, TagAttributes } from './templates/document'; diff --git a/src/templates/document/index.ts b/src/templates/document/index.ts new file mode 100644 index 0000000..a6d116e --- /dev/null +++ b/src/templates/document/index.ts @@ -0,0 +1,18 @@ +/** + * Document shell entry. + * + * Owns the outer HTML document (doctype, html attributes, body attributes) + * and the helpers that build it. Import from the package root or from + * `@llazyemail/template-runtime-display/document`. + */ + +export { document } from './document'; +export type { DocumentProps, DocumentShellOptions } from './document'; + +export { body } from '../body/body'; +export type { BodyProps } from '../body/body'; + +export { openTag, serializeAttributes } from '../../utils/openTag'; +export type { TagAttributes } from '../../utils/openTag'; + +export { mergeShell } from './mergeShell'; diff --git a/src/templates/document/mergeShell.ts b/src/templates/document/mergeShell.ts new file mode 100644 index 0000000..ad5745b --- /dev/null +++ b/src/templates/document/mergeShell.ts @@ -0,0 +1,18 @@ +import type { DocumentShellOptions } from './document'; + +/** + * Merge template-level shell defaults with per-render overrides. + * Defined fields on `overrides` win. Omitted fields keep the default. + * Explicit `false` / `null` for `lang` is preserved. + */ +export function mergeShell( + defaults: DocumentShellOptions | undefined, + overrides: DocumentShellOptions, +): DocumentShellOptions { + const merged: DocumentShellOptions = { ...defaults }; + if (overrides.doctype !== undefined) merged.doctype = overrides.doctype; + if (overrides.lang !== undefined) merged.lang = overrides.lang; + if (overrides.htmlAttributes !== undefined) merged.htmlAttributes = overrides.htmlAttributes; + if (overrides.bodyAttributes !== undefined) merged.bodyAttributes = overrides.bodyAttributes; + return merged; +} diff --git a/src/templates/email/shell.ts b/src/templates/email/shell.ts index fbb76a9..0d96769 100644 --- a/src/templates/email/shell.ts +++ b/src/templates/email/shell.ts @@ -1,6 +1,6 @@ import { displayErrors } from '../../runtime/errors'; -import { document } from '../document/document'; -import type { DocumentShellOptions } from '../document/document'; +import { document, mergeShell } from '../document'; +import type { DocumentShellOptions } from '../document'; import { hasSlot, slot } from '../../runtime/render'; import type { RenderContext, TemplatePart } from '../../runtime/types'; import type { EmailProps } from './types'; @@ -26,22 +26,6 @@ export function shellPart( }; } -/** - * Merge template-level shell defaults with per-render props. - * Props win when both supply a value. - */ -export function mergeShell( - defaults: DocumentShellOptions | undefined, - props: EmailProps, -): DocumentShellOptions { - const merged: DocumentShellOptions = { ...defaults }; - if (props.doctype !== undefined) merged.doctype = props.doctype; - if (props.lang !== undefined) merged.lang = props.lang; - if (props.htmlAttributes !== undefined) merged.htmlAttributes = props.htmlAttributes; - if (props.bodyAttributes !== undefined) merged.bodyAttributes = props.bodyAttributes; - return merged; -} - /** Place custom part HTML inside `<body>`, after `main` and before `footer`. */ export function composeEmail( parts: Readonly<Record<string, string>>, diff --git a/src/templates/email/types.ts b/src/templates/email/types.ts index 2d3320f..7df6895 100644 --- a/src/templates/email/types.ts +++ b/src/templates/email/types.ts @@ -1,5 +1,5 @@ import type { TemplatePart } from '../../runtime/types'; -import type { DocumentShellOptions } from '../document/document'; +import type { DocumentShellOptions } from '../document'; /** Flat props for the built-in email shell. Every field is optional. */ export interface EmailProps extends DocumentShellOptions { diff --git a/tsup.config.ts b/tsup.config.ts index 9378610..a623bf0 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -32,6 +32,7 @@ export default defineConfig([ head: 'src/templates/head/head.ts', body: 'src/templates/body/body.ts', main: 'src/templates/main/main.ts', + document: 'src/templates/document/index.ts', }, format: ['esm', 'cjs'], clean: false,