diff --git a/README.md b/README.md
index c1659cb..efa5dc2 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
## Demo Projects
- tsx-dom-demo\
- A simple To-Do list showing one way you might use [tsx-dom](https://lusito.github.io/tsx-dom/tsx-dom/index.html).
+ A simple [To-Do list](https://lusito.github.io/tsx-dom/demo/) showing one way you might use [tsx-dom](https://lusito.github.io/tsx-dom/tsx-dom/index.html).
- tsx-dom-ssr-demo\
A more complex demo showcasing [tsx-dom-ssr](https://lusito.github.io/tsx-dom/tsx-dom-ssr/index.html) with the [Rick and Morty API](https://rickandmortyapi.com/)
diff --git a/packages/tsx-dom-ssr/docs/custom-elements.md b/packages/tsx-dom-ssr/docs/custom-elements.md
index 09e349f..c9e793b 100644
--- a/packages/tsx-dom-ssr/docs/custom-elements.md
+++ b/packages/tsx-dom-ssr/docs/custom-elements.md
@@ -76,3 +76,27 @@ const child = (
,
);
```
+
+## The `is` Attribute vs `tsxTag`
+
+Let's say you have a custom element `word-count`, but it should fall back to a p tag when the custom-element has not been registered (yet).
+Normally, you would do it like this:
+
+```html
+
+```
+
+This tells the browser it's a `p` tag, but execute the code for `word-count` on it, once it's defined.
+This won't work properly with tsx-dom, as you will get no intellisense for the respective props of that custom element.
+
+In order to solve this issue, we have the `tsxTag` attribute, which does the exact inverse:
+
+```tsx
+const child = ;
+```
+
+By doing this, you'll get the correct props while writing the JSX syntax, but the generated HTML will still be:
+
+```html
+
+```
diff --git a/packages/tsx-dom-ssr/src/types.ts b/packages/tsx-dom-ssr/src/types.ts
index 5bbf40f..d4210a9 100644
--- a/packages/tsx-dom-ssr/src/types.ts
+++ b/packages/tsx-dom-ssr/src/types.ts
@@ -11,6 +11,11 @@ export interface BaseProps {
export interface HTMLComponentProps extends BaseProps {
dangerouslySetInnerHTML?: string;
+ /**
+ * This is essentially a reverse "is" attribute.
+ * If you specify it, the generated tag will be tsxTag and it will receive an "is" attribute with the tag you specified in your JSX.
+ * This is needed because we can't make the is-property associate with the correct component props.
+ */
tsxTag?: keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap;
}
diff --git a/packages/tsx-dom/docs/custom-elements.md b/packages/tsx-dom/docs/custom-elements.md
index a9b73ee..fc51ed3 100644
--- a/packages/tsx-dom/docs/custom-elements.md
+++ b/packages/tsx-dom/docs/custom-elements.md
@@ -123,3 +123,27 @@ document.body.appendChild(
,
);
```
+
+## The `is` Attribute vs `tsxTag`
+
+Let's say you have a custom element `word-count`, but it should fall back to a p tag when the custom-element has not been registered (yet).
+Normally, you would do it like this:
+
+```html
+
+```
+
+This tells the browser it's a `p` tag, but execute the code for `word-count` on it, once it's defined.
+This won't work properly with tsx-dom, as you will get no intellisense for the respective props of that custom element.
+
+In order to solve this issue, we have the `tsxTag` attribute, which does the exact inverse:
+
+```tsx
+const element = ;
+```
+
+By doing this, you'll get the correct props while writing the JSX syntax, but the generated HTML will still be:
+
+```html
+
+```
diff --git a/packages/tsx-dom/src/createElement.ts b/packages/tsx-dom/src/createElement.ts
index 1418f51..ab89936 100644
--- a/packages/tsx-dom/src/createElement.ts
+++ b/packages/tsx-dom/src/createElement.ts
@@ -1,6 +1,6 @@
import { setAttributes } from "./setAttributes";
import type { ComponentAttributes, ComponentChild, FC, RefType } from "./types";
-import { applyChildren, createDomElement } from "./utils";
+import { applyChildren, applyTsxTag, createDomElement } from "./utils";
export function createElement(
tag: string | FC,
@@ -9,9 +9,10 @@ export function createElement(
): JSX.Element {
if (typeof tag === "function") return tag({ ...attrs, children });
- const element = createDomElement(tag, attrs);
+ const { finalTag, finalAttrs } = applyTsxTag(tag, attrs);
+ const element = createDomElement(finalTag, finalAttrs);
- if (attrs) setAttributes(element, attrs);
+ if (finalAttrs) setAttributes(element, finalAttrs);
applyChildren(element, children);
return element;
diff --git a/packages/tsx-dom/src/jsx-runtime.ts b/packages/tsx-dom/src/jsx-runtime.ts
index 838a096..f34a346 100644
--- a/packages/tsx-dom/src/jsx-runtime.ts
+++ b/packages/tsx-dom/src/jsx-runtime.ts
@@ -1,14 +1,15 @@
import { setAttributes } from "./setAttributes";
import type { BaseProps, FC } from "./types";
-import { applyChildren, createDomElement } from "./utils";
+import { applyChildren, createDomElement, applyTsxTag } from "./utils";
export function jsx(tag: string | FC, props: BaseProps): JSX.Element {
if (typeof tag === "function") return tag(props);
const { children, ...attrs } = props;
- const element = createDomElement(tag, attrs);
+ const { finalTag, finalAttrs } = applyTsxTag(tag, attrs);
+ const element = createDomElement(finalTag, finalAttrs);
- if (attrs) setAttributes(element, attrs);
+ setAttributes(element, finalAttrs);
applyChildren(element, [children]);
return element;
diff --git a/packages/tsx-dom/src/setAttributes.ts b/packages/tsx-dom/src/setAttributes.ts
index 712ff34..6f40b39 100644
--- a/packages/tsx-dom/src/setAttributes.ts
+++ b/packages/tsx-dom/src/setAttributes.ts
@@ -15,7 +15,7 @@ const eventAttributeName = /^on\p{Lu}/u;
export function setAttributes(element: JSX.Element, attrs: ComponentAttributes) {
for (const name of Object.keys(attrs)) {
// Ignore some debug props that might be added by bundlers
- if (name === "__source" || name === "__self") continue;
+ if (name === "__source" || name === "__self" || name === "tsxTag") continue;
const value = attrs[name];
if (name === "class") {
diff --git a/packages/tsx-dom/src/types.ts b/packages/tsx-dom/src/types.ts
index ed4710e..5b79961 100644
--- a/packages/tsx-dom/src/types.ts
+++ b/packages/tsx-dom/src/types.ts
@@ -12,6 +12,12 @@ export type ComponentAttributes = {
export interface HTMLComponentProps extends BaseProps {
dangerouslySetInnerHTML?: string;
+ /**
+ * This is essentially a reverse "is" attribute.
+ * If you specify it, the generated tag will be tsxTag and it will receive an "is" attribute with the tag you specified in your JSX.
+ * This is needed because we can't make the is-property associate with the correct component props.
+ */
+ tsxTag?: keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap;
ref?: RefType;
}
diff --git a/packages/tsx-dom/src/utils.ts b/packages/tsx-dom/src/utils.ts
index f8bc6e0..132f663 100644
--- a/packages/tsx-dom/src/utils.ts
+++ b/packages/tsx-dom/src/utils.ts
@@ -23,3 +23,15 @@ export function createDomElement(tag: string, attrs: ComponentAttributes | null)
return document.createElement(tag, options);
}
+
+export function applyTsxTag(tag: string, attrs: T) {
+ let finalTag = tag;
+ let finalAttrs = attrs;
+ if (finalAttrs && "tsxTag" in finalAttrs) {
+ finalTag = finalAttrs.tsxTag as string;
+ if (!finalAttrs.is && tag.includes("-")) {
+ finalAttrs = { ...finalAttrs, is: tag };
+ }
+ }
+ return { finalTag, finalAttrs };
+}