diff --git a/types/petit-dom/.editorconfig b/types/petit-dom/.editorconfig new file mode 100644 index 0000000000..db9b917e06 --- /dev/null +++ b/types/petit-dom/.editorconfig @@ -0,0 +1,5 @@ +[*.ts] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 diff --git a/types/petit-dom/index.d.ts b/types/petit-dom/index.d.ts new file mode 100644 index 0000000000..937fbfa0e7 --- /dev/null +++ b/types/petit-dom/index.d.ts @@ -0,0 +1,187 @@ +// Type definitions for Petit-Dom 0.2 +// Project: // https://github.com/yelouafi/petit-dom +// Definitions by: James Messinger +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 3.0 + +/** + * Creates a VNode of the specified HTML Element type and with the + * specified properties and contents. + * + * @param type - The tag name of element to create + * @param props - Properties to set on the element + * @param ...children - Child nodes + * @returns A new VNode object + */ +export function h( + type: T, + props?: PetitDom.Props | null, + ...children: PetitDom.Content[] +): PetitDom.ElementNode; + +/** + * Creates a VNode using a PetitDom component object. + * + * @param type - A PetitDom component object + * @param props - Properties to set on the component + * @param ...children - Child nodes + * @returns A new VNode object + */ +export function h

( + type: PetitDom.Component

, + props?: P | null, + ...children: PetitDom.Content[] +): PetitDom.ComponentNode

; + +/** + * Creates a VNode using a PetitDom component class. + * + * @param type - A PetitDom component class + * @param props - Properties to set on the component + * @param ...children - Child nodes + * @returns A new VNode object + */ +export function h

( + type: PetitDom.ComponentClass

, + props?: P | null, + ...children: PetitDom.Content[] +): PetitDom.ComponentClassNode

; + +/** + * Creates a VNode using a PetitDom function component. + * + * This function is compatible with both JSX and HyperScript syntax. + * + * @param type - A PetitDom function component + * @param props - Properties to set on the component + * @param ...children - Child nodes + * @returns A new VNode object + */ +export function h

( + type: PetitDom.FunctionComponent

, + props?: P | null, + ...children: PetitDom.Content[] +): PetitDom.FunctionComponentNode

