diff --git a/docs/docs/howto/intro.md b/docs/docs/howto/intro.md index 9dc1bf5..7b9e924 100644 --- a/docs/docs/howto/intro.md +++ b/docs/docs/howto/intro.md @@ -2,7 +2,7 @@ ## Demo -```tsx twoslash +```jsx twoslash // @errors: 2322 function MyComponent({ children }) { @@ -103,7 +103,7 @@ Configure your `tsconfig.json` as follows: If you don't have any other JSX runtimes like React or Preact set up, you can use `typed-htmx/typed-html`, which will convert JSX into strings at runtime. -You can configure the runtime using [`jsxConfig`](/typed-htmx/docs/api/module.index/Variables/variable.jsxConfig-1): +You can configure the runtime using [`jsxConfig`](/typed-htmx/docs/api/index/variables/jsxConfig): ```js twoslash import { jsxConfig } from "typed-htmx"; diff --git a/docs/docs/howto/xternal.md b/docs/docs/howto/xternal.md new file mode 100644 index 0000000..38a3b80 --- /dev/null +++ b/docs/docs/howto/xternal.md @@ -0,0 +1,65 @@ +# Augmenting external JSX libraries + +typed-htmx is extremely minimal and requires the user to manually augment external JSX libraries that provide their own types. + +## Common guidance + +- Create a `types.d.ts` (any name is fine, as long as it ends in `.d.ts`) at the top of your src/ folder, + or anywhere within the configured `include` of your tsconfig.json +- Write a JSX element, e.g. `
`, and inspect its type +- If you see React-related types, you are good to go +- If not, try to discover the common namespace under which all HTML attributes go. + +Let's use [Hono](https://hono.dev/top) as an example. + +```tsx twoslash +// @jsxImportSource: hono/jsx +// In tsconfig.json, jsxImportSource = "hono/jsx" + +// The type we are augmenting in this case is `Hono.HTMLAttributes`. +// hx-boost is not recognized as a proper attribute yet. +
+//^? +``` + +With this knowledge, we can now augment the type of `Hono.HTMLAttributes` assuming it is an interface: + +```tsx twoslash +// @errors: 2322 +// @jsxImportSource: hono/jsx +/// + +declare global { + namespace Hono { + interface HTMLAttributes extends HtmxAttributes {} + } +} + +
+``` + +## Hono + +```ts twoslash +import 'typed-htmx'; + +declare global { + namespace Hono { + interface HTMLAttributes extends HtmxAttributes {} + } +} +``` + +## Astro + +```ts twoslash +import 'typed-htmx'; + +declare global { + namespace astroHTML.JSX { + interface IntrinsicAttributes extends HtmxAttributes {} + } +} +``` diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 94cd39d..dd93ad3 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -21,8 +21,8 @@ const config = { markdown: { format: 'md' }, - onBrokenLinks: "throw", - onBrokenMarkdownLinks: "ignore", + onBrokenLinks: "warn", + onBrokenMarkdownLinks: "warn", i18n: { defaultLocale: "en", @@ -36,12 +36,10 @@ const config = { ({ entryPoints: ["../src/index.ts", "../src/jsx.d.ts"], tsconfig: "../tsconfig.json", - readme: "none", hideInPageTOC: true, + readme: 'none', watch: process.env.npm_lifecycle_event === "start", - // cleanOutputDir: process.env.NODE_ENV !== 'production', - cleanOutputDir: false, - excludeExternals: true, + cleanOutputDir: true, externalPattern: ["node_modules/**/*"], plugin: ["typedoc-plugin-mdn-links"], }), @@ -70,19 +68,17 @@ const config = { defaultOptions: { noErrors: false, }, - /** @type {import('typescript').CompilerOptions} */ defaultCompilerOptions: { - types: ["typed-htmx"], jsx: 4, // react-jsx - jsxImportSource: "typed-htmx/typed-html", + jsxImportSource: 'typed-htmx/typed-html', target: 99, // esnext, strict: true, + checkJs: true, noImplicitAny: false, module: 199, // nodenext, moduleResolution: 99, // nodenext }, includeJSDocInHover: true, - wrapFragments: true, alwayRaiseForTwoslashExceptions: true, disableImplicitReactImport: true, }), diff --git a/docs/package.json b/docs/package.json index d5477de..e36992b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -21,11 +21,11 @@ "clsx": "^1.2.1", "docusaurus-plugin-typedoc": "next", "docusaurus-preset-shiki-twoslash": "^1.1.41", + "hono": "^3.11.12", "object-assign": "^4.1.1", "prism-react-renderer": "^1.3.5", "react": "^18.2.0", "react-dom": "^18.2.0", - "rehype-raw": "^7.0.0", "typed-htmx": "link:..", "typedoc": "^0.25.6", "typedoc-plugin-markdown": "next", @@ -34,8 +34,7 @@ }, "devDependencies": { "@docusaurus/module-type-aliases": "3.0.1", - "@docusaurus/types": "^3.0.1", - "remark-shiki-twoslash": "^3.1.3" + "@docusaurus/types": "^3.0.1" }, "browserslist": { "production": [ diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index ebabc29..6f87673 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -26,6 +26,9 @@ dependencies: docusaurus-preset-shiki-twoslash: specifier: ^1.1.41 version: 1.1.41 + hono: + specifier: ^3.11.12 + version: 3.11.12 object-assign: specifier: ^4.1.1 version: 4.1.1 @@ -38,9 +41,6 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) - rehype-raw: - specifier: ^7.0.0 - version: 7.0.0 typed-htmx: specifier: link:.. version: link:.. @@ -64,9 +64,6 @@ devDependencies: '@docusaurus/types': specifier: ^3.0.1 version: 3.0.1(react-dom@18.2.0)(react@18.2.0) - remark-shiki-twoslash: - specifier: ^3.1.3 - version: 3.1.3(typescript@5.3.3) packages: @@ -2946,6 +2943,7 @@ packages: /@types/unist@2.0.10: resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + dev: false /@types/unist@3.0.2: resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} @@ -2975,6 +2973,7 @@ packages: lz-string: 1.5.0 transitivePeerDependencies: - supports-color + dev: false /@typescript/vfs@1.3.4: resolution: {integrity: sha512-RbyJiaAGQPIcAGWFa3jAXSuAexU4BFiDRF1g3hy7LmRqfNpYlTQWGXjcrOaVZjJ8YkkpuwG0FcsYvtWQpd9igQ==} @@ -2982,6 +2981,7 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color + dev: false /@typescript/vfs@1.3.5: resolution: {integrity: sha512-pI8Saqjupf9MfLw7w2+og+fmb0fZS0J6vsKXXrp4/PDXEFvntgzXmChCXC/KefZZS0YGS6AT8e0hGAJcTsdJlg==} @@ -2989,6 +2989,7 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color + dev: false /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -4142,6 +4143,7 @@ packages: optional: true dependencies: ms: 2.1.2 + dev: false /decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} @@ -4685,6 +4687,7 @@ packages: /fenceparser@1.1.1: resolution: {integrity: sha512-VdkTsK7GWLT0VWMK5S5WTAPn61wJ98WPFwJiRHumhg4ESNUO/tnkU8bzzzc62o6Uk1SVhuZFLnakmDA4SGV7wA==} engines: {node: '>=12'} + dev: false /file-loader@6.2.0(webpack@5.89.0): resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} @@ -5189,6 +5192,11 @@ packages: react-is: 16.13.1 dev: false + /hono@3.11.12: + resolution: {integrity: sha512-TrxH75bc0m2UbvrhaXkoo32A9OhkJtvICAYgYWtxqLDOxBjRqSikyp4K7HTbnWkPeg9Z+2Q3nv0dN4o8kL6yLg==} + engines: {node: '>=16.0.0'} + dev: false + /hpack.js@2.1.6: resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} dependencies: @@ -5748,6 +5756,7 @@ packages: /jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: false /jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -5897,6 +5906,7 @@ packages: /lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true + dev: false /markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} @@ -6638,6 +6648,7 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: false /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -7786,6 +7797,7 @@ packages: /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: false /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -7935,6 +7947,7 @@ packages: unist-util-visit: 2.0.3 transitivePeerDependencies: - supports-color + dev: false /remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} @@ -8256,6 +8269,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: false /shiki@0.10.1: resolution: {integrity: sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==} @@ -8263,6 +8277,7 @@ packages: jsonc-parser: 3.2.0 vscode-oniguruma: 1.7.0 vscode-textmate: 5.2.0 + dev: false /shiki@0.14.7: resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} @@ -8653,6 +8668,7 @@ packages: /tslib@2.1.0: resolution: {integrity: sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==} + dev: false /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -8716,6 +8732,7 @@ packages: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} hasBin: true + dev: false /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -8769,6 +8786,7 @@ packages: /unist-util-is@4.1.0: resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} + dev: false /unist-util-is@6.0.0: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} @@ -8806,6 +8824,7 @@ packages: dependencies: '@types/unist': 2.0.10 unist-util-is: 4.1.0 + dev: false /unist-util-visit-parents@6.0.1: resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} @@ -8820,6 +8839,7 @@ packages: '@types/unist': 2.0.10 unist-util-is: 4.1.0 unist-util-visit-parents: 3.1.1 + dev: false /unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} @@ -8946,9 +8966,11 @@ packages: /vscode-oniguruma@1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} + dev: false /vscode-textmate@5.2.0: resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==} + dev: false /vscode-textmate@8.0.0: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} diff --git a/docs/typedoc.json b/docs/typedoc.json index 08c463a..9785dc3 100644 --- a/docs/typedoc.json +++ b/docs/typedoc.json @@ -1,3 +1,13 @@ { - "categoryOrder": ["core", "*"] + "$schema": "https://typedoc.org/schema.json", + "categoryOrder": ["core", "*"], + "sourceLinkExternal": true, + "excludeExternals": true, + "externalPattern": [ + "**/node_modules/**/*" + ], + "readme": "bogus", + "searchCategoryBoosts": { + "Core": 2 + } } diff --git a/src/index.ts b/src/index.ts index ff535f1..3e84f1a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,6 +95,20 @@ function htmlTransformChildren(value: InterpValue): string { return out.join(" "); } +/** + * A [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) + * that interprets different kinds of {@link InterpValue values} into escaped HTML. + * + * ```ts twoslash + * import { html } from 'typed-htmx'; + * function assertEqual(left: any, right: any) {} + * // ---cut--- + * const template = html` + *
+ * `; + * assertEqual(template, `
`); + * ``` + */ export const html: HtmlTemplator = (raw, ...values) => { const values_ = values.map(htmlTransformChildren); return String.raw(raw, ...values_); diff --git a/src/jsx.d.ts b/src/jsx.d.ts index 9f41109..6bfd6a7 100644 --- a/src/jsx.d.ts +++ b/src/jsx.d.ts @@ -49,22 +49,22 @@ type HxTriggerModifier = /** * An extensible directory of htmx extensions. * - * ## Declaring a new extension + * ### Declaring a new extension * * ```tsx twoslash * // in foo.d.ts: * * declare global { - * namespace JSX { - * interface HtmxExtensions { - * myExtension: "my-extension"; - * } - * interface HtmlTag { - * /** Describe your attribute *\/ - * ["my-extension-attr"]?: string; - * // Add any other attributes your extension uses here - * } - * } + * namespace JSX { + * interface HtmxExtensions { + * myExtension: "my-extension"; + * } + * interface HtmlTag { + * /** Describe your attribute *\/ + * ["my-extension-attr"]?: string; + * // Add any other attributes your extension uses here + * } + * } * } * *
@@ -160,17 +160,10 @@ interface HtmxBuiltinExtensions { morphdom: "morphdom"; } -/** - * Variants of attributes also recognized by htmx. - */ -type HtmxData = { - [K in keyof T as K extends `hx-${string}` ? `data-${K}` : never]: T[K] -} - /** * Definitions for htmx attributes up to 1.9.3. */ -interface HtmxAttributes extends HtmxData { +interface HtmxAttributes { /** @ignore For React compatibility only. */ children?: {}; /** @ignore For React compatibility only. */ @@ -178,13 +171,13 @@ interface HtmxAttributes extends HtmxData { /** * Issues a `GET` to the specified URL. * @see https://htmx.org/attributes/hx-get/ - * @category core + * @category Core */ ["hx-get"]?: string; /** * Issues a `POST` to the specified URL. * @see https://htmx.org/attributes/hx-post/ - * @category core + * @category Core */ ["hx-post"]?: string; /** @@ -207,13 +200,13 @@ interface HtmxAttributes extends HtmxData { * for links and forms. * * @see https://htmx.org/attributes/hx-boost/ - * @category core + * @category Core */ ["hx-boost"]?: BoolStr; /** * Handle any event with a script inline. * @see https://htmx.org/attributes/hx-on/ - * @category core + * @category Core * @remarks Event listeners on htmx-specific events need to be specified with a spread attribute, and * are otherwise not supported in vanilla JSX. * ```jsx @@ -232,26 +225,26 @@ interface HtmxAttributes extends HtmxData { /** * Pushes the URL into the browser location bar, creating a new history entry. * @see https://htmx.org/attributes/hx-push-url/ - * @category core + * @category Core */ ["hx-push-url"]?: BoolStr | AnyStr; /** * Select content to swap in from a response. * @see https://htmx.org/attributes/hx-select/ - * @category core + * @category Core */ ["hx-select"]?: string; /** * Select content to swap in from a response, out of band (somewhere other than the target). * @see https://htmx.org/attributes/hx-select-oob/ - * @category core + * @category Core */ ["hx-select-oob"]?: string; /** * Controls how content is swapped in (`outerHTML`, `beforeend`, `afterend`, …). * @see https://htmx.org/attributes/hx-swap/ - * @see {@linkcode InsertPosition} which is used in [{@linkcode Element.insertAdjacentHTML}](https://developer.mozilla.org/docs/Web/API/Element/insertAdjacentHTML) - * @category core + * @see [`InsertPosition`](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML#position) which is used in `Element.insertAdjacentHTML`. + * @category Core * @remarks * - `morph` swaps are part of the {@linkcode HtmxBuiltinExtensions.idiomorph idiomorph} extension. * - `morphdom` swaps are part of the {@linkcode HtmxBuiltinExtensions.morphdom morphdom} extension. @@ -265,19 +258,19 @@ interface HtmxAttributes extends HtmxData { /** * Specifies the target element to be swapped. * @see https://htmx.org/attributes/hx-target/ - * @category core + * @category Core */ ["hx-target"]?: HxTarget | AnyStr; /** * Specifies the event that triggers the request. * @see https://htmx.org/attributes/hx-trigger/ - * @category core + * @category Core */ ["hx-trigger"]?: "every " | HxTriggerModifier | AnyStr; /** * Adds values to the parameters to submit with the request (JSON-formatted). * @see https://htmx.org/attributes/hx-params/ - * @category core + * @category Core */ ["hx-vals"]?: AnyStr | "javascript:" | "js:" | Record; /** @@ -426,6 +419,5 @@ declare namespace JSX { interface HtmlTag extends HtmxAttributes {} } -// React (and other similar frameworks) /** @ignore */ interface HTMLElement extends HtmxAttributes {} diff --git a/tsconfig.json b/tsconfig.json index c9cea6c..dec6343 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["./src/**/*"], + "include": ["src/**/*"], "compilerOptions": { "target": "es2022", "module": "commonjs",