diff --git a/types/color/index.d.ts b/types/color/index.d.ts index 83302cc38d..0b7b505c01 100644 --- a/types/color/index.d.ts +++ b/types/color/index.d.ts @@ -66,6 +66,7 @@ declare namespace Color { keyword(): string | undefined; hex(color: string): Color; hex(): string; + rgb(): Color; rgbNumber(): number; luminosity(): number; contrast(color: Color): number; diff --git a/types/graphlib/graphlib-tests.ts b/types/graphlib/graphlib-tests.ts index 114deef66a..d808f1ec2e 100644 --- a/types/graphlib/graphlib-tests.ts +++ b/types/graphlib/graphlib-tests.ts @@ -23,6 +23,7 @@ function test_graph() { g.setDefaultEdgeLabel(() => 'e42'); g.setNodes(['a', 'b', 'c'], 42); g.setParent('d', 'a'); + g.setParent('d'); g.parent('d'); g.children('a'); g.filterNodes(v => true); diff --git a/types/graphlib/index.d.ts b/types/graphlib/index.d.ts index 773cb621d1..3f35b82112 100644 --- a/types/graphlib/index.d.ts +++ b/types/graphlib/index.d.ts @@ -65,7 +65,8 @@ declare module "graphlib" { setNodes(names: string[], label?: any): Graph; /** - * Sets node p as a parent for node v. Method throws an exception in case of + * Sets node p as a parent for node v if it is defined, or removes the + * parent for v if p is undefined. Method throws an exception in case of * invoking it in context of noncompound graph. * Average-case complexity: O(1). * @@ -73,7 +74,7 @@ declare module "graphlib" { * @argument p - node to be parent for v. * @returns the graph, allowing this to be chained with other functions. */ - setParent(v: string, p: string): Graph; + setParent(v: string, p?: string): Graph; /** * Gets parent node for node v. diff --git a/types/mithril/hyperscript.d.ts b/types/mithril/hyperscript.d.ts new file mode 100644 index 0000000000..6f83f2244f --- /dev/null +++ b/types/mithril/hyperscript.d.ts @@ -0,0 +1,3 @@ +import { Hyperscript } from "mithril"; +declare const h: Hyperscript; +export = h; diff --git a/types/mithril/index.d.ts b/types/mithril/index.d.ts index d1c2ba78a6..88b3886a18 100644 --- a/types/mithril/index.d.ts +++ b/types/mithril/index.d.ts @@ -1,863 +1,245 @@ -// Type definitions for Mithril -// Project: http://lhorie.github.io/mithril/ -// Definitions by: Leo Horie , Chris Bowdon +// Type definitions for Mithril 1.1 +// Project: https://mithril.js.org/ +// Definitions by: Mike Linkovich , András Parditka , Isiah Meadows // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.2 -// Mithril type definitions for Typescript +/** Manually triggers a redraw of mounted components. */ +declare function redraw(): void; + +/** Renders a vnode structure into a DOM element. */ +declare function render(el: Element, vnodes: Mithril.Children): void; + +/** Mounts a component to a DOM element, enabling it to autoredraw on user events. */ +declare function mount(element: Element, component: Mithril.ComponentTypes): void; +/** Unmounts a component from a DOM element. */ +declare function mount(element: Element, component: null): void; + +/** Makes an XHR request and returns a promise. */ +declare function request (options: Mithril.RequestOptions & { url: string }): Promise; +/** Makes an XHR request and returns a promise. */ +declare function request (url: string, options?: Mithril.RequestOptions): Promise; + +/** Makes a JSON-P request and returns a promise. */ +declare function jsonp(options: Mithril.JsonpOptions & { url: string }): Promise; +/** Makes a JSON-P request and returns a promise. */ +declare function jsonp(url: string, options?: Mithril.JsonpOptions): Promise; + +/** Creates an event handler which takes the value of the specified DOM element property and calls a function with it as the argument. */ +declare function withAttr(name: string, callback: (value: any) => any): (e: { currentTarget: any, [p: string]: any }) => void; +/** Creates an event handler which takes the value of the specified DOM element property and calls a function with it as the argument. */ +declare function withAttr(name: string, callback: (this: T, value: any) => any, thisArg: T): (e: { currentTarget: any, [p: string]: any }) => void; -/** -* This is the module containing all the types/declarations/etc. for Mithril -*/ declare namespace Mithril { - interface ChildArray extends Array {} - type Children = Child | ChildArray; - type Child = string | VirtualElement | Component; - - interface Static { - /** - * Creates a virtual element for use with m.render, m.mount, etc. - * - * @param selector A simple CSS selector. May include SVG tags. Nested - * selectors are not supported. - * @param attributes Attributes to add. Any DOM attribute may be used - * as an attribute, although innerHTML and the like may be overwritten - * silently. - * @param children Child elements, components, and text to add. - * @return A virtual element. - * - * @see m.render - * @see m.mount - * @see m.component - */ - ( - selector: string, - ...children: Children[] - ): VirtualElement; - - /** - * Creates a virtual element for use with m.render, m.mount, etc. - * - * @param selector A simple CSS selector. May include SVG tags. Nested - * selectors are not supported. - * @param attributes Attributes to add. Any DOM attribute may be used - * as an attribute, although innerHTML and the like may be overwritten - * silently. - * @param children Child elements, components, and text to add. - * @return A virtual element. - * - * @see m.render - * @see m.mount - * @see m.component - */ - ( - selector: string, - attributes: Attributes, - ...children: Children[] - ): VirtualElement; - - /** - * Initializes a component for use with m.render, m.mount, etc. - * - * @param component A component. - * @param args Arguments to optionally pass to the component. - * @return A component. - * - * @see m.render - * @see m.mount - * @see m - */ - ( - component: Component, - ...args: any[] - ): Component; - - /** - * Creates a getter-setter function that wraps a Mithril promise. Useful - * for uniform data access, m.withAttr, etc. - * - * @param promise A thennable to initialize the property with. It may - * optionally be a Mithril promise. - * @return A getter-setter function wrapping the promise. - * - * @see m.withAttr - */ - prop(promise: Thennable) : Promise; - - /** - * Creates a getter-setter function that wraps a simple value. Useful - * for uniform data access, m.withAttr, etc. - * - * @param value A value to initialize the property with - * @return A getter-setter function wrapping the value. - * - * @see m.withAttr - */ - prop(value: T): BasicProperty; - - /** - * Creates a getter-setter function that wraps a simple value. Useful - * for uniform data access, m.withAttr, etc. - * - * @return A getter-setter function wrapping the value. - * - * @see m.withAttr - */ - prop(): BasicProperty; - - /** - * Returns a event handler that can be bound to an element, firing with - * the specified property. - * - * @param property The property to get from the event. - * @param callback The handler to use the value from the event. - * @return A function suitable for listening to an event. - */ - withAttr( - property: string, - callback: (value: any) => any, - callbackThis?: any - ): (e: Event) => void; - - /** - * @deprecated Use m.mount instead - */ - module( - rootElement: Node, - component: Component - ): T; - - /** - * Mounts a component to a base DOM node. - * - * @param rootElement The base node. - * @param component The component to mount. - * @return An instance of the top-level component's controller - */ - mount( - rootElement: Node, - component: Component - ): T; - - /** - * Initializes a component for use with m.render, m.mount, etc. - * - * @param selector A component. - * @param args Arguments to optionally pass to the component. - * @return A component. - * - * @see m.render - * @see m.mount - * @see m - */ - component( - component: Component, - ...args: any[] - ): Component; - - /** - * Trust this string of HTML. - * - * @param html The HTML to trust - * @return A String object instance with an added internal flag to mark - * it as trusted. - */ - trust(html: string): TrustedString; - - /** - * Render a virtual DOM tree. - * - * @param rootElement The base element/node to render the tree from. - * @param children One or more child nodes to add to the tree. - * @param forceRecreation If true, overwrite the entire tree without - * diffing against it. - */ - render( - rootElement: Element, - children: VirtualElement|VirtualElement[], - forceRecreation?: boolean - ): void; - - redraw: { - /** - * Force a redraw the active component. It redraws asynchronously by - * default to allow for simultaneous events to run before redrawing, - * such as the event combination keypress + input frequently used for - * input. - * - * @param force If true, redraw synchronously. - */ - (force?: boolean): void; - - /** - * Gets/sets the current redraw strategy, which returns one of the - * following: - * - * "all" - recreates the DOM tree from scratch - * "diff" - recreates the DOM tree from scratch - * "none" - leaves the DOM tree intact - * - * This is useful for event handlers, which may want to cancel - * the next redraw if the event doesn't update the UI. - * - * @return The current strategy - */ - strategy: BasicProperty<"all" | "diff" | "none">; - } - - route: { - /** - * Enable routing, mounting a controller based on the route. It - * automatically mounts the components for you, starting with the one - * specified by the default route. - * - * @param rootElement The element to mount the active controller to. - * @param defaultRoute The route to start with. - * @param routes A key-value mapping of pathname to controller. - */ - ( - rootElement: Element, - defaultRoute: string, - routes: Routes - ): void; - - /** - * This allows m.route to be used as the `config` attribute for a - * virtual element, particularly useful for cases like this: - * - * ```ts - * // Note that the '#' is not required in `href`, thanks to the - * `config` setting. - * m("a[href='/dashboard/alicesmith']", {config: m.route}); - * ``` - */ - ( - element: Element, - isInitialized: boolean, - context?: Context, - vdom?: VirtualElement - ): void; - - /** - * Programmatically redirect to another route. - * - * @param path The route to go to. - * @param params Parameters to pass as a query string. - * @param shouldReplaceHistory Whether to replace the current history - * instead of adding a new one. - */ - (path: string, params?: any, shouldReplaceHistory?: boolean): void; - - /** - * Gets the current route. - * - * @return The current route. - */ - (): string; - - /** - * Gets a route parameter. - * - * @param key The key to get. - * @return The value associated with the parameter key. - */ - param(key: string): string; - - /** - * The current routing mode. This may be changed before calling - * m.route to change the part of the URL used to perform the routing. - * - * The value can be set to one of the following, defaulting to - * "hash": - * - * "search" - Uses the query string. This allows for named anchors to - * work on the page, but changes cause IE8 and lower to refresh the - * page. - * - * "hash" - Uses the hash. This is the only routing mode that does - * not cause page refreshes on any browser, but it does not support - * named anchors. - * - * "pathname" - Uses the URL pathname. This requires server-side - * setup to support bookmarking and page refreshes. It always causes - * page refreshes on IE8 and lower. Note that this requires that the - * application to be run from the root of the URL. - */ - mode: "search" | "hash" | "pathname"; - - /** - * Serialize an object into a query string. - * - * @param data The data to serialize. - * @return The serialized string. - */ - buildQueryString(data: Object): string; - - /** - * Parse a query string into an object. - * - * @param data The data to parse. - * @return The parsed object data. - */ - parseQueryString(data: string): Object; - } - - /** - * Send an XHR request to a server. Note that the `url` option is - * required. - * - * @param options The options to use for the request. - * @return A promise to the returned data, or void if not applicable. - * - * @see XHROptions for the available options. - */ - request(options: XHROptions): Promise - - /** - * Send a JSONP request to a server. Note that the `url` option is - * required. - * - * @param options The options to use - * @return A promise to the returned data. - * - * @see JSONPOptions for the available options. - */ - request(options: JSONPOptions): Promise; - - deferred: { - /** - * Create a Mithril deferred object. It behaves synchronously if - * possible, an intentional deviation from Promises/A+. Note that - * deferreds are completely separate from the redrawing system, and - * never trigger a redraw on their own. - * - * @return A new Mithril deferred instance. - * - * @see m.deferred.onerror for the error callback called for Error - * subclasses - */ - (): Deferred; - - /** - * A callback for all uncaught native Error subclasses in deferreds. - * This defaults to synchronously rethrowing all errors, a deviation - * from Promises/A+, but the behavior is configurable. To restore - * Promises/A+-compatible behavior. simply set this to a no-op. - */ - onerror(e: Error): void; - } - - /** - * Takes a list of promises or thennables and returns a Mithril promise - * that resolves once all in the list are resolved, or rejects if any of - * them reject. - * - * @param promises A list of promises to try to resolve. - * @return A promise that resolves to all the promises if all resolve, or - * rejects with the error contained in the first rejection. - */ - sync(promises: Thennable[]): Promise; - - /** - * Use this and endComputation if your views aren't redrawing after - * calls to third-party libraries. For integrating asynchronous code, - * this should be called before any asynchronous work is done. For - * synchronous code, this should be called at the beginning of the - * problematic segment. Note that these calls must be balanced, much like - * braces and parentheses. This is mostly used internally. Prefer - * m.redraw where possible, especially when making repeated calls. - * - * @see endComputation - * @see m.render - */ - startComputation(): void; - - /** - * Use startComputation and this if your views aren't redrawing after - * calls to third-party libraries. For integrating asynchronous code, - * this should be called after all asynchronous work completes. For - * synchronous code, this should be called at the end of the problematic - * segment. Note that these calls must be balanced, much like braces and - * parentheses. This is mostly used internally. Prefer m.redraw where - * possible, especially when making repeated calls. - * - * @see startComputation - * @see m.render - */ - endComputation(): void; - - /** - * This overwrites the internal version of window used by Mithril. - * It's mostly useful for testing, and is also used internally by - * Mithril to test itself. By default Mithril uses `window` for the - * dependency. - * - * @param mockWindow The mock to use for the window. - * @return The mock that was passed in. - */ - deps(mockWindow: Window): Window; + export interface Lifecycle { + /** The oninit hook is called before a vnode is touched by the virtual DOM engine. */ + oninit?(this: State, vnode: Vnode): any; + /** The oncreate hook is called after a DOM element is created and attached to the document. */ + oncreate?(this: State, vnode: VnodeDOM): any; + /** The onbeforeupdate hook is called before a vnode is diffed in a update. */ + onbeforeremove?(this: State, vnode: VnodeDOM): Promise | void; + /** The onupdate hook is called after a DOM element is updated, while attached to the document. */ + onremove?(this: State, vnode: VnodeDOM): any; + /** The onbeforeremove hook is called before a DOM element is detached from the document. If a Promise is returned, Mithril only detaches the DOM element after the promise completes. */ + onbeforeupdate?(this: State, vnode: Vnode, old: VnodeDOM): boolean | void; + /** The onremove hook is called before a DOM element is removed from the document. */ + onupdate?(this: State, vnode: VnodeDOM): any; } - interface TrustedString extends String { - /** @private Implementation detail. Don't depend on it. */ - $trusted: boolean; + export interface Hyperscript { + /** Creates a virtual element (Vnode). */ + (selector: string, ...children: Children[]): Vnode; + /** Creates a virtual element (Vnode). */ + (selector: string, attributes: Attributes, ...children: Children[]): Vnode; + /** Creates a virtual element (Vnode). */ + (component: ComponentTypes, attributes: Attrs & Lifecycle & { key?: string | number }, ...args: Children[]): Vnode; + /** Creates a virtual element (Vnode). */ + (component: ComponentTypes, ...args: Children[]): Vnode; + /** Creates a fragment virtual element (Vnode). */ + fragment(attrs: Lifecycle & { [key: string]: any }, children: ChildArrayOrPrimitive): Vnode; + /** Turns an HTML string into a virtual element (Vnode). Do not use trust on unsanitized user input. */ + trust(html: string): Vnode; } - /** - * The interface for a virtual element. It's best to consider this immutable - * for most use cases. - * - * @see m - */ - interface VirtualElement { - /** - * The tag name of this element. - */ - tag: string; - - /** - * The attributes of this element. - */ - attrs: Attributes; - - /** - * The children of this element. - */ - children: Children[]; + export interface RouteResolver { + /** The onmatch hook is called when the router needs to find a component to render. */ + render?(this: State, vnode: Vnode): Children; + /** The render method is called on every redraw for a matching route. */ + onmatch?(args: Params, requestedPath: string): Component | Promise | void; } - /** - * An event passed by Mithril to unload event handlers. - */ - interface Event { - /** - * Prevent the default behavior of scrolling the page and updating the - * URL on next route change. - */ - preventDefault(): void; + /** This represents a key-value mapping linking routes to components. */ + export interface RouteDefs { + /** The key represents the route. The value represents the corresponding component. */ + [url: string]: ComponentTypes | RouteResolver; } - /** - * A context object for configuration functions. - * - * @see ElementConfig - */ - interface Context { - /** - * A function to call when the node is unloaded. Useful for cleanup. - */ - onunload?(): any; - - /** - * Set true if the backing DOM node needs to be retained between route - * changes if possible. Set false if this node needs to be recreated - * every single time, regardless of how "different" it is. - */ - retain?: boolean; + export interface RouteOptions { + /** Routing parameters. If path has routing parameter slots, the properties of this object are interpolated into the path string. */ + replace?: boolean; + /** The state object to pass to the underlying history.pushState / history.replaceState call. */ + state?: any; + /** The title string to pass to the underlying history.pushState / history.replaceState call. */ + title?: string; } - /** - * This represents a callback function for a virtual element's config - * attribute. It's a low-level function useful for extra cleanup after - * removal from the tree, storing instances of third-party classes that - * need to be associated with the DOM, etc. - * - * @see Attributes - * @see Context - */ - interface ElementConfig { - /** - * A callback function for a virtual element's config attribute. - * - * @param element The associated DOM element. - * @param isInitialized Whether this is the first call for the virtual - * element or not. - * @param context The associated context for this element. - * @param vdom The associated virtual element. - */ - ( - element: Element, - isInitialized: boolean, - context: Context, - vdom: VirtualElement - ): void; + export interface Route { + /** Creates application routes and mounts Components and/or RouteResolvers to a DOM element. */ + (element: Element, defaultRoute: string, routes: RouteDefs): void; + /** Returns the last fully resolved routing path, without the prefix. */ + get(): string; + /** Redirects to a matching route or to the default route if no matching routes can be found. */ + set(route: string, data?: any, options?: RouteOptions): void; + /** Defines a router prefix which is a fragment of the URL that dictates the underlying strategy used by the router. */ + prefix(urlFragment: string): void; + /** This method is meant to be used in conjunction with an Vnode's oncreate hook. */ + link(vnode: Vnode): (e?: Event) => any; + /** Returns the named parameter value from the current route. */ + param(name: string): string; + /** Gets all route parameters. */ + param(): any; } - /** - * This represents the attributes available for configuring virtual elements, - * beyond the applicable DOM attributes. - * - * @see m - */ - interface Attributes { - /** - * The class name(s) for this virtual element, as a space-separated list. - */ - className?: string; + export interface RequestOptions { + /** The HTTP method to use. */ + method?: string; + /** The data to be interpolated into the URL and serialized into the querystring (for GET requests) or body (for other types of requests). */ + data?: any; + /** Whether the request should be asynchronous. Defaults to true. */ + async?: boolean; + /** A username for HTTP authorization. */ + user?: string; + /** A password for HTTP authorization. */ + password?: string; + /** Whether to send cookies to 3rd party domains. */ + withCredentials?: boolean; + /** Exposes the underlying XMLHttpRequest object for low-level configuration. */ + config?(xhr: XMLHttpRequest): any; + /** Headers to append to the request before sending it. */ + headers?: { [key: string]: string }; + /** A constructor to be applied to each object in the response. */ + type?: new (o: any) => any; + /** A serialization method to be applied to data. Defaults to JSON.stringify, or if options.data is an instance of FormData, defaults to the identity function. */ + serialize?(data: any): any; + /** A deserialization method to be applied to the response. Defaults to a small wrapper around JSON.parse that returns null for empty responses. */ + deserialize?(data: string): T; + /** A hook to specify how the XMLHttpRequest response should be read. Useful for reading response headers and cookies. Defaults to a function that returns xhr.responseText */ + extract?(xhr: XMLHttpRequest, options: this): T; + /** Force the use of the HTTP body section for data in GET requests when set to true, or the use of querystring for other HTTP methods when set to false. Defaults to false for GET requests and true for other methods. */ + useBody?: boolean; + /** If false, redraws mounted components upon completion of the request. If true, it does not. */ + background?: boolean; + } - /** - * The class name(s) for this virtual element, as a space-separated list. - */ - class?: string; + export interface JsonpOptions { + /** The data to be interpolated into the URL and serialized into the querystring. */ + data?: any; + /** A constructor to be applied to each object in the response. */ + type?: new (o: any) => any; + /** The name of the function that will be called as the callback. */ + callbackName?: string; + /** The name of the querystring parameter name that specifies the callback name. */ + callbackKey?: string; + /** If false, redraws mounted components upon completion of the request. If true, it does not. */ + background?: boolean; + } - /** - * A custom, low-level configuration in case this element needs special - * cleanup after removal from the tree. - * - * @see ElementConfig - */ - config?: ElementConfig; + export interface Static extends Hyperscript { + route: Route; + mount: typeof mount; + withAttr: typeof withAttr; + render: typeof render; + redraw: typeof redraw; + request: typeof request; + jsonp: typeof jsonp; + /** Returns an object with key/value pairs parsed from a string of the form: ?a=1&b=2 */ + parseQueryString(queryString: string): { [p: string]: any }; + /** Turns the key/value pairs of an object into a string of the form: a=1&b=2 */ + buildQueryString(values: { [p: string]: any }): string; + /** A string containing the semver value for the current Mithril release. */ + version: string; + } - /** - * A key to optionally associate with this element. - */ + // Vnode children types + export type Child = Vnode | string | number | boolean | null | undefined; + export interface ChildArray extends Array { } + export type Children = Child | ChildArray; + export type ChildArrayOrPrimitive = ChildArray | string | number | boolean; + + /** Virtual DOM nodes, or vnodes, are Javascript objects that represent an element (or parts of the DOM). */ + export interface Vnode> { + /** The nodeName of a DOM element. It may also be the string [ if a vnode is a fragment, # if it's a text vnode, or < if it's a trusted HTML vnode. Additionally, it may be a component. */ + tag: string | Component; + /** A hashmap of DOM attributes, events, properties and lifecycle methods. */ + attrs: Attrs; + /** An object that is persisted between redraws. In component vnodes, state is a shallow clone of the component object. */ + state: State; + /** The value used to map a DOM element to its respective item in an array of data. */ key?: string | number; + /** In most vnode types, the children property is an array of vnodes. For text and trusted HTML vnodes, The children property is either a string, a number or a boolean. */ + children?: ChildArrayOrPrimitive; + /** This is used instead of children if a vnode contains a text node as its only child. This is done for performance reasons. Component vnodes never use the text property even if they have a text node as their only child. */ + text?: string | number | boolean; + } - /** - * Any other virtual element properties, including attributes and event - * handlers. - */ + // In some lifecycle methods, Vnode will have a dom property + // and possibly a domSize property. + export interface VnodeDOM> extends Vnode { + /** Points to the element that corresponds to the vnode. */ + dom: Element; + /** This defines the number of DOM elements that the vnode represents (starting from the element referenced by the dom property). */ + domSize?: number; + } + + export interface CVnode extends Vnode> { } + + export interface CVnodeDOM extends VnodeDOM> { } + + /** Components are a mechanism to encapsulate parts of a view to make code easier to organize and/or reuse. Any Javascript object that has a view method can be used as a Mithril component. Components can be consumed via the m() utility. */ + export interface Component> extends Lifecycle { + /** Creates a view out of virtual elements. */ + view(this: State, vnode: Vnode): Children | null | void; + } + + /** Components are a mechanism to encapsulate parts of a view to make code easier to organize and/or reuse. Any class that implements a view method can be used as a Mithril component. Components can be consumed via the m() utility. */ + export interface ClassComponent extends Lifecycle> { + /** The oninit hook is called before a vnode is touched by the virtual DOM engine. */ + oninit?(vnode: Vnode): any; + /** The oncreate hook is called after a DOM element is created and attached to the document. */ + oncreate?(vnode: VnodeDOM): any; + /** The onbeforeupdate hook is called before a vnode is diffed in a update. */ + onbeforeremove?(vnode: VnodeDOM): Promise | void; + /** The onupdate hook is called after a DOM element is updated, while attached to the document. */ + onremove?(vnode: VnodeDOM): any; + /** The onbeforeremove hook is called before a DOM element is detached from the document. If a Promise is returned, Mithril only detaches the DOM element after the promise completes. */ + onbeforeupdate?(vnode: Vnode, old: VnodeDOM): boolean | void; + /** The onremove hook is called before a DOM element is removed from the document. */ + onupdate?(vnode: VnodeDOM): any; + /** Creates a view out of virtual elements. */ + view(vnode: CVnode): Children | null | void; + } + + /** Components are a mechanism to encapsulate parts of a view to make code easier to organize and/or reuse. Any function that returns an object with a view method can be used as a Mithril component. Components can be consumed via the m() utility. */ + export type FactoryComponent = (vnode: Vnode) => Component + + /** Components are a mechanism to encapsulate parts of a view to make code easier to organize and/or reuse. Any Javascript object that has a view method is a Mithril component. Components can be consumed via the m() utility. */ + export type Comp> = Component & State; + + /** Components are a mechanism to encapsulate parts of a view to make code easier to organize and/or reuse. Components can be consumed via the m() utility. */ + export type ComponentTypes = Component | { new (vnode: CVnode): ClassComponent } | FactoryComponent + + /** This represents the attributes available for configuring virtual elements, beyond the applicable DOM attributes. */ + export interface Attributes extends Lifecycle { + /** The class name(s) for this virtual element, as a space-separated list. */ + className?: string; + /** The class name(s) for this virtual element, as a space-separated list. */ + class?: string; + /** A key to optionally associate with this element. */ + key?: string | number; + /** Any other virtual element properties, including attributes and event handlers. */ [property: string]: any; } - - /** - * The basis of a Mithril controller instance. - */ - interface Controller { - /** - * An optional handler to call when the associated virtual element is - * destroyed. - * - * @param evt An associated event. - */ - onunload?(evt: Event): any; - } - - /** - * This represents a controller function. - * - * @see ControllerConstructor - */ - interface ControllerFunction { - (...args: any[]): T; - } - - /** - * This represents a controller constructor. - * - * @see ControllerFunction - */ - interface ControllerConstructor { - new (...args: any[]): T; - } - - /** - * This represents a Mithril component. - * - * @see m - * @see m.component - */ - interface Component { - /** - * The component's controller. - * - * @see m.component - */ - controller: ControllerFunction | ControllerConstructor; - - /** - * Creates a view out of virtual elements. - * - * @see m.component - */ - view(ctrl?: T, ...args: any[]): VirtualElement; - } - - /** - * This is the base interface for property getter-setters - * - * @see m.prop - */ - interface Property { - /** - * Gets the contained value. - * - * @return The contained value. - */ - (): T; - - /** - * Sets the contained value. - * - * @param value The new value to set. - * @return The newly set value. - */ - (value: T): T; - } - - /** - * This represents a non-promise getter-setter functions. - * - * @see m.prop which returns objects that implement this interface. - */ - interface BasicProperty extends Property { - /** - * Makes this serializable to JSON. - */ - toJSON(): T; - } - - /** - * This represents a key-value mapping linking routes to components. - */ - interface Routes { - /** - * The key represents the route. The value represents the corresponding - * component. - */ - [key: string]: Component; - } - - /** - * This represents a Mithril deferred object. - */ - interface Deferred { - /** - * Resolve this deferred's promise with a value. - * - * @param value The value to resolve the promise with. - */ - resolve(value?: T): void; - - /** - * Reject this deferred with an error. - * - * @param value The reason for rejecting the promise. - */ - reject(reason?: any): void; - - /** - * The backing promise. - * - * @see Promise - */ - promise: Promise; - } - - /** - * This represents a thennable success callback. - */ - interface SuccessCallback { - (value: T): U | Thennable; - } - - /** - * This represents a thennable error callback. - */ - interface ErrorCallback { - (value: Error): T | Thennable; - } - - /** - * This represents a thennable. - */ - interface Thennable { - then(success: SuccessCallback): Thennable; - then(success: SuccessCallback, error: ErrorCallback): Thennable; - catch?(error: ErrorCallback): Thennable; - catch?(error: ErrorCallback): Thennable; - } - - /** - * This represents a Mithril promise object. - */ - interface Promise extends Thennable, Property> { - /** - * Chain this promise with a simple success callback, propogating - * rejections. - * - * @param success The callback to call when the promise is resolved. - * @return The chained promise. - */ - then(success: SuccessCallback): Promise; - - /** - * Chain this promise with a success callback and error callback, without - * propogating rejections. - * - * @param success The callback to call when the promise is resolved. - * @param error The callback to call when the promise is rejected. - * @return The chained promise. - */ - then(success: SuccessCallback, error: ErrorCallback): Promise; - - /** - * Chain this promise with a single error callback, without propogating - * rejections. - * - * @param error The callback to call when the promise is rejected. - * @return The chained promise. - */ - catch(error: ErrorCallback): Promise; - } - - /** - * These are the common options shared across normal and JSONP requests. - * - * @see m.request - */ - interface RequestOptions { - /** - * The data to be sent. It's automatically serialized in the right format - * depending on the method (with exception of HTML5 FormData), and put in - * the appropriate section of the request. - */ - data?: any; - - /** - * Whether to run it in the background, i.e. true if it doesn't affect - * template rendering. - */ - background?: boolean; - - /** - * Set an initial value while the request is working, to populate the - * promise getter-setter. - */ - initialValue?: any; - - /** - * An optional preprocessor function to unwrap a successful response, in - * case the response contains metadata wrapping the data. - * - * @param data The data to unwrap. - * @return The unwrapped result. - */ - unwrapSuccess?(data: any): any; - - /** - * An optional preprocessor function to unwrap an unsuccessful response, - * in case the response contains metadata wrapping the data. - * - * @param data The data to unwrap. - * @return The unwrapped result. - */ - unwrapError?(data: any): any; - - /** - * An optional function to serialize the data. This defaults to - * `JSON.stringify`. - * - * @param dataToSerialize The data to serialize. - * @return The serialized form as a string. - */ - serialize?(dataToSerialize: any): string; - - /** - * An optional function to deserialize the data. This defaults to - * `JSON.parse`. - * - * @param dataToSerialize The data to parse. - * @return The parsed form. - */ - deserialize?(dataToDeserialize: string): any; - - /** - * An optional function to extract the data from a raw XMLHttpRequest, - * useful if the relevant data is in a response header or the status - * field. - * - * @param xhr The associated XMLHttpRequest. - * @param options The options passed to this request. - * @return string The serialized format. - */ - extract?(xhr: XMLHttpRequest, options: this): string; - - /** - * The parsed data, or its children if it's an array, will be passed to - * this class constructor if it's given, to parse it into classes. - * - * @param data The data to parse. - * @return The new instance for the list. - */ - type?: new (data: any) => any; - - /** - * The URL to send the request to. - */ - url: string; - } - - /** - * This represents the available options for configuring m.request for JSONP - * requests. - * - * @see m.request - */ - interface JSONPOptions extends RequestOptions { - /** - * For JSONP requests, this must be the string "jsonp". Otherwise, it's - * ignored. - */ - dataType: "jsonp"; - - /** - * The querystring key for the JSONP request callback. This is useful for - * APIs that don't use common conventions, such as - * `www.example.com/?jsonpCallback=doSomething`. It defaults to - * `callback`. - */ - callbackKey?: string; - - /** - * The data to send with the request. This is automatically serialized - * to a querystring. - */ - data?: Object; - } - - /** - * This represents the available options for configuring m.request for - * standard AJAX requests. - * - * @see m.request - */ - interface XHROptions extends RequestOptions { - /** - * This represents the HTTP method used, defaulting to "GET". - */ - method: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS"; - - /** - * The username for HTTP authentication. - */ - user?: string; - - /** - * The password for HTTP authentication. - */ - password?: string; - - /** - * An optional function to run between `open` and `send`, useful for - * adding request headers or using XHR2 features such as the `upload` - * property. It is even possible to override the XHR altogether with a - * similar object, such as an XDomainRequest instance. - * - * @param xhr The associated XMLHttpRequest. - * @param options The options passed to this request. - * @return The new XMLHttpRequest, or nothing if the same one is kept. - */ - config?(xhr: XMLHttpRequest, options: this): any; - - /** - * The data to send with the request. - */ - data?: Object; - } } -declare const m: Mithril.Static; - -declare module "mithril" { - export = m; -} +declare const Mithril: Mithril.Static; +export = Mithril; diff --git a/types/mithril/mithril-tests.ts b/types/mithril/mithril-tests.ts deleted file mode 100644 index 8479074e07..0000000000 --- a/types/mithril/mithril-tests.ts +++ /dev/null @@ -1,55 +0,0 @@ - -// This is the todolist example from http://lhorie.github.io/mithril/getting-started.html - -var todo = { - - //the Todo class has two properties - Todo: function(data: any) { - this.description = m.prop(data.description); - this.done = m.prop(false); - }, - - //the TodoList class is a list of Todo's - TodoList: Array, - - //the controller uses three model-level entities, of which one is a custom defined class: - //`Todo` is the central class in this application - //`list` is merely a generic array, with standard array methods - //`description` is a temporary storage box that holds a string - // - //the `add` method simply adds a new todo to the list - controller: function() { - this.list = new todo.TodoList(); - this.description = m.prop(""); - - this.add = function() { - if (this.description()) { - this.list.push(new (todo.Todo)({description: this.description()})); - this.description(""); - } - }.bind(this); - }, - - //here's the view - view: function(ctrl: any) { - return m("html", [ - m("body", [ - m("input", {onchange: m.withAttr("value", ctrl.description), value: ctrl.description()} as any /* TODO remove `as any` */), - m("button", {onclick: ctrl.add} as any /* TODO remove `as any` */, "Add"), - m("table", [ - ctrl.list.map(function(task: any) { - return m("tr", [ - m("td", [ - m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()} as any /* TODO remove `as any` */) - ]), - m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}} as any /* TODO remove `as any` */, task.description()), - ]) - }) - ]) - ]) - ]); - }, -}; - -//initialize the application -m.mount(document, todo); diff --git a/types/mithril/mount.d.ts b/types/mithril/mount.d.ts new file mode 100644 index 0000000000..af79b918a9 --- /dev/null +++ b/types/mithril/mount.d.ts @@ -0,0 +1,3 @@ +import { mount as _mount } from "mithril"; +declare const mount: typeof _mount; +export = mount; diff --git a/types/mithril/redraw.d.ts b/types/mithril/redraw.d.ts new file mode 100644 index 0000000000..26c0a36b03 --- /dev/null +++ b/types/mithril/redraw.d.ts @@ -0,0 +1,11 @@ +import { redraw, render } from "mithril"; + +declare namespace RedrawService { + export interface Static { + render: typeof render; + redraw: typeof redraw; + } +} + +declare const RedrawService: RedrawService.Static; +export = RedrawService; diff --git a/types/mithril/render.d.ts b/types/mithril/render.d.ts new file mode 100644 index 0000000000..d1144ced0d --- /dev/null +++ b/types/mithril/render.d.ts @@ -0,0 +1,10 @@ +import { render } from "mithril"; + +declare namespace RenderService { + export interface Static { + render: typeof render; + } +} + +declare const RenderService: RenderService.Static; +export = RenderService; diff --git a/types/mithril/request.d.ts b/types/mithril/request.d.ts new file mode 100644 index 0000000000..d4483c76d2 --- /dev/null +++ b/types/mithril/request.d.ts @@ -0,0 +1,11 @@ +import { request, jsonp } from "mithril"; + +declare namespace RequestService { + export interface Static { + request: typeof request; + jsonp: typeof jsonp; + } +} + +declare const RequestService: RequestService.Static; +export = RequestService; diff --git a/types/mithril/route.d.ts b/types/mithril/route.d.ts new file mode 100644 index 0000000000..df8a13181c --- /dev/null +++ b/types/mithril/route.d.ts @@ -0,0 +1,3 @@ +import { Route } from "mithril"; +declare const route: Route; +export = route; diff --git a/types/mithril/stream/index.d.ts b/types/mithril/stream/index.d.ts new file mode 100644 index 0000000000..e1850e7291 --- /dev/null +++ b/types/mithril/stream/index.d.ts @@ -0,0 +1,42 @@ +declare namespace Stream { + export interface Stream { + /** Returns the value of the stream. */ + (): T; + /** Sets the value of the stream. */ + (value: T): this; + /** Creates a dependent stream whose value is set to the result of the callback function. */ + map(f: (current: T) => Stream | T | void): Stream; + /** Creates a dependent stream whose value is set to the result of the callback function. */ + map(f: (current: T) => Stream | U): Stream; + /** This method is functionally identical to stream. It exists to conform to Fantasy Land's Applicative specification. */ + of(val?: T): Stream; + /** Apply. */ + ap(f: Stream<(value: T) => U>): Stream; + /** A co-dependent stream that unregisters dependent streams when set to true. */ + end: Stream; + /** When a stream is passed as the argument to JSON.stringify(), the value of the stream is serialized. */ + toJSON(): string; + /** Returns the value of the stream. */ + valueOf(): T; + } + + export interface Static { + /** Creates a stream. */ + (value?: T): Stream; + /** Creates a computed stream that reactively updates if any of its upstreams are updated. */ + combine(combiner: (...streams: any[]) => T, streams: Stream[]): Stream; + /** Creates a stream whose value is the array of values from an array of streams. */ + merge(streams: Stream[]): Stream; + /** Creates a new stream with the results of calling the function on every incoming stream with and accumulator and the incoming value. */ + scan(fn: (acc: U, value: T) => U, acc: U, stream: Stream): Stream; + /** Takes an array of pairs of streams and scan functions and merges all those streams using the given functions into a single stream. */ + scanMerge(pairs: [Stream, (acc: U, value: T) => U][], acc: U): Stream; + /** Takes an array of pairs of streams and scan functions and merges all those streams using the given functions into a single stream. */ + scanMerge(pairs: [Stream, (acc: U, value: any) => U][], acc: U): Stream; + /** A special value that can be returned to stream callbacks to halt execution of downstreams. */ + readonly HALT: any; + } +} + +declare const Stream: Stream.Static; +export = Stream; diff --git a/types/mithril/test/test-api.ts b/types/mithril/test/test-api.ts new file mode 100644 index 0000000000..221cad02e8 --- /dev/null +++ b/types/mithril/test/test-api.ts @@ -0,0 +1,720 @@ +// Typescript adaptation of mithril's test suite. +// Not intended to be run; only to compile & check types. + +import * as m from 'mithril' +import * as stream from 'mithril/stream' + +const FRAME_BUDGET = 100 + +{ + const vnode = m("div") + console.assert(vnode.tag === "div") + console.assert(typeof m.version === "string") + console.assert(m.version.indexOf(".") > -1) +} + +{ + const vnode = m.trust("
") +} + +{ + const vnode = m.fragment({key: 123}, [m("div")]) + console.assert((vnode.children as m.Vnode[]).length === 1) + console.assert(vnode.children![0].tag === 'div') +} + +{ + const handler = m.withAttr("value", (value) => {}) + handler({currentTarget: {value: 10}}) +} + +{ + const params = m.parseQueryString("?a=1&b=2") + const query = m.buildQueryString({a: 1, b: 2}) +} + +{ + const root = window.document.createElement("div") + m.render(root, m("div")) + console.assert(root.childNodes.length === 1) +} + +{ + const root = window.document.createElement("div") + m.mount(root, {view() {return m("div")}}) + console.assert(root.childNodes.length === 1) + console.assert(root.firstChild!.nodeName === "DIV") +} + +{ + const root = window.document.createElement("div") + m.route(root, "/a", { + "/a": {view() {return m("div")}} + }) + + setTimeout(() => { + console.assert(root.childNodes.length === 1) + console.assert(root.firstChild!.nodeName === "DIV") + }, FRAME_BUDGET) +} + +{ + const root = window.document.createElement("div") + m.route.prefix("#") + m.route(root, "/a", { + "/a": {view() {return m("div")}} + }) + + setTimeout(() => { + console.assert(root.childNodes.length === 1) + console.assert(root.firstChild!.nodeName === "DIV") + }, FRAME_BUDGET) +} + +{ + const root = window.document.createElement("div") + m.route(root, "/a", { + "/a": {view() {return m("div")}} + }) + + setTimeout(() => { + console.assert(m.route.get() === "/a") + }, FRAME_BUDGET) +} + +{ + const root = window.document.createElement("div") + m.route(root, "/a", { + "/:id": {view() {return m("div")}} + }) + + setTimeout(() => { + m.route.set("/b") + setTimeout(() => { + console.assert(m.route.get() === "/b") + }, FRAME_BUDGET) + }, FRAME_BUDGET) +} + +{ + let count = 0 + const root = window.document.createElement("div") + m.mount(root, {view() {count++}}) + setTimeout(() => { + m.redraw() + console.assert(count === 2) + }, FRAME_BUDGET) +} + +// +// Additional tests by andraaspar +// + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/hyperscript.html#components +//////////////////////////////////////////////////////////////////////////////// + +{ + // define a component + const Greeter: m.Comp<{}, {}> = { + view(vnode) { + return m("div", vnode.attrs, ["Hello ", vnode.children]) + } + } + + // consume it + m(Greeter, { style: "color:red;" }, "world") +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/hyperscript.html#keys +//////////////////////////////////////////////////////////////////////////////// + +{ + const users = [ + { id: 1, name: "John" }, + { id: 2, name: "Mary" }, + ] + + const userInputs = function(users: { id: number, name: string }[]) { + return users.map(u => { + return m("input", { key: u.id }, u.name) + }) + } + + m.render(document.body, userInputs(users)) +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/components.html#state +//////////////////////////////////////////////////////////////////////////////// + +{ + const ComponentWithInitialState: m.Comp<{}, {data: string}> = { + data: "Initial content", + view(vnode) { + return m("div", vnode.state.data) + } + } + + m(ComponentWithInitialState) +} + +{ + const ComponentWithDynamicState: m.Comp<{text: string}, {data?: string}> = { + oninit(vnode) { + vnode.state.data = vnode.attrs.text + }, + view(vnode) { + return m("div", vnode.state.data) + } + } + + m(ComponentWithDynamicState, { text: "Hello" }) +} + +{ + const ComponentUsingThis: m.Comp<{text: string}, {data?: string}> = { + oninit(vnode) { + this.data = vnode.attrs.text + }, + view(vnode) { + return m("div", this.data) + } + } + + m(ComponentUsingThis, { text: "Hello" }) +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/lifecycle-methods.html +//////////////////////////////////////////////////////////////////////////////// + +{ + const Fader: m.Comp<{}, {}> = { + onbeforeremove(vnode) { + vnode.dom.classList.add("fade-out") + return new Promise(resolve => { + setTimeout(resolve, 1000) + }) + }, + view() { + return m("div", "Bye") + }, + } +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/route.html#wrapping-a-layout-component +//////////////////////////////////////////////////////////////////////////////// + +{ + const Home = { + view() { + return "Welcome" + } + } + + const state = { + term: "", + search() { + // save the state for this route + // this is equivalent to `history.replaceState({term: state.term}, null, location.href)` + m.route.set(m.route.get(), null, { replace: true, state: { term: state.term } }) + + // navigate away + location.href = "https://google.com/?q=" + state.term + } + } + + const Form: m.Comp<{term: string}, {}> = { + oninit(vnode) { + state.term = vnode.attrs.term || "" // populated from the `history.state` property if the user presses the back button + }, + view() { + return m("form", [ + m("input[placeholder='Search']", { oninput: m.withAttr("value", v => { state.term = v }), value: state.term }), + m("button", { onclick: state.search }, "Search") + ]) + } + } + + const Layout: m.Comp<{}, {}> = { + view(vnode) { + return m(".layout", vnode.children) + } + } + + // example 1 + m.route(document.body, "/", { + "/": { + view() { + return m(Layout, m(Home)) + }, + }, + "/form": { + view() { + return m(Layout, m(Form)) + }, + } + }) + + // example 2 + m.route(document.body, "/", { + "/": { + render() { + return m(Layout, m(Home)) + }, + }, + "/form": { + render() { + return m(Layout, m(Form)) + }, + } + }) + + // functionally equivalent to example 1 + const Anon1 = { + view() { + return m(Layout, m(Home)) + }, + } + const Anon2 = { + view() { + return m(Layout, m(Form)) + }, + } + + m.route(document.body, "/", { + "/": { + render() { + return m(Anon1) + } + }, + "/form": { + render() { + return m(Anon2) + } + }, + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/route.html#preloading-data +//////////////////////////////////////////////////////////////////////////////// + +{ + const state = { + users: [] as any[], + loadUsers() { + return m.request("/api/v1/users").then(users => { + state.users = users + }) + } + } + + m.route(document.body, "/user/list", { + "/user/list": { + onmatch: state.loadUsers, + render() { + return state.users.map(user => m("div", user.id)) + } + }, + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/request.html#monitoring-progress +//////////////////////////////////////////////////////////////////////////////// + +{ + let progress = 0 + + m.mount(document.body, { + view() { + return [ + m("input[type=file]", { onchange: upload }), + progress + "% completed" + ] + } + }) + + const upload = function(e: Event) { + const file = ((e.target as HTMLInputElement).files as FileList)[0] + const data = new FormData() + data.append("myfile", file) + m.request({ + method: "POST", + url: "/api/v1/upload", + data, + config: xhr => { + xhr.addEventListener("progress", e => { + progress = e.loaded / e.total + m.redraw() // tell Mithril that data changed and a re-render is needed + }) + } + }) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/request.html#casting-response-to-a-type +//////////////////////////////////////////////////////////////////////////////// + +{ + // Start rewrite to TypeScript + class User { + name: string; + constructor(data: any) { + this.name = data.firstName + " " + data.lastName + } + } + // End rewrite to TypeScript + + // function User(data) { + // this.name = data.firstName + " " + data.lastName + // } + + m.request({ + method: "GET", + url: "/api/v1/users", + type: User + }) + .then(users => { + console.log(users[0].name) // logs a name + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/request.html#non-json-responses +//////////////////////////////////////////////////////////////////////////////// + +{ + m.request({ + method: "GET", + url: "/files/icon.svg", + deserialize: value => value + }) + .then(svg => { + m.render(document.body, m.trust(svg)) + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/jsonp.html +//////////////////////////////////////////////////////////////////////////////// + +{ + m.jsonp({ + url: "/api/v1/users/:id", + data: { id: 1 }, + callbackKey: "callback", + }) + .then(result => { + console.log(result) + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/fragment.html +//////////////////////////////////////////////////////////////////////////////// + +{ + const groupVisible = true + const log = function() { + console.log("group is now visible") + } + + m("ul", [ + m("li", "child 1"), + m("li", "child 2"), + groupVisible ? m.fragment({ oninit: log }, [ + // a fragment containing two elements + m("li", "child 3"), + m("li", "child 4"), + ]) : null + ]) +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/stream.html#computed-properties +//////////////////////////////////////////////////////////////////////////////// + +{ + const firstName = stream("John") + const lastName = stream("Doe") + const fullName = stream.merge([firstName, lastName]).map(values => { + return values.join(" ") + }) + + console.log(fullName()) // logs "John Doe" + + firstName("Mary") + + console.log(fullName()) // logs "Mary Doe" +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/stream.html#chaining-streams +//////////////////////////////////////////////////////////////////////////////// + +{ + const halted = stream(1).map(value => { + return stream.HALT + }) + + halted.map(() => { + // never runs + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/stream.html#combining-streams +//////////////////////////////////////////////////////////////////////////////// + +{ + const a = stream(5) + const b = stream(7) + + const added = stream.combine((a: stream.Stream, b: stream.Stream) => { + return a() + b() + }, [a, b]) + + console.log(added()) // logs 12 +} + +//////////////////////////////////////////////////////////////////////////////// +// http://mithril.js.org/stream.html#ended-state +//////////////////////////////////////////////////////////////////////////////// + +{ + const value = stream() + const doubled = value.map(value => value * 2) + + value.end(true) // set to ended state + + value(5) + + console.log(doubled()) +} + +//////////////////////////////////////////////////////////////////////////////// +// https://github.com/lhorie/mithril.js/blob/master/examples/animation/mosaic.html +//////////////////////////////////////////////////////////////////////////////// + +// Excerpt + +{ + const root = document.getElementById("root")! + const empty: any[] = [] + const full: any[] = [] + for (let i = 0; i < 100; i++) full.push(i) + let cells: any[] + + const view = function() { + return m(".container", cells.map(i => { + return m(".slice", { + style: {backgroundPosition: (i % 10 * 11) + "% " + (Math.floor(i / 10) * 11) + "%"}, + onbeforeremove: exit + }) + })) + } + + const exit = function(vnode: m.VnodeDOM) { + vnode.dom.classList.add("exit") + return new Promise(resolve => { + setTimeout(resolve, 1000) + }) + } + + const run = function() { + cells = cells === full ? empty : full + + m.render(root, [view()]) + + setTimeout(run, 2000) + } + + run() +} + +//////////////////////////////////////////////////////////////////////////////// +// https://github.com/lhorie/mithril.js/blob/master/examples/editor/index.html +//////////////////////////////////////////////////////////////////////////////// + +// Excerpt + +{ + // Start extra declarations + const marked = (v: string) => v + // End extra declarations + + // model + const state = { + text: "# Markdown Editor\n\nType on the left panel and see the result on the right panel", + update(value: string) { + state.text = value + } + } + + // view + const Editor = { + view() { + return [ + m("textarea.input", { + oninput: m.withAttr("value", state.update), + value: state.text + }), + m(".preview", m.trust(marked(state.text))), + ] + } + } + + m.mount(document.getElementById("editor")!, Editor) +} + +//////////////////////////////////////////////////////////////////////////////// +// https://github.com/lhorie/mithril.js/blob/master/examples/todomvc/todomvc.js +//////////////////////////////////////////////////////////////////////////////// + +{ + // model + const state = { + dispatch(action: string, args?: any) { + (state as any)[action].apply(state, args || []) + requestAnimationFrame(() => { + localStorage["todos-mithril"] = JSON.stringify(state.todos) + }) + }, + + todos: JSON.parse(localStorage["todos-mithril"] || "[]"), + editing: null as any, + filter: "", + remaining: 0, + todosByStatus: [] as any[], + showing: undefined as any, + + createTodo(title: string) { + state.todos.push({title: title.trim(), completed: false}) + }, + setStatuses(completed: boolean) { + for (let i = 0; i < state.todos.length; i++) state.todos[i].completed = completed + }, + setStatus(todo: any, completed: boolean) { + todo.completed = completed + }, + destroy(todo: any) { + const index = state.todos.indexOf(todo) + if (index > -1) state.todos.splice(index, 1) + }, + clear() { + for (let i = 0; i < state.todos.length; i++) { + if (state.todos[i].completed) state.destroy(state.todos[i--]) + } + }, + + edit(todo: any) { + state.editing = todo + }, + update(title: string) { + if (state.editing != null) { + state.editing.title = title.trim() + if (state.editing.title === "") state.destroy(state.editing) + state.editing = null + } + }, + reset() { + state.editing = null + }, + + computed(vnode: m.Vnode) { + state.showing = vnode.attrs.status || "" + state.remaining = state.todos.filter((todo: any) => !todo.completed).length + state.todosByStatus = state.todos.filter((todo: any) => { + switch (state.showing) { + case "": return true + case "active": return !todo.completed + case "completed": return todo.completed + } + }) + } + } + + // view + const Todos: m.Comp<{}, { + add(e: Event): void + toggleAll(): void + toggle(todo: any): void + focus(vnode: m.VnodeDOM, todo: any): void + save(e: KeyboardEvent): void + }> = { + add(e: KeyboardEvent) { + if (e.keyCode === 13) { + state.dispatch("createTodo", [(e.target as HTMLInputElement).value]); + (e.target as HTMLInputElement).value = "" + } + }, + toggleAll() { + state.dispatch("setStatuses", [(document.getElementById("toggle-all") as HTMLInputElement).checked]) + }, + toggle(todo: any) { + state.dispatch("setStatus", [todo, !todo.completed]) + }, + focus(vnode: m.VnodeDOM, todo: any) { + if (todo === state.editing && vnode.dom !== document.activeElement) { + const el = vnode.dom as HTMLInputElement + el.value = todo.title + el.focus() + el.selectionStart = el.selectionEnd = todo.title.length + } + }, + save(e: KeyboardEvent) { + if (e.keyCode === 13 || e.type === "blur") state.dispatch("update", [(e.target as HTMLInputElement).value]) + else if (e.keyCode === 27) state.dispatch("reset") + }, + oninit: state.computed, + onbeforeupdate: state.computed, + view(vnode) { + const ui = vnode.state + return [ + m("header.header", [ + m("h1", "todos"), + m("input#new-todo[placeholder='What needs to be done?'][autofocus]", {onkeypress: ui.add}), + ]), + m("section#main", {style: {display: state.todos.length > 0 ? "" : "none"}}, [ + m("input#toggle-all[type='checkbox']", {checked: state.remaining === 0, onclick: ui.toggleAll}), + m("label[for='toggle-all']", {onclick: ui.toggleAll}, "Mark all as complete"), + m("ul#todo-list", [ + state.todosByStatus.map((todo: any) => { + return m("li", {class: (todo.completed ? "completed" : "") + " " + (todo === state.editing ? "editing" : "")}, [ + m(".view", [ + m("input.toggle[type='checkbox']", {checked: todo.completed, onclick: () => {ui.toggle(todo)}}), + m("label", {ondblclick: () => {state.dispatch("edit", [todo])}}, todo.title), + m("button.destroy", {onclick: () => {state.dispatch("destroy", [todo])}}), + ]), + m("input.edit", {onupdate(vnode: m.VnodeDOM) {ui.focus(vnode, todo)}, onkeypress: ui.save, onblur: ui.save}) + ]) + }), + ]), + ]), + state.todos.length ? m("footer#footer", [ + m("span#todo-count", [ + m("strong", state.remaining), + state.remaining === 1 ? " item left" : " items left", + ]), + m("ul#filters", [ + m("li", m("a[href='/']", {oncreate: m.route.link, class: state.showing === "" ? "selected" : ""}, "All")), + m("li", m("a[href='/active']", {oncreate: m.route.link, class: state.showing === "active" ? "selected" : ""}, "Active")), + m("li", m("a[href='/completed']", {oncreate: m.route.link, class: state.showing === "completed" ? "selected" : ""}, "Completed")), + ]), + m("button#clear-completed", {onclick: () => {state.dispatch("clear")}}, "Clear completed"), + ]) : null, + ] + } + } + + m.route(document.getElementById("todoapp")!, "/", { + "/": Todos, + "/:status": Todos, + }) +} diff --git a/types/mithril/test/test-class-component.ts b/types/mithril/test/test-class-component.ts new file mode 100644 index 0000000000..3b99bb63ef --- /dev/null +++ b/types/mithril/test/test-class-component.ts @@ -0,0 +1,135 @@ +import * as m from 'mithril' +import {ClassComponent, CVnode, CVnodeDOM} from 'mithril' + +/////////////////////////////////////////////////////////// +// 0. +// Simplest component example - no attrs or state. +// +class Comp0 implements ClassComponent<{}> { + constructor (vnode: CVnode<{}>) { + } + view() { + return m('span', "Test") + } +} + +// Mount the component +m.mount(document.getElementById('comp0')!, Comp0) + +// Unmount the component +m.mount(document.getElementById('comp0')!, null) + +/////////////////////////////////////////////////////////// +// 1. +// Simple example with lifecycle methods. +// +class Comp1 implements ClassComponent<{}> { + oninit (vnode: CVnode<{}>) { + } + oncreate ({dom}: CVnodeDOM<{}>) { + } + view (vnode: CVnode<{}>) { + return m('span', "Test") + } +} + +/////////////////////////////////////////////////////////// +// 2. +// Component with attrs type +// +interface Comp2Attrs { + title: string + description: string +} + +class Comp2 implements ClassComponent { + view ({attrs: {title, description}}: CVnode) { + return [m('h2', title), m('p', description)] + } +} + +/////////////////////////////////////////////////////////// +// 3. +// Declares attrs type inline. +// Uses comp2 with typed attrs and makes use of `onremove` +// lifecycle method. +// +class Comp3 implements ClassComponent<{pageHead: string}> { + oncreate ({dom}: CVnodeDOM<{pageHead: string}>) { + // Can do stuff with dom + } + view ({attrs}: CVnode<{pageHead: string}>) { + return m('.page', + m('h1', attrs.pageHead), + m(Comp2, + { + // attrs is type checked - nice! + title: "A Title", + description: "Some descriptive text.", + onremove: (vnode) => { + // Vnode type is inferred + console.log("comp2 was removed") + }, + } + ), + // Test other hyperscript parameter variations + m(Comp1, m(Comp1)), + m('br') + ) + } +} + +/////////////////////////////////////////////////////////// +// 4. +// Typed attrs, component with state, methods +// +interface Comp4Attrs { + name: string +} + +class Comp4 implements ClassComponent { + count: number + constructor (vnode: CVnode) { + this.count = 0 + } + add (num: number) { + this.count += num + } + view ({attrs}: CVnode) { + return [ + m('h1', `This ${attrs.name} has been clicked ${this.count} times`), + m('button', + { + onclick: () => this.add(1) + }, + "Click me") + ] + } +} + +/////////////////////////////////////////////////////////// +// +// Test that all are mountable components +// +m.route(document.body, '/', { + '/comp0': Comp0, + '/comp1': Comp1, + '/comp2': Comp2, + '/comp3': Comp3, + '/comp4': Comp4 +}) + +/////////////////////////////////////////////////////////// +// +// Concise module example with default export +// +export interface Attrs { + name: string +} + +export default class MyComponent implements ClassComponent { + count = 0 + view ({attrs}: CVnode) { + return m('span', `name: ${attrs.name}, count: ${this.count}`) + } +} diff --git a/types/mithril/test/test-component.ts b/types/mithril/test/test-component.ts new file mode 100644 index 0000000000..c97c9eafeb --- /dev/null +++ b/types/mithril/test/test-component.ts @@ -0,0 +1,159 @@ +import * as m from 'mithril' +import {Component, Comp} from 'mithril' + +/////////////////////////////////////////////////////////// +// 0. +// Simplest component example - no attrs or state. +// +const comp0 = { + view() { + return m('span', "Test") + } +} + +// Mount the component +m.mount(document.getElementById('comp0')!, comp0) + +// Unmount the component +m.mount(document.getElementById('comp0')!, null) + +/////////////////////////////////////////////////////////// +// 1. +// Simple example. Vnode type for component methods is inferred. +// +const comp1: Component<{},{}> = { + oncreate ({dom}) { + // vnode.dom type inferred + }, + view (vnode) { + return m('span', "Test") + } +} + +/////////////////////////////////////////////////////////// +// 2. +// Component with attrs +// +interface Comp2Attrs { + title: string + description: string +} + +const comp2: Component = { + view ({attrs: {title, description}}) { // Comp2Attrs type is inferred + return [m('h2', title), m('p', description)] + } +} + +/////////////////////////////////////////////////////////// +// 3. +// Declares attrs type inline. +// Uses comp2 with typed attrs and makes use of `onremove` +// lifecycle method. +// +const comp3: Component<{pageHead: string},{}> = { + oncreate ({dom}) { + // Can do stuff with dom + }, + view ({attrs}) { + return m('.page', + m('h1', attrs.pageHead), + m(comp2, + { + // attrs is type checked - nice! + title: "A Title", + description: "Some descriptive text.", + onremove: (vnode) => { + console.log("comp2 was removed") + }, + } + ), + // Test other hyperscript parameter variations + m(comp1, m(comp1)), + m('br') + ) + } +} + +/////////////////////////////////////////////////////////// +// 4. +// Typed attrs and state, and `this` type is inferred. +// +interface Comp4Attrs { + name: string +} + +interface Comp4State { + count: number + add(this: Comp4State, num: number): void +} + +// Either of these two Comp4 defs will work: +type Comp4 = Component & Comp4State +// interface Comp4 extends Component, Comp4State {} + +const comp4: Comp4 = { + count: 0, // <- Must be declared to satisfy Comp4 type which includes Comp4State type + add (num) { + // num and this types inferred + this.count += num + }, + oninit() { + this.count = 0 + }, + view ({attrs}) { + return [ + m('h1', `This ${attrs.name} has been clicked ${this.count} times`), + m('button', + { + // 'this' is typed! + onclick: () => this.add(1) + }, + "Click me") + ] + } +} + +/////////////////////////////////////////////////////////// +// 5. +// Stateful component (Equivalent to Comp4 example.) +// Avoids the use of `this` completely; state manipulated +// through vnode.state. +// +const comp5: Component = { + oninit ({state}) { + state.count = 0 + state.add = num => {state.count += num} + }, + view ({attrs, state}) { + return [ + m('h1', `This ${attrs.name} has been clicked ${state.count} times`), + m('button', + { + onclick: () => {state.add(1)} + }, + "Click me" + ) + ] + } +} + +/////////////////////////////////////////////////////////// +// +// Concise module example with default export +// +interface Attrs { + name: string +} + +interface State { + count: number +} + +export default { + count: 0, + view ({attrs}) { + return m('span', `name: ${attrs.name}, count: ${this.count}`) + } +} as Comp +// Using the Comp type will apply the State intersection type for us. diff --git a/types/mithril/test/test-factory-component.ts b/types/mithril/test/test-factory-component.ts new file mode 100644 index 0000000000..1f8e10bb73 --- /dev/null +++ b/types/mithril/test/test-factory-component.ts @@ -0,0 +1,179 @@ +import * as m from 'mithril' +import {Component, FactoryComponent, Vnode} from 'mithril' + +/////////////////////////////////////////////////////////// +// 0. +// Simplest component example - no attrs or state. +// +function comp0() { + return { + view() { + return m('span', "Test") + } + } +} + +// Mount the component +m.mount(document.getElementById('comp0')!, comp0) + +// Unmount the component +m.mount(document.getElementById('comp0')!, null) + +/////////////////////////////////////////////////////////// +// 1. +// Simple example. Vnode type for component methods is inferred. +// +function comp1() { + return { + oncreate ({dom}) { + // vnode.dom type inferred + }, + view (vnode) { + return m('span', "Test") + } + } as Component<{},{}> +} + +/////////////////////////////////////////////////////////// +// 2. +// Component with attrs type. Different type annotation +// style to infer factory vnode type. +// +interface Comp2Attrs { + title: string + description: string +} + +const comp2 = function (vnode) { // vnode is inferred + return { + view ({attrs: {title, description}}) { // Comp2Attrs type is inferred + return [m('h2', title), m('p', description)] + } + } +} as FactoryComponent + +/////////////////////////////////////////////////////////// +// 3. +// Declares attrs type inline. +// Uses comp2 with typed attrs and makes use of `onremove` +// lifecycle method. +// +const comp3 = function() { + return { + oncreate ({dom}) { + // Can do stuff with dom + }, + view ({attrs}) { + return m('.page', + m('h1', attrs.pageHead), + m(comp2, + { + // attrs is type checked - nice! + title: "A Title", + description: "Some descriptive text.", + onremove: (vnode) => { + console.log("comp2 was removed") + }, + } + ), + // Test other hyperscript parameter variations + m(comp1, m(comp1)), + m('br') + ) + } + } +} as FactoryComponent<{pageHead: string}> + +/////////////////////////////////////////////////////////// +// 4. +// Stateful component using closure method & var +// to hold state. +// +interface Comp4Attrs { + name: string +} + +function comp4(): Component { + let count = 0 + + function add (num: number) { + count += num + } + + return { + oninit() { + count = 0 + }, + view ({attrs}) { + return [ + m('h1', `This ${attrs.name} has been clicked ${count} times`), + m('button', + { + onclick: () => add(1) + }, + "Click me" + ) + ] + } + } +} + +/////////////////////////////////////////////////////////// +// 5. +// Stateful component (Equivalent to Comp4 example.) +// Uses vnode.state instead of closure. +// +interface Comp5State { + count: number + add (num: number): void +} + +function comp5(): Component { + return { + oninit ({state}) { + state.count = 0 + state.add = num => {state.count += num} + }, + view ({attrs, state}) { + return [ + m('h1', `This ${attrs.name} has been clicked ${state.count} times`), + m('button', + { + onclick: () => {state.add(1)} + }, + "Click me" + ) + ] + } + } +} + +/////////////////////////////////////////////////////////// +// +// Test that all are mountable components +// +m.route(document.body, '/', { + '/comp0': comp0, + '/comp1': comp1, + '/comp2': comp2, + '/comp3': comp3, + '/comp4': comp4, + '/comp5': comp5 +}) + +/////////////////////////////////////////////////////////// +// +// Concise module example with default export +// +interface Attrs { + name: string +} + +export default (): Component => { + let count = 0 + return { + view ({attrs}) { + return m('span', `name: ${attrs.name}, count: ${count}`) + } + } +} diff --git a/types/mithril/test/test-fragment.ts b/types/mithril/test/test-fragment.ts new file mode 100644 index 0000000000..364680bbb2 --- /dev/null +++ b/types/mithril/test/test-fragment.ts @@ -0,0 +1,17 @@ +import * as m from 'mithril' +import {Vnode} from 'mithril/' +import * as h from 'mithril/hyperscript' + +const vnode = m.fragment({id: 'abc'}, ['test']) + +m.fragment({}, ['Test', 123]) + +m.fragment( + { + id: 'abc', + oninit: (vnode: Vnode) => { + console.log('oninit') + } + }, + [h('p', 'test1'), [123, h('p', 'abc'), ['abc']], 'Abc', h('p', 'test2')] +) diff --git a/types/mithril/test/test-jsonp.ts b/types/mithril/test/test-jsonp.ts new file mode 100644 index 0000000000..856fe8236d --- /dev/null +++ b/types/mithril/test/test-jsonp.ts @@ -0,0 +1,27 @@ +import {jsonp} from 'mithril/request' + +interface Result { + id: number +} + +jsonp('/item').then(data => { + console.log(data.id) +}) + +class User { + id: number + constructor (result: Result) { + this.id = result.id + } +} + +jsonp({ + url: '/user', + data: {test: 'abc'}, + type: User, + callbackName: 'getuser', + callbackKey: 'key', + background: true +}).then(user => { + console.log(user.id) +}) diff --git a/types/mithril/test/test-misc.ts b/types/mithril/test/test-misc.ts new file mode 100644 index 0000000000..97ce9b5344 --- /dev/null +++ b/types/mithril/test/test-misc.ts @@ -0,0 +1,23 @@ +import {trust, parseQueryString, buildQueryString} from 'mithril' +import * as h from 'mithril/hyperscript' +import {render} from 'mithril/render' +import {redraw} from 'mithril/redraw' +import * as withAttr from 'mithril/withAttr' + +const vnode = trust('Some bold text.') + +const params = parseQueryString('?id=123') + +const qstr = buildQueryString({id: 123}) + +render(document.body, 'Hello') +render(document.body, h('h1', 'Test')) +render(document.body, [ + h('h1', 'Test'), "abc", null, 123, false, h('p', 'Vnode array'), + ['a', 123, undefined, h('div', 'Nested')] +]) + +redraw() + +const handler = withAttr("value", (value) => {}) +handler({currentTarget: {value: 10}}) diff --git a/types/mithril/test/test-request.ts b/types/mithril/test/test-request.ts new file mode 100644 index 0000000000..7042790427 --- /dev/null +++ b/types/mithril/test/test-request.ts @@ -0,0 +1,73 @@ +import {request} from 'mithril/request' + +interface Result { + id: number +} + +request({method: "GET", url: "/item"}).then(result => { + console.log(result.id) +}) + +request<{a: string}>("/item", {method: "POST"}).then(result => { + console.log(result.a) +}) + +request({ + method: "GET", + url: "/item", + data: {x: "y"} +}).then(result => { + console.log(result) +}) + +request({ + method: "GET", + url: "/item", + data: 5, + serialize: (data: number) => "id=" + data.toString() +}).then(result => { + console.log(result) +}) + +request('/item', { + method: "GET", + deserialize: str => JSON.parse(str) as Result +}).then(result => { + console.log(result.id) +}) + +request('/id', { + method: "GET", + extract: xhr => ({id: Number(xhr.responseText)}) +}).then(result => { + console.log(result.id) +}) + +request('/item', { + config: xhr => { + xhr.setRequestHeader('accept', '*') + }, + headers: {"Content-Type": "application/json"}, + background: true, +}).then(result => { + console.log(result.id) +}) + +class Item { + identifier: number + constructor(result: Result) { + this.identifier = result.id + } +} + +request('/item', { + method: 'GET', + async: true, + user: "Me", + password: "qwerty", + withCredentials: true, + type: Item, + useBody: false +}).then(item => { + console.log(item.identifier) +}) diff --git a/types/mithril/test/test-route.ts b/types/mithril/test/test-route.ts new file mode 100644 index 0000000000..900bb2f1f0 --- /dev/null +++ b/types/mithril/test/test-route.ts @@ -0,0 +1,60 @@ +import {Component} from 'mithril' +import * as h from 'mithril/hyperscript' +import * as route from 'mithril/route' + +const component1 = { + view() { + return h('h1', 'Test') + } +} + +const component2 = { + view ({attrs: {title}}) { + return h('h1', title) + } +} as Component<{title: string},{}> + +route(document.body, '/', { + '/': component1, + '/test1': { + onmatch (args, path) { + return component1 + } + }, + '/test2': { + render(vnode) { + return h(component1) + } + }, + 'test3': { + onmatch (args, path) { + return component2 + }, + render (vnode) { + return ['abc', 123, null, h(component2), ['nested', h('p', 123)]] + } + }, + 'test4': { + onmatch (args, path) { + // Must provide a Promise type if we want type checking + return new Promise>((resolve, reject) => { + resolve(component2) + }) + } + } +}) + +route.prefix('/app') +route.set('/test1') + +route.set('/test/:id', {id: 1}) + +route.set('/test2', undefined, { + replace: true, + state: {abc: 123}, + title: "Title" +}) + +const path: string = route.get() + +const fn = route.link(h('div', 'test')) diff --git a/types/mithril/test/test-stream.ts b/types/mithril/test/test-stream.ts new file mode 100644 index 0000000000..7e55d0273d --- /dev/null +++ b/types/mithril/test/test-stream.ts @@ -0,0 +1,391 @@ +import * as stream from 'mithril/stream' +import {Stream} from 'mithril/stream' + +{ + const s = stream(1) + const initialValue = s() + s(2) + const newValue = s() + console.assert(initialValue === 1) + console.assert(newValue === 2) +} + +{ + const s = stream() + console.assert(s() === undefined) +} + +{ + const s: Stream = stream(1) + s(undefined) + console.assert(s() === undefined) +} + +{ + const s = stream(stream(1)) + console.assert(s()() === 1) +} + +{ + const s = stream() + const doubled = stream.combine(function(s) {return s() * 2}, [s]) + s(2) + console.assert(doubled() === 4) +} + +{ + const s = stream(2) + const doubled = stream.combine(function(s) {return s() * 2}, [s]) + console.assert(doubled() === 4) +} + +{ + const s1 = stream() + const s2 = stream() + const added = stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) + s1(2) + s2(3) + console.assert(added() === 5) +} + +{ + const s1 = stream(2) + const s2 = stream(3) + const added = stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) + console.assert(added() === 5) +} + +{ + const s1 = stream(2) + const s2 = stream() + const added = stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) + s2(3) + console.assert(added() === 5) +} + +{ + let count = 0 + const a = stream() + const b = stream.combine(function(a) {return a() * 2}, [a]) + const c = stream.combine(function(a) {return a() * a()}, [a]) + const d = stream.combine(function(b, c) { + count++ + return b() + c() + }, [b, c]) + a(3) + console.assert(d() === 15) + console.assert(count === 1) +} + +{ + let count = 0 + const a = stream(3) + const b = stream.combine(function(a) {return a() * 2}, [a]) + const c = stream.combine(function(a) {return a() * a()}, [a]) + const d = stream.combine(function(b, c) { + count++ + return b() + c() + }, [b, c]) + console.assert(d() === 15) + console.assert(count === 1) +} + +{ + let streams: Stream[] = [] + const a = stream() + const b = stream() + const c = stream.combine(function(a, b, changed) { + streams = changed + }, [a, b]) + a(3) + b(5) + console.assert(streams.length === 1) + console.assert(streams[0] === b) +} + +{ + let streams: Stream[] = [] + const a = stream(3) + const b = stream(5) + const c = stream.combine(function(a, b, changed) { + streams = changed + }, [a, b]) + a(7) + console.assert(streams.length === 1) + console.assert(streams[0] === a) +} + +{ + const a = stream(1) + const b = stream.combine(function(a) { + return undefined + }, [a]) + + console.assert(b() === undefined) +} + +{ + const a = stream(1) + const b = stream.combine(function(a) { + return stream(2) + }, [a]) + console.assert(b()() === 2) +} + +{ + const a = stream(1) + const b = stream.combine(function(a) { + return stream() + }, [a]) + console.assert(b()() === undefined) +} + +{ + let count = 0 + const a = stream(1) + const b = stream.combine(function(a) { + return stream.HALT + }, [a]) + ["fantasy-land/map"](function() { + count++ + return 1 + }) + console.assert(b() === undefined) +} + +{ + const all = stream.merge([ + stream(10), + stream("20"), + stream({value: 30}), + ]) +} + +{ + const straggler = stream() + const all = stream.merge([ + stream(10), + stream("20"), + straggler, + ]) + console.assert(all() === undefined) + straggler(30) +} + +{ + let value = 0 + const id = function(value: number) {return value} + const a = stream() + const b = stream() + + const all = stream.merge([a.map(id), b.map(id)]).map(function(data) { + value = data[0] + data[1] + }) + + a(1) + b(2) + console.assert(value === 3) + + a(3) + b(4) + console.assert(value === 7) +} + +{ + const s = stream() + const doubled = stream.combine(function(stream) {return stream() * 2}, [s]) + s.end(true) + s(3) + console.assert(doubled() === undefined) +} + +{ + const s = stream(2) + const doubled = stream.combine(function(stream) {return stream() * 2}, [s]) + s.end(true) + s(3) + console.assert(doubled() === 4) +} + +{ + const s = stream(2) + s.end(true) + const doubled = stream.combine(function(stream) {return stream() * 2}, [s]) + s(3) + console.assert(doubled() === undefined) +} + +{ + const s = stream(2) + const doubled = stream.combine(function(stream) {return stream() * 2}, [s]) + doubled.end(true) + s(4) + console.assert(doubled() === 4) +} + +{ + const s = stream() + const doubled = s["fantasy-land/map"](function(value: number) {return value * 2}) + s(3) + console.assert(doubled() === 6) +} + +{ + const s = stream(3) + const doubled = s["fantasy-land/map"](function(value: number) {return value * 2}) + console.assert(doubled() === 6) +} + +{ + const s = stream() + const mapped = s["fantasy-land/map"](function(value: undefined) {return String(value)}) + s(undefined) + console.assert(mapped() === "undefined") +} + +{ + const s = stream(undefined) + const mapped = s["fantasy-land/map"](function(value: undefined) {return String(value)}) + console.assert(mapped() === "undefined") +} + +{ + const s = stream(undefined) + const mapped = s["fantasy-land/map"](function(value: undefined) {return stream()}) + console.assert(mapped()() === undefined) +} + +{ + const s = stream(undefined) + console.assert(s["fantasy-land/map"] === s.map) +} + +{ + const apply = stream(function(value: number) {return value * 2}) + const s = stream(3) + const applied = s["fantasy-land/ap"](apply) + console.assert(applied() === 6) + apply(function(value) {return value / 3}) + console.assert(applied() === 1) + s(9) + console.assert(applied() === 3) +} + +{ + const apply = stream(function(value: undefined) {return String(value)}) + const s = stream(undefined) + const applied = s["fantasy-land/ap"](apply) + console.assert(applied() === "undefined") + apply(function(value) {return String(value) + "a"}) + console.assert(applied() === "undefineda") +} + +{ + const s = stream(3) + const mapped = s["fantasy-land/map"](function(value: number) {return value}) + console.assert(s() === mapped()) +} + +{ + const f = function f(x: number) {return x * 2} + const g = function g(x: number) {return x * x} + const s = stream(3) + const mapped = s["fantasy-land/map"](function(value: any) {return f(g(value))}) + const composed = s["fantasy-land/map"](g)["fantasy-land/map"](f) + console.assert(mapped() === 18) + console.assert(mapped() === composed()) +} + +{ + const a = stream(function(value: number) {return value * 2}) + const u = stream(function(value: number) {return value * 3}) + const v = stream(5) + const mapped = v["fantasy-land/ap"](u["fantasy-land/ap"](a["fantasy-land/map"](function(f: any) { + return function(g: any) { + return function(x: any) { + return f(g(x)) + } + } + }))) + const composed = v["fantasy-land/ap"](u)["fantasy-land/ap"](a) + console.assert(mapped() === 30) + console.assert(mapped() === composed()) +} + +{ + const a = stream()["fantasy-land/of"](function(value: number) {return value}) + const v = stream(5) + console.assert(v["fantasy-land/ap"](a)() === 5) + console.assert(v["fantasy-land/ap"](a)() === v()) +} + +{ + const a = stream(0) + const f = function(value: number) {return value * 2} + const x = 3 + console.assert(a["fantasy-land/of"](x)["fantasy-land/ap"](a["fantasy-land/of"](f))() === 6) + console.assert(a["fantasy-land/of"](x)["fantasy-land/ap"](a["fantasy-land/of"](f))() === a["fantasy-land/of"](f(x))()) +} + +{ + const u = stream(function(value: number) {return value * 2}) + const a = stream() + const y = 3 + console.assert(a["fantasy-land/of"](y)["fantasy-land/ap"](u)() === 6) + console.assert(a["fantasy-land/of"](y)["fantasy-land/ap"](u)() === u["fantasy-land/ap"](a["fantasy-land/of"](function(f: any) {return f(y)}))()) +} + +// scan + +{ + const parent = stream() + const child = stream.scan((out, p) => out - p, 123, parent) +} + +{ + const parent = stream() + const child = stream.scan((arr, p) => arr.concat(p), [] as number[], parent) + parent(7) +} + +// scanMerge + +{ + const parent1 = stream() + const parent2 = stream() + + const child = stream.scanMerge([ + [parent1, (out, p1) => out + p1], + [parent2, (out, p2) => out + p2] + ], -10) +} + +{ + const parent1 = stream() + const parent2 = stream() + + const child = stream.scanMerge([ + [parent1, (out, p1) => out + p1], + [parent2, (out, p2) => out + p2 + p2] + ], "a") + + parent1("b") + parent2("c") + parent1("b") + + console.assert(child() === 'abccb') +} + +{ + const parent1 = stream() + const parent2 = stream() + const child = stream.scanMerge([ + [parent1, (out, p1) => out + p1], + [parent2, (out, p2) => out + p2 + p2] + ], "a") + + parent1("a") + parent2(1) + + console.assert(child() === 'aa11') +} diff --git a/types/mithril/tsconfig.json b/types/mithril/tsconfig.json index a80a6ce9f2..8189032c62 100644 --- a/types/mithril/tsconfig.json +++ b/types/mithril/tsconfig.json @@ -1,23 +1,39 @@ { - "compilerOptions": { - "module": "commonjs", - "lib": [ - "es6", - "dom" - ], - "noImplicitAny": true, - "noImplicitThis": false, - "strictNullChecks": false, - "baseUrl": "../", - "typeRoots": [ - "../" - ], - "types": [], - "noEmit": true, - "forceConsistentCasingInFileNames": true - }, - "files": [ - "index.d.ts", - "mithril-tests.ts" - ] -} \ No newline at end of file + "compilerOptions": { + "module": "commonjs", + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "../", + "typeRoots": ["../"], + "types": [] + }, + "files": [ + "test/test-api.ts", + "test/test-class-component.ts", + "test/test-component.ts", + "test/test-factory-component.ts", + "test/test-fragment.ts", + "test/test-jsonp.ts", + "test/test-misc.ts", + "test/test-request.ts", + "test/test-route.ts", + "test/test-stream.ts", + "index.d.ts", + "hyperscript.d.ts", + "mount.d.ts", + "redraw.d.ts", + "render.d.ts", + "request.d.ts", + "route.d.ts", + "withAttr.d.ts", + "stream/index.d.ts" + ], + "atom": { + "rewriteTsconfig": false + } +} diff --git a/types/mithril/tslint.json b/types/mithril/tslint.json new file mode 100644 index 0000000000..250b1b7b5f --- /dev/null +++ b/types/mithril/tslint.json @@ -0,0 +1,23 @@ +{ + "extends": "../tslint.json", + "rules": { + "array-type": [false, "array"], + "quotemark": [false, "double"], + "semicolon": [false, "always"], + "space-before-function-paren": [false, "always"], + "max-line-length": [false, 200], + "no-empty-interface": false, + "no-redundant-modifiers": false, + "only-arrow-functions": [false], + "prefer-for-of": false, + "unified-signatures": false, + "whitespace": [ + false, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +} diff --git a/types/mithril/withAttr.d.ts b/types/mithril/withAttr.d.ts new file mode 100644 index 0000000000..7d1ea786ad --- /dev/null +++ b/types/mithril/withAttr.d.ts @@ -0,0 +1,3 @@ +import { withAttr as _withAttr } from "mithril"; +declare const withAttr: typeof _withAttr; +export = withAttr; diff --git a/types/react-datepicker/index.d.ts b/types/react-datepicker/index.d.ts index 0cbe0ba202..467981c2fc 100644 --- a/types/react-datepicker/index.d.ts +++ b/types/react-datepicker/index.d.ts @@ -16,21 +16,28 @@ declare module "react-datepicker" { dateFormat?: string; dateFormatCalendar?: string; disabled?: boolean; + dropdownMode?: string; endDate?: moment.Moment; excludeDates?: any[]; filterDate?(): any; fixedHeight?: boolean; + forceShowMonthNavigation?: boolean; + highlightDates?: any[]; id?: string; includeDates?: any[]; + inline?: boolean; isClearable?: boolean; locale?: string; maxDate?: moment.Moment; minDate?: moment.Moment; + monthsShown?: number; name?: string; onBlur?(event: React.FocusEvent): void; onChange(date: moment.Moment | null, event: React.SyntheticEvent | undefined): any; onChangeRaw?(event: React.FocusEvent): void; onFocus?(event: React.FocusEvent): void; + onMonthChange?(date: moment.Moment): void; + openToDate?: moment.Moment; peekNextMonth?: boolean; placeholderText?: string; popoverAttachment?: string; diff --git a/types/react-native-sortable-list/react-native-sortable-list-tests.tsx b/types/react-native-sortable-list/react-native-sortable-list-tests.tsx index 57a59b8d20..91be1dfed4 100644 --- a/types/react-native-sortable-list/react-native-sortable-list-tests.tsx +++ b/types/react-native-sortable-list/react-native-sortable-list-tests.tsx @@ -8,8 +8,9 @@ import { Image, View, Dimensions, - FlexJustifyType, - FlexAlignType + ViewStyle, + TextStyle, + ImageStyle, } from 'react-native'; import SortableList, {RowProps} from 'react-native-sortable-list'; @@ -61,20 +62,20 @@ const data = { const styles = StyleSheet.create({ container: { flex: 1, - justifyContent: "center" as FlexJustifyType, - alignItems: "center" as FlexAlignType, + justifyContent: 'center', + alignItems: 'center', backgroundColor: '#eee', paddingTop: 60, - }, + } as ViewStyle, list: { flex: 1, - }, + } as ViewStyle, contentContainer: { width: window.width, paddingHorizontal: 30, - }, + } as ViewStyle, row: { flexDirection: 'row', @@ -89,18 +90,18 @@ const styles = StyleSheet.create({ shadowOpacity: 1, shadowOffset: {height: 2, width: 2}, shadowRadius: 2, - }, + } as ViewStyle, image: { width: 60, height: 60, marginRight: 30, borderRadius: 30, - }, + } as ImageStyle, text: { fontSize: 24, - }, + } as TextStyle, }); diff --git a/types/react-native/index.d.ts b/types/react-native/index.d.ts index e282f17191..84a58f9212 100644 --- a/types/react-native/index.d.ts +++ b/types/react-native/index.d.ts @@ -1,6 +1,7 @@ -// Type definitions for react-native 0.42 +// Type definitions for react-native 0.43 // Project: https://github.com/facebook/react-native // Definitions by: Eloy Durán +// Fedor Nezhivoi // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.1 @@ -531,9 +532,7 @@ export interface LayoutAnimationStatic { spring: (config: LayoutAnimationConfig, onAnimationDidEnd?: () => void) => void } -export type FlexAlignType = "flex-start" | "flex-end" | "center" | "stretch" | "baseline"; -export type FlexJustifyType = "flex-start" | "flex-end" | "center" | "space-between" | "space-around"; -export type FlexDirection = "row" | "column" | "row-reverse" | "column-reverse"; +type FlexAlignType = "flex-start" | "flex-end" | "center" | "stretch" | "baseline"; /** * Flex Prop Types @@ -541,9 +540,9 @@ export type FlexDirection = "row" | "column" | "row-reverse" | "column-reverse"; * @see LayoutPropTypes.js */ export interface FlexStyle { - - alignItems?: FlexAlignType; - alignSelf?: "auto" | FlexAlignType; + alignContent?: "flex-start" | "flex-end" | "center" | "stretch" | "space-between" | "space-around" + alignItems?: FlexAlignType + alignSelf?: "auto" | FlexAlignType borderBottomWidth?: number borderLeftWidth?: number borderRightWidth?: number @@ -551,18 +550,14 @@ export interface FlexStyle { borderWidth?: number bottom?: number | string flex?: number + flexBasis?: number | string + flexDirection?: "row" | "column" | "row-reverse" | "column-reverse" flexGrow?: number flexShrink?: number - flexBasis?: number | string - flexDirection?: FlexDirection flexWrap?: "wrap" | "nowrap" height?: number | string - justifyContent?: FlexJustifyType + justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" left?: number | string - minWidth?: number | string - maxWidth?: number | string - minHeight?: number | string - maxHeight?: number | string margin?: number | string marginBottom?: number | string marginHorizontal?: number | string @@ -570,6 +565,10 @@ export interface FlexStyle { marginRight?: number | string marginTop?: number | string marginVertical?: number | string + maxHeight?: number | string + maxWidth?: number | string + minHeight?: number | string + minWidth?: number | string overflow?: "visible" | "hidden" | "scroll" padding?: number | string paddingBottom?: number | string @@ -582,11 +581,12 @@ export interface FlexStyle { right?: number | string top?: number | string width?: number | string + zIndex?: number /** * @platform ios */ - zIndex?: number + direction?: 'inherit' | 'ltr' | 'rtl' } /** @@ -7156,6 +7156,57 @@ export interface UIManagerStatic { quality ?: number, } ) => Promise + + /** + * Determines the location on screen, width, and height of the given view and + * returns the values via an async callback. If successful, the callback will + * be called with the following arguments: + * + * - x + * - y + * - width + * - height + * - pageX + * - pageY + * + * Note that these measurements are not available until after the rendering + * has been completed in native. If you need the measurements as soon as + * possible, consider using the [`onLayout` + * prop](docs/view.html#onlayout) instead. + */ + measure(node: number, callback: MeasureOnSuccessCallback): void; + + /** + * Determines the location of the given view in the window and returns the + * values via an async callback. If the React root view is embedded in + * another native view, this will give you the absolute coordinates. If + * successful, the callback will be called with the following + * arguments: + * + * - x + * - y + * - width + * - height + * + * Note that these measurements are not available until after the rendering + * has been completed in native. + */ + measureInWindow(node: number, callback: MeasureInWindowOnSuccessCallback): void; + + /** + * Like [`measure()`](#measure), but measures the view relative an ancestor, + * specified as `relativeToNativeNode`. This means that the returned x, y + * are relative to the origin x, y of the ancestor view. + * + * As always, to obtain a native node handle for a component, you can use + * `React.findNodeHandle(component)`. + */ + measureLayout( + node: number, + relativeToNativeNode: number, + onFail: () => void, /* currently unused */ + onSuccess: MeasureLayoutOnSuccessCallback + ): void; } export interface SwitchPropertiesIOS extends ViewProperties, React.Props { diff --git a/types/react-router/index.d.ts b/types/react-router/index.d.ts index a3e05a29cb..4eb3aa14f9 100644 --- a/types/react-router/index.d.ts +++ b/types/react-router/index.d.ts @@ -30,7 +30,7 @@ declare module 'react-router' { } } interface MemoryRouterProps { - initialEntries?: H.Location[]; + initialEntries?: H.LocationDescriptor[]; initialIndex?: number; getUserConfirmation?: () => void; keyLength?: number; diff --git a/types/react-router/test/MemoryRouter.tsx b/types/react-router/test/MemoryRouter.tsx new file mode 100644 index 0000000000..1dd247acb2 --- /dev/null +++ b/types/react-router/test/MemoryRouter.tsx @@ -0,0 +1,43 @@ +import * as React from 'react' +import { + MemoryRouter, + Route, + Link +} from 'react-router-dom' + +const initialRouterEntries = [ + '/', + { + pathname: '/about' + } +] + +const MemoryRouterExample = () => ( + +
+
    +
  • Home
  • +
  • About
  • +
+ +
+ + + +
+
+) + +const Home = () => ( +
+

Home

+
+) + +const About = () => ( +
+

About

+
+) + +export default MemoryRouterExample diff --git a/types/react-router/tsconfig.json b/types/react-router/tsconfig.json index 239a137bbc..9ae373d000 100644 --- a/types/react-router/tsconfig.json +++ b/types/react-router/tsconfig.json @@ -21,6 +21,7 @@ "test/CustomLink.tsx", "test/ModalGallery.tsx", "test/NavigateWithContext.tsx", + "test/MemoryRouter.tsx", "test/NoMatch.tsx", "test/Params.tsx", "test/PreventingTransitions.tsx",