Type definitions for Mithril 1.1

This commit is contained in:
spacejack
2017-03-28 20:47:25 -04:00
parent 49f185c6ec
commit 8e1326969f
22 changed files with 2191 additions and 898 deletions
+3
View File
@@ -0,0 +1,3 @@
import { Hyperscript } from "mithril";
declare const h: Hyperscript;
export = h;
+238 -821
View File
File diff suppressed because it is too large Load Diff
-55
View File
@@ -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 (<any>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);
+3
View File
@@ -0,0 +1,3 @@
import { Mount } from "mithril";
declare const mount: Mount;
export = mount;
+3
View File
@@ -0,0 +1,3 @@
import { Redraw } from "mithril";
declare const redraw: Redraw;
export = redraw;
+3
View File
@@ -0,0 +1,3 @@
import { Render } from "mithril";
declare const render: Render;
export = render;
+3
View File
@@ -0,0 +1,3 @@
import { Request } from "mithril";
declare const request: Request;
export = request;
+3
View File
@@ -0,0 +1,3 @@
import { Route } from "mithril";
declare const route: Route;
export = route;
+44
View File
@@ -0,0 +1,44 @@
declare namespace Stream {
export type Combiner<T> = (...streams: any[]) => T;
export interface Stream<T> {
/** 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> | T | void): Stream<T>;
/** Creates a dependent stream whose value is set to the result of the callback function. */
map<U>(f: (current: T) => Stream<U> | U): Stream<U>;
/** This method is functionally identical to stream. It exists to conform to Fantasy Land's Applicative specification. */
of(val?: T): Stream<T>;
/** Apply. */
ap<U>(f: Stream<(value: T) => U>): Stream<U>;
/** A co-dependent stream that unregisters dependent streams when set to true. */
end: Stream<boolean>;
/** 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. */
<T>(value?: T): Stream<T>;
/** Creates a computed stream that reactively updates if any of its upstreams are updated. */
combine<T>(combiner: Combiner<T>, streams: Stream<any>[]): Stream<T>;
/** Creates a stream whose value is the array of values from an array of streams. */
merge(streams: Stream<any>[]): Stream<any[]>;
/** Creates a new stream with the results of calling the function on every incoming stream with and accumulator and the incoming value. */
scan<T, U>(fn: (acc: U, value: T) => U, acc: U, stream: Stream<T>): Stream<U>;
/** Takes an array of pairs of streams and scan functions and merges all those streams using the given functions into a single stream. */
scanMerge<T,U>(pairs: [Stream<T>, (acc: U, value: T) => U][], acc: U): Stream<U>;
/** Takes an array of pairs of streams and scan functions and merges all those streams using the given functions into a single stream. */
scanMerge<U>(pairs: [Stream<any>, (acc: U, value: any) => U][], acc: U): Stream<U>;
/** 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;
+738
View File
@@ -0,0 +1,738 @@
// 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
{
let vnode = m("div")
console.assert(vnode.tag === "div")
console.assert(typeof m.version === "string")
console.assert(m.version.indexOf(".") > -1)
}
{
const vnode = m.trust("<br>")
}
{
const vnode = m.fragment({key: 123}, [m("div")])
console.assert((vnode.children as m.Vnode<any,any>[]).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: function() {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: function() {return m("div")}}
})
setTimeout(function() {
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: function() {return m("div")}}
})
setTimeout(function() {
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: function() {return m("div")}}
})
setTimeout(function() {
console.assert(m.route.get() === "/a")
}, FRAME_BUDGET)
}
{
const root = window.document.createElement("div")
m.route(root, "/a", {
"/:id": {view: function() {return m("div")}}
})
setTimeout(function() {
m.route.set("/b")
setTimeout(function() {
console.assert(m.route.get() === "/b")
}, FRAME_BUDGET)
}, FRAME_BUDGET)
}
{
let count = 0
const root = window.document.createElement("div")
m.mount(root, {view: function() {count++}})
setTimeout(function() {
m.redraw()
console.assert(count === 2)
}, FRAME_BUDGET)
}
//
// Additional tests by andraaspar
//
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/hyperscript.html#components
////////////////////////////////////////////////////////////////////////////////
;(function() {
// define a component
let Greeter: m.Comp<{}, {}> = {
view: function(vnode) {
return m("div", vnode.attrs, ["Hello ", vnode.children])
}
}
// consume it
m(Greeter, { style: "color:red;" }, "world")
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/hyperscript.html#keys
////////////////////////////////////////////////////////////////////////////////
;(function() {
let users = [
{ id: 1, name: "John" },
{ id: 2, name: "Mary" },
]
function userInputs(users: { id: number, name: string }[]) {
return users.map(function(u) {
return m("input", { key: u.id }, u.name)
})
}
m.render(document.body, userInputs(users))
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/components.html#state
////////////////////////////////////////////////////////////////////////////////
;(function() {
let ComponentWithInitialState: m.Comp<{}, {data: string}> = {
data: "Initial content",
view: function(vnode) {
return m("div", vnode.state.data)
}
}
m(ComponentWithInitialState)
})
;(function() {
let ComponentWithDynamicState: m.Comp<{text: string}, {data?: string}> = {
oninit: function(vnode) {
vnode.state.data = vnode.attrs.text
},
view: function(vnode) {
return m("div", vnode.state.data)
}
}
m(ComponentWithDynamicState, { text: "Hello" })
})
;(function() {
let ComponentUsingThis: m.Comp<{text: string}, {data?: string}> = {
oninit: function(vnode) {
this.data = vnode.attrs.text
},
view: function(vnode) {
return m("div", this.data)
}
}
m(ComponentUsingThis, { text: "Hello" })
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/lifecycle-methods.html
////////////////////////////////////////////////////////////////////////////////
;(function() {
let Fader: m.Comp<{}, {}> = {
onbeforeremove: function(vnode) {
vnode.dom.classList.add("fade-out")
return new Promise(function(resolve) {
setTimeout(resolve, 1000)
})
},
view: function() {
return m("div", "Bye")
},
}
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/route.html#wrapping-a-layout-component
////////////////////////////////////////////////////////////////////////////////
;(function() {
let Home = {
view: function() {
return "Welcome"
}
}
let state = {
term: "",
search: function() {
// 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
}
}
let Form: m.Comp<{term: string}, {}> = {
oninit: function(vnode) {
state.term = vnode.attrs.term || "" // populated from the `history.state` property if the user presses the back button
},
view: function() {
return m("form", [
m("input[placeholder='Search']", { oninput: m.withAttr("value", function(v) { state.term = v }), value: state.term }),
m("button", { onclick: state.search }, "Search")
])
}
}
let Layout: m.Comp<{}, {}> = {
view: function(vnode) {
return m(".layout", vnode.children)
}
}
// example 1
m.route(document.body, "/", {
"/": {
view: function() {
return m(Layout, m(Home))
},
},
"/form": {
view: function() {
return m(Layout, m(Form))
},
}
})
// example 2
m.route(document.body, "/", {
"/": {
render: function() {
return m(Layout, m(Home))
},
},
"/form": {
render: function() {
return m(Layout, m(Form))
},
}
})
// functionally equivalent to example 1
let Anon1 = {
view: function() {
return m(Layout, m(Home))
},
}
let Anon2 = {
view: function() {
return m(Layout, m(Form))
},
}
m.route(document.body, "/", {
"/": {
render: function() {
return m(Anon1)
}
},
"/form": {
render: function() {
return m(Anon2)
}
},
})
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/route.html#preloading-data
////////////////////////////////////////////////////////////////////////////////
;(function() {
let state = {
users: <any[]>[],
loadUsers: function() {
return m.request<any>("/api/v1/users").then(function(users) {
state.users = users
})
}
}
m.route(document.body, "/user/list", {
"/user/list": {
onmatch: state.loadUsers,
render: function() {
return state.users.map(function(user) {
return m("div", user.id)
})
}
},
})
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/request.html#monitoring-progress
////////////////////////////////////////////////////////////////////////////////
;(function() {
let progress = 0
m.mount(document.body, {
view: function() {
return [
m("input[type=file]", { onchange: upload }),
progress + "% completed"
]
}
})
function upload(e: Event) {
let file = (<FileList>(<HTMLInputElement>e.target).files)[0]
let data = new FormData()
data.append("myfile", file)
m.request({
method: "POST",
url: "/api/v1/upload",
data: data,
config: function(xhr) {
xhr.addEventListener("progress", function(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
////////////////////////////////////////////////////////////////////////////////
;(function() {
// 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<User[]>({
method: "GET",
url: "/api/v1/users",
type: User
})
.then(function(users) {
console.log(users[0].name) // logs a name
})
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/request.html#non-json-responses
////////////////////////////////////////////////////////////////////////////////
;(function() {
m.request<string>({
method: "GET",
url: "/files/icon.svg",
deserialize: function(value) { return value }
})
.then(function(svg) {
m.render(document.body, m.trust(svg))
})
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/jsonp.html
////////////////////////////////////////////////////////////////////////////////
;(function() {
m.jsonp({
url: "/api/v1/users/:id",
data: { id: 1 },
callbackKey: "callback",
})
.then(function(result) {
console.log(result)
})
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/fragment.html
////////////////////////////////////////////////////////////////////////////////
;(function() {
let groupVisible = true
let 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
////////////////////////////////////////////////////////////////////////////////
;(function() {
let firstName = stream("John")
let lastName = stream("Doe")
let fullName = stream.merge([firstName, lastName]).map(function(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
////////////////////////////////////////////////////////////////////////////////
;(function() {
let halted = stream(1).map(function(value) {
return stream.HALT
})
halted.map(function() {
// never runs
})
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/stream.html#combining-streams
////////////////////////////////////////////////////////////////////////////////
;(function() {
let a = stream(5)
let b = stream(7)
let added = stream.combine(function(a: stream.Stream<number>, b: stream.Stream<number>) {
return a() + b()
}, [a, b])
console.log(added()) // logs 12
})
////////////////////////////////////////////////////////////////////////////////
// http://mithril.js.org/stream.html#ended-state
////////////////////////////////////////////////////////////////////////////////
;(function() {
let value = stream<number>()
let doubled = value.map(function(value) { return 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
;(function() {
let root = (<Element>document.getElementById("root"))
let empty: any[] = []
let full: any[] = []
for (let i = 0; i < 100; i++) full.push(i)
let cells: any[]
function view() {
return m(".container", cells.map(function(i) {
return m(".slice", {
style: {backgroundPosition: (i % 10 * 11) + "% " + (Math.floor(i / 10) * 11) + "%"},
onbeforeremove: exit
})
}))
}
function exit(vnode: m.VnodeDOM<any, any>) {
vnode.dom.classList.add("exit")
return new Promise(function(resolve) {
setTimeout(resolve, 1000)
})
}
function run() {
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
;(function() {
// Start extra declarations
let marked = (v: string) => v
// End extra declarations
//model
let state = {
text: "# Markdown Editor\n\nType on the left panel and see the result on the right panel",
update: function(value: string) {
state.text = value
}
}
//view
let Editor = {
view: function() {
return [
m("textarea.input", {
oninput: m.withAttr("value", state.update),
value: state.text
}),
m(".preview", m.trust(marked(state.text))),
]
}
}
m.mount(<Element>document.getElementById("editor"), Editor)
})
////////////////////////////////////////////////////////////////////////////////
// https://github.com/lhorie/mithril.js/blob/master/examples/todomvc/todomvc.js
////////////////////////////////////////////////////////////////////////////////
;(function() {
//model
let state = {
dispatch: function(action: string, args?: any) {
(<any>state)[action].apply(state, args || [])
requestAnimationFrame(function() {
localStorage["todos-mithril"] = JSON.stringify(state.todos)
})
},
todos: JSON.parse(localStorage["todos-mithril"] || "[]"),
editing: <any>null,
filter: "",
remaining: 0,
todosByStatus: <any>[],
showing: <string><any>undefined,
createTodo: function(title: string) {
state.todos.push({title: title.trim(), completed: false})
},
setStatuses: function(completed: boolean) {
for (let i = 0; i < state.todos.length; i++) state.todos[i].completed = completed
},
setStatus: function(todo: any, completed: boolean) {
todo.completed = completed
},
destroy: function(todo: any) {
let index = state.todos.indexOf(todo)
if (index > -1) state.todos.splice(index, 1)
},
clear: function() {
for (let i = 0; i < state.todos.length; i++) {
if (state.todos[i].completed) state.destroy(state.todos[i--])
}
},
edit: function(todo: any) {
state.editing = todo
},
update: function(title: string) {
if (state.editing != null) {
state.editing.title = title.trim()
if (state.editing.title === "") state.destroy(state.editing)
state.editing = null
}
},
reset: function() {
state.editing = null
},
computed: function(vnode: m.Vnode<any, any>) {
state.showing = vnode.attrs.status || ""
state.remaining = state.todos.filter(function(todo: any) {return !todo.completed}).length
state.todosByStatus = state.todos.filter(function(todo: any) {
switch (state.showing) {
case "": return true
case "active": return !todo.completed
case "completed": return todo.completed
}
})
}
}
//view
let Todos: m.Comp<{}, {
add(e: Event): void
toggleAll(): void
toggle(todo: any): void
focus(vnode: m.VnodeDOM<any, any>, todo: any): void
save(e: KeyboardEvent): void
}> = {
add: function(e: KeyboardEvent) {
if (e.keyCode === 13) {
state.dispatch("createTodo", [(<HTMLInputElement>e.target).value]);
(<HTMLInputElement>e.target).value = ""
}
},
toggleAll: function() {
state.dispatch("setStatuses", [(<HTMLInputElement>document.getElementById("toggle-all")).checked])
},
toggle: function(todo: any) {
state.dispatch("setStatus", [todo, !todo.completed])
},
focus: function(vnode: m.VnodeDOM<any, any>, todo: any) {
if (todo === state.editing && vnode.dom !== document.activeElement) {
(<HTMLInputElement>vnode.dom).value = todo.title
(<HTMLInputElement>vnode.dom).focus()
(<HTMLInputElement>vnode.dom).selectionStart = (<HTMLInputElement>vnode.dom).selectionEnd = todo.title.length
}
},
save: function(e: KeyboardEvent) {
if (e.keyCode === 13 || e.type === "blur") state.dispatch("update", [(<HTMLInputElement>e.target).value])
else if (e.keyCode === 27) state.dispatch("reset")
},
oninit: state.computed,
onbeforeupdate: state.computed,
view: function(vnode) {
let 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(function(todo: any) {
return m("li", {class: (todo.completed ? "completed" : "") + " " + (todo === state.editing ? "editing" : "")}, [
m(".view", [
m("input.toggle[type='checkbox']", {checked: todo.completed, onclick: function() {ui.toggle(todo)}}),
m("label", {ondblclick: function() {state.dispatch("edit", [todo])}}, todo.title),
m("button.destroy", {onclick: function() {state.dispatch("destroy", [todo])}}),
]),
m("input.edit", {onupdate: function(vnode: m.VnodeDOM<any, any>) {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: function() {state.dispatch("clear")}}, "Clear completed"),
]) : null,
]
}
}
m.route(document.getElementById("todoapp")!, "/", {
"/": Todos,
"/:status": Todos,
})
})
+135
View File
@@ -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<Comp2Attrs> {
view ({attrs: {title, description}}: CVnode<Comp2Attrs>) {
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<Comp4Attrs> {
count: number
constructor (vnode: CVnode<Comp4Attrs>) {
this.count = 0
}
add (num: number) {
this.count += num
}
view ({attrs}: CVnode<Comp4Attrs>) {
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<Attrs> {
count = 0
view ({attrs}: CVnode<Attrs>) {
return m('span', `name: ${attrs.name}, count: ${this.count}`)
}
}
+160
View File
@@ -0,0 +1,160 @@
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<Comp2Attrs,{}> = {
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<Comp4Attrs,Comp4State> & Comp4State
//interface Comp4 extends Component<Comp4Attrs,Comp4State>, 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<Comp4Attrs,Comp4State> = {
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<Attrs,State>
// Using the Comp type will apply the State intersection type for us.
@@ -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<Comp2Attrs>
///////////////////////////////////////////////////////////
// 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<Comp4Attrs,{}> {
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<Comp4Attrs,Comp5State> {
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<Attrs,{}> => {
let count = 0
return {
view ({attrs}) {
return m('span', `name: ${attrs.name}, count: ${count}`)
}
}
}
+17
View File
@@ -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<any,any>) => {
console.log('oninit')
}
},
[h('p', 'test1'), [123, h('p', 'abc'), ['abc']], 'Abc', h('p', 'test2')]
)
+27
View File
@@ -0,0 +1,27 @@
import * as m from 'mithril'
interface Result {
id: number
}
m.jsonp<Result>('/item').then(data => {
console.log(data.id)
})
class User {
id: number
constructor (result: Result) {
this.id = result.id
}
}
m.jsonp<User>({
url: '/user',
data: {test: 'abc'},
type: User,
callbackName: 'getuser',
callbackKey: 'key',
background: true
}).then(user => {
console.log(user.id)
})
+16
View File
@@ -0,0 +1,16 @@
import * as m from 'mithril'
const vnode = m.trust('Some <strong>bold</strong> text.')
const params = m.parseQueryString('?id=123')
const qstr = m.buildQueryString({id: 123})
m.render(document.body, 'Hello')
m.render(document.body, m('h1', 'Test'))
m.render(document.body, [
m('h1', 'Test'), "abc", null, 123, false, m('p', 'Vnode array'),
['a', 123, undefined, m('div', 'Nested')]
])
m.redraw()
+73
View File
@@ -0,0 +1,73 @@
import * as request from 'mithril/request'
interface Result {
id: number
}
request<Result>({method: "GET", url: "/item"}).then(result => {
console.log(result.id)
})
request<{a: string}>("/item", {method: "POST"}).then(result => {
console.log(result.a)
})
request<any>({
method: "GET",
url: "/item",
data: {x: "y"}
}).then(result => {
console.log(result)
})
request<Result>({
method: "GET",
url: "/item",
data: 5,
serialize: (data: number) => "id=" + data.toString()
}).then(result => {
console.log(result)
})
request<Result>('/item', {
method: "GET",
deserialize: str => JSON.parse(str) as Result
}).then(result => {
console.log(result.id)
})
request<Result>('/id', {
method: "GET",
extract: xhr => ({id: Number(xhr.responseText)})
}).then(result => {
console.log(result.id)
})
request<Result>('/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>('/item', {
method: 'GET',
async: true,
user: "Me",
password: "qwerty",
withCredentials: true,
type: Item,
useBody: false
}).then(item => {
console.log(item.identifier)
})
+60
View File
@@ -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<Component<{title: string},{}>>((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'))
+391
View File
@@ -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<number | undefined> = 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<any>[] = []
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<number>[] = []
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<number>()
const b = stream<number>()
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<number>()
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<undefined>()
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<number>()
const child = stream.scan((out, p) => out - p, 123, parent)
}
{
const parent = stream<number>()
const child = stream.scan((arr, p) => arr.concat(p), [] as number[], parent)
parent(7)
}
// scanMerge
{
const parent1 = stream<number>()
const parent2 = stream<number>()
const child = stream.scanMerge([
[parent1, (out, p1) => out + p1],
[parent2, (out, p2) => out + p2]
], -10)
}
{
const parent1 = stream<string>()
const parent2 = stream<string>()
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<string>()
const parent2 = stream<number>()
const child = stream.scanMerge([
[parent1, (out, p1) => out + p1],
[parent2, (out, p2) => out + p2 + p2]
], "a")
parent1("a")
parent2(1)
console.assert(child() === 'aa11')
}
+38 -22
View File
@@ -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"
]
}
"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
}
}
+54
View File
@@ -0,0 +1,54 @@
{
"rules": {
"class-name": true,
"comment-format": [
false,
"check-space"
],
"indent": [
true,
"tabs"
],
"no-duplicate-variable": true,
"no-eval": true,
"no-internal-module": false,
"no-trailing-whitespace": true,
"no-var-keyword": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"quotemark": [
false,
"double"
],
"semicolon": [false, "always"],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
false,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": [
true,
"ban-keywords"
],
"whitespace": [
false,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}
+3
View File
@@ -0,0 +1,3 @@
import { WithAttr } from "mithril";
declare const withAttr: WithAttr;
export = withAttr;