; + +/** + * Creates actual DOM Elements for the given VNode and its children. + * + * @param vnode - The VNode object to mount + * @returns The newly-created DOM element + */ +export function mount(vnode: PetitDom.VNode): Element; + +/** + * Diffs two VNodes and applies the necessary DOM changes + * + * @param newVNode - The new VNode object, which will be mounted to the DOM element of oldVNode + * @param oldVNode - The old VNode object to diff against + * @param parent - The parent DOM element. Used internally by PetitDom to recursively patch child nodes + * @returns The updated DOM element. This element is also moved from oldVNode to newVNode + */ +export function patch(newVNode: PetitDom.VNode, oldVNode: PetitDom.VNode, parent?: Element): Element; + +/** + * Removes the given VNode from the actual DOM + * + * @param vnode - The VNode object to unmount + */ +export function unmount(vnode: PetitDom.VNode): void; + +export namespace PetitDom { + type Key = string | number; + type ComponentProps = object; + type Content = string | VNode; + + type DOMElementProps = { + [P in keyof E]?: E[P]; + }; + + interface IntrinsicProps { + key?: Key; + } + + type Props = IntrinsicProps & DOMElementProps; + + type ShouldUpdate

= ( + newProps: P, + oldProps: P, + newContent: ReadonlyArray, + oldContent: ReadonlyArray + ) => boolean; + + interface FunctionComponent

{ + (props: P, content: ReadonlyArray): FunctionComponentNode

; + shouldUpdate?: ShouldUpdate

; + } + + interface ComponentClass

{ + new(props: P, content: ReadonlyArray): Component

; + } + + interface Component

{ + mount(props: P, content: ReadonlyArray): Element; + patch(element: Element, newProps: P, oldProps: P, newContent: ReadonlyArray, oldContent: ReadonlyArray): Element; + unmount(element: Element): void; + } + + interface VNode { + readonly isSVG: boolean; + readonly type: any; + readonly key: Key | null; + readonly props: any; + readonly content: ReadonlyArray; + } + + interface ElementNode extends VNode { + readonly type: T; + readonly props: Props; + } + + interface ComponentNode

extends VNode { + readonly type: Component

; + readonly props: P & IntrinsicProps; + } + + interface ComponentClassNode

extends VNode { + readonly type: ComponentClass

; + readonly props: P & IntrinsicProps; + } + + interface FunctionComponentNode

extends VNode { + readonly type: FunctionComponent

; + readonly props: P & IntrinsicProps; + } + + interface DomElements extends HTMLElementTagNameMap, SVGElementTagNameMap { + "main": HTMLMainElement; + } +} + +declare global { + namespace JSX { + // tslint:disable-next-line:no-empty-interface + interface Element extends PetitDom.VNode { } + + // tslint:disable-next-line:no-empty-interface + interface ElementClass extends PetitDom.Component { } + + // tslint:disable-next-line:no-empty-interface + interface IntrinsicClassAttributes extends PetitDom.Props { } + + // tslint:disable-next-line:no-empty-interface + interface IntrinsicAttributes extends PetitDom.IntrinsicProps { } + + interface ElementAttributesProperty { props: PetitDom.Props; } + + interface ElementChildrenAttribute { content: PetitDom.VNode[]; } + + type IntrinsicElements = { + [P in keyof PetitDom.DomElements]: + PetitDom.Props & + { + content?: PetitDom.Content | ReadonlyArray; + }; + }; + } +} diff --git a/types/petit-dom/petit-dom-tests.tsx b/types/petit-dom/petit-dom-tests.tsx new file mode 100644 index 0000000000..bde10bad3a --- /dev/null +++ b/types/petit-dom/petit-dom-tests.tsx @@ -0,0 +1,368 @@ +/* @jsx h */ +// tslint:disable:no-empty +// tslint:disable:no-null-keyword + +import { h, mount, patch, PetitDom, unmount } from "petit-dom"; + +function assertEqual(a: T, b: T) { } + +function eventHandler(event: Event): void { } + +interface CustomProps { + name: string; + count: number; + onSomeEvent(event: Event): void; +} + +/** + * Create an element with text content, using HyperScript syntax and JSX syntax + */ +export function testHtmlElementWithTextContent() { + // HyperScript syntax returns an ElementNode object, with typed properties + const aNode = h("a", { href: "link", onclick: eventHandler }, "click here"); + + assertEqual(aNode.isSVG, false); + assertEqual(aNode.type, "a"); + assertEqual(aNode.key, null); + assertEqual(aNode.props.href, "link"); + assertEqual(aNode.props.onclick, eventHandler); + assertEqual(aNode.content.length, 1); + + // JSX syntax returns a VNode object, so the "type" and "props" properties are "any" + const jsxNode = click here; + const jsxNodeType = jsxNode.type as string; + const jsxNodeProps = jsxNode.props as PetitDom.Props; + + assertEqual(jsxNode.isSVG, false); + assertEqual(jsxNodeType, "a"); + assertEqual(jsxNode.key, null); + assertEqual(jsxNodeProps.href, "link"); + assertEqual(jsxNodeProps.onclick, eventHandler); + assertEqual(jsxNode.content.length, 1); +} + +/** + * Create a

element with both text content and child elements, using HyperScript syntax and JSX syntax + */ +export function testHtmlElementWithMixedContent() { + // HyperScript syntax returns an ElementNode object, with typed properties + const formNode = h( + "form", + { key: 1, method: "POST", onsubmit: eventHandler }, + "Hello ", h("span", null, "World") + ); + + assertEqual(formNode.isSVG, false); + assertEqual(formNode.type, "form"); + assertEqual(formNode.key, 1); + assertEqual(formNode.props.method, "POST"); + assertEqual(formNode.props.onsubmit, eventHandler); + assertEqual(formNode.content.length, 2); + + // JSX syntax returns a VNode object, so the "type" and "props" properties are "any" + const jsxNode = Hello World; + const jsxNodeType = jsxNode.type as string; + const jsxNodeProps = jsxNode.props as PetitDom.Props; + + assertEqual(jsxNode.isSVG, false); + assertEqual(jsxNodeType, "form"); + assertEqual(jsxNode.key, 1); + assertEqual(jsxNodeProps.method, "POST"); + assertEqual(jsxNodeProps.onsubmit, eventHandler); + assertEqual(jsxNode.content.length, 2); +} + +/** + * Create an element with a child element, using HyperScript syntax and JSX syntax + */ +export function testSvgElementWithChild() { + // HyperScript syntax returns an ElementNode object, with typed properties + const svgNode = h("svg", { key: 2, currentScale: 1 }, h("path")); + + assertEqual(svgNode.isSVG, true); + assertEqual(svgNode.type, "svg"); + assertEqual(svgNode.key, 2); + assertEqual(svgNode.props.currentScale, 1); + assertEqual(svgNode.content.length, 1); + + // JSX syntax returns a VNode object, so the "type" and "props" properties are "any" + const jsxNode = ; + const jsxNodeType = jsxNode.type as string; + const jsxNodeProps = jsxNode.props as PetitDom.Props; + + assertEqual(jsxNode.isSVG, true); + assertEqual(jsxNodeType, "svg"); + assertEqual(jsxNode.key, 2); + assertEqual(jsxNodeProps.currentScale, 1); + assertEqual(jsxNode.content.length, 1); +} + +/** + * Create a function component, using HyperScript syntax and JSX syntax + */ +export function testFunctionComponent() { + function FunctionComponent(): JSX.Element { + return
Hello World
; + } + + // HyperScript syntax returns a FunctionComponentNode object, with typed properties + const node = h(FunctionComponent, { key: "1" }); + + assertEqual(node.isSVG, false); + assertEqual(node.type, FunctionComponent); + assertEqual(node.key, "1"); + assertEqual(node.props.key, "1"); + assertEqual(node.content.length, 0); + + // JSX syntax returns a VNode object, so the "type" and "props" properties are "any" + const jsxNode = ; + const jsxNodeType = jsxNode.type as PetitDom.FunctionComponent<{}>; + const jsxNodeProps = jsxNode.props as PetitDom.IntrinsicProps; + + assertEqual(jsxNode.isSVG, false); + assertEqual(jsxNodeType, FunctionComponent); + assertEqual(jsxNode.key, "1"); + assertEqual(jsxNodeProps.key, "1"); + assertEqual(jsxNode.content.length, 0); +} + +/** + * Create a function component with props, using HyperScript syntax and JSX syntax + */ +export function testFunctionComponentWithProps() { + function FunctionComponentWithProps(props: CustomProps): JSX.Element { + const { name, count, onSomeEvent } = props; + return
; + } + + // HyperScript syntax returns a FunctionComponentNode object, with typed properties + const node = h(FunctionComponentWithProps, { name: "xyz", count: 123, onSomeEvent: eventHandler }); + + assertEqual(node.isSVG, false); + assertEqual(node.type, FunctionComponentWithProps); + assertEqual(node.key, null); + assertEqual(node.props.name, "xyz"); + assertEqual(node.props.count, 123); + assertEqual(node.props.onSomeEvent, eventHandler); + assertEqual(node.content.length, 0); + + // JSX syntax returns a VNode object, so the "type" and "props" properties are "any" + const jsxNode = ; + const jsxNodeType = jsxNode.type as PetitDom.FunctionComponent; + const jsxNodeProps = jsxNode.props as CustomProps; + + assertEqual(jsxNode.isSVG, false); + assertEqual(jsxNodeType, FunctionComponentWithProps); + assertEqual(jsxNode.key, null); + assertEqual(jsxNodeProps.name, "xyz"); + assertEqual(jsxNodeProps.count, 123); + assertEqual(jsxNodeProps.onSomeEvent, eventHandler); + assertEqual(jsxNode.content.length, 0); +} + +/** + * Create a function component with child content, using HyperScript syntax and JSX syntax + */ +export function testFunctionComponentWithChildren() { + function FunctionComponentWithChildren(props: CustomProps, content: ReadonlyArray): JSX.Element { + const { name, count, onSomeEvent } = props; + return
{content}
; + } + + // HyperScript syntax returns a FunctionComponentNode object, with typed properties + const node = h( + FunctionComponentWithChildren, + { name: "xyz", count: 123, onSomeEvent: eventHandler }, + "Hello", + h("span", null, "World") + ); + + assertEqual(node.isSVG, false); + assertEqual(node.type, FunctionComponentWithChildren); + assertEqual(node.key, null); + assertEqual(node.props.name, "xyz"); + assertEqual(node.props.count, 123); + assertEqual(node.props.onSomeEvent, eventHandler); + assertEqual(node.content.length, 2); + + // JSX syntax returns a VNode object, so the "type" and "props" properties are "any" + const jsxNode = ( + + Hello World + + ); + const jsxNodeType = jsxNode.type as PetitDom.FunctionComponent; + const jsxNodeProps = jsxNode.props as CustomProps; + + assertEqual(jsxNode.isSVG, false); + assertEqual(jsxNodeType, FunctionComponentWithChildren); + assertEqual(jsxNode.key, null); + assertEqual(jsxNodeProps.name, "xyz"); + assertEqual(jsxNodeProps.count, 123); + assertEqual(jsxNodeProps.onSomeEvent, eventHandler); + assertEqual(jsxNode.content.length, 2); +} + +/** + * Create a component class, using HyperScript syntax and JSX syntax + */ +export function testComponentClass() { + class ComponentClass { + props = {}; + + mount(): Element { + return mount(
); + } + + patch(element: Element, newProps: object, oldProps: object, newContent: ReadonlyArray, oldContent: ReadonlyArray): Element { + patch( +
{oldContent}
, +
{newContent}
+ ); + return element; + } + + unmount(element: Element): void { + unmount(
Hello World
); + } + } + + // HyperScript syntax returns a ComponentClassNode object, with typed properties + const node = h(ComponentClass, { key: "1" }); + + assertEqual(node.isSVG, false); + assertEqual(node.type, ComponentClass); + assertEqual(node.key, "1"); + assertEqual(node.props.key, "1"); + assertEqual(node.content.length, 0); + + // JSX syntax returns a VNode object, so the "type" and "props" properties are "any" + const jsxNode = ; + const jsxNodeType = jsxNode.type as PetitDom.ComponentClass<{}>; + const jsxNodeProps = jsxNode.props as PetitDom.IntrinsicProps; + + assertEqual(jsxNode.isSVG, false); + assertEqual(jsxNodeType, ComponentClass); + assertEqual(jsxNode.key, "1"); + assertEqual(jsxNodeProps.key, "1"); + assertEqual(jsxNode.content.length, 0); +} + +/** + * Create a component class with props, using HyperScript syntax and JSX syntax + */ +export function testComponentClassWithProps() { + class ComponentClassWithProps { + props: CustomProps; + + constructor(props: CustomProps) { + this.props = props; + } + + mount(props: CustomProps): Element { + const { name, count, onSomeEvent } = props; + return mount(
); + } + + patch(element: Element, newProps: CustomProps, oldProps: CustomProps, newContent: ReadonlyArray, oldContent: ReadonlyArray): Element { + patch( +
{oldContent}
, +
{newContent}
+ ); + return element; + } + + unmount(element: Element): void { + unmount(
Hello World
); + } + } + + // HyperScript syntax returns a ComponentClassNode object, with typed properties + const node = h(ComponentClassWithProps, { name: "xyz", count: 123, onSomeEvent: eventHandler }); + + assertEqual(node.isSVG, false); + assertEqual(node.type, ComponentClassWithProps); + assertEqual(node.key, null); + assertEqual(node.props.name, "xyz"); + assertEqual(node.props.count, 123); + assertEqual(node.props.onSomeEvent, eventHandler); + assertEqual(node.content.length, 0); + + // JSX syntax returns a VNode object, so the "type" and "props" properties are "any" + const jsxNode = ; + const jsxNodeType = jsxNode.type as PetitDom.ComponentClass; + const jsxNodeProps = jsxNode.props as CustomProps; + + assertEqual(jsxNode.isSVG, false); + assertEqual(jsxNodeType, ComponentClassWithProps); + assertEqual(jsxNode.key, null); + assertEqual(jsxNodeProps.name, "xyz"); + assertEqual(jsxNodeProps.count, 123); + assertEqual(jsxNodeProps.onSomeEvent, eventHandler); + assertEqual(jsxNode.content.length, 0); +} + +/** + * Create a component class with child content, using HyperScript syntax and JSX syntax + */ +export function testComponentClassWithChildren() { + class ComponentClassWithChildren { + props: CustomProps; + + constructor(props: CustomProps) { + this.props = props; + } + + mount(props: CustomProps, content: ReadonlyArray): Element { + const { name, count, onSomeEvent } = props; + return mount( +
{content}
+ ); + } + + patch(element: Element, newProps: CustomProps, oldProps: CustomProps, newContent: ReadonlyArray, oldContent: ReadonlyArray): Element { + patch( +
{oldContent}
, +
{newContent}
+ ); + return element; + } + + unmount(element: Element): void { + unmount(
Hello World
); + } + } + + // HyperScript syntax returns a ComponentClassNode object, with typed properties + const node = h( + ComponentClassWithChildren, + { name: "xyz", count: 123, onSomeEvent: eventHandler }, + "Hello", + h("span", null, "World") + ); + + assertEqual(node.isSVG, false); + assertEqual(node.type, ComponentClassWithChildren); + assertEqual(node.key, null); + assertEqual(node.props.name, "xyz"); + assertEqual(node.props.count, 123); + assertEqual(node.props.onSomeEvent, eventHandler); + assertEqual(node.content.length, 2); + + // JSX syntax returns a VNode object, so the "type" and "props" properties are "any" + const jsxNode = ( + + Hello World + + ); + const jsxNodeType = jsxNode.type as PetitDom.ComponentClass; + const jsxNodeProps = jsxNode.props as CustomProps; + + assertEqual(jsxNode.isSVG, false); + assertEqual(jsxNodeType, ComponentClassWithChildren); + assertEqual(jsxNode.key, null); + assertEqual(jsxNodeProps.name, "xyz"); + assertEqual(jsxNodeProps.count, 123); + assertEqual(jsxNodeProps.onSomeEvent, eventHandler); + assertEqual(jsxNode.content.length, 2); +} diff --git a/types/petit-dom/tsconfig.json b/types/petit-dom/tsconfig.json new file mode 100644 index 0000000000..203b451193 --- /dev/null +++ b/types/petit-dom/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "jsx": "react", + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": false, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "petit-dom-tests.tsx" + ] +} diff --git a/types/petit-dom/tslint.json b/types/petit-dom/tslint.json new file mode 100644 index 0000000000..f93cf8562a --- /dev/null +++ b/types/petit-dom/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "dtslint/dt.json" +}