This commit is contained in:
Irakli Gozalishvili 2015-10-15 23:44:26 -07:00
Коммит 6498c51d0d
20 изменённых файлов: 1184 добавлений и 0 удалений

15
.flowconfig Normal file
Просмотреть файл

@ -0,0 +1,15 @@
[ignore]
.*/examples/.*
.*/src/test/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex/lib/.*
.*/node_modules/reflex/dist/.*
.*/reflex-virtual-dom-renderer/lib/.*
.*/reflex-virtual-dom-renderer/dist/.*
[libs]
./node_modules/reflex/interfaces/
[include]
[options]

2
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
lib
dist

2
.npmignore Normal file
Просмотреть файл

@ -0,0 +1,2 @@
*~
~*

Просмотреть файл

@ -0,0 +1,16 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
.*/node_modules/reflex/lib/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-renderer/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'

Просмотреть файл

@ -0,0 +1,135 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import gutil from 'gulp-util';
import watchify from 'watchify';
import child from 'child_process';
import http from 'http';
import path from 'path';
import babelify from 'babelify';
import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
this.bundler = browserify({
entries: ['./src/' + entry],
debug: settings.debug,
cache: {},
transform: settings.transform,
plugin: settings.plugin
});
this.watcher = settings.watch &&
watchify(this.bundler)
.on('update', this.build);
}
Bundler.prototype.bundle = function() {
gutil.log(`Begin bundling: '${this.entry}'`);
return this.watcher ? this.watcher.bundle() : this.bundler.bundle();
}
Bundler.prototype.build = function() {
var bundle = this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message);
})
.pipe(source(this.entry + '.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.on('error', (error) => {
gutil.beep();
console.error(`Failed to make source maps for: '${this.entry}'`,
error.message);
});
return (this.compression ? bundle.pipe(uglify(this.compression)) : bundle)
.on('error', (error) => {
gutil.beep();
console.error(`Failed to bundle: '${this.entry}'`,
error.message);
})
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/'))
.on('end', () => {
gutil.log(`Completed bundling: '${this.entry}'`);
});
}
var bundler = function(entry) {
return gulp.task(entry, function() {
return new Bundler(entry).build();
});
}
// Starts a static http server that serves browser.html directory.
gulp.task('server', function() {
var server = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
}));
server.listen(settings.port);
});
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
gulp.task('hotreload', function() {
settings.plugin.push(hmr);
settings.transform.push(hotify);
});
bundler('index');
gulp.task('build', [
'compressor',
'index'
]);
gulp.task('watch', [
'watcher',
'index'
]);
gulp.task('develop', sequencial('watch', 'server'));
gulp.task('live', ['hotreload', 'develop']);
gulp.task('default', ['live']);

Просмотреть файл

@ -0,0 +1,10 @@
<html>
<head>
<title>Sample App</title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,34 @@
{
"name": "counter",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-renderer": "latest"
},
"devDependencies": {
"browserify": "11.0.1",
"watchify": "3.3.1",
"babelify": "6.1.3",
"browserify-hmr": "0.3.0",
"hotify": "0.0.1",
"babel-core": "5.8.23",
"babel-runtime": "5.8.20",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
}
}

Просмотреть файл

