mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
move snabbdom into libs/
This commit is contained in:
Executable
+50
@@ -0,0 +1,50 @@
|
||||
import {vnode, VNode, VNodeData} from './vnode';
|
||||
export type VNodes = Array<VNode>;
|
||||
export type VNodeChildElement = VNode | string | number | undefined | null;
|
||||
export type ArrayOrElement<T> = T | T[];
|
||||
export type VNodeChildren = ArrayOrElement<VNodeChildElement>
|
||||
import * as is from './is';
|
||||
|
||||
function addNS(data: any, children: VNodes | undefined, sel: string | undefined): void {
|
||||
data.ns = 'http://www.w3.org/2000/svg';
|
||||
if (sel !== 'foreignObject' && children !== undefined) {
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
let childData = children[i].data;
|
||||
if (childData !== undefined) {
|
||||
addNS(childData, (children[i] as VNode).children as VNodes, children[i].sel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function h(sel: string): VNode;
|
||||
export function h(sel: string, data: VNodeData): VNode;
|
||||
export function h(sel: string, children: VNodeChildren): VNode;
|
||||
export function h(sel: string, data: VNodeData, children: VNodeChildren): VNode;
|
||||
export function h(sel: any, b?: any, c?: any): VNode {
|
||||
var data: VNodeData = {}, children: any, text: any, i: number;
|
||||
if (c !== undefined) {
|
||||
data = b;
|
||||
if (is.array(c)) { children = c; }
|
||||
else if (is.primitive(c)) { text = c; }
|
||||
else if (c && c.sel) { children = [c]; }
|
||||
} else if (b !== undefined) {
|
||||
if (is.array(b)) { children = b; }
|
||||
else if (is.primitive(b)) { text = b; }
|
||||
else if (b && b.sel) { children = [b]; }
|
||||
else { data = b; }
|
||||
}
|
||||
if (children !== undefined) {
|
||||
for (i = 0; i < children.length; ++i) {
|
||||
if (is.primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined);
|
||||
}
|
||||
}
|
||||
if (
|
||||
sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' &&
|
||||
(sel.length === 3 || sel[3] === '.' || sel[3] === '#')
|
||||
) {
|
||||
addNS(data, children, sel);
|
||||
}
|
||||
return vnode(sel, data, children, text, undefined);
|
||||
};
|
||||
export default h;
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
import {VNode, VNodeData} from '../vnode';
|
||||
|
||||
export interface AttachData {
|
||||
[key: string]: any
|
||||
[i: number]: any
|
||||
placeholder?: any
|
||||
real?: Node
|
||||
}
|
||||
|
||||
interface VNodeDataWithAttach extends VNodeData {
|
||||
attachData: AttachData
|
||||
}
|
||||
|
||||
interface VNodeWithAttachData extends VNode {
|
||||
data: VNodeDataWithAttach
|
||||
}
|
||||
|
||||
function pre(vnode: VNodeWithAttachData, newVnode: VNodeWithAttachData): void {
|
||||
const attachData = vnode.data.attachData;
|
||||
// Copy created placeholder and real element from old vnode
|
||||
newVnode.data.attachData.placeholder = attachData.placeholder;
|
||||
newVnode.data.attachData.real = attachData.real;
|
||||
// Mount real element in vnode so the patch process operates on it
|
||||
vnode.elm = vnode.data.attachData.real;
|
||||
}
|
||||
|
||||
function post(_: any, vnode: VNodeWithAttachData): void {
|
||||
// Mount dummy placeholder in vnode so potential reorders use it
|
||||
vnode.elm = vnode.data.attachData.placeholder;
|
||||
}
|
||||
|
||||
function destroy(vnode: VNodeWithAttachData): void {
|
||||
// Remove placeholder
|
||||
if (vnode.elm !== undefined) {
|
||||
(vnode.elm.parentNode as HTMLElement).removeChild(vnode.elm);
|
||||
}
|
||||
// Remove real element from where it was inserted
|
||||
vnode.elm = vnode.data.attachData.real;
|
||||
}
|
||||
|
||||
function create(_: any, vnode: VNodeWithAttachData): void {
|
||||
const real = vnode.elm, attachData = vnode.data.attachData;
|
||||
const placeholder = document.createElement('span');
|
||||
// Replace actual element with dummy placeholder
|
||||
// Snabbdom will then insert placeholder instead
|
||||
vnode.elm = placeholder;
|
||||
attachData.target.appendChild(real);
|
||||
attachData.real = real;
|
||||
attachData.placeholder = placeholder;
|
||||
}
|
||||
|
||||
export function attachTo(target: Element, vnode: VNode): VNode {
|
||||
if (vnode.data === undefined) vnode.data = {};
|
||||
if (vnode.data.hook === undefined) vnode.data.hook = {};
|
||||
const data = vnode.data;
|
||||
const hook = vnode.data.hook;
|
||||
data.attachData = {target: target, placeholder: undefined, real: undefined};
|
||||
hook.create = create;
|
||||
hook.prepatch = pre;
|
||||
hook.postpatch = post;
|
||||
hook.destroy = destroy;
|
||||
return vnode;
|
||||
};
|
||||
export default attachTo;
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
import {VNode} from './vnode';
|
||||
|
||||
export type PreHook = () => any;
|
||||
export type InitHook = (vNode: VNode) => any;
|
||||
export type CreateHook = (emptyVNode: VNode, vNode: VNode) => any;
|
||||
export type InsertHook = (vNode: VNode) => any;
|
||||
export type PrePatchHook = (oldVNode: VNode, vNode: VNode) => any;
|
||||
export type UpdateHook = (oldVNode: VNode, vNode: VNode) => any;
|
||||
export type PostPatchHook = (oldVNode: VNode, vNode: VNode) => any;
|
||||
export type DestroyHook = (vNode: VNode) => any;
|
||||
export type RemoveHook = (vNode: VNode, removeCallback: () => void) => any;
|
||||
export type PostHook = () => any;
|
||||
|
||||
export interface Hooks {
|
||||
pre?: PreHook;
|
||||
init?: InitHook;
|
||||
create?: CreateHook;
|
||||
insert?: InsertHook;
|
||||
prepatch?: PrePatchHook;
|
||||
update?: UpdateHook;
|
||||
postpatch?: PostPatchHook;
|
||||
destroy?: DestroyHook;
|
||||
remove?: RemoveHook;
|
||||
post?: PostHook;
|
||||
}
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
export interface DOMAPI {
|
||||
createElement: (tagName: any) => HTMLElement;
|
||||
createElementNS: (namespaceURI: string, qualifiedName: string) => Element;
|
||||
createTextNode: (text: string) => Text;
|
||||
createComment: (text: string) => Comment;
|
||||
insertBefore: (parentNode: Node, newNode: Node, referenceNode: Node | null) => void;
|
||||
removeChild: (node: Node, child: Node) => void;
|
||||
appendChild: (node: Node, child: Node) => void;
|
||||
parentNode: (node: Node) => Node;
|
||||
nextSibling: (node: Node) => Node;
|
||||
tagName: (elm: Element) => string;
|
||||
setTextContent: (node: Node, text: string | null) => void;
|
||||
getTextContent: (node: Node) => string | null;
|
||||
isElement: (node: Node) => node is Element;
|
||||
isText: (node: Node) => node is Text;
|
||||
isComment: (node: Node) => node is Comment;
|
||||
}
|
||||
|
||||
function createElement(tagName: any): HTMLElement {
|
||||
return document.createElement(tagName);
|
||||
}
|
||||
|
||||
function createElementNS(namespaceURI: string, qualifiedName: string): Element {
|
||||
return document.createElementNS(namespaceURI, qualifiedName);
|
||||
}
|
||||
|
||||
function createTextNode(text: string): Text {
|
||||
return document.createTextNode(text);
|
||||
}
|
||||
|
||||
function createComment(text: string): Comment {
|
||||
return document.createComment(text);
|
||||
}
|
||||
|
||||
function insertBefore(parentNode: Node, newNode: Node, referenceNode: Node | null): void {
|
||||
parentNode.insertBefore(newNode, referenceNode);
|
||||
}
|
||||
|
||||
function removeChild(node: Node, child: Node): void {
|
||||
node.removeChild(child);
|
||||
}
|
||||
|
||||
function appendChild(node: Node, child: Node): void {
|
||||
node.appendChild(child);
|
||||
}
|
||||
|
||||
function parentNode(node: Node): Node | null {
|
||||
return node.parentNode;
|
||||
}
|
||||
|
||||
function nextSibling(node: Node): Node | null {
|
||||
return node.nextSibling;
|
||||
}
|
||||
|
||||
function tagName(elm: Element): string {
|
||||
return elm.tagName;
|
||||
}
|
||||
|
||||
function setTextContent(node: Node, text: string | null): void {
|
||||
node.textContent = text;
|
||||
}
|
||||
|
||||
function getTextContent(node: Node): string | null {
|
||||
return node.textContent;
|
||||
}
|
||||
|
||||
function isElement(node: Node): node is Element {
|
||||
return node.nodeType === 1;
|
||||
}
|
||||
|
||||
function isText(node: Node): node is Text {
|
||||
return node.nodeType === 3;
|
||||
}
|
||||
|
||||
function isComment(node: Node): node is Comment {
|
||||
return node.nodeType === 8;
|
||||
}
|
||||
|
||||
export const htmlDomApi = {
|
||||
createElement,
|
||||
createElementNS,
|
||||
createTextNode,
|
||||
createComment,
|
||||
insertBefore,
|
||||
removeChild,
|
||||
appendChild,
|
||||
parentNode,
|
||||
nextSibling,
|
||||
tagName,
|
||||
setTextContent,
|
||||
getTextContent,
|
||||
isElement,
|
||||
isText,
|
||||
isComment,
|
||||
} as DOMAPI;
|
||||
|
||||
export default htmlDomApi;
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
export const array = Array.isArray;
|
||||
export function primitive(s: any): s is (string | number) {
|
||||
return typeof s === 'string' || typeof s === 'number';
|
||||
}
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
import {VNode, VNodeData} from '../vnode';
|
||||
import {Module} from './module';
|
||||
|
||||
// because those in TypeScript are too restrictive: https://github.com/Microsoft/TSJS-lib-generator/pull/237
|
||||
declare global {
|
||||
interface Element {
|
||||
setAttribute(name: string, value: string | number | boolean): void;
|
||||
setAttributeNS(namespaceURI: string, qualifiedName: string, value: string | number | boolean): void;
|
||||
}
|
||||
}
|
||||
|
||||
export type Attrs = Record<string, string | number | boolean>
|
||||
|
||||
const xlinkNS = 'http://www.w3.org/1999/xlink';
|
||||
const xmlNS = 'http://www.w3.org/XML/1998/namespace';
|
||||
const colonChar = 58;
|
||||
const xChar = 120;
|
||||
|
||||
function updateAttrs(oldVnode: VNode, vnode: VNode): void {
|
||||
var key: string, elm: Element = vnode.elm as Element,
|
||||
oldAttrs = (oldVnode.data as VNodeData).attrs,
|
||||
attrs = (vnode.data as VNodeData).attrs;
|
||||
|
||||
if (!oldAttrs && !attrs) return;
|
||||
if (oldAttrs === attrs) return;
|
||||
oldAttrs = oldAttrs || {};
|
||||
attrs = attrs || {};
|
||||
|
||||
// update modified attributes, add new attributes
|
||||
for (key in attrs) {
|
||||
const cur = attrs[key];
|
||||
const old = oldAttrs[key];
|
||||
if (old !== cur) {
|
||||
if (cur === true) {
|
||||
elm.setAttribute(key, "");
|
||||
} else if (cur === false) {
|
||||
elm.removeAttribute(key);
|
||||
} else {
|
||||
if (key.charCodeAt(0) !== xChar) {
|
||||
elm.setAttribute(key, cur);
|
||||
} else if (key.charCodeAt(3) === colonChar) {
|
||||
// Assume xml namespace
|
||||
elm.setAttributeNS(xmlNS, key, cur);
|
||||
} else if (key.charCodeAt(5) === colonChar) {
|
||||
// Assume xlink namespace
|
||||
elm.setAttributeNS(xlinkNS, key, cur);
|
||||
} else {
|
||||
elm.setAttribute(key, cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove removed attributes
|
||||
// use `in` operator since the previous `for` iteration uses it (.i.e. add even attributes with undefined value)
|
||||
// the other option is to remove all attributes with value == undefined
|
||||
for (key in oldAttrs) {
|
||||
if (!(key in attrs)) {
|
||||
elm.removeAttribute(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const attributesModule = {create: updateAttrs, update: updateAttrs} as Module;
|
||||
export default attributesModule;
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
import {VNode, VNodeData} from '../vnode';
|
||||
import {Module} from './module';
|
||||
|
||||
export type Classes = Record<string, boolean>
|
||||
|
||||
function updateClass(oldVnode: VNode, vnode: VNode): void {
|
||||
var cur: any, name: string, elm: Element = vnode.elm as Element,
|
||||
oldClass = (oldVnode.data as VNodeData).class,
|
||||
klass = (vnode.data as VNodeData).class;
|
||||
|
||||
if (!oldClass && !klass) return;
|
||||
if (oldClass === klass) return;
|
||||
oldClass = oldClass || {};
|
||||
klass = klass || {};
|
||||
|
||||
for (name in oldClass) {
|
||||
if (!klass[name]) {
|
||||
elm.classList.remove(name);
|
||||
}
|
||||
}
|
||||
for (name in klass) {
|
||||
cur = klass[name];
|
||||
if (cur !== oldClass[name]) {
|
||||
(elm.classList as any)[cur ? 'add' : 'remove'](name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const classModule = {create: updateClass, update: updateClass} as Module;
|
||||
export default classModule;
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
import {VNode, VNodeData} from '../vnode';
|
||||
import {Module} from './module';
|
||||
|
||||
export type Dataset = Record<string, string>;
|
||||
|
||||
const CAPS_REGEX = /[A-Z]/g;
|
||||
|
||||
function updateDataset(oldVnode: VNode, vnode: VNode): void {
|
||||
let elm: HTMLElement = vnode.elm as HTMLElement,
|
||||
oldDataset = (oldVnode.data as VNodeData).dataset,
|
||||
dataset = (vnode.data as VNodeData).dataset,
|
||||
key: string;
|
||||
|
||||
if (!oldDataset && !dataset) return;
|
||||
if (oldDataset === dataset) return;
|
||||
oldDataset = oldDataset || {};
|
||||
dataset = dataset || {};
|
||||
const d = elm.dataset;
|
||||
|
||||
for (key in oldDataset) {
|
||||
if (!dataset[key]) {
|
||||
if (d) {
|
||||
if (key in d) {
|
||||
delete d[key];
|
||||
}
|
||||
} else {
|
||||
elm.removeAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (key in dataset) {
|
||||
if (oldDataset[key] !== dataset[key]) {
|
||||
if (d) {
|
||||
d[key] = dataset[key];
|
||||
} else {
|
||||
elm.setAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(), dataset[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const datasetModule = {create: updateDataset, update: updateDataset} as Module;
|
||||
export default datasetModule;
|
||||
Executable
+111
@@ -0,0 +1,111 @@
|
||||
import {VNode, VNodeData} from '../vnode';
|
||||
import {Module} from './module';
|
||||
|
||||
export type On = {
|
||||
[N in keyof HTMLElementEventMap]?: (ev: HTMLElementEventMap[N]) => void
|
||||
} & {
|
||||
[event: string]: EventListener
|
||||
};
|
||||
|
||||
function invokeHandler(handler: any, vnode?: VNode, event?: Event): void {
|
||||
if (typeof handler === "function") {
|
||||
// call function handler
|
||||
handler.call(vnode, event, vnode);
|
||||
} else if (typeof handler === "object") {
|
||||
// call handler with arguments
|
||||
if (typeof handler[0] === "function") {
|
||||
// special case for single argument for performance
|
||||
if (handler.length === 2) {
|
||||
handler[0].call(vnode, handler[1], event, vnode);
|
||||
} else {
|
||||
var args = handler.slice(1);
|
||||
args.push(event);
|
||||
args.push(vnode);
|
||||
handler[0].apply(vnode, args);
|
||||
}
|
||||
} else {
|
||||
// call multiple handlers
|
||||
for (var i = 0; i < handler.length; i++) {
|
||||
invokeHandler(handler[i], vnode, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleEvent(event: Event, vnode: VNode) {
|
||||
var name = event.type,
|
||||
on = (vnode.data as VNodeData).on;
|
||||
|
||||
// call event handler(s) if exists
|
||||
if (on && on[name]) {
|
||||
invokeHandler(on[name], vnode, event);
|
||||
}
|
||||
}
|
||||
|
||||
function createListener() {
|
||||
return function handler(event: Event) {
|
||||
handleEvent(event, (handler as any).vnode);
|
||||
}
|
||||
}
|
||||
|
||||
function updateEventListeners(oldVnode: VNode, vnode?: VNode): void {
|
||||
var oldOn = (oldVnode.data as VNodeData).on,
|
||||
oldListener = (oldVnode as any).listener,
|
||||
oldElm: Element = oldVnode.elm as Element,
|
||||
on = vnode && (vnode.data as VNodeData).on,
|
||||
elm: Element = (vnode && vnode.elm) as Element,
|
||||
name: string;
|
||||
|
||||
// optimization for reused immutable handlers
|
||||
if (oldOn === on) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove existing listeners which no longer used
|
||||
if (oldOn && oldListener) {
|
||||
// if element changed or deleted we remove all existing listeners unconditionally
|
||||
if (!on) {
|
||||
for (name in oldOn) {
|
||||
// remove listener if element was changed or existing listeners removed
|
||||
oldElm.removeEventListener(name, oldListener, false);
|
||||
}
|
||||
} else {
|
||||
for (name in oldOn) {
|
||||
// remove listener if existing listener removed
|
||||
if (!on[name]) {
|
||||
oldElm.removeEventListener(name, oldListener, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add new listeners which has not already attached
|
||||
if (on) {
|
||||
// reuse existing listener or create new
|
||||
var listener = (vnode as any).listener = (oldVnode as any).listener || createListener();
|
||||
// update vnode for listener
|
||||
listener.vnode = vnode;
|
||||
|
||||
// if element changed or added we add all needed listeners unconditionally
|
||||
if (!oldOn) {
|
||||
for (name in on) {
|
||||
// add listener if element was changed or new listeners added
|
||||
elm.addEventListener(name, listener, false);
|
||||
}
|
||||
} else {
|
||||
for (name in on) {
|
||||
// add listener if new listener added
|
||||
if (!oldOn[name]) {
|
||||
elm.addEventListener(name, listener, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const eventListenersModule = {
|
||||
create: updateEventListeners,
|
||||
update: updateEventListeners,
|
||||
destroy: updateEventListeners
|
||||
} as Module;
|
||||
export default eventListenersModule;
|
||||
Executable
+165
@@ -0,0 +1,165 @@
|
||||
import {VNode, VNodeData} from '../vnode';
|
||||
import {Module} from './module';
|
||||
|
||||
export type Hero = { id: string }
|
||||
|
||||
var raf = (typeof window !== 'undefined' && window.requestAnimationFrame) || setTimeout;
|
||||
var nextFrame = function(fn: any) { raf(function() { raf(fn); }); };
|
||||
|
||||
function setNextFrame(obj: any, prop: string, val: any): void {
|
||||
nextFrame(function() { obj[prop] = val; });
|
||||
}
|
||||
|
||||
function getTextNodeRect(textNode: Text): ClientRect | undefined {
|
||||
var rect: ClientRect | undefined;
|
||||
if (document.createRange) {
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(textNode);
|
||||
if (range.getBoundingClientRect) {
|
||||
rect = range.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
function calcTransformOrigin(isTextNode: boolean,
|
||||
textRect: ClientRect | undefined,
|
||||
boundingRect: ClientRect): string {
|
||||
if (isTextNode) {
|
||||
if (textRect) {
|
||||
//calculate pixels to center of text from left edge of bounding box
|
||||
var relativeCenterX = textRect.left + textRect.width/2 - boundingRect.left;
|
||||
var relativeCenterY = textRect.top + textRect.height/2 - boundingRect.top;
|
||||
return relativeCenterX + 'px ' + relativeCenterY + 'px';
|
||||
}
|
||||
}
|
||||
return '0 0'; //top left
|
||||
}
|
||||
|
||||
function getTextDx(oldTextRect: ClientRect | undefined,
|
||||
newTextRect: ClientRect | undefined): number {
|
||||
if (oldTextRect && newTextRect) {
|
||||
return ((oldTextRect.left + oldTextRect.width/2) - (newTextRect.left + newTextRect.width/2));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
function getTextDy(oldTextRect: ClientRect | undefined,
|
||||
newTextRect: ClientRect | undefined): number {
|
||||
if (oldTextRect && newTextRect) {
|
||||
return ((oldTextRect.top + oldTextRect.height/2) - (newTextRect.top + newTextRect.height/2));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isTextElement(elm: Element | Text): elm is Text {
|
||||
return elm.childNodes.length === 1 && elm.childNodes[0].nodeType === 3;
|
||||
}
|
||||
|
||||
var removed: any, created: any;
|
||||
|
||||
function pre() {
|
||||
removed = {};
|
||||
created = [];
|
||||
}
|
||||
|
||||
function create(oldVnode: VNode, vnode: VNode): void {
|
||||
var hero = (vnode.data as VNodeData).hero;
|
||||
if (hero && hero.id) {
|
||||
created.push(hero.id);
|
||||
created.push(vnode);
|
||||
}
|
||||
}
|
||||
|
||||
function destroy(vnode: VNode): void {
|
||||
var hero = (vnode.data as VNodeData).hero;
|
||||
if (hero && hero.id) {
|
||||
var elm = vnode.elm;
|
||||
(vnode as any).isTextNode = isTextElement(elm as Element | Text); //is this a text node?
|
||||
(vnode as any).boundingRect = (elm as Element).getBoundingClientRect(); //save the bounding rectangle to a new property on the vnode
|
||||
(vnode as any).textRect = (vnode as any).isTextNode ? getTextNodeRect((elm as Element).childNodes[0] as Text) : null; //save bounding rect of inner text node
|
||||
var computedStyle = window.getComputedStyle(elm as Element, void 0); //get current styles (includes inherited properties)
|
||||
(vnode as any).savedStyle = JSON.parse(JSON.stringify(computedStyle)); //save a copy of computed style values
|
||||
removed[hero.id] = vnode;
|
||||
}
|
||||
}
|
||||
|
||||
function post() {
|
||||
var i: number, id: any, newElm: Element, oldVnode: VNode, oldElm: Element,
|
||||
hRatio: number, wRatio: number,
|
||||
oldRect: ClientRect, newRect: ClientRect, dx: number, dy: number,
|
||||
origTransform: string | null, origTransition: string | null,
|
||||
newStyle: CSSStyleDeclaration, oldStyle: CSSStyleDeclaration,
|
||||
newComputedStyle: CSSStyleDeclaration, isTextNode: boolean,
|
||||
newTextRect: ClientRect | undefined, oldTextRect: ClientRect | undefined;
|
||||
for (i = 0; i < created.length; i += 2) {
|
||||
id = created[i];
|
||||
newElm = created[i+1].elm;
|
||||
oldVnode = removed[id];
|
||||
if (oldVnode) {
|
||||
isTextNode = (oldVnode as any).isTextNode && isTextElement(newElm); //Are old & new both text?
|
||||
newStyle = (newElm as HTMLElement).style;
|
||||
newComputedStyle = window.getComputedStyle(newElm, void 0); //get full computed style for new element
|
||||
oldElm = oldVnode.elm as Element;
|
||||
oldStyle = (oldElm as HTMLElement).style;
|
||||
//Overall element bounding boxes
|
||||
newRect = newElm.getBoundingClientRect();
|
||||
oldRect = (oldVnode as any).boundingRect; //previously saved bounding rect
|
||||
//Text node bounding boxes & distances
|
||||
if (isTextNode) {
|
||||
newTextRect = getTextNodeRect(newElm.childNodes[0] as Text);
|
||||
oldTextRect = (oldVnode as any).textRect;
|
||||
dx = getTextDx(oldTextRect, newTextRect);
|
||||
dy = getTextDy(oldTextRect, newTextRect);
|
||||
} else {
|
||||
//Calculate distances between old & new positions
|
||||
dx = oldRect.left - newRect.left;
|
||||
dy = oldRect.top - newRect.top;
|
||||
}
|
||||
hRatio = newRect.height / (Math.max(oldRect.height, 1));
|
||||
wRatio = isTextNode ? hRatio : newRect.width / (Math.max(oldRect.width, 1)); //text scales based on hRatio
|
||||
// Animate new element
|
||||
origTransform = newStyle.transform;
|
||||
origTransition = newStyle.transition;
|
||||
if (newComputedStyle.display === 'inline') //inline elements cannot be transformed
|
||||
newStyle.display = 'inline-block'; //this does not appear to have any negative side effects
|
||||
newStyle.transition = origTransition + 'transform 0s';
|
||||
newStyle.transformOrigin = calcTransformOrigin(isTextNode, newTextRect, newRect);
|
||||
newStyle.opacity = '0';
|
||||
newStyle.transform = origTransform + 'translate('+dx+'px, '+dy+'px) ' +
|
||||
'scale('+1/wRatio+', '+1/hRatio+')';
|
||||
setNextFrame(newStyle, 'transition', origTransition);
|
||||
setNextFrame(newStyle, 'transform', origTransform);
|
||||
setNextFrame(newStyle, 'opacity', '1');
|
||||
// Animate old element
|
||||
for (var key in (oldVnode as any).savedStyle) { //re-apply saved inherited properties
|
||||
if (parseInt(key) != key as any as number) {
|
||||
var ms = key.substring(0,2) === 'ms';
|
||||
var moz = key.substring(0,3) === 'moz';
|
||||
var webkit = key.substring(0,6) === 'webkit';
|
||||
if (!ms && !moz && !webkit) //ignore prefixed style properties
|
||||
(oldStyle as any)[key] = (oldVnode as any).savedStyle[key];
|
||||
}
|
||||
}
|
||||
oldStyle.position = 'absolute';
|
||||
oldStyle.top = oldRect.top + 'px'; //start at existing position
|
||||
oldStyle.left = oldRect.left + 'px';
|
||||
oldStyle.width = oldRect.width + 'px'; //Needed for elements who were sized relative to their parents
|
||||
oldStyle.height = oldRect.height + 'px'; //Needed for elements who were sized relative to their parents
|
||||
oldStyle.margin = '0'; //Margin on hero element leads to incorrect positioning
|
||||
oldStyle.transformOrigin = calcTransformOrigin(isTextNode, oldTextRect, oldRect);
|
||||
oldStyle.transform = '';
|
||||
oldStyle.opacity = '1';
|
||||
document.body.appendChild(oldElm);
|
||||
setNextFrame(oldStyle, 'transform', 'translate('+ -dx +'px, '+ -dy +'px) scale('+wRatio+', '+hRatio+')'); //scale must be on far right for translate to be correct
|
||||
setNextFrame(oldStyle, 'opacity', '0');
|
||||
oldElm.addEventListener('transitionend', function (ev: TransitionEvent) {
|
||||
if (ev.propertyName === 'transform')
|
||||
document.body.removeChild(ev.target as Node);
|
||||
});
|
||||
}
|
||||
}
|
||||
removed = created = undefined;
|
||||
}
|
||||
|
||||
export const heroModule = {pre, create, destroy, post} as Module;
|
||||
export default heroModule;
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
import {PreHook, CreateHook, UpdateHook, DestroyHook, RemoveHook, PostHook} from '../hooks';
|
||||
|
||||
export interface Module {
|
||||
pre: PreHook;
|
||||
create: CreateHook;
|
||||
update: UpdateHook;
|
||||
destroy: DestroyHook;
|
||||
remove: RemoveHook;
|
||||
post: PostHook;
|
||||
}
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
import {VNode, VNodeData} from '../vnode';
|
||||
import {Module} from './module';
|
||||
|
||||
export type Props = Record<string, any>;
|
||||
|
||||
function updateProps(oldVnode: VNode, vnode: VNode): void {
|
||||
var key: string, cur: any, old: any, elm = vnode.elm,
|
||||
oldProps = (oldVnode.data as VNodeData).props,
|
||||
props = (vnode.data as VNodeData).props;
|
||||
|
||||
if (!oldProps && !props) return;
|
||||
if (oldProps === props) return;
|
||||
oldProps = oldProps || {};
|
||||
props = props || {};
|
||||
|
||||
for (key in oldProps) {
|
||||
if (!props[key]) {
|
||||
delete (elm as any)[key];
|
||||
}
|
||||
}
|
||||
for (key in props) {
|
||||
cur = props[key];
|
||||
old = oldProps[key];
|
||||
if (old !== cur && (key !== 'value' || (elm as any)[key] !== cur)) {
|
||||
(elm as any)[key] = cur;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const propsModule = {create: updateProps, update: updateProps} as Module;
|
||||
export default propsModule;
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
import {VNode, VNodeData} from '../vnode';
|
||||
import {Module} from './module';
|
||||
|
||||
export type VNodeStyle = Record<string, string> & {
|
||||
delayed?: Record<string, string>
|
||||
remove?: Record<string, string>
|
||||
}
|
||||
|
||||
// Bindig `requestAnimationFrame` like this fixes a bug in IE/Edge. See #360 and #409.
|
||||
var raf = (typeof window !== 'undefined' && (window.requestAnimationFrame).bind(window)) || setTimeout;
|
||||
var nextFrame = function(fn: any) { raf(function() { raf(fn); }); };
|
||||
var reflowForced = false;
|
||||
|
||||
function setNextFrame(obj: any, prop: string, val: any): void {
|
||||
nextFrame(function() { obj[prop] = val; });
|
||||
}
|
||||
|
||||
function updateStyle(oldVnode: VNode, vnode: VNode): void {
|
||||
var cur: any, name: string, elm = vnode.elm,
|
||||
oldStyle = (oldVnode.data as VNodeData).style,
|
||||
style = (vnode.data as VNodeData).style;
|
||||
|
||||
if (!oldStyle && !style) return;
|
||||
if (oldStyle === style) return;
|
||||
oldStyle = oldStyle || {} as VNodeStyle;
|
||||
style = style || {} as VNodeStyle;
|
||||
var oldHasDel = 'delayed' in oldStyle;
|
||||
|
||||
for (name in oldStyle) {
|
||||
if (!style[name]) {
|
||||
if (name[0] === '-' && name[1] === '-') {
|
||||
(elm as any).style.removeProperty(name);
|
||||
} else {
|
||||
(elm as any).style[name] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
for (name in style) {
|
||||
cur = style[name];
|
||||
if (name === 'delayed' && style.delayed) {
|
||||
for (let name2 in style.delayed) {
|
||||
cur = style.delayed[name2];
|
||||
if (!oldHasDel || cur !== (oldStyle.delayed as any)[name2]) {
|
||||
setNextFrame((elm as any).style, name2, cur);
|
||||
}
|
||||
}
|
||||
} else if (name !== 'remove' && cur !== oldStyle[name]) {
|
||||
if (name[0] === '-' && name[1] === '-') {
|
||||
(elm as any).style.setProperty(name, cur);
|
||||
} else {
|
||||
(elm as any).style[name] = cur;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyDestroyStyle(vnode: VNode): void {
|
||||
var style: any, name: string, elm = vnode.elm, s = (vnode.data as VNodeData).style;
|
||||
if (!s || !(style = s.destroy)) return;
|
||||
for (name in style) {
|
||||
(elm as any).style[name] = style[name];
|
||||
}
|
||||
}
|
||||
|
||||
function applyRemoveStyle(vnode: VNode, rm: () => void): void {
|
||||
var s = (vnode.data as VNodeData).style;
|
||||
if (!s || !s.remove) {
|
||||
rm();
|
||||
return;
|
||||
}
|
||||
if(!reflowForced) {
|
||||
getComputedStyle(document.body).transform;
|
||||
reflowForced = true;
|
||||
}
|
||||
var name: string, elm = vnode.elm, i = 0, compStyle: CSSStyleDeclaration,
|
||||
style = s.remove, amount = 0, applied: Array<string> = [];
|
||||
for (name in style) {
|
||||
applied.push(name);
|
||||
(elm as any).style[name] = style[name];
|
||||
}
|
||||
compStyle = getComputedStyle(elm as Element);
|
||||
var props = (compStyle as any)['transition-property'].split(', ');
|
||||
for (; i < props.length; ++i) {
|
||||
if(applied.indexOf(props[i]) !== -1) amount++;
|
||||
}
|
||||
(elm as Element).addEventListener('transitionend', function (ev: TransitionEvent) {
|
||||
if (ev.target === elm) --amount;
|
||||
if (amount === 0) rm();
|
||||
});
|
||||
}
|
||||
|
||||
function forceReflow() {
|
||||
reflowForced = false;
|
||||
}
|
||||
|
||||
export const styleModule = {
|
||||
pre: forceReflow,
|
||||
create: updateStyle,
|
||||
update: updateStyle,
|
||||
destroy: applyDestroyStyle,
|
||||
remove: applyRemoveStyle
|
||||
} as Module;
|
||||
export default styleModule;
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
import {init} from './snabbdom';
|
||||
import {attributesModule} from './modules/attributes'; // for setting attributes on DOM elements
|
||||
import {classModule} from './modules/class'; // makes it easy to toggle classes
|
||||
import {propsModule} from './modules/props'; // for setting properties on DOM elements
|
||||
import {styleModule} from './modules/style'; // handles styling on elements with support for animations
|
||||
import {eventListenersModule} from './modules/eventlisteners'; // attaches event listeners
|
||||
import {h} from './h'; // helper function for creating vnodes
|
||||
var patch = init([ // Init patch function with choosen modules
|
||||
attributesModule,
|
||||
classModule,
|
||||
propsModule,
|
||||
styleModule,
|
||||
eventListenersModule
|
||||
]) as (oldVNode: any, vnode: any) => any;
|
||||
export const snabbdomBundle = { patch, h: h as any };
|
||||
export default snabbdomBundle;
|
||||
Executable
+318
@@ -0,0 +1,318 @@
|
||||
/* global module, document, Node */
|
||||
import {Module} from './modules/module';
|
||||
import {Hooks} from './hooks';
|
||||
import vnode, {VNode, VNodeData, Key} from './vnode';
|
||||
import * as is from './is';
|
||||
import htmlDomApi, {DOMAPI} from './htmldomapi';
|
||||
|
||||
function isUndef(s: any): boolean { return s === undefined; }
|
||||
function isDef(s: any): boolean { return s !== undefined; }
|
||||
|
||||
type VNodeQueue = Array<VNode>;
|
||||
|
||||
const emptyNode = vnode('', {}, [], undefined, undefined);
|
||||
|
||||
function sameVnode(vnode1: VNode, vnode2: VNode): boolean {
|
||||
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
|
||||
}
|
||||
|
||||
function isVnode(vnode: any): vnode is VNode {
|
||||
return vnode.sel !== undefined;
|
||||
}
|
||||
|
||||
type KeyToIndexMap = {[key: string]: number};
|
||||
|
||||
type ArraysOf<T> = {
|
||||
[K in keyof T]: (T[K])[];
|
||||
}
|
||||
|
||||
type ModuleHooks = ArraysOf<Module>;
|
||||
|
||||
function createKeyToOldIdx(children: Array<VNode>, beginIdx: number, endIdx: number): KeyToIndexMap {
|
||||
let i: number, map: KeyToIndexMap = {}, key: Key | undefined, ch;
|
||||
for (i = beginIdx; i <= endIdx; ++i) {
|
||||
ch = children[i];
|
||||
if (ch != null) {
|
||||
key = ch.key;
|
||||
if (key !== undefined) map[key] = i;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
const hooks: (keyof Module)[] = ['create', 'update', 'remove', 'destroy', 'pre', 'post'];
|
||||
|
||||
export {h} from './h';
|
||||
export {thunk} from './thunk';
|
||||
|
||||
export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
|
||||
let i: number, j: number, cbs = ({} as ModuleHooks);
|
||||
|
||||
const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi;
|
||||
|
||||
for (i = 0; i < hooks.length; ++i) {
|
||||
cbs[hooks[i]] = [];
|
||||
for (j = 0; j < modules.length; ++j) {
|
||||
const hook = modules[j][hooks[i]];
|
||||
if (hook !== undefined) {
|
||||
(cbs[hooks[i]] as Array<any>).push(hook);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emptyNodeAt(elm: Element) {
|
||||
const id = elm.id ? '#' + elm.id : '';
|
||||
const c = elm.className ? '.' + elm.className.split(' ').join('.') : '';
|
||||
return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
|
||||
}
|
||||
|
||||
function createRmCb(childElm: Node, listeners: number) {
|
||||
return function rmCb() {
|
||||
if (--listeners === 0) {
|
||||
const parent = api.parentNode(childElm);
|
||||
api.removeChild(parent, childElm);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
|
||||
let i: any, data = vnode.data;
|
||||
if (data !== undefined) {
|
||||
if (isDef(i = data.hook) && isDef(i = i.init)) {
|
||||
i(vnode);
|
||||
data = vnode.data;
|
||||
}
|
||||
}
|
||||
let children = vnode.children, sel = vnode.sel;
|
||||
if (sel === '!') {
|
||||
if (isUndef(vnode.text)) {
|
||||
vnode.text = '';
|
||||
}
|
||||
vnode.elm = api.createComment(vnode.text as string);
|
||||
} else if (sel !== undefined) {
|
||||
// Parse selector
|
||||
const hashIdx = sel.indexOf('#');
|
||||
const dotIdx = sel.indexOf('.', hashIdx);
|
||||
const hash = hashIdx > 0 ? hashIdx : sel.length;
|
||||
const dot = dotIdx > 0 ? dotIdx : sel.length;
|
||||
const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel;
|
||||
const elm = vnode.elm = isDef(data) && isDef(i = (data as VNodeData).ns) ? api.createElementNS(i, tag)
|
||||
: api.createElement(tag);
|
||||
if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot));
|
||||
if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' '));
|
||||
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);
|
||||
if (is.array(children)) {
|
||||
for (i = 0; i < children.length; ++i) {
|
||||
const ch = children[i];
|
||||
if (ch != null) {
|
||||
api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue));
|
||||
}
|
||||
}
|
||||
} else if (is.primitive(vnode.text)) {
|
||||
api.appendChild(elm, api.createTextNode(vnode.text));
|
||||
}
|
||||
i = (vnode.data as VNodeData).hook; // Reuse variable
|
||||
if (isDef(i)) {
|
||||
if (i.create) i.create(emptyNode, vnode);
|
||||
if (i.insert) insertedVnodeQueue.push(vnode);
|
||||
}
|
||||
} else {
|
||||
vnode.elm = api.createTextNode(vnode.text as string);
|
||||
}
|
||||
return vnode.elm;
|
||||
}
|
||||
|
||||
function addVnodes(parentElm: Node,
|
||||
before: Node | null,
|
||||
vnodes: Array<VNode>,
|
||||
startIdx: number,
|
||||
endIdx: number,
|
||||
insertedVnodeQueue: VNodeQueue) {
|
||||
for (; startIdx <= endIdx; ++startIdx) {
|
||||
const ch = vnodes[startIdx];
|
||||
if (ch != null) {
|
||||
api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function invokeDestroyHook(vnode: VNode) {
|
||||
let i: any, j: number, data = vnode.data;
|
||||
if (data !== undefined) {
|
||||
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode);
|
||||
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);
|
||||
if (vnode.children !== undefined) {
|
||||
for (j = 0; j < vnode.children.length; ++j) {
|
||||
i = vnode.children[j];
|
||||
if (i != null && typeof i !== "string") {
|
||||
invokeDestroyHook(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeVnodes(parentElm: Node,
|
||||
vnodes: Array<VNode>,
|
||||
startIdx: number,
|
||||
endIdx: number): void {
|
||||
for (; startIdx <= endIdx; ++startIdx) {
|
||||
let i: any, listeners: number, rm: () => void, ch = vnodes[startIdx];
|
||||
if (ch != null) {
|
||||
if (isDef(ch.sel)) {
|
||||
invokeDestroyHook(ch);
|
||||
listeners = cbs.remove.length + 1;
|
||||
rm = createRmCb(ch.elm as Node, listeners);
|
||||
for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);
|
||||
if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) {
|
||||
i(ch, rm);
|
||||
} else {
|
||||
rm();
|
||||
}
|
||||
} else { // Text node
|
||||
api.removeChild(parentElm, ch.elm as Node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateChildren(parentElm: Node,
|
||||
oldCh: Array<VNode>,
|
||||
newCh: Array<VNode>,
|
||||
insertedVnodeQueue: VNodeQueue) {
|
||||
let oldStartIdx = 0, newStartIdx = 0;
|
||||
let oldEndIdx = oldCh.length - 1;
|
||||
let oldStartVnode = oldCh[0];
|
||||
let oldEndVnode = oldCh[oldEndIdx];
|
||||
let newEndIdx = newCh.length - 1;
|
||||
let newStartVnode = newCh[0];
|
||||
let newEndVnode = newCh[newEndIdx];
|
||||
let oldKeyToIdx: any;
|
||||
let idxInOld: number;
|
||||
let elmToMove: VNode;
|
||||
let before: any;
|
||||
|
||||
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
|
||||
if (oldStartVnode == null) {
|
||||
oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
|
||||
} else if (oldEndVnode == null) {
|
||||
oldEndVnode = oldCh[--oldEndIdx];
|
||||
} else if (newStartVnode == null) {
|
||||
newStartVnode = newCh[++newStartIdx];
|
||||
} else if (newEndVnode == null) {
|
||||
newEndVnode = newCh[--newEndIdx];
|
||||
} else if (sameVnode(oldStartVnode, newStartVnode)) {
|
||||
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
|
||||
oldStartVnode = oldCh[++oldStartIdx];
|
||||
newStartVnode = newCh[++newStartIdx];
|
||||
} else if (sameVnode(oldEndVnode, newEndVnode)) {
|
||||
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
|
||||
oldEndVnode = oldCh[--oldEndIdx];
|
||||
newEndVnode = newCh[--newEndIdx];
|
||||
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
|
||||
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
|
||||
api.insertBefore(parentElm, oldStartVnode.elm as Node, api.nextSibling(oldEndVnode.elm as Node));
|
||||
oldStartVnode = oldCh[++oldStartIdx];
|
||||
newEndVnode = newCh[--newEndIdx];
|
||||
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
|
||||
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
|
||||
api.insertBefore(parentElm, oldEndVnode.elm as Node, oldStartVnode.elm as Node);
|
||||
oldEndVnode = oldCh[--oldEndIdx];
|
||||
newStartVnode = newCh[++newStartIdx];
|
||||
} else {
|
||||
if (oldKeyToIdx === undefined) {
|
||||
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
|
||||
}
|
||||
idxInOld = oldKeyToIdx[newStartVnode.key as string];
|
||||
if (isUndef(idxInOld)) { // New element
|
||||
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm as Node);
|
||||
newStartVnode = newCh[++newStartIdx];
|
||||
} else {
|
||||
elmToMove = oldCh[idxInOld];
|
||||
if (elmToMove.sel !== newStartVnode.sel) {
|
||||
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm as Node);
|
||||
} else {
|
||||
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
|
||||
oldCh[idxInOld] = undefined as any;
|
||||
api.insertBefore(parentElm, (elmToMove.elm as Node), oldStartVnode.elm as Node);
|
||||
}
|
||||
newStartVnode = newCh[++newStartIdx];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
|
||||
if (oldStartIdx > oldEndIdx) {
|
||||
before = newCh[newEndIdx+1] == null ? null : newCh[newEndIdx+1].elm;
|
||||
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
|
||||
} else {
|
||||
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
|
||||
let i: any, hook: any;
|
||||
if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
|
||||
i(oldVnode, vnode);
|
||||
}
|
||||
const elm = vnode.elm = (oldVnode.elm as Node);
|
||||
let oldCh = oldVnode.children;
|
||||
let ch = vnode.children;
|
||||
if (oldVnode === vnode) return;
|
||||
if (vnode.data !== undefined) {
|
||||
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
|
||||
i = vnode.data.hook;
|
||||
if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode);
|
||||
}
|
||||
if (isUndef(vnode.text)) {
|
||||
if (isDef(oldCh) && isDef(ch)) {
|
||||
if (oldCh !== ch) updateChildren(elm, oldCh as Array<VNode>, ch as Array<VNode>, insertedVnodeQueue);
|
||||
} else if (isDef(ch)) {
|
||||
if (isDef(oldVnode.text)) api.setTextContent(elm, '');
|
||||
addVnodes(elm, null, ch as Array<VNode>, 0, (ch as Array<VNode>).length - 1, insertedVnodeQueue);
|
||||
} else if (isDef(oldCh)) {
|
||||
removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
|
||||
} else if (isDef(oldVnode.text)) {
|
||||
api.setTextContent(elm, '');
|
||||
}
|
||||
} else if (oldVnode.text !== vnode.text) {
|
||||
if (isDef(oldCh)) {
|
||||
removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
|
||||
}
|
||||
api.setTextContent(elm, vnode.text as string);
|
||||
}
|
||||
if (isDef(hook) && isDef(i = hook.postpatch)) {
|
||||
i(oldVnode, vnode);
|
||||
}
|
||||
}
|
||||
|
||||
return function patch(oldVnode: VNode | Element, vnode: VNode): VNode {
|
||||
let i: number, elm: Node, parent: Node;
|
||||
const insertedVnodeQueue: VNodeQueue = [];
|
||||
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
|
||||
|
||||
if (!isVnode(oldVnode)) {
|
||||
oldVnode = emptyNodeAt(oldVnode);
|
||||
}
|
||||
|
||||
if (sameVnode(oldVnode, vnode)) {
|
||||
patchVnode(oldVnode, vnode, insertedVnodeQueue);
|
||||
} else {
|
||||
elm = oldVnode.elm as Node;
|
||||
parent = api.parentNode(elm);
|
||||
|
||||
createElm(vnode, insertedVnodeQueue);
|
||||
|
||||
if (parent !== null) {
|
||||
api.insertBefore(parent, vnode.elm as Node, api.nextSibling(elm));
|
||||
removeVnodes(parent, [oldVnode], 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < insertedVnodeQueue.length; ++i) {
|
||||
(((insertedVnodeQueue[i].data as VNodeData).hook as Hooks).insert as any)(insertedVnodeQueue[i]);
|
||||
}
|
||||
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
|
||||
return vnode;
|
||||
};
|
||||
}
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
import {VNode, VNodeData} from './vnode';
|
||||
import {h} from './h';
|
||||
|
||||
export interface ThunkData extends VNodeData {
|
||||
fn: () => VNode;
|
||||
args: Array<any>;
|
||||
}
|
||||
|
||||
export interface Thunk extends VNode {
|
||||
data: ThunkData;
|
||||
}
|
||||
|
||||
export interface ThunkFn {
|
||||
(sel: string, fn: Function, args: Array<any>): Thunk;
|
||||
(sel: string, key: any, fn: Function, args: Array<any>): Thunk;
|
||||
}
|
||||
|
||||
function copyToThunk(vnode: VNode, thunk: VNode): void {
|
||||
thunk.elm = vnode.elm;
|
||||
(vnode.data as VNodeData).fn = (thunk.data as VNodeData).fn;
|
||||
(vnode.data as VNodeData).args = (thunk.data as VNodeData).args;
|
||||
thunk.data = vnode.data;
|
||||
thunk.children = vnode.children;
|
||||
thunk.text = vnode.text;
|
||||
thunk.elm = vnode.elm;
|
||||
}
|
||||
|
||||
function init(thunk: VNode): void {
|
||||
const cur = thunk.data as VNodeData;
|
||||
const vnode = (cur.fn as any).apply(undefined, cur.args);
|
||||
copyToThunk(vnode, thunk);
|
||||
}
|
||||
|
||||
function prepatch(oldVnode: VNode, thunk: VNode): void {
|
||||
let i: number, old = oldVnode.data as VNodeData, cur = thunk.data as VNodeData;
|
||||
const oldArgs = old.args, args = cur.args;
|
||||
if (old.fn !== cur.fn || (oldArgs as any).length !== (args as any).length) {
|
||||
copyToThunk((cur.fn as any).apply(undefined, args), thunk);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < (args as any).length; ++i) {
|
||||
if ((oldArgs as any)[i] !== (args as any)[i]) {
|
||||
copyToThunk((cur.fn as any).apply(undefined, args), thunk);
|
||||
return;
|
||||
}
|
||||
}
|
||||
copyToThunk(oldVnode, thunk);
|
||||
}
|
||||
|
||||
export const thunk = function thunk(sel: string, key?: any, fn?: any, args?: any): VNode {
|
||||
if (args === undefined) {
|
||||
args = fn;
|
||||
fn = key;
|
||||
key = undefined;
|
||||
}
|
||||
return h(sel, {
|
||||
key: key,
|
||||
hook: {init: init, prepatch: prepatch},
|
||||
fn: fn,
|
||||
args: args
|
||||
});
|
||||
} as ThunkFn;
|
||||
|
||||
export default thunk;
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
import vnode, {VNode} from './vnode';
|
||||
import htmlDomApi, {DOMAPI} from './htmldomapi';
|
||||
|
||||
export function toVNode(node: Node, domApi?: DOMAPI): VNode {
|
||||
const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi;
|
||||
let text: string;
|
||||
if (api.isElement(node)) {
|
||||
const id = node.id ? '#' + node.id : '';
|
||||
const cn = node.getAttribute('class');
|
||||
const c = cn ? '.' + cn.split(' ').join('.') : '';
|
||||
const sel = api.tagName(node).toLowerCase() + id + c;
|
||||
const attrs: any = {};
|
||||
const children: Array<VNode> = [];
|
||||
let name: string;
|
||||
let i: number, n: number;
|
||||
const elmAttrs = node.attributes;
|
||||
const elmChildren = node.childNodes;
|
||||
for (i = 0, n = elmAttrs.length; i < n; i++) {
|
||||
name = elmAttrs[i].nodeName;
|
||||
if (name !== 'id' && name !== 'class') {
|
||||
attrs[name] = elmAttrs[i].nodeValue;
|
||||
}
|
||||
}
|
||||
for (i = 0, n = elmChildren.length; i < n; i++) {
|
||||
children.push(toVNode(elmChildren[i], domApi));
|
||||
}
|
||||
return vnode(sel, {attrs}, children, undefined, node);
|
||||
} else if (api.isText(node)) {
|
||||
text = api.getTextContent(node) as string;
|
||||
return vnode(undefined, undefined, undefined, text, node);
|
||||
} else if (api.isComment(node)) {
|
||||
text = api.getTextContent(node) as string;
|
||||
return vnode('!', {}, [], text, node as any);
|
||||
} else {
|
||||
return vnode('', {}, [], undefined, node as any);
|
||||
}
|
||||
}
|
||||
|
||||
export default toVNode;
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
import {Hooks} from './hooks';
|
||||
import {AttachData} from './helpers/attachto'
|
||||
import {VNodeStyle} from './modules/style'
|
||||
import {On} from './modules/eventlisteners'
|
||||
import {Attrs} from './modules/attributes'
|
||||
import {Classes} from './modules/class'
|
||||
import {Props} from './modules/props'
|
||||
import {Dataset} from './modules/dataset'
|
||||
import {Hero} from './modules/hero'
|
||||
|
||||
export type Key = string | number;
|
||||
|
||||
export interface VNode {
|
||||
sel: string | undefined;
|
||||
data: VNodeData | undefined;
|
||||
children: Array<VNode | string> | undefined;
|
||||
elm: Node | undefined;
|
||||
text: string | undefined;
|
||||
key: Key | undefined;
|
||||
}
|
||||
|
||||
export interface VNodeData {
|
||||
props?: Props;
|
||||
attrs?: Attrs;
|
||||
class?: Classes;
|
||||
style?: VNodeStyle;
|
||||
dataset?: Dataset;
|
||||
on?: On;
|
||||
hero?: Hero;
|
||||
attachData?: AttachData;
|
||||
hook?: Hooks;
|
||||
key?: Key;
|
||||
ns?: string; // for SVGs
|
||||
fn?: () => VNode; // for thunks
|
||||
args?: Array<any>; // for thunks
|
||||
[key: string]: any; // for any other 3rd party module
|
||||
}
|
||||
|
||||
export function vnode(sel: string | undefined,
|
||||
data: any | undefined,
|
||||
children: Array<VNode | string> | undefined,
|
||||
text: string | undefined,
|
||||
elm: Element | Text | undefined): VNode {
|
||||
let key = data === undefined ? undefined : data.key;
|
||||
return {sel: sel, data: data, children: children,
|
||||
text: text, elm: elm, key: key};
|
||||
}
|
||||
|
||||
export default vnode;
|
||||
Reference in New Issue
Block a user