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 + diff --git a/README.md b/README.md index 7c969ff..ec88377 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Typerapp is type-safe [Hyperapp V2](https://github.com/jorgebucaran/hyperapp) + α. It's written in TypeScript. -Sample: [![Edit typerapp-sample](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/diontools/typerapp-sample/tree/master/?fontsize=14&module=%2Fsrc%2Findex.tsx) +Sample: [![Edit typerapp-sample](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/diontools/typerapp-sample/tree/v0.1.0/typerapp-sample/?fontsize=14&module=%2Fsrc%2Findex.tsx) -Minimum sample: [![Edit typerapp-minimum-sample](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/diontools/typerapp-minimum-sample/tree/master/?fontsize=14&module=%2Fsrc%2Findex.tsx) +Minimum sample: [![Edit typerapp-minimum-sample](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/diontools/typerapp-sample/tree/v0.1.0/typerapp-minimum-sample/?fontsize=14&module=%2Fsrc%2Findex.tsx) ## Install @@ -21,25 +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) => ({...}) -``` - -Typerapp: - -```typescript -const Act: Action = (state, params) => ({...}) -``` +1. Add `dispatch` to `view` arguments +1. Pure DOM Events ### Add `dispatch` to `view` arguments @@ -104,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: @@ -115,9 +98,9 @@ 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 }) ``` @@ -126,7 +109,7 @@ const Add: Action = (state, params) => ({ Type: ```typescript -export type Effect = [(props: P, dispatch: Dispatch) => void, P] +export type Effect = [(dispatch: Dispatch, props: P) => void, P] ``` Define Effect: @@ -139,7 +122,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) } @@ -170,7 +153,7 @@ const DelayAdd: Action = (state, params) => [ Type: ```typescript -export type Subscription = [(props: P, dispatch: Dispatch) => () => void, P] +export type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] ``` Define Subscription: @@ -183,7 +166,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) } @@ -222,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 @@ -266,23 +249,6 @@ export const view: View = ({ part: state }, dispatch) =>

``` -### ActionParamOf - -`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. @@ -308,7 +274,7 @@ const renderHead = (props: { title: string }) => app({ view: (state, dispatch) =>
- +
}) ``` @@ -422,26 +388,7 @@ import "typerapp/main/svg-alias" ``` -### mergeAction - -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. +## Changelog -```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) 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..a92e1c5 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,517 +76,560 @@ 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) + return (vdom.node = node) } -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) - } - } +var getKey = function (vdom: VNode) { + return vdom == null ? null : vdom.key } -var getKey = function (node: VNode) { - return node == null ? null : node.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: VNode | null + var oldVKid: VNode | null - if (node != null) removeElement(parent, node) + var oldKey: Key | null + var newKey: Key | null - 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: Key | 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

{ +/** 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;) { +/** Create VNode */ +export var h = function (name: string | Function, props?: {} | null, _children?: any): VNode { + 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) } +/** 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 payload type of Action */ export type Empty = { ___dummy: never } -export type ActionResult = S | [S, ...Effect[]] -export type Action = (state: S, params: P) => ActionResult +/** Return type of Action */ +export type ActionResult = S | [S, ...Effect[]] + +/** Action: Next state and effects creation. [Details](https://github.com/jorgebucaran/hyperapp/issues/749) */ +export type Action = (state: S, payload: 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 | [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] + +/** Subscription: Event stream subscription. [Details](https://github.com/jorgebucaran/hyperapp/issues/752) */ +export type Subscription = [(dispatch: Dispatch, props: P) => () => void, P] +/** Return type of `subscriptions` function on `app` */ +export type ConditionalSubscription = Subscription | boolean + +/** Return type of `subscriptions` function on `app` */ +export type Subscriptions = ConditionalSubscription | ConditionalSubscription[] + +/** `dispatch` function type */ export type Dispatch = { + /** Dispatch Action without payload */ (action: Action): void -

(action: Action, params: P): void -

(actionWithParams: [Action, P]): void + + /** Dispatch Action with payload */ +

(action: Action, payload: P): void + + /** Dispatch Action with payload (tuple style) */ +

(actionWithPayload: [Action, P]): void + + /** Dispatch ActionResult */ (result: ActionResult): void -

(all: Action | [Action, P] | ActionResult): void + + /** + * WARNING: This Action NOT used payload + * @deprecated + */ + (effectAction: Action, effectPayload: EP): void + + /** Dispatch Action for Effect */ + (effectAction: EffectAction, effectPayload: EP): void + + /** Dispatch Action for Effect without Payload Creator */ +

(effectAction: EffectAction): void + + /** Dispatch for init */ + (init: Action | 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 + + /** render VDOM */ + view: View, + + /** Subscribe subscriptions */ + subscriptions?: (state: S) => Subscriptions, + + /** Element for rendering target */ + node: Element, + + /** Interrupt `dispatch` */ + middleware?: (dispatch: Dispatch) => Dispatch, } -export type Children = VNode | string | number | null +/** `key` prop type of VNode */ +export type Key = string | number +/** VNode props type */ export type VNodeProps = { [key: string]: any } +/** Lazy props type */ +export type LazyProp

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

extends VNode { - lazy: P - render: () => VNode + lazy?: LazyProp, } +/** 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 +/** Modularized Action for `actionCreator` */ +export type ModularizedAction = (state: S[N], payload: P) => S[N] | [S[N], ...Effect[]] + +/** Modularization function. [Details](https://github.com/diontools/typerapp#actioncreator) */ export function actionCreator() { - return (name: N): ((action: Action) => Action) => { + return (name: N): (

(action: ModularizedAction) => Action) => { return (action) => { - return (state, params) => { - const r = action(state[name], params) + return (state, payload) => { + const r = action(state[name], payload) if (Array.isArray(r)) { - const a: ActionResult = r[0] === state[name] ? [state] : [{ ...state, [name]: r[0] }] - for (let i = 1; i < r.length; i++) a.push(r[i] as Effect) - return a + // force replace state + r[0] = r[0] === state[name] ? state : { ...state, [name]: r[0] } as any + return r as any } return r === state[name] ? state : { ...state, [name]: r } } diff --git a/package.json b/package.json index c7eee1b..c8ccda9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typerapp", - "version": "0.0.1", + "version": "0.1.0-beta.3", "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..135a55b 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, {}], '/') @@ -124,6 +132,20 @@ http(HttpParamedAction, '/') http(null, '/') +const HttpResponseAction: Action = (state, payload) => ({ + ...state, + value: payload.response.status +}) + +// OK +http(HttpResponseAction, '/') +http([HttpResponseAction, p => p], '/') +http([HttpResponseAction, { response: {} as Response }], '/') + +// NG +http([HttpResponseAction], '/') +http([HttpResponseAction, undefined], '/') + const partAction = actionCreator()('part') diff --git a/sample/index.tsx b/sample/index.tsx index d900fbd..1d55e88 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'; @@ -6,15 +6,15 @@ import { createRouter, Link, RoutingInfo, Redirect } from 'typerapp/router' import { State, RouteProps } from './states' import * as part from './part' -const Add: Action = (state, params) => ({ +const Add: Action = (state, payload) => ({ ...state, - value: state.value + params.amount, + value: state.value + payload.amount, }) -const AddWithDelay: Action = (state, params) => [ +const AddWithDelay: Action = (state, payload) => [ state, - delay([Add, { amount: params.amount }], { duration: params.duration }), + delay([Add, { amount: payload.amount }], { duration: payload.duration }), ] @@ -25,15 +25,17 @@ const ToggleAuto: Action = state => ({ const Input: Action = (state, value) => ({ ...state, input: value }) +const AddToList: Action = (state, value) => ({ ...state, list: [...state.list, value] }) +const RemoveFromList: Action = (state, index) => ({ ...state, list: state.list.filter((v, i) => i !== index) }) -const OnTextResponse: Action> = (state, params) => ({ +const OnTextResponse: Action = (state, payload) => ({ ...state, - text: params.text + text: payload.text }) const Act1: Action = state => ({ ...state, value: state.value + 1 }) const Act2: Action = state => ({ ...state, value: state.value + 2 }) -const Act3: Action = (state, params) => ({ ...state, value: params.newValue }) +const Act3: Action = (state, payload) => ({ ...state, value: payload.newValue }) execute(dispatch => { dispatch(Act1) @@ -82,7 +84,7 @@ const Counter = (state: State, dispatch: Dispatch, amount: number = 1) =>
value: {state.value}
- +
@@ -104,7 +106,7 @@ const router = createRouter({ path: '/fetch', view: (state, dispatch, params) =>

Fetch

- +
text: {state.text}
, @@ -140,6 +142,16 @@ const router = createRouter({ title: (state, params) => 'Redirect!', path: '/redirect', view: (state, dispatch, params) => , + }, { + title: (state, params) => 'List', + path: '/list', + view: (state, dispatch, params) =>
+ {state.list.map((text, index) =>
{text}
)} +
{ ev.preventDefault(); dispatch(AddToList, state.input)}}> + dispatch(Input, ev.currentTarget.value)} /> + +
+
}], matched: (routing, dispatch) => dispatch(SetRoute, routing), }) @@ -153,11 +165,12 @@ app({ part: { value: 0, }, + list: [], routing: undefined, }, view: (state, dispatch) => (
- +
  • home
  • @@ -169,6 +182,7 @@ app({
  • Style
  • Sub
  • Redirect
  • +
  • List
  • unknown
{state.routing ? state.routing.route.view(state, dispatch, state.routing.params) :
404
} @@ -178,5 +192,9 @@ app({ router, state.auto && timer([Add, { amount: 1 }], { interval: 500 }), ], - container: document.body, + 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/part.tsx b/sample/part.tsx index cc6cfac..d9af938 100644 --- a/sample/part.tsx +++ b/sample/part.tsx @@ -1,17 +1,17 @@ -import { h, View, actionCreator, ActionParamOf } from 'typerapp' +import { h, View, actionCreator } from 'typerapp' import { httpText } from 'typerapp/fx'; import { State } from './states' const createAction = actionCreator()('part') -const TextReceived = createAction>((state, params) => ({ +const TextReceived = createAction<{ text: string }>((state, payload) => ({ ...state, - value: params.text.length, + value: payload.text.length, })) -const RequestText = createAction((state) => [ +const RequestText = createAction((state, url) => [ state, - httpText(TextReceived, '/'), + httpText([TextReceived, res => ({ text: res.text })], url), ]) export const view: View = ({ part: state }, dispatch) =>
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 diff --git a/types/Html.d.ts b/types/Html.d.ts index c3ad792..f9578d0 100644 --- a/types/Html.d.ts +++ b/types/Html.d.ts @@ -1,12 +1,13 @@ // Type definitions for Typerapp // forked from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react -// synced: 2019/04/12 +// synced: 2019/08/08 /// import * as CSS from 'csstype'; -import { VNode, Class } from 'typerapp' +import { VNode, Class, Key } from 'typerapp' +type NativeEvent = Event; type NativeAnimationEvent = AnimationEvent; type NativeClipboardEvent = ClipboardEvent; type NativeCompositionEvent = CompositionEvent; @@ -21,52 +22,25 @@ 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 { - type Key = string | number; - - interface RefObject { - readonly current: T | null; - } - - type Ref = { bivarianceHack(instance: T | null): void }["bivarianceHack"] | RefObject | null; - type LegacyRef = string | Ref; - - type ComponentState = any; - - interface Attributes { - key?: Key; - } - interface RefAttributes extends Attributes { - ref?: Ref; - } - interface ClassAttributes extends Attributes { - ref?: LegacyRef; +declare namespace Typerapp { + interface TyperAppAttribute { + key?: Key } // // Event System // ---------------------------------------------------------------------- - // TODO: change any to unknown when moving to TS v3 - interface BaseSyntheticEvent { - nativeEvent: E; - currentTarget: C; - target: T; - bubbles: boolean; - cancelable: boolean; - defaultPrevented: boolean; - eventPhase: number; - isTrusted: boolean; - preventDefault(): void; - isDefaultPrevented(): boolean; - stopPropagation(): void; - isPropagationStopped(): boolean; - persist(): void; - timeStamp: number; - type: string; - } + + type BaseEvent = { + [K in keyof TEvent]: + K extends keyof TReplaces ? TReplaces[K] : + K extends 'currentTarget' ? TCurrent : + K extends 'target' ? TTarget : + TEvent[K] + } & Pick> // add remain props /** * currentTarget - a reference to the element on which the event listener is registered. @@ -75,136 +49,53 @@ declare namespace TyperApp { * This might be a child element to the element on which the event listener is registered. * If you thought this should be `EventTarget & T`, see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/12239 */ - interface SyntheticEvent extends BaseSyntheticEvent {} + type TypedEvent = BaseEvent - interface ClipboardEvent extends SyntheticEvent { + type ClipboardEvent = TypedEvent - interface CompositionEvent extends SyntheticEvent { - data: string; - } + type CompositionEvent = TypedEvent - interface DragEvent extends MouseEvent { + type DragEvent = MouseEvent - interface PointerEvent extends MouseEvent { - pointerId: number; - pressure: number; - tiltX: number; - tiltY: number; - width: number; - height: number; + type PointerEvent = MouseEvent - interface FocusEvent extends SyntheticEvent { - relatedTarget: EventTarget; + type FocusEvent = TypedEvent - // tslint:disable-next-line:no-empty-interface - interface FormEvent extends SyntheticEvent { - } + type FormEvent = TypedEvent - interface InvalidEvent extends SyntheticEvent { + type ChangeEvent = TypedEvent - interface ChangeEvent extends SyntheticEvent { - target: EventTarget & T; - } + type KeyboardEvent = TypedEvent - interface KeyboardEvent extends SyntheticEvent { - altKey: boolean; - charCode: number; - ctrlKey: boolean; - /** - * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method. - */ - getModifierState(key: string): boolean; - /** - * See the [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#named-key-attribute-values). for possible values - */ - key: string; - keyCode: number; - locale: string; - location: number; - metaKey: boolean; - repeat: boolean; - shiftKey: boolean; - which: number; - } + type MouseEvent = TypedEvent - interface MouseEvent extends SyntheticEvent { - altKey: boolean; - button: number; - buttons: number; - clientX: number; - clientY: number; - ctrlKey: boolean; - /** - * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method. - */ - getModifierState(key: string): boolean; - metaKey: boolean; - movementX: number; - movementY: number; - pageX: number; - pageY: number; - relatedTarget: EventTarget; - screenX: number; - screenY: number; - shiftKey: boolean; - } - - interface TouchEvent extends SyntheticEvent { - altKey: boolean; - changedTouches: TouchList; - ctrlKey: boolean; - /** - * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method. - */ - getModifierState(key: string): boolean; - metaKey: boolean; - shiftKey: boolean; - targetTouches: TouchList; - touches: TouchList; - } + type TouchEvent = TypedEvent - interface UIEvent extends SyntheticEvent { - detail: number; - view: AbstractView; - } + type UIEvent = TypedEvent - interface WheelEvent extends MouseEvent { - deltaMode: number; - deltaX: number; - deltaY: number; - deltaZ: number; - } + type WheelEvent = TypedEvent - interface AnimationEvent extends SyntheticEvent { - animationName: string; - elapsedTime: number; - pseudoElement: string; - } + type AnimationEvent = TypedEvent - interface TransitionEvent extends SyntheticEvent { - elapsedTime: number; - propertyName: string; - pseudoElement: string; - } + type TransitionEvent = TypedEvent // // Event Handler Types // ---------------------------------------------------------------------- - type EventHandler> = { bivarianceHack(event: E): void }["bivarianceHack"]; + type EventHandler> = { bivarianceHack(event: E): void }["bivarianceHack"]; - type ReactEventHandler = EventHandler>; + type TypedEventHandler = EventHandler>; type ClipboardEventHandler = EventHandler>; type CompositionEventHandler = EventHandler>; @@ -225,32 +116,9 @@ declare namespace TyperApp { // Props / DOM Attributes // ---------------------------------------------------------------------- - /** - * @deprecated. This was used to allow clients to pass `ref` and `key` - * to `createElement`, which is no longer necessary due to intersection - * types. If you need to declare a props object before passing it to - * `createElement` or a factory, use `ClassAttributes`: - * - * ```ts - * var b: Button | null; - * var props: ButtonProps & ClassAttributes