@ -0,0 +1,55 @@
/* @flow */
import {Record, Union} from "typed-immutable";
import {html, forward} from "reflex";
/*::
import type {Address} from "reflex/type/signal";
import type {VirtualNode} from "reflex/type/renderer";
export type Model = {value:number};
export type Increment = {$typeof: 'Increment'};
export type Decrement = {$typeof: 'Decrement'};
export type Action = Increment|Decrement;
*/
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
const result = Object.assign({}, record)
result[field] = value
return result
}
export const create = ({value}/*:Model*/)/*:Model*/ => ({value})
export const Inc = ()/*:Increment*/ => ({$typeof: 'Increment'})
export const Dec = ()/*:Decrement*/ => ({$typeof: 'Decrement'})
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
action.$typeof === 'Increment' ?
set(model, 'value', model.value + 1) :
action.$typeof === 'Decrement' ?
set(model, 'value', model.value - 1) :
model
const counterStyle = {
value: {
fontWeight: 'bold'
}
}
// View
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
return html.span({key: 'counter'}, [
html.button({
key: 'decrement',
onClick: forward(address, Dec)
}, ["-"]),
html.span({
key: 'value',
style: counterStyle.value,
}, [String(model.value)]),
html.button({
key: 'increment',
onClick: forward(address, Inc)
}, ["+"])
]);
};

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import * as Counter from "./counter"
import {start} from "reflex"
import {Renderer} from "reflex-virtual-dom-renderer"
var app = start({
initial: Counter.create(window.app != null ?
window.app.model.value :
{value: 0}),
update: Counter.update,
view: Counter.view
});
window.app = app
var renderer = new Renderer({target: document.body})
app.view.subscribe(renderer.address)

5
interfaces/dom.js Normal file
Просмотреть файл

@ -0,0 +1,5 @@
/*flow*/
declare function requestAnimationFrame(callback: any): number;
declare class performance {
static now(): number;
}

53
package.json Normal file
Просмотреть файл

@ -0,0 +1,53 @@
{
"name": "reflex-virtual-dom-renderer",
"version": "0.0.0",
"description": "React based renderer for reflex",
"keywords": [
"reflex",
"react",
"renderer"
],
"author": "Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com)",
"homepage": "https://github.com/Gozala/reflex-react-renderer",
"main": "./lib/index.js",
"dependencies": {
"blanks": "0.0.2",
"object-as-dictionary": "0.0.3",
"react": "0.13.3",
"virtual-dom": "2.1.1"
},
"devDependencies": {
"babel": "5.6.14",
"babel-plugin-flow-comments": "1.0.9",
"reflex": "0.0.40",
"tap": "1.1.0",
"tape": "2.3.2"
},
"babel": {
"sourceMaps": "inline",
"optional": [
"spec.protoToAssign"
]
},
"scripts": {
"test": "tap lib/test/test-*.js",
"build-node": "babel ./src --out-dir ./lib --plugins flow-comments --blacklist flow",
"build-browser": "babel ./src --out-dir ./dist --modules umdStrict",
"build": "npm run build-node && npm run build-browser",
"prepublish": "npm run build"
},
"repository": {
"type": "git",
"url": "https://github.com/Gozala/reflex-react-renderer.git",
"web": "https://github.com/Gozala/reflex-react-renderer"
},
"bugs": {
"url": "https://github.com/Gozala/reflex-react-renderer/issues/"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/Gozala/reflex-react-renderer/License.md"
}
]
}

51
src/hooks/attribute.js Normal file
Просмотреть файл

@ -0,0 +1,51 @@
/* @flow */
import {dictionary} from "object-as-dictionary"
/*::
import * as type from "../../type/index"
*/
const nameOverrides/*:type.nameOverrides*/ = dictionary({
className: 'class',
htmlFor: 'for'
})
export const supportedAttributes/*:type.supportedAttributes*/ =
`accept acceptCharset accessKey action allowFullScreen allowTransparency alt
async autoComplete autoFocus autoPlay capture cellPadding cellSpacing charSet
challenge checked classID className cols colSpan content contentEditable contextMenu
controls coords crossOrigin data dateTime defer dir disabled download draggable
encType form formAction formEncType formMethod formNoValidate formTarget frameBorder
headers height hidden high href hrefLang htmlFor httpEquiv icon id inputMode
keyParams keyType label lang list loop low manifest marginHeight marginWidth max
maxLength media mediaGroup method min minlength multiple muted name noValidate open
optimum pattern placeholder poster preload radioGroup readOnly rel required role
rows rowSpan sandbox scope scoped scrolling seamless selected shape size sizes
span spellCheck src srcDoc srcSet start step summary tabIndex target title
type useMap value width wmode wrap
autoCapitalize autoCorrect
property
itemProp itemScope itemType itemRef itemID
unselectable
results autoSave
clipPath cx cy d dx dy fill fillOpacity fontFamily
fontSize fx fy gradientTransform gradientUnits markerEnd
markerMid markerStart offset opacity patternContentUnits
patternUnits points preserveAspectRatio r rx ry spreadMethod
stopColor stopOpacity stroke strokeDasharray strokeLinecap
strokeOpacity strokeWidth textAnchor transform version
viewBox x1 x2 x xlinkActuate xlinkArcrole xlinkHref xlinkRole
xlinkShow xlinkTitle xlinkType xmlBase xmlLang xmlSpace y1 y2 y`
.split(/\s+/)
.reduce((table, name) => {
table[name] = nameOverrides[name] != null ?
nameOverrides[name] :
name.toLowerCase()
return table
}, dictionary())

