From af3286be1451050ad566161fc8be332bcb3935c0 Mon Sep 17 00:00:00 2001 From: diontools Date: Thu, 15 Aug 2019 23:37:02 +0900 Subject: [PATCH 01/29] upgrade to hyperapp 2.0.1 pre-release --- README.md | 32 +- fx/effects/Delay.ts | 2 +- fx/effects/Execute.ts | 2 +- fx/effects/Http.ts | 17 +- fx/subs/Timer.ts | 2 +- fx/utils.ts | 5 - main/index.ts | 663 ++++++++++++++++++++------------------- package.json | 6 +- router/index.ts | 12 +- sample/errorPatterns.tsx | 14 +- sample/index.tsx | 12 +- sample/part.tsx | 6 +- types/Html.d.ts | 2 +- 13 files changed, 406 insertions(+), 369 deletions(-) delete mode 100644 fx/utils.ts diff --git a/README.md b/README.md index 7c969ff..a5f9fc2 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ npm install typerapp ## Modified points from Hyperapp -1. Remove `data` argument from Action +1. ~~Remove `data` argument from Action~~ 2. Add `dispatch` to `view` arguments 3. Pure DOM Events -### Remove `data` argument from Action +### ~~Remove `data` argument from Action~~ Typerapp Action has only two arguments. @@ -35,6 +35,8 @@ Hyperapp: const Act = (state, { value }, data) => ({...}) ``` +**Obsoluted: Currently Hyperapp V2 does NOT have a `data` argument.** + Typerapp: ```typescript @@ -123,10 +125,12 @@ const Add: Action = (state, params) => ({ ### Effects +**Note: Change signature (move `dispatch` to first argument)** + Type: ```typescript -export type Effect = [(props: P, dispatch: Dispatch) => void, P] +export type Effect = [(dispatch: Dispatch, props: P) => void, P] ``` Define Effect: @@ -139,7 +143,7 @@ export type DelayProps = { } // Delay Effect Runner -const DelayRunner = (props: DelayProps, dispatch: Dispatch) => { +const DelayRunner = (dispatch: Dispatch, props: DelayProps) => { setTimeout(() => dispatch(props.action), props.duration) } @@ -167,10 +171,12 @@ const DelayAdd: Action = (state, params) => [ ### Subscriptions +**Note: Change signature (move `dispatch` to first argument)** + Type: ```typescript -export type Subscription = [(props: P, dispatch: Dispatch) => () => void, P] +export type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] ``` Define Subscription: @@ -183,7 +189,7 @@ export type TimerProps = { } // Timer Subscription Runner -const timerRunner = (props: TimerProps, dispatch: Dispatch) => { +const timerRunner = (dispatch: Dispatch, props: TimerProps) => { const id = setInterval(() => dispatch(props.action), props.interval) return () => clearInterval(id) } @@ -266,9 +272,11 @@ export const view: View = ({ part: state }, dispatch) =>
``` -### ActionParamOf +### ~~ActionParamOf~~ -`ActionParamOf` type gets parameter type of Action from Effect/Subscription Constructor. +**Obsoluted: Payload Creator instead** + +~~`ActionParamOf` type gets parameter type of Action from Effect/Subscription Constructor.~~ ```typescript import { ActionParamOf } from 'typerapp' @@ -422,11 +430,13 @@ import "typerapp/main/svg-alias" ``` -### mergeAction +### ~~mergeAction~~ + +**Obsoluted: Payload Creator instead** -In Typerapp, if your Effect/Subscription returns a value by Action, you must merge a return value into Action parameter, because Typerapp has not `data` of Action. +~~In Typerapp, if your Effect/Subscription returns a value by Action, you must merge a return value into Action parameter, because Typerapp has not `data` of Action.~~ -In that case, you can use `mergeAction` function. +~~In that case, you can use `mergeAction` function.~~ ```typescript import { EffectAction, Dispatch, Effect } from "typerapp" diff --git a/fx/effects/Delay.ts b/fx/effects/Delay.ts index 8bf85c9..958f3e3 100644 --- a/fx/effects/Delay.ts +++ b/fx/effects/Delay.ts @@ -5,7 +5,7 @@ export type DelayProps = { duration: number } -const DelayRunner = (props: DelayProps, dispatch: Dispatch) => { +const DelayRunner = (dispatch: Dispatch, props: DelayProps) => { setTimeout(() => dispatch(props.action), props.duration) } diff --git a/fx/effects/Execute.ts b/fx/effects/Execute.ts index fdc7e5e..11acb30 100644 --- a/fx/effects/Execute.ts +++ b/fx/effects/Execute.ts @@ -2,7 +2,7 @@ import { Dispatch, Effect } from "typerapp" type Callback = (dispatch: Dispatch) => void -const ExecuteRunner = (callback: Callback, dispatch: Dispatch) => callback(dispatch) +const ExecuteRunner = (dispatch: Dispatch, callback: Callback) => callback(dispatch) export function execute(exec: Callback): Effect> { return [ExecuteRunner, exec] diff --git a/fx/effects/Http.ts b/fx/effects/Http.ts index 317cc61..60f036f 100644 --- a/fx/effects/Http.ts +++ b/fx/effects/Http.ts @@ -1,10 +1,9 @@ import { EffectAction, Dispatch, Effect } from 'typerapp' -import { mergeAction } from '../utils' export type HttpProps = string | [string, RequestInit] -export type HttpRunnerPropsBase = { - action: EffectAction +export type HttpRunnerPropsBase = { + action: EffectAction req: HttpProps } @@ -14,11 +13,11 @@ const request = (req: HttpProps) => Array.isArray(req) ? fetch(req[0], req[1]) : export type HttpRunnerProps = HttpRunnerPropsBase -const httpRunner = (props: HttpRunnerProps, dispatch: Dispatch) => { +const httpRunner = (dispatch: Dispatch, props: HttpRunnerProps) => { request(props.req) .then(response => { if (!response.ok) throw response - dispatch(mergeAction(props.action, { response })) + dispatch(props.action, { response }) }) .catch(error => { throw error }) } @@ -30,13 +29,13 @@ export function http(action: HttpRunnerProps['action'], req: HttpPro export type HttpTextRunnerProps = HttpRunnerPropsBase -const httpTextRunner = (props: HttpTextRunnerProps, dispatch: Dispatch) => { +const httpTextRunner = (dispatch: Dispatch, props: HttpTextRunnerProps) => { request(props.req) .then(response => { if (!response.ok) throw response return response.text() }) - .then(text => dispatch(mergeAction(props.action, { text }))) + .then(text => dispatch(props.action, { text })) .catch(error => { throw error }) } @@ -47,13 +46,13 @@ export function httpText(action: HttpTextRunnerProps['action'], req: export type HttpJsonRunnerProps = HttpRunnerPropsBase -const httpJsonRunner = (props: HttpJsonRunnerProps, dispatch: Dispatch) => { +const httpJsonRunner = (dispatch: Dispatch, props: HttpJsonRunnerProps) => { request(props.req) .then(response => { if (!response.ok) throw response return response.json() }) - .then((json: unknown) => dispatch(mergeAction(props.action, { json }))) + .then((json: unknown) => dispatch(props.action, { json })) .catch(error => { throw error }) } diff --git a/fx/subs/Timer.ts b/fx/subs/Timer.ts index 24e0fe9..f3b908b 100644 --- a/fx/subs/Timer.ts +++ b/fx/subs/Timer.ts @@ -5,7 +5,7 @@ export type TimerProps = { interval: number } -const timerRunner = (props: TimerProps, dispatch: Dispatch) => { +const timerRunner = (dispatch: Dispatch, props: TimerProps) => { const id = setInterval(() => dispatch(props.action), props.interval) return () => clearInterval(id) } diff --git a/fx/utils.ts b/fx/utils.ts deleted file mode 100644 index c7eddac..0000000 --- a/fx/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { EffectAction, Action } from "typerapp" - -export function mergeAction(action: EffectAction, extraProps: R): [Action, P & R] { - return Array.isArray(action) ? [action[0], { ...action[1], ...extraProps }] : [action, extraProps] -} \ No newline at end of file diff --git a/main/index.ts b/main/index.ts index 612ee04..86d6054 100644 --- a/main/index.ts +++ b/main/index.ts @@ -8,36 +8,42 @@ const aliases: { [name: string]: string } = { htmlFor: 'for', } +/** For internal */ export const svgAliases: { [name: string]: string } = {} +/** For internal */ export function convName(name: string, isSvg?: boolean) { return isSvg ? svgAliases[name] || name : aliases[name] || name.toLowerCase() } -var DEFAULT_NODE = 0 var RECYCLED_NODE = 1 var LAZY_NODE = 2 var TEXT_NODE = 3 -var EMPTY_OBJECT = {} -var EMPTY_ARRAY: any[] = [] - -var map = EMPTY_ARRAY.map +var EMPTY_OBJ = {} +var EMPTY_ARR: any[] = [] +var map = EMPTY_ARR.map var isArray = Array.isArray -var defer = requestAnimationFrame || setTimeout +var defer = + typeof requestAnimationFrame !== "undefined" + ? requestAnimationFrame + : setTimeout var createClass = function (obj: any): string { var out = "" - var tmp: string = typeof obj - if (tmp === "string" || tmp === "number") return obj + if (typeof obj === "string") return obj if (isArray(obj) && obj.length > 0) { - for (let i = 0; i < obj.length; i++) { - if ((tmp = createClass(obj[i])) !== "") out += (out && " ") + tmp + for (var k = 0, tmp; k < obj.length; k++) { + if ((tmp = createClass(obj[k])) !== "") { + out += (out && " ") + tmp + } } } else { - for (let i in obj) { - if (obj[i]) out += (out && " ") + i + for (let k in obj) { + if (obj[k]) { + out += (out && " ") + k + } } } @@ -45,24 +51,24 @@ var createClass = function (obj: any): string { } var merge = function (a: T1, b: T2): T1 & T2 { - var out = {} + var out = {} as any - for (let i in a) out[i] = a[i] - for (let i in b) out[i] = b[i] + for (let k in a) out[k] = a[k] + for (let k in b) out[k] = b[k] return out } -var flatten = function (arr: any[]): any[] { - return arr.reduce(function (out, obj) { +var batch = function (list: any[]): any[] { + return list.reduce(function (out, item) { return out.concat( - !obj || obj === true - ? false - : typeof obj[0] === "function" - ? [obj] - : flatten(obj) + !item || item === true + ? 0 + : typeof item[0] === "function" + ? [item] + : batch(item) ) - }, EMPTY_ARRAY) + }, EMPTY_ARR) } var isSameAction = function (a: any, b: any) { @@ -70,508 +76,523 @@ var isSameAction = function (a: any, b: any) { } var shouldRestart = function (a: any, b: any) { - for (var k in merge(a, b)) { - if (a[k] === b[k] || isSameAction(a[k], b[k])) b[k] = a[k] - else return true + if (a !== b) { + for (var k in merge(a, b)) { + if (a[k] !== b[k] && !isSameAction(a[k], b[k])) return true + b[k] = a[k] + } } } -var patchSub = function (sub: any[], newSub: any[], dispatch: any) { - for (var i = 0, a, b, out = []; i < sub.length || i < newSub.length; i++) { - a = sub[i] - out.push( - (b = newSub[i]) - ? !a || b[0] !== a[0] || shouldRestart(b[1], a[1]) - ? [b[0], b[1], b[0](b[1], dispatch), a && a[2]()] - : a - : a && a[2]() +var patchSubs = function (oldSubs: any[], newSubs: any[], dispatch: any) { + for ( + var i = 0, oldSub, newSub, subs = []; + i < oldSubs.length || i < newSubs.length; + i++ + ) { + oldSub = oldSubs[i] + newSub = newSubs[i] + subs.push( + newSub + ? !oldSub || + newSub[0] !== oldSub[0] || + shouldRestart(newSub[1], oldSub[1]) + ? [ + newSub[0], + newSub[1], + newSub[0](dispatch, newSub[1]), + oldSub && oldSub[2]() + ] + : oldSub + : oldSub && oldSub[2]() ) } - return out + return subs } -var updateProperty = function (element: Element, name: string, value: any, newValue: any, eventCb: EventCb, isSvg?: boolean) { - if (name === "key") { - } else if (name === "style") { - for (var i in merge(value, newValue)) { - var style = newValue == null || newValue[i] == null ? "" : newValue[i] - if (i[0] === "-") { - element[name].setProperty(i, style) +var patchProperty = function (node: Element, key: string, oldValue: any, newValue: any, listener: EventListener, isSvg?: boolean) { + if (key === "key") { + } else if (key === "style") { + for (var k in merge(oldValue, newValue)) { + oldValue = newValue == null || newValue[k] == null ? "" : newValue[k] + if (k[0] === "-") { + node[key].setProperty(k, oldValue) } else { - element[name][i as any] = style + node[key][k as any] = oldValue } } - } else if (name[0] === "o" && name[1] === "n") { + } else if (key[0] === "o" && key[1] === "n") { if ( - !((element.events || (element.events = {}))[ - (name = name.slice(2).toLowerCase()) + !((node.actions || (node.actions = {}))[ + (key = key.slice(2).toLowerCase()) ] = newValue) ) { - element.removeEventListener(name, eventCb) - } else if (!value) { - element.addEventListener(name, eventCb) + node.removeEventListener(key, listener) + } else if (!oldValue) { + node.addEventListener(key, listener) } - } else if (name !== "list" && !isSvg && name in element) { - (element as any)[name] = newValue == null ? "" : newValue + } else if (!isSvg && key !== "list" && key in node) { + (node as any)[key] = newValue == null ? "" : newValue } else if ( newValue == null || newValue === false || - (name === "class" && !(newValue = createClass(newValue))) + (key === "class" && !(newValue = createClass(newValue))) ) { - element.removeAttribute(convName(name, isSvg)) + node.removeAttribute(convName(key, isSvg)) } else { - element.setAttribute(convName(name, isSvg), newValue) + node.setAttribute(convName(key, isSvg), newValue) } } -var removeElement = function (parent: Node, node: VNode) { - parent.removeChild(node.element) -} - -var createElement = function (node: VNode, eventCb: EventCb, isSvg?: boolean) { - var element = - node.type === TEXT_NODE - ? document.createTextNode(node.name) - : (isSvg = isSvg || node.name === "svg") - ? document.createElementNS("http://www.w3.org/2000/svg", node.name) - : document.createElement(node.name) - var props = node.props +var createNode = function (vdom: VNode, listener: EventListener, isSvg?: boolean) { + var ns = "http://www.w3.org/2000/svg" + var props = vdom.props + var node = + vdom.type === TEXT_NODE + ? document.createTextNode(vdom.name) + : (isSvg = isSvg || vdom.name === "svg") + ? document.createElementNS(ns, vdom.name, { is: props.is }) + : document.createElement(vdom.name, { is: props.is }) for (var k in props) { - updateProperty(element, k, null, props[k], eventCb, isSvg) + patchProperty(node as Element, k, null, props[k], listener, isSvg) } - for (var i = 0, len = node.children.length; i < len; i++) { - element.appendChild( - createElement( - (node.children[i] = getNode(node.children[i])), - eventCb, + for (var i = 0, len = vdom.children.length; i < len; i++) { + node.appendChild( + createNode( + (vdom.children[i] = getVNode(vdom.children[i])), + listener, isSvg ) ) } - return (node.element = element) -} - -var updateElement = function (element: Element, props: VNodeProps, newProps: VNodeProps, eventCb: EventCb, isSvg?: boolean) { - for (var k in merge(props, newProps)) { - if ( - (k === "value" || k === "selected" || k === "checked" - ? (element as any)[k] - : props[k]) !== newProps[k] - ) { - updateProperty(element, k, props[k], newProps[k], eventCb, isSvg) - } - } + return (vdom.node = node) } -var getKey = function (node: VNode) { - return node == null ? null : node.key +var getKey = function (vdom: VNode) { + return vdom == null ? null : vdom.key } -var patch = function (parent: Node, element: Element | Text, node: VNode | null, newNode: VNode, eventCb: EventCb, isSvg?: boolean) { - if (newNode === node) { +var patch = function (parent: Node, node: Element | Text, oldVNode: VNode | null, newVNode: VNode, listener: EventListener, isSvg?: boolean) { + if (oldVNode === newVNode) { } else if ( - node != null && - node.type === TEXT_NODE && - newNode.type === TEXT_NODE + oldVNode != null && + oldVNode.type === TEXT_NODE && + newVNode.type === TEXT_NODE ) { - if (node.name !== newNode.name) element.nodeValue = newNode.name - } else if (node == null || node.name !== newNode.name) { - var newElement = parent.insertBefore( - createElement((newNode = getNode(newNode)), eventCb, isSvg), - element + if (oldVNode.name !== newVNode.name) node.nodeValue = newVNode.name + } else if (oldVNode == null || oldVNode.name !== newVNode.name) { + node = parent.insertBefore( + createNode((newVNode = getVNode(newVNode)), listener, isSvg), + node ) + if (oldVNode != null) { + parent.removeChild(oldVNode.node) + } + } else { + var tmpVKid + var oldVKid - if (node != null) removeElement(parent, node) + var oldKey + var newKey - element = newElement - } else { - updateElement( - element, - node.props, - newNode.props, - eventCb, - (isSvg = isSvg || newNode.name === "svg") - ) + var oldVProps = oldVNode.props + var newVProps = newVNode.props - var savedNode - var childNode + var oldVKids = oldVNode.children + var newVKids = newVNode.children - var key - var children = node.children - var start = 0 - var end = children.length - 1 + var oldHead = 0 + var newHead = 0 + var oldTail = oldVKids.length - 1 + var newTail = newVKids.length - 1 - var newKey - var newChildren = newNode.children - var newStart = 0 - var newEnd = newChildren.length - 1 + isSvg = isSvg || newVNode.name === "svg" - while (newStart <= newEnd && start <= end) { - key = getKey(children[start]) - newKey = getKey(newChildren[newStart]) + for (var i in merge(oldVProps, newVProps)) { + if ( + (i === "value" || i === "selected" || i === "checked" + ? (node as any)[i] + : oldVProps[i]) !== newVProps[i] + ) { + patchProperty(node as Element, i, oldVProps[i], newVProps[i], listener, isSvg) + } + } - if (key == null || key !== newKey) break + while (newHead <= newTail && oldHead <= oldTail) { + if ( + (oldKey = getKey(oldVKids[oldHead])) == null || + oldKey !== getKey(newVKids[newHead]) + ) { + break + } patch( - element, - children[start].element, - children[start], - (newChildren[newStart] = getNode( - newChildren[newStart], - children[start] + node, + oldVKids[oldHead].node, + oldVKids[oldHead], + (newVKids[newHead] = getVNode( + newVKids[newHead++], + oldVKids[oldHead++] )), - eventCb, + listener, isSvg ) - - start++ - newStart++ } - while (newStart <= newEnd && start <= end) { - key = getKey(children[end]) - newKey = getKey(newChildren[newEnd]) - - if (key == null || key !== newKey) break + while (newHead <= newTail && oldHead <= oldTail) { + if ( + (oldKey = getKey(oldVKids[oldTail])) == null || + oldKey !== getKey(newVKids[newTail]) + ) { + break + } patch( - element, - children[end].element, - children[end], - (newChildren[newEnd] = getNode(newChildren[newEnd], children[end])), - eventCb, + node, + oldVKids[oldTail].node, + oldVKids[oldTail], + (newVKids[newTail] = getVNode( + newVKids[newTail--], + oldVKids[oldTail--] + )), + listener, isSvg ) - - end-- - newEnd-- } - if (start > end) { - while (newStart <= newEnd) { - element.insertBefore( - createElement( - (newChildren[newStart] = getNode(newChildren[newStart++])), - eventCb, + if (oldHead > oldTail) { + while (newHead <= newTail) { + node.insertBefore( + createNode( + (newVKids[newHead] = getVNode(newVKids[newHead++])), + listener, isSvg ), - (childNode = children[start]) && childNode.element + (oldVKid = oldVKids[oldHead]) && oldVKid.node ) } - } else if (newStart > newEnd) { - while (start <= end) { - removeElement(element, children[start++]) + } else if (newHead > newTail) { + while (oldHead <= oldTail) { + node.removeChild(oldVKids[oldHead++].node) } } else { - for (var i = start, keyed: VNodeWithKey = {}, newKeyed: VNodeWithKey = {}; i <= end; i++) { - if ((key = children[i].key) != null) { - keyed[key] = children[i] + var newKeyed: VNodeWithKey = {}, keyed: VNodeWithKey = {} + for (let i = oldHead; i <= oldTail; i++) { + if ((oldKey = oldVKids[i].key) != null) { + keyed[oldKey] = oldVKids[i] } } - while (newStart <= newEnd) { - key = getKey((childNode = children[start])) + while (newHead <= newTail) { + oldKey = getKey((oldVKid = oldVKids[oldHead])) newKey = getKey( - (newChildren[newStart] = getNode(newChildren[newStart], childNode)) + (newVKids[newHead] = getVNode(newVKids[newHead], oldVKid)) ) if ( - newKeyed[key!] || - (newKey != null && newKey === getKey(children[start + 1])) + newKeyed[oldKey!] || + (newKey != null && newKey === getKey(oldVKids[oldHead + 1])) ) { - if (key == null) { - removeElement(element, childNode) + if (oldKey == null) { + node.removeChild(oldVKid.node) } - start++ + oldHead++ continue } - if (newKey == null || node.type === RECYCLED_NODE) { - if (key == null) { + if (newKey == null || oldVNode.type === RECYCLED_NODE) { + if (oldKey == null) { patch( - element, - childNode && childNode.element, - childNode, - newChildren[newStart], - eventCb, + node, + oldVKid && oldVKid.node, + oldVKid, + newVKids[newHead], + listener, isSvg ) - newStart++ + newHead++ } - start++ + oldHead++ } else { - if (key === newKey) { + if (oldKey === newKey) { patch( - element, - childNode.element, - childNode, - newChildren[newStart], - eventCb, + node, + oldVKid.node, + oldVKid, + newVKids[newHead], + listener, isSvg ) newKeyed[newKey] = true - start++ + oldHead++ } else { - if ((savedNode = keyed[newKey]) != null) { + if ((tmpVKid = keyed[newKey] as VNode) != null) { patch( - element, - element.insertBefore( - savedNode.element, - childNode && childNode.element - ), - savedNode, - newChildren[newStart], - eventCb, + node, + node.insertBefore(tmpVKid.node, oldVKid && oldVKid.node), + tmpVKid, + newVKids[newHead], + listener, isSvg ) newKeyed[newKey] = true } else { patch( - element, - childNode && childNode.element, + node, + oldVKid && oldVKid.node, null, - newChildren[newStart], - eventCb, + newVKids[newHead], + listener, isSvg ) } } - newStart++ + newHead++ } } - while (start <= end) { - if (getKey((childNode = children[start++])) == null) { - removeElement(element, childNode) + while (oldHead <= oldTail) { + if (getKey((oldVKid = oldVKids[oldHead++])) == null) { + node.removeChild(oldVKid.node) } } - for (let key in keyed) { - if (newKeyed[key] == null) { - removeElement(element, keyed[key]) + for (var i in keyed) { + if (newKeyed[i] == null) { + node.removeChild((keyed[i] as VNode).node) } } } } - return (newNode.element = element) + return (newVNode.node = node) } -var shouldUpdate = function (a: any, b: any) { +var propsChanged = function (a: any, b: any) { for (var k in a) if (a[k] !== b[k]) return true for (var k in b) if (a[k] !== b[k]) return true } -var getNode = function (newNode: VNode, node?: VNode) { - return newNode.type === LAZY_NODE - ? !node || shouldUpdate(newNode.lazy, node.lazy) - ? newNode.render!() - : node - : newNode +var getVNode = function (newVNode: VNode, oldVNode?: VNode) { + return newVNode.type === LAZY_NODE + ? ((!oldVNode || propsChanged(oldVNode.lazy, newVNode.lazy)) && + ((oldVNode = newVNode.lazy!.view(newVNode.lazy)).lazy = newVNode.lazy), + oldVNode!) + : newVNode } -var createVNode = function (name: string, props: VNodeProps, children: VNode[], element: Element | undefined, key: string | null, type: number): VNode { +var createVNode = function (name: string, props: VNodeProps, children: VNode[], node: Element | undefined, key: string | undefined, type?: number): VNode { return { name: name, props: props, children: children, - element: element!, - type: type, - key: key + node: node!, + type: type!, + key: key! } } -var createTextVNode = function (text: string, element?: Element) { - return createVNode(text, EMPTY_OBJECT, EMPTY_ARRAY, element, null, TEXT_NODE) +var createTextVNode = function (value: string, node?: Element) { + return createVNode(value, EMPTY_OBJ, EMPTY_ARR, node, undefined, TEXT_NODE) } -var recycleElement = function (element: Element): VNode { - return element.nodeType === TEXT_NODE - ? createTextVNode(element.nodeValue!, element) +var recycleNode = function (node: Element): VNode { + return node.nodeType === TEXT_NODE + ? createTextVNode(node.nodeValue!, node) : createVNode( - element.nodeName.toLowerCase(), - EMPTY_OBJECT, - map.call(element.childNodes, recycleElement) as VNode[], - element, - null, + node.nodeName.toLowerCase(), + EMPTY_OBJ, + map.call(node.childNodes, recycleNode) as VNode[], + node, + undefined, RECYCLED_NODE ) } -export var Lazy = function

VNode }>(props: P): LazyVNode

{ +/** Function to create Lazy VNode */ +export var Lazy = function

(props: LazyProp

): VNode { return { - type: LAZY_NODE, - key: props.key, - lazy: props, - render: function () { - var node = props.render(props) - node.lazy = props - return node - } - } as LazyVNode

+ lazy: props as LazyProp, + type: LAZY_NODE + } as VNode } -export var h = function (name: string | Function, props: any, _children?: any): VNode { - for (var node, rest = [], children = [], i = arguments.length; i-- > 2;) { +/** Function to create VNode */ +export var h = function (name: string | Function, props?: {} | null, _children?: any) { + for (var vdom: any, rest = [], children = [], i = arguments.length; i-- > 2;) { rest.push(arguments[i]) } while (rest.length > 0) { - if (isArray((node = rest.pop()))) { - for (i = node.length; i-- > 0;) rest.push(node[i]) - } else if (node === false || node === true || node == null) { + if (isArray((vdom = rest.pop()))) { + for (let i = vdom.length; i-- > 0;) { + rest.push(vdom[i]) + } + } else if (vdom === false || vdom === true || vdom == null) { } else { - children.push(typeof node === "object" ? node : createTextVNode(node)) + children.push(typeof vdom === "object" ? vdom : createTextVNode(vdom)) } } - props = props || EMPTY_OBJECT + props = props || EMPTY_OBJ return typeof name === "function" ? name(props, children) - : createVNode(name, props, children, undefined, props.key, DEFAULT_NODE) + : createVNode(name, props, children, undefined, (props as any).key) } +/** Function to start typerapp application */ export var app = function (props: AppProps) { - var container = props.container - var element: Element | Text = container && container.children[0] - var node = element && recycleElement(element) - var subs = props.subscriptions - var view = props.view + var state: S = {} as any var lock = false - var state: S = {} as S - var sub: any[] = [] + var view = props.view + var node: Element | Text = props.node + var vdom = node && recycleNode(node) + var subscriptions = props.subscriptions + var subs: any[] = [] - var eventCb: EventCb = function (event) { - (event.currentTarget as Element).events![event.type](event) + var listener: EventListener = function (event) { + (event.currentTarget as Element).actions![event.type](event) } var setState = function (newState: S) { - if (!(state === newState || lock)) { - lock = true - defer(render) - } - state = newState - } - - var dispatch: Dispatch = function (obj: any, props?: any) { - if (typeof obj === "function") { - dispatch(obj(state, props)) - } else if (isArray(obj)) { - if (typeof obj[0] === "function") { - dispatch(obj[0](state, obj[1], props)) - } else { - flatten(obj.slice(1)).map(function (fx) { - fx && fx[0](fx[1], dispatch) - }, setState(obj[0])) + if (state !== newState) { + state = newState + if (subscriptions) { + subs = patchSubs(subs, batch([subscriptions(state)]), dispatch) } - } else { - setState(obj) + if (view && !lock) defer(render, (lock = true) as any) } + return state } + var dispatch: Dispatch = (props.middleware || + function (obj: any) { + return obj + })(function (action: any, props?: any) { + return typeof action === "function" + ? dispatch(action(state, props)) + : isArray(action) + ? typeof action[0] === "function" + ? dispatch( + action[0], + typeof action[1] === "function" ? action[1](props) : action[1] + ) + : (batch(action.slice(1)).map(function (fx) { + fx && fx[0](dispatch, fx[1]) + }, setState(action[0])), + state) + : setState(action) + }) + var render = function () { lock = false - if (subs) sub = patchSub(sub, flatten(subs(state)), dispatch) - if (view) { - element = patch(container, element, node, (node = view(state, dispatch)), eventCb) - } + node = patch( + node.parentNode!, + node, + vdom, + (vdom = + typeof (vdom = view(state, dispatch)) === "string" + ? createTextVNode(vdom) + : vdom), + listener + ) } - dispatch(props.init) + dispatch(props.init) } -type EventCb = (ev: Event) => void +type EventListener = (ev: Event) => void type VNodeWithKey = { [key: string]: VNode | boolean } +/** Dummy param type of Action */ export type Empty = { ___dummy: never } +/** Return type of Action */ export type ActionResult = S | [S, ...Effect[]] + +/** Action type */ export type Action = (state: S, params: P) => ActionResult -export type EffectAction = - R extends undefined +/** Action type for Effect */ +export type EffectAction = + EP extends undefined ? Action | [Action, P] - : Action | [Action, P] - -// extract property types -type PropertyOf = - T extends any[] ? T : - T extends Function ? T : - T extends { [P in keyof T]: infer R } ? R : - T -// extract from union -type Filter = T extends U ? T : never -// tuple to union -type ElementOf = T extends (infer E)[] ? E : T - -type ActionParamType = T extends Action ? R : never - -/** Extract action parameter type from Effect Constructor */ -export type ActionParamOf any> = - ActionParamType< - Filter< - PropertyOf< - ElementOf< - Parameters - > - >, - Action - > - > - -export type Effect = [(props: P, dispatch: Dispatch) => void, P] - -export type Subscription = [(props: P, dispatch: Dispatch) => () => void, P] + : Action | [Action, P] | [Action, (effectPayload: EP) => P] + +/** Effect object type */ +export type Effect = [(dispatch: Dispatch, props: P) => void, P] + +/** Subscription object type */ +export type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] +/** Return type of subscriptions function on app */ +export type Subscriptions = Subscription | boolean | (Subscription | boolean)[] + +/** dispatch function type */ export type Dispatch = { (action: Action): void

(action: Action, params: P): void

(actionWithParams: [Action, P]): void (result: ActionResult): void + (effectAction: EffectAction, effectPayload: EP): void

(all: Action | [Action, P] | ActionResult): void } +/** Return type of view function on app */ export type View = (state: S, dispatch: Dispatch) => VNode +/** Argument type of app function */ export type AppProps = { + /** Initial state, Effect or Action */ init: Action | ActionResult, - view?: View, - subscriptions?: (state: S) => (Subscription | boolean)[], - container: Element -} -export type Children = VNode | string | number | null + /** VDOM rendering function */ + view: View, + + /** Create subscription function */ + subscriptions?: (state: S) => Subscriptions, + /** Element for rendering target */ + node: Element, + + /** Interrupt dispatch function */ + middleware?: (dispatch: Dispatch) => Dispatch, +} + +/** VNode props type */ export type VNodeProps = { [key: string]: any } +/** Lazy props type */ +export type LazyProp

= { key: string, view: (props: P) => VNode } & P + +/** Node type of VDOM */ export interface VNode { name: string, props: Props, children: Array - element: Element | Text, + node: Element | Text, key: string | null, type: number, - lazy?: any + lazy?: LazyProp, render?: () => VNode } -export interface LazyVNode

extends VNode { - lazy: P - render: () => VNode -} - +/** Class object type */ export interface ClassObject { [key: string]: boolean | any } +/** Array type of Class object type */ export interface ClassArray extends Array { } +/** Class type */ export type Class = string | number | ClassObject | ClassArray +/** Modularization function */ export function actionCreator() { return (name: N): ((action: Action) => Action) => { return (action) => { diff --git a/package.json b/package.json index c7eee1b..104fe37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typerapp", - "version": "0.0.1", + "version": "0.1.0", "description": "Typerapp is type-safe Hyperapp V2.", "main": "lib/main/index.js", "types": "main/index.ts", @@ -22,8 +22,7 @@ }, "homepage": "https://github.com/diontools/typerapp#readme", "dependencies": { - "csstype": "^2.6.3", - "typescript": "^3.3.4000" + "csstype": "^2.6.3" }, "devDependencies": { "@types/html-webpack-plugin": "^3.2.0", @@ -33,6 +32,7 @@ "ts-loader": "^5.3.3", "ts-node": "^8.0.3", "tsconfig-paths-webpack-plugin": "^3.2.0", + "typescript": "^3.4.5", "webpack": "^4.29.6", "webpack-cli": "^3.3.0", "webpack-dev-server": "^3.2.1" diff --git a/router/index.ts b/router/index.ts index c3f6ae0..335b282 100644 --- a/router/index.ts +++ b/router/index.ts @@ -7,6 +7,7 @@ function debug(args: any[]) { export interface RouterProps { routes: Route[], matched: (routing: RoutingInfo | undefined, dispatch: Dispatch) => void, + _init?: boolean, } export type PathSeg = string | { @@ -30,7 +31,7 @@ export type RoutingInfo = { params: RouteParams } -const runner = (props: RouterProps, dispatch: Dispatch) => { +const runner = (dispatch: Dispatch, props: RouterProps) => { debug(['routing']) function onLocationChanged() { const pathname = window.location.pathname @@ -58,7 +59,10 @@ const runner = (props: RouterProps, dispatch: Dispatch) => { } window.addEventListener("popstate", onLocationChanged) - onLocationChanged() + if (!props._init) { + props._init = true + onLocationChanged() + } return () => { debug(['unrouting']) @@ -118,11 +122,11 @@ function replaceHistory(to: string) { } export function PushHistory(to: string): Effect { - return [(to: string, dispatch: Dispatch) => pushHistory(to), to] + return [(dispatch: Dispatch, to: string) => pushHistory(to), to] } export function ReplaceHistory(to: string): Effect { - return [(to: string, dispatch: Dispatch) => replaceHistory(to), to] + return [(dispatch: Dispatch, to: string) => replaceHistory(to), to] } export interface LinkProps { diff --git a/sample/errorPatterns.tsx b/sample/errorPatterns.tsx index 5346cfa..41e6e07 100644 --- a/sample/errorPatterns.tsx +++ b/sample/errorPatterns.tsx @@ -32,6 +32,7 @@ const view: View = (state, dispatch) =>

+ @@ -52,6 +53,7 @@ const view: View = (state, dispatch) =>
+
@@ -90,8 +92,10 @@ delay(DelayParamedAction, { duration: 1000 }) const HttpAction: Action = state => state -// NG +// OK http(HttpAction, '/') + +// NG http([HttpAction, {}], '/') http(undefined, '/') http(null, '/') @@ -104,15 +108,19 @@ http([HttpAction, 1], '/') http([HttpAction, { a: 1 }], '/') -const HttpParamedAction: Action> = (state, params) => ({ +const HttpParamedAction: Action = (state, params) => ({ ...state, value: params.value + params.response.status, }) // OK -http([HttpParamedAction, { value: 1 }], '/') +http([HttpParamedAction, ret => ({ value: 1, response: ret.response })], '/') +http([HttpParamedAction, { value: 1, response: {} as Response }], '/') // NG +http([HttpParamedAction, ret => ({ a: 1, response: ret.response })], '/') +http([HttpParamedAction, ret => ret], '/') +http([HttpParamedAction, { value: 1 }], '/') http([HttpParamedAction, { value: 'a' }], '/') http([HttpParamedAction, { a: 1 }], '/') http([HttpParamedAction, {}], '/') diff --git a/sample/index.tsx b/sample/index.tsx index d900fbd..2484487 100644 --- a/sample/index.tsx +++ b/sample/index.tsx @@ -1,4 +1,4 @@ -import { h, app, Action, Lazy, Dispatch, ActionParamOf } from 'typerapp' +import { h, app, Action, Lazy, Dispatch } from 'typerapp' import { Helmet } from 'typerapp/helmet' import { style } from 'typerapp/style' import { timer, httpText, execute, delay } from 'typerapp/fx'; @@ -26,7 +26,7 @@ const ToggleAuto: Action = state => ({ const Input: Action = (state, value) => ({ ...state, input: value }) -const OnTextResponse: Action> = (state, params) => ({ +const OnTextResponse: Action = (state, params) => ({ ...state, text: params.text }) @@ -82,7 +82,7 @@ const Counter = (state: State, dispatch: Dispatch, amount: number = 1) =>
value: {state.value}
- +
@@ -104,7 +104,7 @@ const router = createRouter({ path: '/fetch', view: (state, dispatch, params) =>

Fetch

- +
text: {state.text}
, @@ -157,7 +157,7 @@ app({ }, view: (state, dispatch) => (
- +
}], matched: (routing, dispatch) => dispatch(SetRoute, routing), }) @@ -153,6 +165,7 @@ app({ part: { value: 0, }, + list: [], routing: undefined, }, view: (state, dispatch) => ( @@ -169,6 +182,7 @@ app({
  • Style
  • Sub
  • Redirect
  • +
  • List
  • unknown
  • {state.routing ? state.routing.route.view(state, dispatch, state.routing.params) :
    404
    } @@ -179,4 +193,8 @@ app({ state.auto && timer([Add, { amount: 1 }], { interval: 500 }), ], node: document.body, + middleware: dispatch => (action: any, props?: any) => { + console.log('dispatch', action, props) + dispatch(action, props) + }, }) \ No newline at end of file diff --git a/sample/states.ts b/sample/states.ts index 3407060..698f629 100644 --- a/sample/states.ts +++ b/sample/states.ts @@ -8,9 +8,10 @@ export type State = { value: number, text: string, auto: boolean, - input: string, part: { value: number, }, + input: string, + list: string[], routing?: RoutingInfo, } \ No newline at end of file From b9024ef230789ab10004eb9194a4bfac0618792d Mon Sep 17 00:00:00 2001 From: diontools Date: Thu, 22 Aug 2019 16:21:54 +0900 Subject: [PATCH 23/29] Html.d.ts: rename namespace TyperApp to Typerapp --- types/Html.d.ts | 356 ++++++++++++++++++++++++------------------------ 1 file changed, 178 insertions(+), 178 deletions(-) diff --git a/types/Html.d.ts b/types/Html.d.ts index b29c028..f9578d0 100644 --- a/types/Html.d.ts +++ b/types/Html.d.ts @@ -22,10 +22,10 @@ type NativeUIEvent = UIEvent; type NativeWheelEvent = WheelEvent; // tslint:disable-next-line:export-just-namespace -export = TyperApp; -export as namespace TyperApp; +export = Typerapp; +export as namespace Typerapp; -declare namespace TyperApp { +declare namespace Typerapp { interface TyperAppAttribute { key?: Key } @@ -1384,183 +1384,183 @@ declare global { interface IntrinsicElements { // HTML - a: TyperApp.DetailedHTMLProps, HTMLAnchorElement>; - abbr: TyperApp.DetailedHTMLProps, HTMLElement>; - address: TyperApp.DetailedHTMLProps, HTMLElement>; - area: TyperApp.DetailedHTMLProps, HTMLAreaElement>; - article: TyperApp.DetailedHTMLProps, HTMLElement>; - aside: TyperApp.DetailedHTMLProps, HTMLElement>; - audio: TyperApp.DetailedHTMLProps, HTMLAudioElement>; - b: TyperApp.DetailedHTMLProps, HTMLElement>; - base: TyperApp.DetailedHTMLProps, HTMLBaseElement>; - bdi: TyperApp.DetailedHTMLProps, HTMLElement>; - bdo: TyperApp.DetailedHTMLProps, HTMLElement>; - big: TyperApp.DetailedHTMLProps, HTMLElement>; - blockquote: TyperApp.DetailedHTMLProps, HTMLElement>; - body: TyperApp.DetailedHTMLProps, HTMLBodyElement>; - br: TyperApp.DetailedHTMLProps, HTMLBRElement>; - button: TyperApp.DetailedHTMLProps, HTMLButtonElement>; - canvas: TyperApp.DetailedHTMLProps, HTMLCanvasElement>; - caption: TyperApp.DetailedHTMLProps, HTMLElement>; - cite: TyperApp.DetailedHTMLProps, HTMLElement>; - code: TyperApp.DetailedHTMLProps, HTMLElement>; - col: TyperApp.DetailedHTMLProps, HTMLTableColElement>; - colgroup: TyperApp.DetailedHTMLProps, HTMLTableColElement>; - data: TyperApp.DetailedHTMLProps, HTMLDataElement>; - datalist: TyperApp.DetailedHTMLProps, HTMLDataListElement>; - dd: TyperApp.DetailedHTMLProps, HTMLElement>; - del: TyperApp.DetailedHTMLProps, HTMLElement>; - details: TyperApp.DetailedHTMLProps, HTMLElement>; - dfn: TyperApp.DetailedHTMLProps, HTMLElement>; - dialog: TyperApp.DetailedHTMLProps, HTMLDialogElement>; - div: TyperApp.DetailedHTMLProps, HTMLDivElement>; - dl: TyperApp.DetailedHTMLProps, HTMLDListElement>; - dt: TyperApp.DetailedHTMLProps, HTMLElement>; - em: TyperApp.DetailedHTMLProps, HTMLElement>; - embed: TyperApp.DetailedHTMLProps, HTMLEmbedElement>; - fieldset: TyperApp.DetailedHTMLProps, HTMLFieldSetElement>; - figcaption: TyperApp.DetailedHTMLProps, HTMLElement>; - figure: TyperApp.DetailedHTMLProps, HTMLElement>; - footer: TyperApp.DetailedHTMLProps, HTMLElement>; - form: TyperApp.DetailedHTMLProps, HTMLFormElement>; - h1: TyperApp.DetailedHTMLProps, HTMLHeadingElement>; - h2: TyperApp.DetailedHTMLProps, HTMLHeadingElement>; - h3: TyperApp.DetailedHTMLProps, HTMLHeadingElement>; - h4: TyperApp.DetailedHTMLProps, HTMLHeadingElement>; - h5: TyperApp.DetailedHTMLProps, HTMLHeadingElement>; - h6: TyperApp.DetailedHTMLProps, HTMLHeadingElement>; - head: TyperApp.DetailedHTMLProps, HTMLHeadElement>; - header: TyperApp.DetailedHTMLProps, HTMLElement>; - hgroup: TyperApp.DetailedHTMLProps, HTMLElement>; - hr: TyperApp.DetailedHTMLProps, HTMLHRElement>; - html: TyperApp.DetailedHTMLProps, HTMLHtmlElement>; - i: TyperApp.DetailedHTMLProps, HTMLElement>; - iframe: TyperApp.DetailedHTMLProps, HTMLIFrameElement>; - img: TyperApp.DetailedHTMLProps, HTMLImageElement>; - input: TyperApp.DetailedHTMLProps, HTMLInputElement>; - ins: TyperApp.DetailedHTMLProps, HTMLModElement>; - kbd: TyperApp.DetailedHTMLProps, HTMLElement>; - keygen: TyperApp.DetailedHTMLProps, HTMLElement>; - label: TyperApp.DetailedHTMLProps, HTMLLabelElement>; - legend: TyperApp.DetailedHTMLProps, HTMLLegendElement>; - li: TyperApp.DetailedHTMLProps, HTMLLIElement>; - link: TyperApp.DetailedHTMLProps, HTMLLinkElement>; - main: TyperApp.DetailedHTMLProps, HTMLElement>; - map: TyperApp.DetailedHTMLProps, HTMLMapElement>; - mark: TyperApp.DetailedHTMLProps, HTMLElement>; - menu: TyperApp.DetailedHTMLProps, HTMLElement>; - menuitem: TyperApp.DetailedHTMLProps, HTMLElement>; - meta: TyperApp.DetailedHTMLProps, HTMLMetaElement>; - meter: TyperApp.DetailedHTMLProps, HTMLElement>; - nav: TyperApp.DetailedHTMLProps, HTMLElement>; - noindex: TyperApp.DetailedHTMLProps, HTMLElement>; - noscript: TyperApp.DetailedHTMLProps, HTMLElement>; - object: TyperApp.DetailedHTMLProps, HTMLObjectElement>; - ol: TyperApp.DetailedHTMLProps, HTMLOListElement>; - optgroup: TyperApp.DetailedHTMLProps, HTMLOptGroupElement>; - option: TyperApp.DetailedHTMLProps, HTMLOptionElement>; - output: TyperApp.DetailedHTMLProps, HTMLElement>; - p: TyperApp.DetailedHTMLProps, HTMLParagraphElement>; - param: TyperApp.DetailedHTMLProps, HTMLParamElement>; - picture: TyperApp.DetailedHTMLProps, HTMLElement>; - pre: TyperApp.DetailedHTMLProps, HTMLPreElement>; - progress: TyperApp.DetailedHTMLProps, HTMLProgressElement>; - q: TyperApp.DetailedHTMLProps, HTMLQuoteElement>; - rp: TyperApp.DetailedHTMLProps, HTMLElement>; - rt: TyperApp.DetailedHTMLProps, HTMLElement>; - ruby: TyperApp.DetailedHTMLProps, HTMLElement>; - s: TyperApp.DetailedHTMLProps, HTMLElement>; - samp: TyperApp.DetailedHTMLProps, HTMLElement>; - script: TyperApp.DetailedHTMLProps, HTMLScriptElement>; - section: TyperApp.DetailedHTMLProps, HTMLElement>; - select: TyperApp.DetailedHTMLProps, HTMLSelectElement>; - small: TyperApp.DetailedHTMLProps, HTMLElement>; - source: TyperApp.DetailedHTMLProps, HTMLSourceElement>; - span: TyperApp.DetailedHTMLProps, HTMLSpanElement>; - strong: TyperApp.DetailedHTMLProps, HTMLElement>; - style: TyperApp.DetailedHTMLProps, HTMLStyleElement>; - sub: TyperApp.DetailedHTMLProps, HTMLElement>; - summary: TyperApp.DetailedHTMLProps, HTMLElement>; - sup: TyperApp.DetailedHTMLProps, HTMLElement>; - table: TyperApp.DetailedHTMLProps, HTMLTableElement>; - template: TyperApp.DetailedHTMLProps, HTMLTemplateElement>; - tbody: TyperApp.DetailedHTMLProps, HTMLTableSectionElement>; - td: TyperApp.DetailedHTMLProps, HTMLTableDataCellElement>; - textarea: TyperApp.DetailedHTMLProps, HTMLTextAreaElement>; - tfoot: TyperApp.DetailedHTMLProps, HTMLTableSectionElement>; - th: TyperApp.DetailedHTMLProps, HTMLTableHeaderCellElement>; - thead: TyperApp.DetailedHTMLProps, HTMLTableSectionElement>; - time: TyperApp.DetailedHTMLProps, HTMLElement>; - title: TyperApp.DetailedHTMLProps, HTMLTitleElement>; - tr: TyperApp.DetailedHTMLProps, HTMLTableRowElement>; - track: TyperApp.DetailedHTMLProps, HTMLTrackElement>; - u: TyperApp.DetailedHTMLProps, HTMLElement>; - ul: TyperApp.DetailedHTMLProps, HTMLUListElement>; - "var": TyperApp.DetailedHTMLProps, HTMLElement>; - video: TyperApp.DetailedHTMLProps, HTMLVideoElement>; - wbr: TyperApp.DetailedHTMLProps, HTMLElement>; - webview: TyperApp.DetailedHTMLProps, HTMLWebViewElement>; + a: Typerapp.DetailedHTMLProps, HTMLAnchorElement>; + abbr: Typerapp.DetailedHTMLProps, HTMLElement>; + address: Typerapp.DetailedHTMLProps, HTMLElement>; + area: Typerapp.DetailedHTMLProps, HTMLAreaElement>; + article: Typerapp.DetailedHTMLProps, HTMLElement>; + aside: Typerapp.DetailedHTMLProps, HTMLElement>; + audio: Typerapp.DetailedHTMLProps, HTMLAudioElement>; + b: Typerapp.DetailedHTMLProps, HTMLElement>; + base: Typerapp.DetailedHTMLProps, HTMLBaseElement>; + bdi: Typerapp.DetailedHTMLProps, HTMLElement>; + bdo: Typerapp.DetailedHTMLProps, HTMLElement>; + big: Typerapp.DetailedHTMLProps, HTMLElement>; + blockquote: Typerapp.DetailedHTMLProps, HTMLElement>; + body: Typerapp.DetailedHTMLProps, HTMLBodyElement>; + br: Typerapp.DetailedHTMLProps, HTMLBRElement>; + button: Typerapp.DetailedHTMLProps, HTMLButtonElement>; + canvas: Typerapp.DetailedHTMLProps, HTMLCanvasElement>; + caption: Typerapp.DetailedHTMLProps, HTMLElement>; + cite: Typerapp.DetailedHTMLProps, HTMLElement>; + code: Typerapp.DetailedHTMLProps, HTMLElement>; + col: Typerapp.DetailedHTMLProps, HTMLTableColElement>; + colgroup: Typerapp.DetailedHTMLProps, HTMLTableColElement>; + data: Typerapp.DetailedHTMLProps, HTMLDataElement>; + datalist: Typerapp.DetailedHTMLProps, HTMLDataListElement>; + dd: Typerapp.DetailedHTMLProps, HTMLElement>; + del: Typerapp.DetailedHTMLProps, HTMLElement>; + details: Typerapp.DetailedHTMLProps, HTMLElement>; + dfn: Typerapp.DetailedHTMLProps, HTMLElement>; + dialog: Typerapp.DetailedHTMLProps, HTMLDialogElement>; + div: Typerapp.DetailedHTMLProps, HTMLDivElement>; + dl: Typerapp.DetailedHTMLProps, HTMLDListElement>; + dt: Typerapp.DetailedHTMLProps, HTMLElement>; + em: Typerapp.DetailedHTMLProps, HTMLElement>; + embed: Typerapp.DetailedHTMLProps, HTMLEmbedElement>; + fieldset: Typerapp.DetailedHTMLProps, HTMLFieldSetElement>; + figcaption: Typerapp.DetailedHTMLProps, HTMLElement>; + figure: Typerapp.DetailedHTMLProps, HTMLElement>; + footer: Typerapp.DetailedHTMLProps, HTMLElement>; + form: Typerapp.DetailedHTMLProps, HTMLFormElement>; + h1: Typerapp.DetailedHTMLProps, HTMLHeadingElement>; + h2: Typerapp.DetailedHTMLProps, HTMLHeadingElement>; + h3: Typerapp.DetailedHTMLProps, HTMLHeadingElement>; + h4: Typerapp.DetailedHTMLProps, HTMLHeadingElement>; + h5: Typerapp.DetailedHTMLProps, HTMLHeadingElement>; + h6: Typerapp.DetailedHTMLProps, HTMLHeadingElement>; + head: Typerapp.DetailedHTMLProps, HTMLHeadElement>; + header: Typerapp.DetailedHTMLProps, HTMLElement>; + hgroup: Typerapp.DetailedHTMLProps, HTMLElement>; + hr: Typerapp.DetailedHTMLProps, HTMLHRElement>; + html: Typerapp.DetailedHTMLProps, HTMLHtmlElement>; + i: Typerapp.DetailedHTMLProps, HTMLElement>; + iframe: Typerapp.DetailedHTMLProps, HTMLIFrameElement>; + img: Typerapp.DetailedHTMLProps, HTMLImageElement>; + input: Typerapp.DetailedHTMLProps, HTMLInputElement>; + ins: Typerapp.DetailedHTMLProps, HTMLModElement>; + kbd: Typerapp.DetailedHTMLProps, HTMLElement>; + keygen: Typerapp.DetailedHTMLProps, HTMLElement>; + label: Typerapp.DetailedHTMLProps, HTMLLabelElement>; + legend: Typerapp.DetailedHTMLProps, HTMLLegendElement>; + li: Typerapp.DetailedHTMLProps, HTMLLIElement>; + link: Typerapp.DetailedHTMLProps, HTMLLinkElement>; + main: Typerapp.DetailedHTMLProps, HTMLElement>; + map: Typerapp.DetailedHTMLProps, HTMLMapElement>; + mark: Typerapp.DetailedHTMLProps, HTMLElement>; + menu: Typerapp.DetailedHTMLProps, HTMLElement>; + menuitem: Typerapp.DetailedHTMLProps, HTMLElement>; + meta: Typerapp.DetailedHTMLProps, HTMLMetaElement>; + meter: Typerapp.DetailedHTMLProps, HTMLElement>; + nav: Typerapp.DetailedHTMLProps, HTMLElement>; + noindex: Typerapp.DetailedHTMLProps, HTMLElement>; + noscript: Typerapp.DetailedHTMLProps, HTMLElement>; + object: Typerapp.DetailedHTMLProps, HTMLObjectElement>; + ol: Typerapp.DetailedHTMLProps, HTMLOListElement>; + optgroup: Typerapp.DetailedHTMLProps, HTMLOptGroupElement>; + option: Typerapp.DetailedHTMLProps, HTMLOptionElement>; + output: Typerapp.DetailedHTMLProps, HTMLElement>; + p: Typerapp.DetailedHTMLProps, HTMLParagraphElement>; + param: Typerapp.DetailedHTMLProps, HTMLParamElement>; + picture: Typerapp.DetailedHTMLProps, HTMLElement>; + pre: Typerapp.DetailedHTMLProps, HTMLPreElement>; + progress: Typerapp.DetailedHTMLProps, HTMLProgressElement>; + q: Typerapp.DetailedHTMLProps, HTMLQuoteElement>; + rp: Typerapp.DetailedHTMLProps, HTMLElement>; + rt: Typerapp.DetailedHTMLProps, HTMLElement>; + ruby: Typerapp.DetailedHTMLProps, HTMLElement>; + s: Typerapp.DetailedHTMLProps, HTMLElement>; + samp: Typerapp.DetailedHTMLProps, HTMLElement>; + script: Typerapp.DetailedHTMLProps, HTMLScriptElement>; + section: Typerapp.DetailedHTMLProps, HTMLElement>; + select: Typerapp.DetailedHTMLProps, HTMLSelectElement>; + small: Typerapp.DetailedHTMLProps, HTMLElement>; + source: Typerapp.DetailedHTMLProps, HTMLSourceElement>; + span: Typerapp.DetailedHTMLProps, HTMLSpanElement>; + strong: Typerapp.DetailedHTMLProps, HTMLElement>; + style: Typerapp.DetailedHTMLProps, HTMLStyleElement>; + sub: Typerapp.DetailedHTMLProps, HTMLElement>; + summary: Typerapp.DetailedHTMLProps, HTMLElement>; + sup: Typerapp.DetailedHTMLProps, HTMLElement>; + table: Typerapp.DetailedHTMLProps, HTMLTableElement>; + template: Typerapp.DetailedHTMLProps, HTMLTemplateElement>; + tbody: Typerapp.DetailedHTMLProps, HTMLTableSectionElement>; + td: Typerapp.DetailedHTMLProps, HTMLTableDataCellElement>; + textarea: Typerapp.DetailedHTMLProps, HTMLTextAreaElement>; + tfoot: Typerapp.DetailedHTMLProps, HTMLTableSectionElement>; + th: Typerapp.DetailedHTMLProps, HTMLTableHeaderCellElement>; + thead: Typerapp.DetailedHTMLProps, HTMLTableSectionElement>; + time: Typerapp.DetailedHTMLProps, HTMLElement>; + title: Typerapp.DetailedHTMLProps, HTMLTitleElement>; + tr: Typerapp.DetailedHTMLProps, HTMLTableRowElement>; + track: Typerapp.DetailedHTMLProps, HTMLTrackElement>; + u: Typerapp.DetailedHTMLProps, HTMLElement>; + ul: Typerapp.DetailedHTMLProps, HTMLUListElement>; + "var": Typerapp.DetailedHTMLProps, HTMLElement>; + video: Typerapp.DetailedHTMLProps, HTMLVideoElement>; + wbr: Typerapp.DetailedHTMLProps, HTMLElement>; + webview: Typerapp.DetailedHTMLProps, HTMLWebViewElement>; // SVG - svg: TyperApp.SVGProps; - - animate: TyperApp.SVGProps; // TODO: It is SVGAnimateElement but is not in TypeScript's lib.dom.d.ts for now. - animateMotion: TyperApp.SVGProps; - animateTransform: TyperApp.SVGProps; // TODO: It is SVGAnimateTransformElement but is not in TypeScript's lib.dom.d.ts for now. - mpath: TyperApp.SVGProps; - circle: TyperApp.SVGProps; - clipPath: TyperApp.SVGProps; - defs: TyperApp.SVGProps; - desc: TyperApp.SVGProps; - ellipse: TyperApp.SVGProps; - feBlend: TyperApp.SVGProps; - feColorMatrix: TyperApp.SVGProps; - feComponentTransfer: TyperApp.SVGProps; - feComposite: TyperApp.SVGProps; - feConvolveMatrix: TyperApp.SVGProps; - feDiffuseLighting: TyperApp.SVGProps; - feDisplacementMap: TyperApp.SVGProps; - feDistantLight: TyperApp.SVGProps; - feDropShadow: TyperApp.SVGProps; - feFlood: TyperApp.SVGProps; - feFuncA: TyperApp.SVGProps; - feFuncB: TyperApp.SVGProps; - feFuncG: TyperApp.SVGProps; - feFuncR: TyperApp.SVGProps; - feGaussianBlur: TyperApp.SVGProps; - feImage: TyperApp.SVGProps; - feMerge: TyperApp.SVGProps; - feMergeNode: TyperApp.SVGProps; - feMorphology: TyperApp.SVGProps; - feOffset: TyperApp.SVGProps; - fePointLight: TyperApp.SVGProps; - feSpecularLighting: TyperApp.SVGProps; - feSpotLight: TyperApp.SVGProps; - feTile: TyperApp.SVGProps; - feTurbulence: TyperApp.SVGProps; - filter: TyperApp.SVGProps; - foreignObject: TyperApp.SVGProps; - g: TyperApp.SVGProps; - image: TyperApp.SVGProps; - line: TyperApp.SVGProps; - linearGradient: TyperApp.SVGProps; - marker: TyperApp.SVGProps; - mask: TyperApp.SVGProps; - metadata: TyperApp.SVGProps; - path: TyperApp.SVGProps; - pattern: TyperApp.SVGProps; - polygon: TyperApp.SVGProps; - polyline: TyperApp.SVGProps; - radialGradient: TyperApp.SVGProps; - rect: TyperApp.SVGProps; - stop: TyperApp.SVGProps; - switch: TyperApp.SVGProps; - symbol: TyperApp.SVGProps; - text: TyperApp.SVGProps; - textPath: TyperApp.SVGProps; - tspan: TyperApp.SVGProps; - use: TyperApp.SVGProps; - view: TyperApp.SVGProps; + svg: Typerapp.SVGProps; + + animate: Typerapp.SVGProps; // TODO: It is SVGAnimateElement but is not in TypeScript's lib.dom.d.ts for now. + animateMotion: Typerapp.SVGProps; + animateTransform: Typerapp.SVGProps; // TODO: It is SVGAnimateTransformElement but is not in TypeScript's lib.dom.d.ts for now. + mpath: Typerapp.SVGProps; + circle: Typerapp.SVGProps; + clipPath: Typerapp.SVGProps; + defs: Typerapp.SVGProps; + desc: Typerapp.SVGProps; + ellipse: Typerapp.SVGProps; + feBlend: Typerapp.SVGProps; + feColorMatrix: Typerapp.SVGProps; + feComponentTransfer: Typerapp.SVGProps; + feComposite: Typerapp.SVGProps; + feConvolveMatrix: Typerapp.SVGProps; + feDiffuseLighting: Typerapp.SVGProps; + feDisplacementMap: Typerapp.SVGProps; + feDistantLight: Typerapp.SVGProps; + feDropShadow: Typerapp.SVGProps; + feFlood: Typerapp.SVGProps; + feFuncA: Typerapp.SVGProps; + feFuncB: Typerapp.SVGProps; + feFuncG: Typerapp.SVGProps; + feFuncR: Typerapp.SVGProps; + feGaussianBlur: Typerapp.SVGProps; + feImage: Typerapp.SVGProps; + feMerge: Typerapp.SVGProps; + feMergeNode: Typerapp.SVGProps; + feMorphology: Typerapp.SVGProps; + feOffset: Typerapp.SVGProps; + fePointLight: Typerapp.SVGProps; + feSpecularLighting: Typerapp.SVGProps; + feSpotLight: Typerapp.SVGProps; + feTile: Typerapp.SVGProps; + feTurbulence: Typerapp.SVGProps; + filter: Typerapp.SVGProps; + foreignObject: Typerapp.SVGProps; + g: Typerapp.SVGProps; + image: Typerapp.SVGProps; + line: Typerapp.SVGProps; + linearGradient: Typerapp.SVGProps; + marker: Typerapp.SVGProps; + mask: Typerapp.SVGProps; + metadata: Typerapp.SVGProps; + path: Typerapp.SVGProps; + pattern: Typerapp.SVGProps; + polygon: Typerapp.SVGProps; + polyline: Typerapp.SVGProps; + radialGradient: Typerapp.SVGProps; + rect: Typerapp.SVGProps; + stop: Typerapp.SVGProps; + switch: Typerapp.SVGProps; + symbol: Typerapp.SVGProps; + text: Typerapp.SVGProps; + textPath: Typerapp.SVGProps; + tspan: Typerapp.SVGProps; + use: Typerapp.SVGProps; + view: Typerapp.SVGProps; } } } \ No newline at end of file From ea5b8f86c0807066f2725112adb95cb23bcc1e58 Mon Sep 17 00:00:00 2001 From: diontools Date: Thu, 22 Aug 2019 17:10:28 +0900 Subject: [PATCH 24/29] 0.1.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40fcdd8..6049444 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typerapp", - "version": "0.1.0-beta.1", + "version": "0.1.0-beta.2", "description": "Typerapp is type-safe Hyperapp V2.", "main": "lib/main/index.js", "types": "main/index.ts", From 8f28e924ec94ec69a34cb9dfedb9792cbdaacc1b Mon Sep 17 00:00:00 2001 From: diontools Date: Thu, 22 Aug 2019 17:24:26 +0900 Subject: [PATCH 25/29] conditional subscription type --- main/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main/index.ts b/main/index.ts index 238c828..16ef038 100644 --- a/main/index.ts +++ b/main/index.ts @@ -530,7 +530,10 @@ export type Effect = [(dispatch: Dispatch, props: P) => void, P export type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] /** Return type of `subscriptions` function on `app` */ -export type Subscriptions = Subscription | boolean | (Subscription | boolean)[] +export type ConditionalSubscription = Subscription | boolean + +/** Return type of `subscriptions` function on `app` */ +export type Subscriptions = ConditionalSubscription | ConditionalSubscription[] /** `dispatch` function type */ export type Dispatch = { From f1efd534f16cab05b57b1889bcca8d689c634d7f Mon Sep 17 00:00:00 2001 From: diontools Date: Thu, 22 Aug 2019 18:25:47 +0900 Subject: [PATCH 26/29] change default props type of Effect/Subs --- main/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/index.ts b/main/index.ts index 16ef038..a92e1c5 100644 --- a/main/index.ts +++ b/main/index.ts @@ -524,10 +524,10 @@ export type EffectAction = : Action | [Action, P] | Action | [Action, (effectPayload: EP) => P] /** Effect: Side effect declaration. [Details](https://github.com/jorgebucaran/hyperapp/issues/750) */ -export type Effect = [(dispatch: Dispatch, props: P) => void, P] +export type Effect = [(dispatch: Dispatch, props: P) => void, P] /** Subscription: Event stream subscription. [Details](https://github.com/jorgebucaran/hyperapp/issues/752) */ -export type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] +export type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] /** Return type of `subscriptions` function on `app` */ export type ConditionalSubscription = Subscription | boolean From 0a8b7cdddfd418924ee7f4dac9927117f1d46f29 Mon Sep 17 00:00:00 2001 From: diontools Date: Fri, 23 Aug 2019 11:45:03 +0900 Subject: [PATCH 27/29] add CHANGELOG --- CHANGELOG.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..de87875 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,85 @@ +# Changelog + +## v0.1.0 + +Upgraded to Hyperapp 2.0.1 base. + +### Breaking Changes + +* Changed Signature of Effect / Subscription. + +```tsx +// new: dispatch moved to first argument. And, props default type is undefined. +type Effect = [(dispatch: Dispatch, props: P) => void, P] +type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] + +// old +type Effect = [(props: P, dispatch: Dispatch) => void, P] +type Subscription = [(props: P, dispatch: Dispatch) => () => void, P] +``` + +* Renamed `container` to `node`. + +```tsx +// new +app({ + node: document.getElementById('app') +}) + +// old +app({ + container: document.getElementById('app') +}) +``` + +* Renamed `render` to `view` of `Lazy`. + +```tsx +// new + + +// old + +``` + +* Use native event type instead of React specific SyntheticEvent on Html.d.ts. + +### Obsoleted + +* Deleted `ActionParamOf`. Payload Creator instead. +* Deleted `mergedAction`. Payload Creator instead. + +### New API + +* Payload Creator + +```tsx +import { httpText } from 'typerapp/fx' + +const Act: Action = (state, payload) => ({ ...state, value: payload.value }) + +// create payload { value: string } from result by httpText. +httpText([Act, result => ({ value: result.text })], '/') +``` + +* Allow number of `key`. + +```tsx +
    +``` + +* Add `middleware` function. + +```tsx +app({ + middleware: dispatch => (action: any, props?: any) => { + console.log('dispatch', action, props); + dispatch(action, props); + } +}) +``` + +## v0.0.1 + +Initial Release + From 5ee0d107ca950916f4c5555f2be7c738966a5a7a Mon Sep 17 00:00:00 2001 From: diontools Date: Fri, 23 Aug 2019 11:45:15 +0900 Subject: [PATCH 28/29] fix README --- README.md | 87 ++++++++----------------------------------------------- 1 file changed, 12 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index f419a1b..ec88377 100644 --- a/README.md +++ b/README.md @@ -21,27 +21,8 @@ npm install typerapp ## Modified points from Hyperapp -1. ~~Remove `data` argument from Action~~ -2. Add `dispatch` to `view` arguments -3. Pure DOM Events - -### ~~Remove `data` argument from Action~~ - -Typerapp Action has only two arguments. - -Hyperapp: - -```js -const Act = (state, { value }, data) => ({...}) -``` - -**Obsoluted: Currently Hyperapp V2 does NOT have a `data` argument.** - -Typerapp: - -```typescript -const Act: Action = (state, params) => ({...}) -``` +1. Add `dispatch` to `view` arguments +1. Pure DOM Events ### Add `dispatch` to `view` arguments @@ -106,8 +87,8 @@ Type-safe Actions, Effects, Subscriptions, HTML Elements, and more... Type: ```typescript -export type ActionResult = S | [S, ...Effect[]] -export type Action = (state: S, params: P) => ActionResult +export type ActionResult = S | [S, ...Effect[]] +export type Action = (state: S, payload: P) => ActionResult ``` Use: @@ -117,20 +98,18 @@ Use: const Increment: Action = state => ({ ...state, value: state.value + 1 }) // with parameter -const Add: Action = (state, params) => ({ +const Add: Action = (state, payload) => ({ ...state, - value: state.value + params.amount + value: state.value + payload.amount }) ``` ### Effects -**Note: Change signature (move `dispatch` to first argument)** - Type: ```typescript -export type Effect = [(dispatch: Dispatch, props: P) => void, P] +export type Effect = [(dispatch: Dispatch, props: P) => void, P] ``` Define Effect: @@ -171,12 +150,10 @@ const DelayAdd: Action = (state, params) => [ ### Subscriptions -**Note: Change signature (move `dispatch` to first argument)** - Type: ```typescript -export type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] +export type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] ``` Define Subscription: @@ -228,7 +205,7 @@ const Act: Action = state => ({ Workaround: ```typescript -// type alias for Action/ActionResult +// type alias for Action/ActionResult (for writing short) type MyAction

    = Action type MyResult = ActionResult @@ -272,25 +249,6 @@ export const view: View = ({ part: state }, dispatch) =>

    ``` -### ~~ActionParamOf~~ - -**Obsoluted: Payload Creator instead** - -~~`ActionParamOf` type gets parameter type of Action from Effect/Subscription Constructor.~~ - -```typescript -import { ActionParamOf } from 'typerapp' -import { httpJson } from 'typerapp/fx' - -// { json: unknown } -type ParamType = ActionParamOf - -const JsonReceived: Action = (state, params) => ({ - ...state, - text: JSON.stringify(params.json) -}) -``` - ### Helmet `Helmet` renders to the head element of DOM. @@ -316,7 +274,7 @@ const renderHead = (props: { title: string }) => app({ view: (state, dispatch) =>
    - +
    }) ``` @@ -430,28 +388,7 @@ import "typerapp/main/svg-alias" ``` -### ~~mergeAction~~ - -**Obsoluted: Payload Creator instead** - -~~In Typerapp, if your Effect/Subscription returns a value by Action, you must merge a return value into Action parameter, because Typerapp has not `data` of Action.~~ +## Changelog -~~In that case, you can use `mergeAction` function.~~ - -```typescript -import { EffectAction, Dispatch, Effect } from "typerapp" -import { mergeAction } from 'typerapp/fx/utils' - -export type RunnerProps = { - action: EffectAction -} - -const effectRunner = (props: RunnerProps, dispatch: Dispatch) => { - dispatch(mergeAction(props.action, { returnValue: 1234 })) -} - -export function effect(action: RunnerProps["action"]): Effect> { - return [effectRunner, { action }] -} -``` +[See CHANGELOG.md](CHANGELOG.md) From b78eb26f74d49eac3d3fc636870be263dd0ab923 Mon Sep 17 00:00:00 2001 From: diontools Date: Fri, 23 Aug 2019 14:43:20 +0900 Subject: [PATCH 29/29] 0.1.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6049444..c8ccda9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typerapp", - "version": "0.1.0-beta.2", + "version": "0.1.0-beta.3", "description": "Typerapp is type-safe Hyperapp V2.", "main": "lib/main/index.js", "types": "main/index.ts",