Skip to content

Commit

Permalink
feat(helper components): Add support for using functions as HTML elem…
Browse files Browse the repository at this point in the history
…ents
  • Loading branch information
derekhawker authored and nicojs committed Jan 22, 2018
1 parent 39d066e commit bdf0b3a
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 11 deletions.
25 changes: 25 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,31 @@ function listItem(n: number) {
</ul>
```

#### Using a helper template like an element

Want a helper component? Create a function that implements CustomElementHandler and you can call it like an HTML element.

```typescript
import {Attributes, CustomElementHandler} from "typed-html"

function Button(attributes: Attributes | undefined, contents: string[]) {
return <div><button type="button" class="original-class" {...attributes}>{contents}</button></div>;
}
// Or
const Button: CustomElementHandler = (attributes, contents) => <div><button type="button" class="original-class" {...attributes}>{contents}</button></div>;
}

console.log(<Button style="color:#f00">Button Text</Button>);
```

Prints:

```html
<div>
<button type="button" class="original-class" style="color:#f00">Button Text</button>
</div>
```

## Supported HTML

All HTML elements and attributes are supported, except for the [svg](https://www.w3.org/TR/SVG/).
Expand Down
32 changes: 21 additions & 11 deletions src/elements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@

import * as os from 'os';

type AttributeValue = number | string | Date | boolean;

export interface CustomElementHandler {
(attributes: Attributes | undefined, contents: string[]): string
}

export interface Attributes {
[key: string]: AttributeValue;
}

const capitalACharCode = 'A'.charCodeAt(0);
const capitalZCharCode = 'Z'.charCodeAt(0);

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

type AttributeValue = number | string | Date | boolean;

interface Attributes {
[key: string]: AttributeValue;
}

const toKebabCase = (camelCased: string) => {
let kebabCased = '';
for (let i = 0; i < camelCased.length; i++) {
Expand Down Expand Up @@ -103,11 +107,17 @@ const isVoidElement = (tagName: string) => {
].indexOf(tagName) > -1;
};

export function createElement(name: string, attributes: Attributes | undefined, ...contents: string[]) {
const tagName = toKebabCase(name);
if (isVoidElement(tagName) && !contents.length) {
return `<${tagName}${attributesToString(attributes)}>`;
export function createElement(name: string | CustomElementHandler,
attributes: Attributes | undefined,
...contents: string[]) {
if (typeof name === 'function') {
return name(attributes, contents);
} else {
return `<${tagName}${attributesToString(attributes)}>${contentsToString(contents)}</${tagName}>`;
const tagName = toKebabCase(name);
if (isVoidElement(tagName) && !contents.length) {
return `<${tagName}${attributesToString(attributes)}>`;
} else {
return `<${tagName}${attributesToString(attributes)}>${contentsToString(contents)}</${tagName}>`;
}
}
}
12 changes: 12 additions & 0 deletions test/html-fragments.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,16 @@ describe('using a number attribute', () => {
describe('custom elements', () => {
testEqual('<custom-element a-custom-attr="value" custom-li-attr="li"></custom-element>', () => <customElement ACustomAttr="value" customLIAttr="li"></customElement>);
testEqual('<div some-data="s"></div>', () => <div some-data="s"></div>);
});

describe('helper components', () => {
const Header: elements.CustomElementHandler = (attributes, contents) => <h1 {...attributes}>{contents}</h1>;

function Button(attributes: elements.Attributes | undefined, contents: string[]) {
return <button type='button' class='original-class' {...attributes}>{contents}</button>;
}

testEqual('<h1 class="title"><span>Header Text</span></h1>', () => <Header class='title'><span>Header Text</span></Header>);
testEqual('<button class="override" type="button"></button>', () => <Button class='override'/>);
testEqual('<button class="original-class" type="button">Button Text</button>', () => <Button>Button Text</Button>);
});

0 comments on commit bdf0b3a

Please sign in to comment.