165
src/hooks/event-handler.js Normal file
Просмотреть файл

@ -0,0 +1,165 @@
/* @flow */
import {dictionary} from "object-as-dictionary"
/*::
import * as type from "../../type"
*/
const nameOverrides/*:type.nameOverrides*/ = dictionary({
DoubleClick: 'dblclick',
})
export const supportedEvents/*:type.supportedEvents*/ = [
// Clipboard Events
'Copy',
'Cut',
'Paste',
// Keyboard Events
'KeyDown',
'KeyPress',
'KeyUp',
// Focus Events
'Focus',
'Blur',
// Form Events
'Change',
'Input',
'Submit',
// Mouse Events
'Click',
'ContextMenu',
'DoubleClick',
'Drag',
'DragEnd',
'DragEnter',
'DragExit',
'DragLeave',
'DragOver',
'DragStart',
'Drop',
'MouseDown',
'MouseEnter',
'MouseLeave',
'MouseMove',
'MouseOut',
'MouseOver',
'MouseUp',
// Touch events
'TouchStart',
'TouchMove',
'TouchCancel',
'TouchEnd',
// UI Events
'Scroll',
// Wheel Events
'Wheel',
// Media Events
'Abort',
'CanPlay',
'CanPlayThrough',
'DurationChange',
'Emptied',
'Encrypted',
'Ended',
'Error',
'LoadedData',
'LoadedMetadata',
'LoadStart',
'Pause',
'Play',
'Playing',
'Progress',
'RateChange',
'Seeked',
'Seeking',
'Stalled',
'Suspend',
'TimeUpdate',
'VolumeChange',
'Waiting',
// Image Events
'Load',
'Error',
// Composition ?
'BeforeInput',
'CompositionEnd',
'CompositionStart',
'CompositionUpdate'
].reduce((table, name) => {
const type = nameOverrides[name] == null ? name.toLowerCase() :
nameOverrides[name]
table[`on${name}`] = {type, capture:false}
table[`on${name}Capture`] = {type, capture:true}
return table
}, dictionary())
const handleEvent/*:type.handleEvent*/ = phase => event => {
const {currentTarget, type} = event
const handler = currentTarget[`on${type}${phase}`]
if (typeof(handler) === 'function') {
handler(event)
}
if (handler != null && typeof(handler.handleEvent) === 'function') {
handler.handleEvent(event)
}
}
const handleCapturing = handleEvent('capture')
const handleBubbling = handleEvent('bubble')
export class EventHandler {
/*::
handler: type.EventListener;
*/
constructor(handler/*:type.EventListener*/) {
this.handler = handler
}
hook(node/*:type.EventTarget*/, name/*:string*/, previous/*:any*/) {
const config = supportedEvents[name]
if (config != null) {
const {type, capture} = config
const phase = capture ? 'capture' : 'bubble'
if (!(previous instanceof EventHandler)) {
const handler = capture ? handleCapturing : handleBubbling
node.addEventListener(type, handler, capture)
}
node[`on${type}${phase}`] = this.handler
}
}
unhook(node/*:type.EventTarget*/, name/*:string*/, next/*:any*/) {
const config = supportedEvents[name]
if (config != null) {
if (!(next instanceof EventHandler)) {
const {type, capture} = config
const phase = capture ? 'capture' : 'bubble'
const id = `on${type}${phase}`
const handler = node[id]
if (handler != null) {
node.removeEventListener(type, handler, capture)
}
delete node[id]
}
}
}
}
export const eventHandler/*:type.eventHandler*/ = address => {
const handler = address.reflexEventListener
if (handler == null) {
const handler = new EventHandler(address)
address.reflexEventListener = handler
return handler
} else {
return handler
}
}

