Skip to content

Commit bdf0b3a

Browse files
derekhawkernicojs
authored andcommitted
feat(helper components): Add support for using functions as HTML elements
1 parent 39d066e commit bdf0b3a

File tree

3 files changed

+58
-11
lines changed

3 files changed

+58
-11
lines changed

readme.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,31 @@ function listItem(n: number) {
113113
</ul>
114114
```
115115

116+
#### Using a helper template like an element
117+
118+
Want a helper component? Create a function that implements CustomElementHandler and you can call it like an HTML element.
119+
120+
```typescript
121+
import {Attributes, CustomElementHandler} from "typed-html"
122+
123+
function Button(attributes: Attributes | undefined, contents: string[]) {
124+
return <div><button type="button" class="original-class" {...attributes}>{contents}</button></div>;
125+
}
126+
// Or
127+
const Button: CustomElementHandler = (attributes, contents) => <div><button type="button" class="original-class" {...attributes}>{contents}</button></div>;
128+
}
129+
130+
console.log(<Button style="color:#f00">Button Text</Button>);
131+
```
132+
133+
Prints:
134+
135+
```html
136+
<div>
137+
<button type="button" class="original-class" style="color:#f00">Button Text</button>
138+
</div>
139+
```
140+
116141
## Supported HTML
117142

118143
All HTML elements and attributes are supported, except for the [svg](https://www.w3.org/TR/SVG/).

src/elements.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44

55
import * as os from 'os';
66

7+
type AttributeValue = number | string | Date | boolean;
8+
9+
export interface CustomElementHandler {
10+
(attributes: Attributes | undefined, contents: string[]): string
11+
}
12+
13+
export interface Attributes {
14+
[key: string]: AttributeValue;
15+
}
16+
717
const capitalACharCode = 'A'.charCodeAt(0);
818
const capitalZCharCode = 'Z'.charCodeAt(0);
919

@@ -12,12 +22,6 @@ const isUpper = (input: string, index: number) => {
1222
return capitalACharCode <= charCode && capitalZCharCode >= charCode;
1323
};
1424

15-
type AttributeValue = number | string | Date | boolean;
16-
17-
interface Attributes {
18-
[key: string]: AttributeValue;
19-
}
20-
2125
const toKebabCase = (camelCased: string) => {
2226
let kebabCased = '';
2327
for (let i = 0; i < camelCased.length; i++) {
@@ -103,11 +107,17 @@ const isVoidElement = (tagName: string) => {
103107
].indexOf(tagName) > -1;
104108
};
105109

106-
export function createElement(name: string, attributes: Attributes | undefined, ...contents: string[]) {
107-
const tagName = toKebabCase(name);
108-
if (isVoidElement(tagName) && !contents.length) {
109-
return `<${tagName}${attributesToString(attributes)}>`;
110+
export function createElement(name: string | CustomElementHandler,
111+
attributes: Attributes | undefined,
112+
...contents: string[]) {
113+
if (typeof name === 'function') {
114+
return name(attributes, contents);
110115
} else {
111-
return `<${tagName}${attributesToString(attributes)}>${contentsToString(contents)}</${tagName}>`;
116+
const tagName = toKebabCase(name);
117+
if (isVoidElement(tagName) && !contents.length) {
118+
return `<${tagName}${attributesToString(attributes)}>`;
119+
} else {
120+
return `<${tagName}${attributesToString(attributes)}>${contentsToString(contents)}</${tagName}>`;
121+
}
112122
}
113123
}

test/html-fragments.spec.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,16 @@ describe('using a number attribute', () => {
7979
describe('custom elements', () => {
8080
testEqual('<custom-element a-custom-attr="value" custom-li-attr="li"></custom-element>', () => <customElement ACustomAttr="value" customLIAttr="li"></customElement>);
8181
testEqual('<div some-data="s"></div>', () => <div some-data="s"></div>);
82+
});
83+
84+
describe('helper components', () => {
85+
const Header: elements.CustomElementHandler = (attributes, contents) => <h1 {...attributes}>{contents}</h1>;
86+
87+
function Button(attributes: elements.Attributes | undefined, contents: string[]) {
88+
return <button type='button' class='original-class' {...attributes}>{contents}</button>;
89+
}
90+
91+
testEqual('<h1 class="title"><span>Header Text</span></h1>', () => <Header class='title'><span>Header Text</span></Header>);
92+
testEqual('<button class="override" type="button"></button>', () => <Button class='override'/>);
93+
testEqual('<button class="original-class" type="button">Button Text</button>', () => <Button>Button Text</Button>);
8294
});

0 commit comments

Comments
 (0)