110
src/index.js Normal file
Просмотреть файл

@ -0,0 +1,110 @@
/* @flow */
import diff from "virtual-dom/diff"
import createElement from "virtual-dom/create-element"
import patch from "virtual-dom/patch"
import {TextNode, text} from "./text"
import {VirtualNode, node} from "./node"
import {ThunkNode, thunk} from "./thunk"
/*::
import * as renderer from "reflex/type/renderer"
import * as signal from "reflex/type/signal"
*/
export class Renderer {
/*::
target: Element;
mount: ?(Element & {reflexTree?: renderer.ChildNode});
value: renderer.RootNode;
isScheduled: boolean;
version: number;
address: signal.Address<renderer.ChildNode>;
execute: () => void;
node: renderer.node;
thunk: renderer.thunk;
text: renderer.text;
*/
constructor({target}/*:{target: Element}*/) {
this.target = target
this.mount = (target.children.length === 1 &&
target.children[0].reflexTree != null) ?
target.children[0] :
null
this.address = this.receive.bind(this)
this.execute = this.execute.bind(this)
this.node = node
this.thunk = thunk
this.text = text
}
toString()/*:string*/{
return `Renderer({target: ${this.target}})`
}
receive(value/*:renderer.RootNode*/) {
this.value = value
this.schedule()
}
schedule() {
if (!this.isScheduled) {
this.isScheduled = true
this.version = requestAnimationFrame(this.execute)
}
}
execute(_/*:number*/) {
if (profile) {
console.time('render')
}
const start = performance.now()
// It is important to mark `isScheduled` as `false` before doing actual
// rendering since state changes in effect of reflecting current state
// won't be handled by this render cycle. For example rendering a state
// with updated focus will cause `blur` & `focus` events to be dispatched
// that happen synchronously, and there for another render cycle may be
// scheduled for which `isScheduled` must be `false`. Attempt to render
// this state may also cause a runtime exception but even then we would
// rather attempt to render updated states that end up being blocked
// forever.
this.isScheduled = false
if (profile) {
console.time('render')
}
this.value.renderWith(this)
const end = performance.now()
const time = end - start
if (time > 16) {
console.warn(`Render took ${time}ms & will cause frame drop`)
}
if (profile) {
console.timeEnd('render')
}
}
render(tree/*:renderer.ChildNode*/) {
const {mount, target} = this
if (mount) {
patch(mount, diff(mount.reflexTree, tree))
mount.reflexTree = tree
} else {
const mount = createElement(tree)
mount.reflexTree = tree
target.innerHTML = ""
this.mount = mount
target.appendChild(mount)
}
}
}
let profile = null
export const time = (name/*:string*/)/*:void*/ =>
void(profile = `${name == null ? "" : name} `)
export const timeEnd = () =>
void(profile = null)

165
src/node.js Normal file
Просмотреть файл

@ -0,0 +1,165 @@
/* @flow */
/*::
import * as type from "../type"
*/
import isVirtualNode from "virtual-dom/vnode/is-vnode"
import isWidget from "virtual-dom/vnode/is-widget"
import isThunk from "virtual-dom/vnode/is-thunk"
import isHook from "virtual-dom/vnode/is-vhook"
import version from "virtual-dom/vnode/version"
import SoftSetHook from "virtual-dom/virtual-hyperscript/hooks/soft-set-hook"
import {TextNode} from "./text"
import {empty} from "blanks/lib/array"
import {blank} from "blanks/lib/object"
import {supportedEvents, eventHandler} from "./hooks/event-handler"
import {supportedAttributes} from "./hooks/attribute"
export class VirtualNode {
/*::
$$typeof: "VirtualNode";
type: "VirtualNode";
version: number;
tagName: type.TagName;
namespace: ?string;
key: ?string;
properties: type.PropertyDictionary;
children: Array<type.ChildNode>;
count: number;
descendants: number;
hasWidgets: boolean;
hasThunks: boolean;
hooks: ?type.HookDictionary;
*/
constructor(tagName/*:string*/, namespace/*:?string*/, properties/*:type.PropertyDictionary*/, children/*:Array<type.ChildNode>*/) {
this.tagName = tagName
this.namespace = namespace
this.key = properties.key != null ? String(properties.key) : null
this.children = children
let count = children.length || 0
let descendants = 0
let hasWidgets = false
let hasThunks = false
let descendantHooks = false
let hooks = null
let attributes = properties.attributes != null ? properties.attributes : null
for (let key in properties) {
if (properties.hasOwnProperty(key)) {
const property = properties[key]
if (isHook(property)) {
if (hooks == null) {
hooks = {}
}
hooks[key] = property
} else {
// Event handlers
if (supportedEvents[key] != null) {
if (hooks == null) {
hooks = {}
}
const handler = eventHandler(property)
hooks[key] = handler
properties[key] = handler
}
// Special handlind of input.value
else if (key === 'value' &&
tagName.toLowerCase() === 'input' &&
property != null) {
if (hooks == null) {
hooks = {}
}
const hook = new SoftSetHook(property)
hooks[key] = hook
properties[key] = hook
}
// Attributes
else if (supportedAttributes[key] != null) {
if (attributes == null) {
attributes = {}
}
attributes[supportedAttributes[key]] = property
delete properties[key]
}
else if (key.indexOf('data-') === 0 || key.indexOf('aria-') === 0) {
if (attributes == null) {
attributes = {}
}
attributes[key] = property
delete properties[key]
}
}
}
}
if (attributes != null) {
properties.attributes = attributes
}
let index = 0
while (index < count) {
const child = children[index]
if (typeof(child) === "string") {
children[index] = new TextNode(child)
}
else if (child.$$typeof === "OrphanNode") {
children[index] = child.force()
index = index - 1
} else if (child instanceof VirtualNode) {
descendants += child.count
if (!hasWidgets && child.hasWidgets) {
hasWidgets = true
}
if (!hasThunks && child.hasThunks) {
hasThunks = true
}
if (!descendantHooks && (child.hooks != null || child.descendantHooks)) {
descendantHooks = true
}
}
else if (!hasWidgets && isWidget(child)) {
hasWidgets = true
}
else if (!hasThunks && isThunk(child)) {
hasThunks = true
}
index = index + 1
}
this.count = count + descendants
this.hasWidgets = hasWidgets
this.hasThunks = hasThunks
this.properties = properties
this.hooks = hooks
}
}
VirtualNode.prototype.$$typeof = "VirtualNode"
VirtualNode.prototype.type = "VirtualNode"
VirtualNode.prototype.version = version
export const node/*:type.node*/ = (tagName, properties, children) =>
new VirtualNode(tagName,
null,
properties == null ? blank : properties,
children == null ? empty : children)

29
src/text.js Normal file
Просмотреть файл

@ -0,0 +1,29 @@
/* @flow */
import version from "virtual-dom/vnode/version"
/*::
import * as type from "../type"
*/
export class TextNode {
/*::
$$typeof: "TextNode";
type: "VirtualText";
version: string;
text: string;
*/
constructor(text/*:string*/) {
this.text = text
this.$$typeof = "TextNode"
this.type = "VirtualText"
this.version = version
}
}
TextNode.prototype.$$typeof = "TextNode"
TextNode.prototype.type = "VirtualText"
TextNode.prototype.version = version
export const text/*:type.text*/ = text => new TextNode(text)

129
src/thunk.js Normal file
Просмотреть файл

@ -0,0 +1,129 @@
/* @flow */
/*::
import * as type from "../type"
*/
const redirect/*:type.redirect*/ = (addressBook, index) =>
action => addressBook[index](action);
export class ThunkNode {
/*::
$$typeof: "ThunkNode";
type: "Thunk";
key: type.Key;
view: type.View;
args: Array<any>;
addressBook: ?type.AddressBook<any>;
value: ?type.ChildNode;
*/
constructor(key/*:type.Key*/, view/*:type.View*/, args/*:Array<any>*/) {
this.key = key
this.view = view
this.args = args
this.addressBook = null
this.value = null
}
render(previous/*:?type.ChildNode*/) {
if (previous instanceof ThunkNode && previous.value != null) {
if (profile) {
console.time(`${this.key}.receive`)
}
const {view, args: passed, key} = this
const {args, addressBook, value} = previous
this.addressBook = addressBook
this.args = args
this.value = value
const count = passed.length
let index = 0
let isUpdated = view !== previous.view
|| key !== previous.key
if (args.length !== count) {
isUpdated = true
args.length = count
addressBook.length = count
}
while (index < count) {
const next = passed[index]
const arg = args[index]
if (next !== arg) {
const isNextAddress = typeof(next) === 'function'
const isCurrentAddress = typeof(arg) === 'function'
if (isNextAddress && isCurrentAddress) {
// Update adrress book with a new address.
addressBook[index] = next
} else {
isUpdated = true
if (isNextAddress) {
addressBook[index] = next
args[index] = redirect(addressBook, index)
} else {
args[index] = next
}
}
}
index = index + 1
}
if (profile) {
console.timeEnd(`${key}.receive`)
}
if (isUpdated) {
if (profile) {
console.time(`${key}.render`)
}
this.value = view(...args)
if (profile) {
console.timeEnd(`${key}.render`)
}
}
} else {
if (profile) {
console.time(`${this.key}.render`)
}
const addressBook = []
const {args, view, key} = this
const count = args.length
let index = 0
while (index < count) {
const arg = args[index]
if (typeof(arg) === 'function') {
addressBook[index] = arg
args[index] = redirect(addressBook, index)
} else {
args[index] = arg
}
index = index + 1
}
this.addressBook = addressBook
this.value = view(...args)
if (profile) {
console.timeEnd(`${key}.render`)
}
}
return this.value
}
}
ThunkNode.prototype.type = "Thunk"
ThunkNode.prototype.$$typeof = "ThunkNode"
let profile = null
export const thunk/*:type.thunk*/ = (key, view, ...args) =>
new ThunkNode(key, view, args)

3
type/dom.js Normal file
Просмотреть файл

@ -0,0 +1,3 @@
/* @flow */
export {EventListener, EventHandler, Event, EventTarget}

73
type/index.js Normal file
Просмотреть файл

@ -0,0 +1,73 @@
/* @flow */
import {Dictionary} from "object-as-dictionary"
import * as Signal from "reflex/type/signal"
import * as DOM from "./dom"
import * as Renderer from "reflex/type/renderer"
import * as VirtualDOM from "./virtual-dom"
// hooks
export type Hook <target> = {
hook: (node:target, name:string, previous:any) => void,
unhook: (node:target, name:string, next:any) => void
}
export type nameOverrides = Dictionary<string>
export type supportedAttributes = Dictionary<string>
export type HookDictionary = Dictionary<Hook<any>>
// hooks/event-handler
export type EventConfig = {type:string, capture:boolean}
export type supportedEvents = Dictionary<EventConfig>
export type EventHandler = DOM.EventHandler
export type EventListener = DOM.EventListener
export type EventPhaseName
= 'capture'
| 'bubble'
export type EventTarget
= DOM.EventTarget
& {[key:string]: EventListener}
export type DOMEvent = Event
export type Event
= DOM.Event
& {currentTarget: EventTarget}
export type handleEvent = (phase:EventPhaseName) =>
(event:Event) => void
export type Address <message>
= Signal.Address <message>
& {reflexEventListener?: EventHandler}
export type eventHandler = (address:Address) =>
Hook<EventTarget>
export type AddressBook <message>
= Array<Address<message>>
export type redirect <message>
= (addressBook:AddressBook<message>, index:number) => Address<message>
export type Key = Renderer.Key
export type TagName = Renderer.TagName
export type AttributeDictionary = Renderer.AttributeDictionary
export type StyleDictionary = Renderer.StyleDictionary
export type PropertyDictionary = Renderer.PropertyDictionary
export type VirtualNode = Renderer.VirtualNode
export type TextNode = Renderer.TextNode
export type Text = Renderer.Text
export type ChildNode = Renderer.ChildNode
export type ThunkNode = Renderer.ThunkNode
export type View = Renderer.View
export type text = Renderer.text
export type node = Renderer.node
export type thunk = Renderer.thunk

114
type/virtual-dom.js Normal file
Просмотреть файл

@ -0,0 +1,114 @@
/* @flow */
// vtext
export type VirtualText = {
type: "VirtualText",
version: string,
text: string
}
// vnode
export type HookTarget <extension>
= Element
& EventTarget
& extension
export type Hook <extension> = {
hook:(node:HookTarget<extension>, key:string, previous:any) => void,
unhook:(node:HookTarget<extension>, key:string, next:any) => void
}
export type HookDictionary <extension> = {[key:string]: Hook<extension>}
export type AttributeDictionary = {
[key:string]: string|number|boolean
}
export type StyleDictionary = {
[key:string]: string|number|boolean
}
export type VirtualProperties = {
attributes: ?Hook<any>|AttributeDictionary,
style: ?StyleDictionary,
[key:string]: Hook<any>|Function|string|number|boolean|EventListener
}
export type VirtualNode = {
type: "VirtualNode",
version: string,
tagName: string,
properties: VirtualProperties,
children: Array<Entity>,
hooks: ?HookDictionary<any>,
count: number,
hasWidgets: boolean,
hasThunks: boolean,
descendantHooks: boolean
}
// thunk
export type Thunk = {
type: "Thunk",
vnode: ?VNode,
render: (previous: ?Entity) => VNode
}
// widget
export type Widget = {
type: "Widget",
init: () => HTMLElement,
update: (previous:Widget, element:HTMLElement) => ?HTMLElement,
destroy: (element:HTMLElement) => void
}
export type VNode
= VirtualText
| VirtualNode
| Widget
export type Entity
= VirtualText
| VirtualNode
| Thunk
| Widget
// virtual-dom/diff
export type NONE = 0
export type VTEXT = 1
export type VNODE = 2
export type WIDGET = 3
export type PROPS = 4
export type ORDER = 5
export type INSERT = 6
export type REMOVE = 7
export type THUNK = 8
export type VirtualPatch <type:NONE|VTEXT|VNODE|WIDGET|PROPS|ORDER|INSERT|REMOVE|THUNK>
= {type: type, vnode: Entity, patch: any}
export type Delta = {
a: Entity,
[key:number]: VirtualPatch<any>|Array<VirtualPatch<any>>
}
export type diff = (left:Entity, right:Entity) => Delta
// virtual-dom/create-element
export type createElement = (left:Entity) => HTMLElement
// virtual-dom/patch
export type patch = (target:HTMLElement, delta:Delta) => void