-
-
Save trusktr/e37bfe5572fcd0759d1729f06e3dcf35 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var infamous = (function (exports) { | |
'use strict'; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* A high-performance static matrix math library used to calculate | |
* affine transforms on surfaces and other renderables. | |
* Famo.us uses 4x4 matrices corresponding directly to | |
* WebKit matrices (column-major order). | |
* | |
* The internal "type" of a Matrix is a 16-long float array in | |
* row-major order, with: | |
* elements [0],[1],[2],[4],[5],[6],[8],[9],[10] forming the 3x3 | |
* transformation matrix; | |
* elements [12], [13], [14] corresponding to the t_x, t_y, t_z | |
* translation; | |
* elements [3], [7], [11] set to 0; | |
* element [15] set to 1. | |
* All methods are static. | |
* | |
* @static | |
* | |
* @class Transform | |
*/ | |
var Transform = {}; | |
// WARNING: these matrices correspond to WebKit matrices, which are | |
// transposed from their math counterparts | |
Transform.precision = 1e-6; | |
Transform.identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; | |
/** | |
* Multiply two or more Transform matrix types to return a Transform matrix. | |
* | |
* @method multiply4x4 | |
* @static | |
* @param {Transform} a left Transform | |
* @param {Transform} b right Transform | |
* @return {Transform} | |
*/ | |
Transform.multiply4x4 = function multiply4x4(a, b) { | |
return [ | |
a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3], | |
a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3], | |
a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3], | |
a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3], | |
a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7], | |
a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7], | |
a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7], | |
a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7], | |
a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11], | |
a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11], | |
a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11], | |
a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11], | |
a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15], | |
a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15], | |
a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15], | |
a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15] | |
]; | |
}; | |
/** | |
* Fast-multiply two Transform matrix types to return a | |
* Matrix, assuming bottom row on each is [0 0 0 1]. | |
* | |
* @method multiply | |
* @static | |
* @param {Transform} a left Transform | |
* @param {Transform} b right Transform | |
* @return {Transform} | |
*/ | |
Transform.multiply = function multiply(a, b) { | |
return [ | |
a[0] * b[0] + a[4] * b[1] + a[8] * b[2], | |
a[1] * b[0] + a[5] * b[1] + a[9] * b[2], | |
a[2] * b[0] + a[6] * b[1] + a[10] * b[2], | |
0, | |
a[0] * b[4] + a[4] * b[5] + a[8] * b[6], | |
a[1] * b[4] + a[5] * b[5] + a[9] * b[6], | |
a[2] * b[4] + a[6] * b[5] + a[10] * b[6], | |
0, | |
a[0] * b[8] + a[4] * b[9] + a[8] * b[10], | |
a[1] * b[8] + a[5] * b[9] + a[9] * b[10], | |
a[2] * b[8] + a[6] * b[9] + a[10] * b[10], | |
0, | |
a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12], | |
a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13], | |
a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14], | |
1 | |
]; | |
}; | |
/** | |
* Return a Transform translated by additional amounts in each | |
* dimension. This is equivalent to the result of | |
* | |
* Transform.multiply(Matrix.translate(t[0], t[1], t[2]), m). | |
* | |
* @method thenMove | |
* @static | |
* @param {Transform} m a Transform | |
* @param {Array.Number} t floats delta vector of length 2 or 3 | |
* @return {Transform} | |
*/ | |
Transform.thenMove = function thenMove(m, t) { | |
if (!t[2]) { t[2] = 0; } | |
return [m[0], m[1], m[2], 0, m[4], m[5], m[6], 0, m[8], m[9], m[10], 0, m[12] + t[0], m[13] + t[1], m[14] + t[2], 1]; | |
}; | |
/** | |
* Return a Transform matrix which represents the result of a transform matrix | |
* applied after a move. This is faster than the equivalent multiply. | |
* This is equivalent to the result of: | |
* | |
* Transform.multiply(m, Transform.translate(t[0], t[1], t[2])). | |
* | |
* @method moveThen | |
* @static | |
* @param {Array.Number} v vector representing initial movement | |
* @param {Transform} m matrix to apply afterwards | |
* @return {Transform} the resulting matrix | |
*/ | |
Transform.moveThen = function moveThen(v, m) { | |
if (!v[2]) { v[2] = 0; } | |
var t0 = v[0] * m[0] + v[1] * m[4] + v[2] * m[8]; | |
var t1 = v[0] * m[1] + v[1] * m[5] + v[2] * m[9]; | |
var t2 = v[0] * m[2] + v[1] * m[6] + v[2] * m[10]; | |
return Transform.thenMove(m, [t0, t1, t2]); | |
}; | |
/** | |
* Return a Transform which represents a translation by specified | |
* amounts in each dimension. | |
* | |
* @method translate | |
* @static | |
* @param {Number} x x translation | |
* @param {Number} y y translation | |
* @param {Number} z z translation | |
* @return {Transform} | |
*/ | |
Transform.translate = function translate(x, y, z) { | |
if (z === undefined) { z = 0; } | |
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1]; | |
}; | |
/** | |
* Return a Transform scaled by a vector in each | |
* dimension. This is a more performant equivalent to the result of | |
* | |
* Transform.multiply(Transform.scale(s[0], s[1], s[2]), m). | |
* | |
* @method thenScale | |
* @static | |
* @param {Transform} m a matrix | |
* @param {Array.Number} s delta vector (array of floats && | |
* array.length == 3) | |
* @return {Transform} | |
*/ | |
Transform.thenScale = function thenScale(m, s) { | |
return [ | |
s[0] * m[0], s[1] * m[1], s[2] * m[2], 0, | |
s[0] * m[4], s[1] * m[5], s[2] * m[6], 0, | |
s[0] * m[8], s[1] * m[9], s[2] * m[10], 0, | |
s[0] * m[12], s[1] * m[13], s[2] * m[14], 1 | |
]; | |
}; | |
/** | |
* Return a Transform which represents a scale by specified amounts | |
* in each dimension. | |
* | |
* @method scale | |
* @static | |
* @param {Number} x x scale factor | |
* @param {Number} y y scale factor | |
* @param {Number} z z scale factor | |
* @return {Transform} | |
*/ | |
Transform.scale = function scale(x, y, z) { | |
if (z === undefined) { z = 1; } | |
if (y === undefined) { y = x; } | |
return [x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1]; | |
}; | |
/** | |
* Return a Transform which represents a clockwise | |
* rotation around the x axis. | |
* | |
* @method rotateX | |
* @static | |
* @param {Number} theta radians | |
* @return {Transform} | |
*/ | |
Transform.rotateX = function rotateX(theta) { | |
var cosTheta = Math.cos(theta); | |
var sinTheta = Math.sin(theta); | |
return [1, 0, 0, 0, 0, cosTheta, sinTheta, 0, 0, -sinTheta, cosTheta, 0, 0, 0, 0, 1]; | |
}; | |
/** | |
* Return a Transform which represents a clockwise | |
* rotation around the y axis. | |
* | |
* @method rotateY | |
* @static | |
* @param {Number} theta radians | |
* @return {Transform} | |
*/ | |
Transform.rotateY = function rotateY(theta) { | |
var cosTheta = Math.cos(theta); | |
var sinTheta = Math.sin(theta); | |
return [cosTheta, 0, -sinTheta, 0, 0, 1, 0, 0, sinTheta, 0, cosTheta, 0, 0, 0, 0, 1]; | |
}; | |
/** | |
* Return a Transform which represents a clockwise | |
* rotation around the z axis. | |
* | |
* @method rotateZ | |
* @static | |
* @param {Number} theta radians | |
* @return {Transform} | |
*/ | |
Transform.rotateZ = function rotateZ(theta) { | |
var cosTheta = Math.cos(theta); | |
var sinTheta = Math.sin(theta); | |
return [cosTheta, sinTheta, 0, 0, -sinTheta, cosTheta, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; | |
}; | |
/** | |
* Return a Transform which represents composed clockwise | |
* rotations along each of the axes. Equivalent to the result of | |
* Matrix.multiply(rotateX(phi), rotateY(theta), rotateZ(psi)). | |
* | |
* @method rotate | |
* @static | |
* @param {Number} phi radians to rotate about the positive x axis | |
* @param {Number} theta radians to rotate about the positive y axis | |
* @param {Number} psi radians to rotate about the positive z axis | |
* @return {Transform} | |
*/ | |
Transform.rotate = function rotate(phi, theta, psi) { | |
var cosPhi = Math.cos(phi); | |
var sinPhi = Math.sin(phi); | |
var cosTheta = Math.cos(theta); | |
var sinTheta = Math.sin(theta); | |
var cosPsi = Math.cos(psi); | |
var sinPsi = Math.sin(psi); | |
var result = [ | |
cosTheta * cosPsi, | |
cosPhi * sinPsi + sinPhi * sinTheta * cosPsi, | |
sinPhi * sinPsi - cosPhi * sinTheta * cosPsi, | |
0, | |
-cosTheta * sinPsi, | |
cosPhi * cosPsi - sinPhi * sinTheta * sinPsi, | |
sinPhi * cosPsi + cosPhi * sinTheta * sinPsi, | |
0, | |
sinTheta, | |
-sinPhi * cosTheta, | |
cosPhi * cosTheta, | |
0, | |
0, 0, 0, 1 | |
]; | |
return result; | |
}; | |
/** | |
* Return a Transform which represents an axis-angle rotation | |
* | |
* @method rotateAxis | |
* @static | |
* @param {Array.Number} v unit vector representing the axis to rotate about | |
* @param {Number} theta radians to rotate clockwise about the axis | |
* @return {Transform} | |
*/ | |
Transform.rotateAxis = function rotateAxis(v, theta) { | |
var sinTheta = Math.sin(theta); | |
var cosTheta = Math.cos(theta); | |
var verTheta = 1 - cosTheta; // versine of theta | |
var xxV = v[0] * v[0] * verTheta; | |
var xyV = v[0] * v[1] * verTheta; | |
var xzV = v[0] * v[2] * verTheta; | |
var yyV = v[1] * v[1] * verTheta; | |
var yzV = v[1] * v[2] * verTheta; | |
var zzV = v[2] * v[2] * verTheta; | |
var xs = v[0] * sinTheta; | |
var ys = v[1] * sinTheta; | |
var zs = v[2] * sinTheta; | |
var result = [ | |
xxV + cosTheta, xyV + zs, xzV - ys, 0, | |
xyV - zs, yyV + cosTheta, yzV + xs, 0, | |
xzV + ys, yzV - xs, zzV + cosTheta, 0, | |
0, 0, 0, 1 | |
]; | |
return result; | |
}; | |
/** | |
* Return a Transform which represents a transform matrix applied about | |
* a separate origin point. | |
* | |
* @method aboutOrigin | |
* @static | |
* @param {Array.Number} v origin point to apply matrix | |
* @param {Transform} m matrix to apply | |
* @return {Transform} | |
*/ | |
Transform.aboutOrigin = function aboutOrigin(v, m) { | |
var t0 = v[0] - (v[0] * m[0] + v[1] * m[4] + v[2] * m[8]); | |
var t1 = v[1] - (v[0] * m[1] + v[1] * m[5] + v[2] * m[9]); | |
var t2 = v[2] - (v[0] * m[2] + v[1] * m[6] + v[2] * m[10]); | |
return Transform.thenMove(m, [t0, t1, t2]); | |
}; | |
/** | |
* Return a Transform representation of a skew transformation | |
* | |
* @method skew | |
* @static | |
* @param {Number} phi scale factor skew in the x axis | |
* @param {Number} theta scale factor skew in the y axis | |
* @param {Number} psi scale factor skew in the z axis | |
* @return {Transform} | |
*/ | |
Transform.skew = function skew(phi, theta, psi) { | |
return [1, Math.tan(theta), 0, 0, Math.tan(psi), 1, 0, 0, 0, Math.tan(phi), 1, 0, 0, 0, 0, 1]; | |
}; | |
/** | |
* Return a Transform representation of a skew in the x-direction | |
* | |
* @method skewX | |
* @static | |
* @param {Number} angle the angle between the top and left sides | |
* @return {Transform} | |
*/ | |
Transform.skewX = function skewX(angle) { | |
return [1, 0, 0, 0, Math.tan(angle), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; | |
}; | |
/** | |
* Return a Transform representation of a skew in the y-direction | |
* | |
* @method skewY | |
* @static | |
* @param {Number} angle the angle between the top and right sides | |
* @return {Transform} | |
*/ | |
Transform.skewY = function skewY(angle) { | |
return [1, Math.tan(angle), 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; | |
}; | |
/** | |
* Returns a perspective Transform matrix | |
* | |
* @method perspective | |
* @static | |
* @param {Number} focusZ z position of focal point | |
* @return {Transform} | |
*/ | |
Transform.perspective = function perspective(focusZ) { | |
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1 / focusZ, 0, 0, 0, 1]; | |
}; | |
/** | |
* Return translation vector component of given Transform | |
* | |
* @method getTranslate | |
* @static | |
* @param {Transform} m Transform | |
* @return {Array.Number} the translation vector [t_x, t_y, t_z] | |
*/ | |
Transform.getTranslate = function getTranslate(m) { | |
return [m[12], m[13], m[14]]; | |
}; | |
/** | |
* Return inverse affine transform for given Transform. | |
* Note: This assumes m[3] = m[7] = m[11] = 0, and m[15] = 1. | |
* Will provide incorrect results if not invertible or preconditions not met. | |
* | |
* @method inverse | |
* @static | |
* @param {Transform} m Transform | |
* @return {Transform} | |
*/ | |
Transform.inverse = function inverse(m) { | |
// only need to consider 3x3 section for affine | |
var c0 = m[5] * m[10] - m[6] * m[9]; | |
var c1 = m[4] * m[10] - m[6] * m[8]; | |
var c2 = m[4] * m[9] - m[5] * m[8]; | |
var c4 = m[1] * m[10] - m[2] * m[9]; | |
var c5 = m[0] * m[10] - m[2] * m[8]; | |
var c6 = m[0] * m[9] - m[1] * m[8]; | |
var c8 = m[1] * m[6] - m[2] * m[5]; | |
var c9 = m[0] * m[6] - m[2] * m[4]; | |
var c10 = m[0] * m[5] - m[1] * m[4]; | |
var detM = m[0] * c0 - m[1] * c1 + m[2] * c2; | |
var invD = 1 / detM; | |
var result = [ | |
invD * c0, -invD * c4, invD * c8, 0, | |
-invD * c1, invD * c5, -invD * c9, 0, | |
invD * c2, -invD * c6, invD * c10, 0, | |
0, 0, 0, 1 | |
]; | |
result[12] = -m[12] * result[0] - m[13] * result[4] - m[14] * result[8]; | |
result[13] = -m[12] * result[1] - m[13] * result[5] - m[14] * result[9]; | |
result[14] = -m[12] * result[2] - m[13] * result[6] - m[14] * result[10]; | |
return result; | |
}; | |
/** | |
* Returns the transpose of a 4x4 matrix | |
* | |
* @method transpose | |
* @static | |
* @param {Transform} m matrix | |
* @return {Transform} the resulting transposed matrix | |
*/ | |
Transform.transpose = function transpose(m) { | |
return [m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]]; | |
}; | |
function _normSquared(v) { | |
return (v.length === 2) ? v[0] * v[0] + v[1] * v[1] : v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; | |
} | |
function _norm(v) { | |
return Math.sqrt(_normSquared(v)); | |
} | |
function _sign(n) { | |
return (n < 0) ? -1 : 1; | |
} | |
/** | |
* Decompose Transform into separate .translate, .rotate, .scale, | |
* and .skew components. | |
* | |
* @method interpret | |
* @static | |
* @param {Transform} M transform matrix | |
* @return {Object} matrix spec object with component matrices .translate, | |
* .rotate, .scale, .skew | |
*/ | |
Transform.interpret = function interpret(M) { | |
// QR decomposition via Householder reflections | |
//FIRST ITERATION | |
//default Q1 to the identity matrix; | |
var x = [M[0], M[1], M[2]]; // first column vector | |
var sgn = _sign(x[0]); // sign of first component of x (for stability) | |
var xNorm = _norm(x); // norm of first column vector | |
var v = [x[0] + sgn * xNorm, x[1], x[2]]; // v = x + sign(x[0])|x|e1 | |
var mult = 2 / _normSquared(v); // mult = 2/v'v | |
//bail out if our Matrix is singular | |
if (mult >= Infinity) { | |
return {translate: Transform.getTranslate(M), rotate: [0, 0, 0], scale: [0, 0, 0], skew: [0, 0, 0]}; | |
} | |
//evaluate Q1 = I - 2vv'/v'v | |
var Q1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; | |
//diagonals | |
Q1[0] = 1 - mult * v[0] * v[0]; // 0,0 entry | |
Q1[5] = 1 - mult * v[1] * v[1]; // 1,1 entry | |
Q1[10] = 1 - mult * v[2] * v[2]; // 2,2 entry | |
//upper diagonal | |
Q1[1] = -mult * v[0] * v[1]; // 0,1 entry | |
Q1[2] = -mult * v[0] * v[2]; // 0,2 entry | |
Q1[6] = -mult * v[1] * v[2]; // 1,2 entry | |
//lower diagonal | |
Q1[4] = Q1[1]; // 1,0 entry | |
Q1[8] = Q1[2]; // 2,0 entry | |
Q1[9] = Q1[6]; // 2,1 entry | |
//reduce first column of M | |
var MQ1 = Transform.multiply(Q1, M); | |
//SECOND ITERATION on (1,1) minor | |
var x2 = [MQ1[5], MQ1[6]]; | |
var sgn2 = _sign(x2[0]); // sign of first component of x (for stability) | |
var x2Norm = _norm(x2); // norm of first column vector | |
var v2 = [x2[0] + sgn2 * x2Norm, x2[1]]; // v = x + sign(x[0])|x|e1 | |
var mult2 = 2 / _normSquared(v2); // mult = 2/v'v | |
//evaluate Q2 = I - 2vv'/v'v | |
var Q2 = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; | |
//diagonal | |
Q2[5] = 1 - mult2 * v2[0] * v2[0]; // 1,1 entry | |
Q2[10] = 1 - mult2 * v2[1] * v2[1]; // 2,2 entry | |
//off diagonals | |
Q2[6] = -mult2 * v2[0] * v2[1]; // 2,1 entry | |
Q2[9] = Q2[6]; // 1,2 entry | |
//calc QR decomposition. Q = Q1*Q2, R = Q'*M | |
var Q = Transform.multiply(Q2, Q1); //note: really Q transpose | |
var R = Transform.multiply(Q, M); | |
//remove negative scaling | |
var remover = Transform.scale(R[0] < 0 ? -1 : 1, R[5] < 0 ? -1 : 1, R[10] < 0 ? -1 : 1); | |
R = Transform.multiply(R, remover); | |
Q = Transform.multiply(remover, Q); | |
//decompose into rotate/scale/skew matrices | |
var result = {}; | |
result.translate = Transform.getTranslate(M); | |
result.rotate = [Math.atan2(-Q[6], Q[10]), Math.asin(Q[2]), Math.atan2(-Q[1], Q[0])]; | |
if (!result.rotate[0]) { | |
result.rotate[0] = 0; | |
result.rotate[2] = Math.atan2(Q[4], Q[5]); | |
} | |
result.scale = [R[0], R[5], R[10]]; | |
result.skew = [Math.atan2(R[9], result.scale[2]), Math.atan2(R[8], result.scale[2]), Math.atan2(R[4], result.scale[0])]; | |
//double rotation workaround | |
if (Math.abs(result.rotate[0]) + Math.abs(result.rotate[2]) > 1.5 * Math.PI) { | |
result.rotate[1] = Math.PI - result.rotate[1]; | |
if (result.rotate[1] > Math.PI) { result.rotate[1] -= 2 * Math.PI; } | |
if (result.rotate[1] < -Math.PI) { result.rotate[1] += 2 * Math.PI; } | |
if (result.rotate[0] < 0) { result.rotate[0] += Math.PI; } | |
else { result.rotate[0] -= Math.PI; } | |
if (result.rotate[2] < 0) { result.rotate[2] += Math.PI; } | |
else { result.rotate[2] -= Math.PI; } | |
} | |
return result; | |
}; | |
/** | |
* Weighted average between two matrices by averaging their | |
* translation, rotation, scale, skew components. | |
* f(M1,M2,t) = (1 - t) * M1 + t * M2 | |
* | |
* @method average | |
* @static | |
* @param {Transform} M1 f(M1,M2,0) = M1 | |
* @param {Transform} M2 f(M1,M2,1) = M2 | |
* @param {Number} t | |
* @return {Transform} | |
*/ | |
Transform.average = function average(M1, M2, t) { | |
t = (t === undefined) ? 0.5 : t; | |
var specM1 = Transform.interpret(M1); | |
var specM2 = Transform.interpret(M2); | |
var specAvg = { | |
translate: [0, 0, 0], | |
rotate: [0, 0, 0], | |
scale: [0, 0, 0], | |
skew: [0, 0, 0] | |
}; | |
for (var i = 0; i < 3; i++) { | |
specAvg.translate[i] = (1 - t) * specM1.translate[i] + t * specM2.translate[i]; | |
specAvg.rotate[i] = (1 - t) * specM1.rotate[i] + t * specM2.rotate[i]; | |
specAvg.scale[i] = (1 - t) * specM1.scale[i] + t * specM2.scale[i]; | |
specAvg.skew[i] = (1 - t) * specM1.skew[i] + t * specM2.skew[i]; | |
} | |
return Transform.build(specAvg); | |
}; | |
/** | |
* Compose .translate, .rotate, .scale, .skew components into | |
* Transform matrix | |
* | |
* @method build | |
* @static | |
* @param {matrixSpec} spec object with component matrices .translate, | |
* .rotate, .scale, .skew | |
* @return {Transform} composed transform | |
*/ | |
Transform.build = function build(spec) { | |
var scaleMatrix = Transform.scale(spec.scale[0], spec.scale[1], spec.scale[2]); | |
var skewMatrix = Transform.skew(spec.skew[0], spec.skew[1], spec.skew[2]); | |
var rotateMatrix = Transform.rotate(spec.rotate[0], spec.rotate[1], spec.rotate[2]); | |
return Transform.thenMove(Transform.multiply(Transform.multiply(rotateMatrix, skewMatrix), scaleMatrix), spec.translate); | |
}; | |
/** | |
* Determine if two Transforms are component-wise equal | |
* Warning: breaks on perspective Transforms | |
* | |
* @method equals | |
* @static | |
* @param {Transform} a matrix | |
* @param {Transform} b matrix | |
* @return {boolean} | |
*/ | |
Transform.equals = function equals(a, b) { | |
return !Transform.notEquals(a, b); | |
}; | |
/** | |
* Determine if two Transforms are component-wise unequal | |
* Warning: breaks on perspective Transforms | |
* | |
* @method notEquals | |
* @static | |
* @param {Transform} a matrix | |
* @param {Transform} b matrix | |
* @return {boolean} | |
*/ | |
Transform.notEquals = function notEquals(a, b) { | |
if (a === b) { return false; } | |
// shortci | |
return !(a && b) || | |
a[12] !== b[12] || a[13] !== b[13] || a[14] !== b[14] || | |
a[0] !== b[0] || a[1] !== b[1] || a[2] !== b[2] || | |
a[4] !== b[4] || a[5] !== b[5] || a[6] !== b[6] || | |
a[8] !== b[8] || a[9] !== b[9] || a[10] !== b[10]; | |
}; | |
/** | |
* Constrain angle-trio components to range of [-pi, pi). | |
* | |
* @method normalizeRotation | |
* @static | |
* @param {Array.Number} rotation phi, theta, psi (array of floats | |
* && array.length == 3) | |
* @return {Array.Number} new phi, theta, psi triplet | |
* (array of floats && array.length == 3) | |
*/ | |
Transform.normalizeRotation = function normalizeRotation(rotation) { | |
var result = rotation.slice(0); | |
if (result[0] === Math.PI * 0.5 || result[0] === -Math.PI * 0.5) { | |
result[0] = -result[0]; | |
result[1] = Math.PI - result[1]; | |
result[2] -= Math.PI; | |
} | |
if (result[0] > Math.PI * 0.5) { | |
result[0] = result[0] - Math.PI; | |
result[1] = Math.PI - result[1]; | |
result[2] -= Math.PI; | |
} | |
if (result[0] < -Math.PI * 0.5) { | |
result[0] = result[0] + Math.PI; | |
result[1] = -Math.PI - result[1]; | |
result[2] -= Math.PI; | |
} | |
while (result[1] < -Math.PI) { result[1] += 2 * Math.PI; } | |
while (result[1] >= Math.PI) { result[1] -= 2 * Math.PI; } | |
while (result[2] < -Math.PI) { result[2] += 2 * Math.PI; } | |
while (result[2] >= Math.PI) { result[2] -= 2 * Math.PI; } | |
return result; | |
}; | |
/** | |
* (Property) Array defining a translation forward in z by 1 | |
* | |
* @property {array} inFront | |
* @static | |
* @final | |
*/ | |
Transform.inFront = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1e-3, 1]; | |
/** | |
* (Property) Array defining a translation backwards in z by 1 | |
* | |
* @property {array} behind | |
* @static | |
* @final | |
*/ | |
Transform.behind = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -1e-3, 1]; | |
var Transform_1 = Transform; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* This namespace holds standalone functionality. | |
* Currently includes name mapping for transition curves, | |
* name mapping for origin pairs, and the after() function. | |
* | |
* @class Utility | |
* @static | |
*/ | |
var Utility = {}; | |
/** | |
* Table of direction array positions | |
* | |
* @property {object} Direction | |
* @final | |
*/ | |
Utility.Direction = { | |
X: 0, | |
Y: 1, | |
Z: 2 | |
}; | |
/** | |
* Return wrapper around callback function. Once the wrapper is called N | |
* times, invoke the callback function. Arguments and scope preserved. | |
* | |
* @method after | |
* | |
* @param {number} count number of calls before callback function invoked | |
* @param {Function} callback wrapped callback function | |
* | |
* @return {function} wrapped callback with coundown feature | |
*/ | |
Utility.after = function after(count, callback) { | |
var counter = count; | |
return function() { | |
counter--; | |
if (counter === 0) { callback.apply(this, arguments); } | |
}; | |
}; | |
/** | |
* Load a URL and return its contents in a callback | |
* | |
* @method loadURL | |
* | |
* @param {string} url URL of object | |
* @param {function} callback callback to dispatch with content | |
*/ | |
Utility.loadURL = function loadURL(url, callback) { | |
var xhr = new XMLHttpRequest(); | |
xhr.onreadystatechange = function onreadystatechange() { | |
if (this.readyState === 4) { | |
if (callback) { callback(this.responseText); } | |
} | |
}; | |
xhr.open('GET', url); | |
xhr.send(); | |
}; | |
/** | |
* Create a document fragment from a string of HTML | |
* | |
* @method createDocumentFragmentFromHTML | |
* | |
* @param {string} html HTML to convert to DocumentFragment | |
* | |
* @return {DocumentFragment} DocumentFragment representing input HTML | |
*/ | |
Utility.createDocumentFragmentFromHTML = function createDocumentFragmentFromHTML(html) { | |
var element = document.createElement('div'); | |
element.innerHTML = html; | |
var result = document.createDocumentFragment(); | |
while (element.hasChildNodes()) { result.appendChild(element.firstChild); } | |
return result; | |
}; | |
/* | |
* Deep clone an object. | |
* @param b {Object} Object to clone | |
* @return a {Object} Cloned object. | |
*/ | |
Utility.clone = function clone(b) { | |
var a; | |
if (typeof b === 'object') { | |
a = (b instanceof Array) ? [] : {}; | |
for (var key in b) { | |
if (typeof b[key] === 'object' && b[key] !== null) { | |
if (b[key] instanceof Array) { | |
a[key] = new Array(b[key].length); | |
for (var i = 0; i < b[key].length; i++) { | |
a[key][i] = Utility.clone(b[key][i]); | |
} | |
} | |
else { | |
a[key] = Utility.clone(b[key]); | |
} | |
} | |
else { | |
a[key] = b[key]; | |
} | |
} | |
} | |
else { | |
a = b; | |
} | |
return a; | |
}; | |
var Utility_1 = Utility; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: david@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/*eslint-disable new-cap */ | |
/** | |
* Transition meta-method to support transitioning multiple | |
* values with scalar-only methods. | |
* | |
* | |
* @class MultipleTransition | |
* @constructor | |
* | |
* @param {Object} method Transionable class to multiplex | |
*/ | |
function MultipleTransition(method) { | |
this.method = method; | |
this._instances = []; | |
this.state = []; | |
} | |
MultipleTransition.SUPPORTS_MULTIPLE = true; | |
/** | |
* Get the state of each transition. | |
* | |
* @method get | |
* | |
* @return state {Number|Array} state array | |
*/ | |
MultipleTransition.prototype.get = function get() { | |
for (var i = 0; i < this._instances.length; i++) { | |
this.state[i] = this._instances[i].get(); | |
} | |
return this.state; | |
}; | |
/** | |
* Set the end states with a shared transition, with optional callback. | |
* | |
* @method set | |
* | |
* @param {Number|Array} endState Final State. Use a multi-element argument for multiple transitions. | |
* @param {Object} transition Transition definition, shared among all instances | |
* @param {Function} callback called when all endStates have been reached. | |
*/ | |
MultipleTransition.prototype.set = function set(endState, transition, callback) { | |
var _allCallback = Utility_1.after(endState.length, callback); | |
for (var i = 0; i < endState.length; i++) { | |
if (!this._instances[i]) { this._instances[i] = new (this.method)(); } | |
this._instances[i].set(endState[i], transition, _allCallback); | |
} | |
}; | |
/** | |
* Reset all transitions to start state. | |
* | |
* @method reset | |
* | |
* @param {Number|Array} startState Start state | |
*/ | |
MultipleTransition.prototype.reset = function reset(startState) { | |
for (var i = 0; i < startState.length; i++) { | |
if (!this._instances[i]) { this._instances[i] = new (this.method)(); } | |
this._instances[i].reset(startState[i]); | |
} | |
}; | |
var MultipleTransition_1 = MultipleTransition; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: david@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* | |
* A state maintainer for a smooth transition between | |
* numerically-specified states. Example numeric states include floats or | |
* Transfornm objects. | |
* | |
* An initial state is set with the constructor or set(startValue). A | |
* corresponding end state and transition are set with set(endValue, | |
* transition). Subsequent calls to set(endValue, transition) begin at | |
* the last state. Calls to get(timestamp) provide the _interpolated state | |
* along the way. | |
* | |
* Note that there is no event loop here - calls to get() are the only way | |
* to find out state projected to the current (or provided) time and are | |
* the only way to trigger callbacks. Usually this kind of object would | |
* be part of the render() path of a visible component. | |
* | |
* @class TweenTransition | |
* @constructor | |
* | |
* @param {Object} options TODO | |
* beginning state | |
*/ | |
function TweenTransition(options) { | |
this.options = Object.create(TweenTransition.DEFAULT_OPTIONS); | |
if (options) { this.setOptions(options); } | |
this._startTime = 0; | |
this._startValue = 0; | |
this._updateTime = 0; | |
this._endValue = 0; | |
this._curve = undefined; | |
this._duration = 0; | |
this._active = false; | |
this._callback = undefined; | |
this.state = 0; | |
this.velocity = undefined; | |
} | |
/** | |
* Transition curves mapping independent variable t from domain [0,1] to a | |
* range within [0,1]. Includes functions 'linear', 'easeIn', 'easeOut', | |
* 'easeInOut', 'easeOutBounce', 'spring'. | |
* | |
* @property {object} Curve | |
* @final | |
*/ | |
TweenTransition.Curves = { | |
linear: function(t) { | |
return t; | |
}, | |
easeIn: function(t) { | |
return t*t; | |
}, | |
easeOut: function(t) { | |
return t*(2-t); | |
}, | |
easeInOut: function(t) { | |
if (t <= 0.5) { return 2*t*t; } | |
else { return -2*t*t + 4*t - 1; } | |
}, | |
easeOutBounce: function(t) { | |
return t*(3 - 2*t); | |
}, | |
spring: function(t) { | |
return (1 - t) * Math.sin(6 * Math.PI * t) + t; | |
} | |
}; | |
TweenTransition.SUPPORTS_MULTIPLE = true; | |
TweenTransition.DEFAULT_OPTIONS = { | |
curve: TweenTransition.Curves.linear, | |
duration: 500, | |
speed: 0 /* considered only if positive */ | |
}; | |
var registeredCurves = {}; | |
/** | |
* Add "unit" curve to internal dictionary of registered curves. | |
* | |
* @method registerCurve | |
* | |
* @static | |
* | |
* @param {string} curveName dictionary key | |
* @param {unitCurve} curve function of one numeric variable mapping [0,1] | |
* to range inside [0,1] | |
* @return {boolean} false if key is taken, else true | |
*/ | |
TweenTransition.registerCurve = function registerCurve(curveName, curve) { | |
if (!registeredCurves[curveName]) { | |
registeredCurves[curveName] = curve; | |
return true; | |
} | |
else { | |
return false; | |
} | |
}; | |
/** | |
* Remove object with key "curveName" from internal dictionary of registered | |
* curves. | |
* | |
* @method unregisterCurve | |
* | |
* @static | |
* | |
* @param {string} curveName dictionary key | |
* @return {boolean} false if key has no dictionary value | |
*/ | |
TweenTransition.unregisterCurve = function unregisterCurve(curveName) { | |
if (registeredCurves[curveName]) { | |
delete registeredCurves[curveName]; | |
return true; | |
} | |
else { | |
return false; | |
} | |
}; | |
/** | |
* Retrieve function with key "curveName" from internal dictionary of | |
* registered curves. Default curves are defined in the | |
* TweenTransition.Curves array, where the values represent | |
* unitCurve functions. | |
* | |
* @method getCurve | |
* | |
* @static | |
* | |
* @param {string} curveName dictionary key | |
* @return {unitCurve} curve function of one numeric variable mapping [0,1] | |
* to range inside [0,1] | |
*/ | |
TweenTransition.getCurve = function getCurve(curveName) { | |
var curve = registeredCurves[curveName]; | |
if (curve !== undefined) { return curve; } | |
else { throw new Error('curve not registered'); } | |
}; | |
/** | |
* Retrieve all available curves. | |
* | |
* @method getCurves | |
* | |
* @static | |
* | |
* @return {object} curve functions of one numeric variable mapping [0,1] | |
* to range inside [0,1] | |
*/ | |
TweenTransition.getCurves = function getCurves() { | |
return registeredCurves; | |
}; | |
// Interpolate: If a linear function f(0) = a, f(1) = b, then return f(t) | |
function _interpolate(a, b, t) { | |
return ((1 - t) * a) + (t * b); | |
} | |
function _clone(obj) { | |
if (obj instanceof Object) { | |
if (obj instanceof Array) { return obj.slice(0); } | |
else { return Object.create(obj); } | |
} | |
else { return obj; } | |
} | |
// Fill in missing properties in "transition" with those in defaultTransition, and | |
// convert internal named curve to function object, returning as new | |
// object. | |
function _normalize(transition, defaultTransition) { | |
var result = {curve: defaultTransition.curve}; | |
if (defaultTransition.duration) { result.duration = defaultTransition.duration; } | |
if (defaultTransition.speed) { result.speed = defaultTransition.speed; } | |
if (transition instanceof Object) { | |
if (transition.duration !== undefined) { result.duration = transition.duration; } | |
if (transition.curve) { result.curve = transition.curve; } | |
if (transition.speed) { result.speed = transition.speed; } | |
} | |
if (typeof result.curve === 'string') { result.curve = TweenTransition.getCurve(result.curve); } | |
return result; | |
} | |
/** | |
* Set internal options, overriding any default options. | |
* | |
* @method setOptions | |
* | |
* | |
* @param {Object} options options object | |
* @param {Object} [options.curve] function mapping [0,1] to [0,1] or identifier | |
* @param {Number} [options.duration] duration in ms | |
* @param {Number} [options.speed] speed in pixels per ms | |
*/ | |
TweenTransition.prototype.setOptions = function setOptions(options) { | |
if (options.curve !== undefined) { this.options.curve = options.curve; } | |
if (options.duration !== undefined) { this.options.duration = options.duration; } | |
if (options.speed !== undefined) { this.options.speed = options.speed; } | |
}; | |
/** | |
* Add transition to end state to the queue of pending transitions. Special | |
* Use: calling without a transition resets the object to that state with | |
* no pending actions | |
* | |
* @method set | |
* | |
* | |
* @param {number|FamousMatrix|Array.Number|Object.<number, number>} endValue | |
* end state to which we _interpolate | |
* @param {transition=} transition object of type {duration: number, curve: | |
* f[0,1] -> [0,1] or name}. If transition is omitted, change will be | |
* instantaneous. | |
* @param {function()=} callback Zero-argument function to call on observed | |
* completion (t=1) | |
*/ | |
TweenTransition.prototype.set = function set(endValue, transition, callback) { | |
if (!transition) { | |
this.reset(endValue); | |
if (callback) { callback(); } | |
return; | |
} | |
this._startValue = _clone(this.get()); | |
transition = _normalize(transition, this.options); | |
if (transition.speed) { | |
var startValue = this._startValue; | |
if (startValue instanceof Object) { | |
var variance = 0; | |
for (var i in startValue) { variance += (endValue[i] - startValue[i]) * (endValue[i] - startValue[i]); } | |
transition.duration = Math.sqrt(variance) / transition.speed; | |
} | |
else { | |
transition.duration = Math.abs(endValue - startValue) / transition.speed; | |
} | |
} | |
this._startTime = Date.now(); | |
this._endValue = _clone(endValue); | |
this._startVelocity = _clone(transition.velocity); | |
this._duration = transition.duration; | |
this._curve = transition.curve; | |
this._active = true; | |
this._callback = callback; | |
}; | |
/** | |
* Cancel all transitions and reset to a stable state | |
* | |
* @method reset | |
* | |
* @param {number|Array.Number|Object.<number, number>} startValue | |
* starting state | |
* @param {number} startVelocity | |
* starting velocity | |
*/ | |
TweenTransition.prototype.reset = function reset(startValue, startVelocity) { | |
if (this._callback) { | |
var callback = this._callback; | |
this._callback = undefined; | |
callback(); | |
} | |
this.state = _clone(startValue); | |
this.velocity = _clone(startVelocity); | |
this._startTime = 0; | |
this._duration = 0; | |
this._updateTime = 0; | |
this._startValue = this.state; | |
this._startVelocity = this.velocity; | |
this._endValue = this.state; | |
this._active = false; | |
}; | |
/** | |
* Get current velocity | |
* | |
* @method getVelocity | |
* | |
* @returns {Number} velocity | |
*/ | |
TweenTransition.prototype.getVelocity = function getVelocity() { | |
return this.velocity; | |
}; | |
/** | |
* Get interpolated state of current action at provided time. If the last | |
* action has completed, invoke its callback. | |
* | |
* @method get | |
* | |
* | |
* @param {number=} timestamp Evaluate the curve at a normalized version of this | |
* time. If omitted, use current time. (Unix epoch time) | |
* @return {number|Object.<number|string, number>} beginning state | |
* _interpolated to this point in time. | |
*/ | |
TweenTransition.prototype.get = function get(timestamp) { | |
this.update(timestamp); | |
return this.state; | |
}; | |
function _calculateVelocity(current, start, curve, duration, t) { | |
var velocity; | |
var eps = 1e-7; | |
var speed = (curve(t) - curve(t - eps)) / eps; | |
if (current instanceof Array) { | |
velocity = []; | |
for (var i = 0; i < current.length; i++){ | |
if (typeof current[i] === 'number') | |
{ velocity[i] = speed * (current[i] - start[i]) / duration; } | |
else | |
{ velocity[i] = 0; } | |
} | |
} | |
else { velocity = speed * (current - start) / duration; } | |
return velocity; | |
} | |
function _calculateState(start, end, t) { | |
var state; | |
if (start instanceof Array) { | |
state = []; | |
for (var i = 0; i < start.length; i++) { | |
if (typeof start[i] === 'number') | |
{ state[i] = _interpolate(start[i], end[i], t); } | |
else | |
{ state[i] = start[i]; } | |
} | |
} | |
else { state = _interpolate(start, end, t); } | |
return state; | |
} | |
/** | |
* Update internal state to the provided timestamp. This may invoke the last | |
* callback and begin a new action. | |
* | |
* @method update | |
* | |
* | |
* @param {number=} timestamp Evaluate the curve at a normalized version of this | |
* time. If omitted, use current time. (Unix epoch time) | |
*/ | |
TweenTransition.prototype.update = function update(timestamp) { | |
if (!this._active) { | |
if (this._callback) { | |
var callback = this._callback; | |
this._callback = undefined; | |
callback(); | |
} | |
return; | |
} | |
if (!timestamp) { timestamp = Date.now(); } | |
if (this._updateTime >= timestamp) { return; } | |
this._updateTime = timestamp; | |
var timeSinceStart = timestamp - this._startTime; | |
if (timeSinceStart >= this._duration) { | |
this.state = this._endValue; | |
this.velocity = _calculateVelocity(this.state, this._startValue, this._curve, this._duration, 1); | |
this._active = false; | |
} | |
else if (timeSinceStart < 0) { | |
this.state = this._startValue; | |
this.velocity = this._startVelocity; | |
} | |
else { | |
var t = timeSinceStart / this._duration; | |
this.state = _calculateState(this._startValue, this._endValue, this._curve(t)); | |
this.velocity = _calculateVelocity(this.state, this._startValue, this._curve, this._duration, t); | |
} | |
}; | |
/** | |
* Is there at least one action pending completion? | |
* | |
* @method isActive | |
* | |
* | |
* @return {boolean} | |
*/ | |
TweenTransition.prototype.isActive = function isActive() { | |
return this._active; | |
}; | |
/** | |
* Halt transition at current state and erase all pending actions. | |
* | |
* @method halt | |
* | |
*/ | |
TweenTransition.prototype.halt = function halt() { | |
this.reset(this.get()); | |
}; | |
// Register all the default curves | |
TweenTransition.registerCurve('linear', TweenTransition.Curves.linear); | |
TweenTransition.registerCurve('easeIn', TweenTransition.Curves.easeIn); | |
TweenTransition.registerCurve('easeOut', TweenTransition.Curves.easeOut); | |
TweenTransition.registerCurve('easeInOut', TweenTransition.Curves.easeInOut); | |
TweenTransition.registerCurve('easeOutBounce', TweenTransition.Curves.easeOutBounce); | |
TweenTransition.registerCurve('spring', TweenTransition.Curves.spring); | |
TweenTransition.customCurve = function customCurve(v1, v2) { | |
v1 = v1 || 0; v2 = v2 || 0; | |
return function(t) { | |
return v1*t + (-2*v1 - v2 + 3)*t*t + (v1 + v2 - 2)*t*t*t; | |
}; | |
}; | |
var TweenTransition_1 = TweenTransition; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: david@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/*eslint-disable new-cap */ | |
/** | |
* A state maintainer for a smooth transition between | |
* numerically-specified states. Example numeric states include floats or | |
* Transform objects. | |
* | |
* An initial state is set with the constructor or set(startState). A | |
* corresponding end state and transition are set with set(endState, | |
* transition). Subsequent calls to set(endState, transition) begin at | |
* the last state. Calls to get(timestamp) provide the interpolated state | |
* along the way. | |
* | |
* Note that there is no event loop here - calls to get() are the only way | |
* to find state projected to the current (or provided) time and are | |
* the only way to trigger callbacks. Usually this kind of object would | |
* be part of the render() path of a visible component. | |
* | |
* @class Transitionable | |
* @constructor | |
* @param {number|Array.Number|Object.<number|string, number>} start | |
* beginning state | |
*/ | |
function Transitionable(start) { | |
this.currentAction = null; | |
this.actionQueue = []; | |
this.callbackQueue = []; | |
this.state = 0; | |
this.velocity = undefined; | |
this._callback = undefined; | |
this._engineInstance = null; | |
this._currentMethod = null; | |
this.set(start); | |
} | |
var transitionMethods = {}; | |
Transitionable.register = function register(methods) { | |
var success = true; | |
for (var method in methods) { | |
if (!Transitionable.registerMethod(method, methods[method])) | |
{ success = false; } | |
} | |
return success; | |
}; | |
Transitionable.registerMethod = function registerMethod(name, engineClass) { | |
if (!(name in transitionMethods)) { | |
transitionMethods[name] = engineClass; | |
return true; | |
} | |
else { return false; } | |
}; | |
Transitionable.unregisterMethod = function unregisterMethod(name) { | |
if (name in transitionMethods) { | |
delete transitionMethods[name]; | |
return true; | |
} | |
else { return false; } | |
}; | |
function _loadNext() { | |
if (this._callback) { | |
var callback = this._callback; | |
this._callback = undefined; | |
callback(); | |
} | |
if (this.actionQueue.length <= 0) { | |
this.set(this.get()); // no update required | |
return; | |
} | |
this.currentAction = this.actionQueue.shift(); | |
this._callback = this.callbackQueue.shift(); | |
var method = null; | |
var endValue = this.currentAction[0]; | |
var transition = this.currentAction[1]; | |
if (transition instanceof Object && transition.method) { | |
method = transition.method; | |
if (typeof method === 'string') { method = transitionMethods[method]; } | |
} | |
else { | |
method = TweenTransition_1; | |
} | |
if (this._currentMethod !== method) { | |
if (!(endValue instanceof Object) || method.SUPPORTS_MULTIPLE === true || endValue.length <= method.SUPPORTS_MULTIPLE) { | |
this._engineInstance = new method(); | |
} | |
else { | |
this._engineInstance = new MultipleTransition_1(method); | |
} | |
this._currentMethod = method; | |
} | |
this._engineInstance.reset(this.state, this.velocity); | |
if (this.velocity !== undefined) { transition.velocity = this.velocity; } | |
this._engineInstance.set(endValue, transition, _loadNext.bind(this)); | |
} | |
/** | |
* Add transition to end state to the queue of pending transitions. Special | |
* Use: calling without a transition resets the object to that state with | |
* no pending actions | |
* | |
* @method set | |
* | |
* @param {number|FamousMatrix|Array.Number|Object.<number, number>} endState | |
* end state to which we interpolate | |
* @param {transition=} transition object of type {duration: number, curve: | |
* f[0,1] -> [0,1] or name}. If transition is omitted, change will be | |
* instantaneous. | |
* @param {function()=} callback Zero-argument function to call on observed | |
* completion (t=1) | |
*/ | |
Transitionable.prototype.set = function set(endState, transition, callback) { | |
if (!transition) { | |
this.reset(endState); | |
if (callback) { callback(); } | |
return this; | |
} | |
var action = [endState, transition]; | |
this.actionQueue.push(action); | |
this.callbackQueue.push(callback); | |
if (!this.currentAction) { _loadNext.call(this); } | |
return this; | |
}; | |
/** | |
* Cancel all transitions and reset to a stable state | |
* | |
* @method reset | |
* | |
* @param {number|Array.Number|Object.<number, number>} startState | |
* stable state to set to | |
*/ | |
Transitionable.prototype.reset = function reset(startState, startVelocity) { | |
this._currentMethod = null; | |
this._engineInstance = null; | |
this._callback = undefined; | |
this.state = startState; | |
this.velocity = startVelocity; | |
this.currentAction = null; | |
this.actionQueue = []; | |
this.callbackQueue = []; | |
}; | |
/** | |
* Add delay action to the pending action queue queue. | |
* | |
* @method delay | |
* | |
* @param {number} duration delay time (ms) | |
* @param {function} callback Zero-argument function to call on observed | |
* completion (t=1) | |
*/ | |
Transitionable.prototype.delay = function delay(duration, callback) { | |
var endValue; | |
if (this.actionQueue.length) { endValue = this.actionQueue[this.actionQueue.length - 1][0]; } | |
else if (this.currentAction) { endValue = this.currentAction[0]; } | |
else { endValue = this.get(); } | |
return this.set(endValue, { duration: duration, | |
curve: function() { | |
return 0; | |
}}, | |
callback | |
); | |
}; | |
/** | |
* Get interpolated state of current action at provided time. If the last | |
* action has completed, invoke its callback. | |
* | |
* @method get | |
* | |
* @param {number=} timestamp Evaluate the curve at a normalized version of this | |
* time. If omitted, use current time. (Unix epoch time) | |
* @return {number|Object.<number|string, number>} beginning state | |
* interpolated to this point in time. | |
*/ | |
Transitionable.prototype.get = function get(timestamp) { | |
if (this._engineInstance) { | |
if (this._engineInstance.getVelocity) | |
{ this.velocity = this._engineInstance.getVelocity(); } | |
this.state = this._engineInstance.get(timestamp); | |
} | |
return this.state; | |
}; | |
/** | |
* Is there at least one action pending completion? | |
* | |
* @method isActive | |
* | |
* @return {boolean} | |
*/ | |
Transitionable.prototype.isActive = function isActive() { | |
return !!this.currentAction; | |
}; | |
/** | |
* Halt transition at current state and erase all pending actions. | |
* | |
* @method halt | |
*/ | |
Transitionable.prototype.halt = function halt() { | |
return this.set(this.get()); | |
}; | |
var Transitionable_1 = Transitionable; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: david@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* A library of curves which map an animation explicitly as a function of time. | |
* | |
* @class Easing | |
*/ | |
var Easing = { | |
/** | |
* @property inQuad | |
* @static | |
*/ | |
inQuad: function(t) { | |
return t*t; | |
}, | |
/** | |
* @property outQuad | |
* @static | |
*/ | |
outQuad: function(t) { | |
return -(t-=1)*t+1; | |
}, | |
/** | |
* @property inOutQuad | |
* @static | |
*/ | |
inOutQuad: function(t) { | |
if ((t/=.5) < 1) { return .5*t*t; } | |
return -.5*((--t)*(t-2) - 1); | |
}, | |
/** | |
* @property inCubic | |
* @static | |
*/ | |
inCubic: function(t) { | |
return t*t*t; | |
}, | |
/** | |
* @property outCubic | |
* @static | |
*/ | |
outCubic: function(t) { | |
return ((--t)*t*t + 1); | |
}, | |
/** | |
* @property inOutCubic | |
* @static | |
*/ | |
inOutCubic: function(t) { | |
if ((t/=.5) < 1) { return .5*t*t*t; } | |
return .5*((t-=2)*t*t + 2); | |
}, | |
/** | |
* @property inQuart | |
* @static | |
*/ | |
inQuart: function(t) { | |
return t*t*t*t; | |
}, | |
/** | |
* @property outQuart | |
* @static | |
*/ | |
outQuart: function(t) { | |
return -((--t)*t*t*t - 1); | |
}, | |
/** | |
* @property inOutQuart | |
* @static | |
*/ | |
inOutQuart: function(t) { | |
if ((t/=.5) < 1) { return .5*t*t*t*t; } | |
return -.5 * ((t-=2)*t*t*t - 2); | |
}, | |
/** | |
* @property inQuint | |
* @static | |
*/ | |
inQuint: function(t) { | |
return t*t*t*t*t; | |
}, | |
/** | |
* @property outQuint | |
* @static | |
*/ | |
outQuint: function(t) { | |
return ((--t)*t*t*t*t + 1); | |
}, | |
/** | |
* @property inOutQuint | |
* @static | |
*/ | |
inOutQuint: function(t) { | |
if ((t/=.5) < 1) { return .5*t*t*t*t*t; } | |
return .5*((t-=2)*t*t*t*t + 2); | |
}, | |
/** | |
* @property inSine | |
* @static | |
*/ | |
inSine: function(t) { | |
return -1.0*Math.cos(t * (Math.PI/2)) + 1.0; | |
}, | |
/** | |
* @property outSine | |
* @static | |
*/ | |
outSine: function(t) { | |
return Math.sin(t * (Math.PI/2)); | |
}, | |
/** | |
* @property inOutSine | |
* @static | |
*/ | |
inOutSine: function(t) { | |
return -.5*(Math.cos(Math.PI*t) - 1); | |
}, | |
/** | |
* @property inExpo | |
* @static | |
*/ | |
inExpo: function(t) { | |
return (t===0) ? 0.0 : Math.pow(2, 10 * (t - 1)); | |
}, | |
/** | |
* @property outExpo | |
* @static | |
*/ | |
outExpo: function(t) { | |
return (t===1.0) ? 1.0 : (-Math.pow(2, -10 * t) + 1); | |
}, | |
/** | |
* @property inOutExpo | |
* @static | |
*/ | |
inOutExpo: function(t) { | |
if (t===0) { return 0.0; } | |
if (t===1.0) { return 1.0; } | |
if ((t/=.5) < 1) { return .5 * Math.pow(2, 10 * (t - 1)); } | |
return .5 * (-Math.pow(2, -10 * --t) + 2); | |
}, | |
/** | |
* @property inCirc | |
* @static | |
*/ | |
inCirc: function(t) { | |
return -(Math.sqrt(1 - t*t) - 1); | |
}, | |
/** | |
* @property outCirc | |
* @static | |
*/ | |
outCirc: function(t) { | |
return Math.sqrt(1 - (--t)*t); | |
}, | |
/** | |
* @property inOutCirc | |
* @static | |
*/ | |
inOutCirc: function(t) { | |
if ((t/=.5) < 1) { return -.5 * (Math.sqrt(1 - t*t) - 1); } | |
return .5 * (Math.sqrt(1 - (t-=2)*t) + 1); | |
}, | |
/** | |
* @property inElastic | |
* @static | |
*/ | |
inElastic: function(t) { | |
var s=1.70158;var p=0;var a=1.0; | |
if (t===0) { return 0.0; } if (t===1) { return 1.0; } if (!p) { p=.3; } | |
s = p/(2*Math.PI) * Math.asin(1.0/a); | |
return -(a*Math.pow(2,10*(t-=1)) * Math.sin((t-s)*(2*Math.PI)/ p)); | |
}, | |
/** | |
* @property outElastic | |
* @static | |
*/ | |
outElastic: function(t) { | |
var s=1.70158;var p=0;var a=1.0; | |
if (t===0) { return 0.0; } if (t===1) { return 1.0; } if (!p) { p=.3; } | |
s = p/(2*Math.PI) * Math.asin(1.0/a); | |
return a*Math.pow(2,-10*t) * Math.sin((t-s)*(2*Math.PI)/p) + 1.0; | |
}, | |
/** | |
* @property inOutElastic | |
* @static | |
*/ | |
inOutElastic: function(t) { | |
var s=1.70158;var p=0;var a=1.0; | |
if (t===0) { return 0.0; } if ((t/=.5)===2) { return 1.0; } if (!p) { p=(.3*1.5); } | |
s = p/(2*Math.PI) * Math.asin(1.0/a); | |
if (t < 1) { return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin((t-s)*(2*Math.PI)/p)); } | |
return a*Math.pow(2,-10*(t-=1)) * Math.sin((t-s)*(2*Math.PI)/p)*.5 + 1.0; | |
}, | |
/** | |
* @property inBack | |
* @static | |
*/ | |
inBack: function(t, s) { | |
if (s === undefined) { s = 1.70158; } | |
return t*t*((s+1)*t - s); | |
}, | |
/** | |
* @property outBack | |
* @static | |
*/ | |
outBack: function(t, s) { | |
if (s === undefined) { s = 1.70158; } | |
return ((--t)*t*((s+1)*t + s) + 1); | |
}, | |
/** | |
* @property inOutBack | |
* @static | |
*/ | |
inOutBack: function(t, s) { | |
if (s === undefined) { s = 1.70158; } | |
if ((t/=.5) < 1) { return .5*(t*t*(((s*=(1.525))+1)*t - s)); } | |
return .5*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); | |
}, | |
/** | |
* @property inBounce | |
* @static | |
*/ | |
inBounce: function(t) { | |
return 1.0 - Easing.outBounce(1.0-t); | |
}, | |
/** | |
* @property outBounce | |
* @static | |
*/ | |
outBounce: function(t) { | |
if (t < (1/2.75)) { | |
return (7.5625*t*t); | |
} else if (t < (2/2.75)) { | |
return (7.5625*(t-=(1.5/2.75))*t + .75); | |
} else if (t < (2.5/2.75)) { | |
return (7.5625*(t-=(2.25/2.75))*t + .9375); | |
} else { | |
return (7.5625*(t-=(2.625/2.75))*t + .984375); | |
} | |
}, | |
/** | |
* @property inOutBounce | |
* @static | |
*/ | |
inOutBounce: function(t) { | |
if (t < .5) { return Easing.inBounce(t*2) * .5; } | |
return Easing.outBounce(t*2-1.0) * .5 + .5; | |
} | |
}; | |
var Easing_1 = Easing; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: david@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* A class for transitioning the state of a Transform by transitioning the | |
* X, Y, and Z axes of it's translate, scale, skew and rotate components | |
* independently. | |
* | |
* @class TransitionableTransform | |
* @constructor | |
* | |
* @param [transform=Transform.identity] {Transform} The initial transform state | |
*/ | |
function TransitionableTransform(transform) { | |
this._final = Transform_1.identity.slice(); | |
this._finalTranslate = [0, 0, 0]; | |
this._finalRotate = [0, 0, 0]; | |
this._finalSkew = [0, 0, 0]; | |
this._finalScale = [1, 1, 1]; | |
this.translate = []; | |
this.rotate = []; | |
this.skew = []; | |
this.scale = []; | |
for (var i=0; i<3; i+=1) { | |
this.translate[i] = new Transitionable_1(this._finalTranslate[i]); | |
this.rotate[i] = new Transitionable_1(this._finalRotate[i]); | |
this.skew[i] = new Transitionable_1(this._finalSkew[i]); | |
this.scale[i] = new Transitionable_1(this._finalScale[i]); | |
} | |
if (transform) { this.set(transform); } | |
} | |
function _build() { | |
return Transform_1.build({ | |
translate: [this.translate[0].get(), this.translate[1].get(), this.translate[2].get()], | |
rotate: [this.rotate[0].get(), this.rotate[1].get(), this.rotate[2].get()], | |
skew: [this.skew[0].get(), this.skew[1].get(), this.skew[2].get()], | |
scale: [this.scale[0].get(), this.scale[1].get(), this.scale[2].get()] | |
}); | |
} | |
function _buildFinal() { | |
return Transform_1.build({ | |
translate: this._finalTranslate, | |
rotate: this._finalRotate, | |
skew: this._finalSkew, | |
scale: this._finalScale | |
}); | |
} | |
function _countOfType(array, type) { | |
var count = 0; | |
for (var i=0; i<array.length; i+=1) { | |
if (typeof array[i] === type+'') { | |
count+=1; | |
} | |
} | |
return count; | |
} | |
/** | |
* An optimized way of setting only the translation component of a Transform. Axes who's values are null will not be affected. | |
* | |
* @method setTranslate | |
* @chainable | |
* | |
* @param translate {Array} New translation state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setTranslate = function setTranslate(translate, transition, callback) { | |
var numberOfAxes = _countOfType(translate, 'number'); | |
var _callback = callback ? Utility_1.after(numberOfAxes, callback) : null; | |
for (var i=0; i<translate.length; i+=1) { | |
if (typeof translate[i] === 'number') { | |
this.translate[i].set(translate[i], transition, _callback); | |
this._finalTranslate[i] = translate[i]; | |
} | |
} | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Translate only along the X axis of the translation component of a Transform. | |
* | |
* @method setTranslateX | |
* @chainable | |
* | |
* @param translate {Number} New translation state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setTranslateX = function setTranslateX(translate, transition, callback) { | |
this.translate[0].set(translate, transition, callback); | |
this._finalTranslate[0] = translate; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Translate only along the Y axis of the translation component of a Transform. | |
* | |
* @method setTranslateY | |
* @chainable | |
* | |
* @param translate {Number} New translation state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setTranslateY = function setTranslateY(translate, transition, callback) { | |
this.translate[1].set(translate, transition, callback); | |
this._finalTranslate[1] = translate; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Translate only along the Z axis of the translation component of a Transform. | |
* | |
* @method setTranslateZ | |
* @chainable | |
* | |
* @param translate {Number} New translation state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setTranslateZ = function setTranslateZ(translate, transition, callback) { | |
this.translate[2].set(translate, transition, callback); | |
this._finalTranslate[2] = translate; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* An optimized way of setting only the scale component of a Transform. Axes who's values are null will not be affected. | |
* | |
* @method setScale | |
* @chainable | |
* | |
* @param scale {Array} New scale state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setScale = function setScale(scale, transition, callback) { | |
var numberOfAxes = _countOfType(scale, 'number'); | |
var _callback = callback ? Utility_1.after(numberOfAxes, callback) : null; | |
for (var i=0; i<scale.length; i+=1) { | |
if (typeof scale[i] === 'number') { | |
this.scale[i].set(scale[i], transition, _callback); | |
this._finalScale[i] = scale[i]; | |
} | |
} | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Scale only along the X axis of the scale component of a Transform. | |
* | |
* @method setScaleX | |
* @chainable | |
* | |
* @param scale {Number} New scale state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setScaleX = function setScaleX(scale, transition, callback) { | |
this.scale[0].set(scale, transition, callback); | |
this._finalScale[0] = scale; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Scale only along the Y axis of the scale component of a Transform. | |
* | |
* @method setScaleY | |
* @chainable | |
* | |
* @param scale {Number} New scale state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setScaleY = function setScaleY(scale, transition, callback) { | |
this.scale[1].set(scale, transition, callback); | |
this._finalScale[1] = scale; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Scale only along the Z axis of the scale component of a Transform. | |
* | |
* @method setScaleZ | |
* @chainable | |
* | |
* @param scale {Number} New scale state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setScaleZ = function setScaleZ(scale, transition, callback) { | |
this.scale[2].set(scale, transition, callback); | |
this._finalScale[2] = scale; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* An optimized way of setting only the rotational component of a Transform. Axes who's values are null will not be affected. | |
* | |
* @method setRotate | |
* @chainable | |
* | |
* @param eulerAngles {Array} Euler angles for new rotation state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setRotate = function setRotate(eulerAngles, transition, callback) { | |
var numberOfAxes = _countOfType(eulerAngles, 'number'); | |
var _callback = callback ? Utility_1.after(numberOfAxes, callback) : null; | |
for (var i=0; i<eulerAngles.length; i+=1) { | |
if (typeof eulerAngles[i] === 'number') { | |
this.rotate[i].set(eulerAngles[i], transition, _callback); | |
this._finalRotate[i] = eulerAngles[i]; | |
} | |
} | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Rotate only about the X axis of the rotational component of a Transform. | |
* | |
* @method setScaleX | |
* @chainable | |
* | |
* @param eulerAngle {Number} New rotational state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setRotateX = function setRotateX(eulerAngle, transition, callback) { | |
this.rotate[0].set(eulerAngle, transition, callback); | |
this._finalRotate[0] = eulerAngle; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Rotate only about the Y axis of the rotational component of a Transform. | |
* | |
* @method setScaleY | |
* @chainable | |
* | |
* @param eulerAngle {Number} New rotational state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setRotateY = function setRotateY(eulerAngle, transition, callback) { | |
this.rotate[1].set(eulerAngle, transition, callback); | |
this._finalRotate[1] = eulerAngle; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Rotate only about the Z axis of the rotational component of a Transform. | |
* | |
* @method setScaleZ | |
* @chainable | |
* | |
* @param eulerAngle {Number} New rotational state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setRotateZ = function setRotateZ(eulerAngle, transition, callback) { | |
this.rotate[2].set(eulerAngle, transition, callback); | |
this._finalRotate[2] = eulerAngle; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* An optimized way of setting only the skew component of a Transform. Axes who's values are null will not be affected. | |
* | |
* @method setSkew | |
* @chainable | |
* | |
* @param skewAngles {Array} New skew state. Axes who's values are null will not be affected. | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setSkew = function setSkew(skewAngles, transition, callback) { | |
var numberOfAxes = _countOfType(skewAngles, 'number'); | |
var _callback = callback ? Utility_1.after(numberOfAxes, callback) : null; | |
for (var i=0; i<skewAngles.length; i+=1) { | |
if (typeof skewAngles[i] === 'number') { | |
this.skew[i].set(skewAngles[i], transition, _callback); | |
this._finalSkew[i] = skewAngles[i]; | |
} | |
} | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Skew only about the X axis of the skew component of a Transform. | |
* | |
* @method setSkewX | |
* @chainable | |
* | |
* @param skewAngle {Number} New skew state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setSkewX = function setSkewX(skewAngle, transition, callback) { | |
this.skew[0].set(skewAngle, transition, callback); | |
this._finalSkew[0] = skewAngle; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Skew only about the Y axis of the skew component of a Transform. | |
* | |
* @method setSkewY | |
* @chainable | |
* | |
* @param skewAngle {Number} New skew state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setSkewY = function setSkewY(skewAngle, transition, callback) { | |
this.skew[1].set(skewAngle, transition, callback); | |
this._finalSkew[1] = skewAngle; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Skew only about the Z axis of the skew component of a Transform. | |
* | |
* @method setSkewZ | |
* @chainable | |
* | |
* @param skewAngle {Number} New skew state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.setSkewZ = function setSkewZ(skewAngle, transition, callback) { | |
this.skew[2].set(skewAngle, transition, callback); | |
this._finalSkew[2] = skewAngle; | |
this._final = _buildFinal.call(this); | |
return this; | |
}; | |
/** | |
* Setter for a TransitionableTransform with optional parameters to transition | |
* between Transforms. Animates all axes of all components. | |
* | |
* @method set | |
* @chainable | |
* | |
* @param transform {Array} New transform state | |
* @param [transition] {Object} Transition definition | |
* @param [callback] {Function} Callback | |
* @return {TransitionableTransform} | |
*/ | |
TransitionableTransform.prototype.set = function set(transform, transition, callback) { | |
var components = Transform_1.interpret(transform); | |
this._finalTranslate = components.translate; | |
this._finalRotate = components.rotate; | |
this._finalSkew = components.skew; | |
this._finalScale = components.scale; | |
this._final = transform; | |
var _callback = callback ? Utility_1.after(12, callback) : null; | |
for (var i=0; i<3; i+=1) { | |
this.translate[i].set(components.translate[i], transition, _callback); | |
this.rotate[i].set(components.rotate[i], transition, _callback); | |
this.skew[i].set(components.skew[i], transition, _callback); | |
this.scale[i].set(components.scale[i], transition, _callback); | |
} | |
return this; | |
}; | |
/** | |
* Sets the default transition to use for transitioning betwen Transform states | |
* | |
* @method setDefaultTransition | |
* | |
* @param transition {Object} Transition definition | |
*/ | |
TransitionableTransform.prototype.setDefaultTransition = function setDefaultTransition(transition) { | |
for (var i=0; i<3; i+=1) { | |
this.translate[i].setDefault(transition); | |
this.rotate[i].setDefault(transition); | |
this.skew[i].setDefault(transition); | |
this.scale[i].setDefault(transition); | |
} | |
}; | |
/** | |
* Getter. Returns the current state of the Transform | |
* | |
* @method get | |
* | |
* @return {Transform} | |
*/ | |
TransitionableTransform.prototype.get = function get() { | |
if (this.isActive()) { | |
return _build.call(this); | |
} | |
else { return this._final; } | |
}; | |
/** | |
* Get the destination state of the Transform | |
* | |
* @method getFinal | |
* | |
* @return Transform {Transform} | |
*/ | |
TransitionableTransform.prototype.getFinal = function getFinal() { | |
return this._final; | |
}; | |
/** | |
* Determine if the TransitionableTransform is currently transitioning | |
* | |
* @method isActive | |
* | |
* @return {Boolean} | |
*/ | |
TransitionableTransform.prototype.isActive = function isActive() { | |
var isActive = false; | |
for (var i=0; i<3; i+=1) { | |
if ( | |
this.translate[i].isActive() | |
|| this.rotate[i].isActive() | |
|| this.skew[i].isActive() | |
|| this.scale[i].isActive() | |
) { | |
isActive = true; break; | |
} | |
} | |
return isActive; | |
}; | |
/** | |
* Halts the transition | |
* | |
* @method halt | |
*/ | |
TransitionableTransform.prototype.halt = function halt() { | |
for (var i=0; i<3; i+=1) { | |
this.translate[i].halt(); | |
this.rotate[i].halt(); | |
this.skew[i].halt(); | |
this.scale[i].halt(); | |
this._finalTranslate[i] = this.translate[i].get(); | |
this._finalRotate[i] = this.rotate[i].get(); | |
this._finalSkew[i] = this.skew[i].get(); | |
this._finalScale[i] = this.scale[i].get(); | |
} | |
this._final = this.get(); | |
return this; | |
}; | |
var TransitionableTransform_1 = TransitionableTransform; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/* TODO: remove these dependencies when deprecation complete */ | |
/** | |
* | |
* A collection of visual changes to be | |
* applied to another renderable component. This collection includes a | |
* transform matrix, an opacity constant, a size, an origin specifier. | |
* Modifier objects can be added to any RenderNode or object | |
* capable of displaying renderables. The Modifier's children and descendants | |
* are transformed by the amounts specified in the Modifier's properties. | |
* | |
* @class Modifier | |
* @constructor | |
* @param {Object} [options] overrides of default options | |
* @param {Transform} [options.transform] affine transformation matrix | |
* @param {Number} [options.opacity] | |
* @param {Array.Number} [options.origin] origin adjustment | |
* @param {Array.Number} [options.size] size to apply to descendants | |
*/ | |
function Modifier(options) { | |
this._transformGetter = null; | |
this._opacityGetter = null; | |
this._originGetter = null; | |
this._alignGetter = null; | |
this._sizeGetter = null; | |
this._proportionGetter = null; | |
/* TODO: remove this when deprecation complete */ | |
this._legacyStates = {}; | |
this._output = { | |
transform: Transform_1.identity, | |
opacity: 1, | |
origin: null, | |
align: null, | |
size: null, | |
proportions: null, | |
target: null | |
}; | |
if (options) { | |
if (options.transform) { this.transformFrom(options.transform); } | |
if (options.opacity !== undefined) { this.opacityFrom(options.opacity); } | |
if (options.origin) { this.originFrom(options.origin); } | |
if (options.align) { this.alignFrom(options.align); } | |
if (options.size) { this.sizeFrom(options.size); } | |
if (options.proportions) { this.proportionsFrom(options.proportions); } | |
} | |
} | |
/** | |
* Function, object, or static transform matrix which provides the transform. | |
* This is evaluated on every tick of the engine. | |
* | |
* @method transformFrom | |
* | |
* @param {Object} transform transform provider object | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.transformFrom = function transformFrom(transform) { | |
if (transform instanceof Function) { this._transformGetter = transform; } | |
else if (transform instanceof Object && transform.get) { this._transformGetter = transform.get.bind(transform); } | |
else { | |
this._transformGetter = null; | |
this._output.transform = transform; | |
} | |
return this; | |
}; | |
/** | |
* Set function, object, or number to provide opacity, in range [0,1]. | |
* | |
* @method opacityFrom | |
* | |
* @param {Object} opacity provider object | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.opacityFrom = function opacityFrom(opacity) { | |
if (opacity instanceof Function) { this._opacityGetter = opacity; } | |
else if (opacity instanceof Object && opacity.get) { this._opacityGetter = opacity.get.bind(opacity); } | |
else { | |
this._opacityGetter = null; | |
this._output.opacity = opacity; | |
} | |
return this; | |
}; | |
/** | |
* Set function, object, or numerical array to provide origin, as [x,y], | |
* where x and y are in the range [0,1]. | |
* | |
* @method originFrom | |
* | |
* @param {Object} origin provider object | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.originFrom = function originFrom(origin) { | |
if (origin instanceof Function) { this._originGetter = origin; } | |
else if (origin instanceof Object && origin.get) { this._originGetter = origin.get.bind(origin); } | |
else { | |
this._originGetter = null; | |
this._output.origin = origin; | |
} | |
return this; | |
}; | |
/** | |
* Set function, object, or numerical array to provide align, as [x,y], | |
* where x and y are in the range [0,1]. | |
* | |
* @method alignFrom | |
* | |
* @param {Object} align provider object | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.alignFrom = function alignFrom(align) { | |
if (align instanceof Function) { this._alignGetter = align; } | |
else if (align instanceof Object && align.get) { this._alignGetter = align.get.bind(align); } | |
else { | |
this._alignGetter = null; | |
this._output.align = align; | |
} | |
return this; | |
}; | |
/** | |
* Set function, object, or numerical array to provide size, as [width, height]. | |
* | |
* @method sizeFrom | |
* | |
* @param {Object} size provider object | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.sizeFrom = function sizeFrom(size) { | |
if (size instanceof Function) { this._sizeGetter = size; } | |
else if (size instanceof Object && size.get) { this._sizeGetter = size.get.bind(size); } | |
else { | |
this._sizeGetter = null; | |
this._output.size = size; | |
} | |
return this; | |
}; | |
/** | |
* Set function, object, or numerical array to provide proportions, as [percent of width, percent of height]. | |
* | |
* @method proportionsFrom | |
* | |
* @param {Object} proportions provider object | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.proportionsFrom = function proportionsFrom(proportions) { | |
if (proportions instanceof Function) { this._proportionGetter = proportions; } | |
else if (proportions instanceof Object && proportions.get) { this._proportionGetter = proportions.get.bind(proportions); } | |
else { | |
this._proportionGetter = null; | |
this._output.proportions = proportions; | |
} | |
return this; | |
}; | |
/** | |
* Deprecated: Prefer transformFrom with static Transform, or use a TransitionableTransform. | |
* @deprecated | |
* @method setTransform | |
* | |
* @param {Transform} transform Transform to transition to | |
* @param {Transitionable} transition Valid transitionable object | |
* @param {Function} callback callback to call after transition completes | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.setTransform = function setTransform(transform, transition, callback) { | |
if (transition || this._legacyStates.transform) { | |
if (!this._legacyStates.transform) { | |
this._legacyStates.transform = new TransitionableTransform_1(this._output.transform); | |
} | |
if (!this._transformGetter) { this.transformFrom(this._legacyStates.transform); } | |
this._legacyStates.transform.set(transform, transition, callback); | |
return this; | |
} | |
else { return this.transformFrom(transform); } | |
}; | |
/** | |
* Deprecated: Prefer opacityFrom with static opacity array, or use a Transitionable with that opacity. | |
* @deprecated | |
* @method setOpacity | |
* | |
* @param {Number} opacity Opacity value to transition to. | |
* @param {Transitionable} transition Valid transitionable object | |
* @param {Function} callback callback to call after transition completes | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.setOpacity = function setOpacity(opacity, transition, callback) { | |
if (transition || this._legacyStates.opacity) { | |
if (!this._legacyStates.opacity) { | |
this._legacyStates.opacity = new Transitionable_1(this._output.opacity); | |
} | |
if (!this._opacityGetter) { this.opacityFrom(this._legacyStates.opacity); } | |
return this._legacyStates.opacity.set(opacity, transition, callback); | |
} | |
else { return this.opacityFrom(opacity); } | |
}; | |
/** | |
* Deprecated: Prefer originFrom with static origin array, or use a Transitionable with that origin. | |
* @deprecated | |
* @method setOrigin | |
* | |
* @param {Array.Number} origin two element array with values between 0 and 1. | |
* @param {Transitionable} transition Valid transitionable object | |
* @param {Function} callback callback to call after transition completes | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.setOrigin = function setOrigin(origin, transition, callback) { | |
/* TODO: remove this if statement when deprecation complete */ | |
if (transition || this._legacyStates.origin) { | |
if (!this._legacyStates.origin) { | |
this._legacyStates.origin = new Transitionable_1(this._output.origin || [0, 0]); | |
} | |
if (!this._originGetter) { this.originFrom(this._legacyStates.origin); } | |
this._legacyStates.origin.set(origin, transition, callback); | |
return this; | |
} | |
else { return this.originFrom(origin); } | |
}; | |
/** | |
* Deprecated: Prefer alignFrom with static align array, or use a Transitionable with that align. | |
* @deprecated | |
* @method setAlign | |
* | |
* @param {Array.Number} align two element array with values between 0 and 1. | |
* @param {Transitionable} transition Valid transitionable object | |
* @param {Function} callback callback to call after transition completes | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.setAlign = function setAlign(align, transition, callback) { | |
/* TODO: remove this if statement when deprecation complete */ | |
if (transition || this._legacyStates.align) { | |
if (!this._legacyStates.align) { | |
this._legacyStates.align = new Transitionable_1(this._output.align || [0, 0]); | |
} | |
if (!this._alignGetter) { this.alignFrom(this._legacyStates.align); } | |
this._legacyStates.align.set(align, transition, callback); | |
return this; | |
} | |
else { return this.alignFrom(align); } | |
}; | |
/** | |
* Deprecated: Prefer sizeFrom with static origin array, or use a Transitionable with that size. | |
* @deprecated | |
* @method setSize | |
* @param {Array.Number} size two element array of [width, height] | |
* @param {Transitionable} transition Valid transitionable object | |
* @param {Function} callback callback to call after transition completes | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.setSize = function setSize(size, transition, callback) { | |
if (size && (transition || this._legacyStates.size)) { | |
if (!this._legacyStates.size) { | |
this._legacyStates.size = new Transitionable_1(this._output.size || [0, 0]); | |
} | |
if (!this._sizeGetter) { this.sizeFrom(this._legacyStates.size); } | |
this._legacyStates.size.set(size, transition, callback); | |
return this; | |
} | |
else { return this.sizeFrom(size); } | |
}; | |
/** | |
* Deprecated: Prefer proportionsFrom with static origin array, or use a Transitionable with those proportions. | |
* @deprecated | |
* @method setProportions | |
* @param {Array.Number} proportions two element array of [percent of width, percent of height] | |
* @param {Transitionable} transition Valid transitionable object | |
* @param {Function} callback callback to call after transition completes | |
* @return {Modifier} this | |
*/ | |
Modifier.prototype.setProportions = function setProportions(proportions, transition, callback) { | |
if (proportions && (transition || this._legacyStates.proportions)) { | |
if (!this._legacyStates.proportions) { | |
this._legacyStates.proportions = new Transitionable_1(this._output.proportions || [0, 0]); | |
} | |
if (!this._proportionGetter) { this.proportionsFrom(this._legacyStates.proportions); } | |
this._legacyStates.proportions.set(proportions, transition, callback); | |
return this; | |
} | |
else { return this.proportionsFrom(proportions); } | |
}; | |
/** | |
* Deprecated: Prefer to stop transform in your provider object. | |
* @deprecated | |
* @method halt | |
*/ | |
Modifier.prototype.halt = function halt() { | |
if (this._legacyStates.transform) { this._legacyStates.transform.halt(); } | |
if (this._legacyStates.opacity) { this._legacyStates.opacity.halt(); } | |
if (this._legacyStates.origin) { this._legacyStates.origin.halt(); } | |
if (this._legacyStates.align) { this._legacyStates.align.halt(); } | |
if (this._legacyStates.size) { this._legacyStates.size.halt(); } | |
if (this._legacyStates.proportions) { this._legacyStates.proportions.halt(); } | |
this._transformGetter = null; | |
this._opacityGetter = null; | |
this._originGetter = null; | |
this._alignGetter = null; | |
this._sizeGetter = null; | |
this._proportionGetter = null; | |
}; | |
/** | |
* Deprecated: Prefer to use your provided transform or output of your transform provider. | |
* @deprecated | |
* @method getTransform | |
* @return {Object} transform provider object | |
*/ | |
Modifier.prototype.getTransform = function getTransform() { | |
return this._transformGetter(); | |
}; | |
/** | |
* Deprecated: Prefer to determine the end state of your transform from your transform provider | |
* @deprecated | |
* @method getFinalTransform | |
* @return {Transform} transform matrix | |
*/ | |
Modifier.prototype.getFinalTransform = function getFinalTransform() { | |
return this._legacyStates.transform ? this._legacyStates.transform.getFinal() : this._output.transform; | |
}; | |
/** | |
* Deprecated: Prefer to use your provided opacity or output of your opacity provider. | |
* @deprecated | |
* @method getOpacity | |
* @return {Object} opacity provider object | |
*/ | |
Modifier.prototype.getOpacity = function getOpacity() { | |
return this._opacityGetter(); | |
}; | |
/** | |
* Deprecated: Prefer to use your provided origin or output of your origin provider. | |
* @deprecated | |
* @method getOrigin | |
* @return {Object} origin provider object | |
*/ | |
Modifier.prototype.getOrigin = function getOrigin() { | |
return this._originGetter(); | |
}; | |
/** | |
* Deprecated: Prefer to use your provided align or output of your align provider. | |
* @deprecated | |
* @method getAlign | |
* @return {Object} align provider object | |
*/ | |
Modifier.prototype.getAlign = function getAlign() { | |
return this._alignGetter(); | |
}; | |
/** | |
* Deprecated: Prefer to use your provided size or output of your size provider. | |
* @deprecated | |
* @method getSize | |
* @return {Object} size provider object | |
*/ | |
Modifier.prototype.getSize = function getSize() { | |
return this._sizeGetter ? this._sizeGetter() : this._output.size; | |
}; | |
/** | |
* Deprecated: Prefer to use your provided proportions or output of your proportions provider. | |
* @deprecated | |
* @method getProportions | |
* @return {Object} proportions provider object | |
*/ | |
Modifier.prototype.getProportions = function getProportions() { | |
return this._proportionGetter ? this._proportionGetter() : this._output.proportions; | |
}; | |
// call providers on tick to receive render spec elements to apply | |
function _update() { | |
if (this._transformGetter) { this._output.transform = this._transformGetter(); } | |
if (this._opacityGetter) { this._output.opacity = this._opacityGetter(); } | |
if (this._originGetter) { this._output.origin = this._originGetter(); } | |
if (this._alignGetter) { this._output.align = this._alignGetter(); } | |
if (this._sizeGetter) { this._output.size = this._sizeGetter(); } | |
if (this._proportionGetter) { this._output.proportions = this._proportionGetter(); } | |
} | |
/** | |
* Return render spec for this Modifier, applying to the provided | |
* target component. This is similar to render() for Surfaces. | |
* | |
* @private | |
* @method modify | |
* | |
* @param {Object} target (already rendered) render spec to | |
* which to apply the transform. | |
* @return {Object} render spec for this Modifier, including the | |
* provided target | |
*/ | |
Modifier.prototype.modify = function modify(target) { | |
_update.call(this); | |
this._output.target = target; | |
return this._output; | |
}; | |
var Modifier_1 = Modifier; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* A singleton that maintains a global registry of Surfaces. | |
* Private. | |
* | |
* @private | |
* @static | |
* @class Entity | |
*/ | |
var entities = []; | |
/** | |
* Get entity from global index. | |
* | |
* @private | |
* @method get | |
* @param {Number} id entity registration id | |
* @return {Surface} entity in the global index | |
*/ | |
function get(id) { | |
return entities[id]; | |
} | |
/** | |
* Overwrite entity in the global index | |
* | |
* @private | |
* @method set | |
* @param {Number} id entity registration id | |
* @param {Surface} entity to add to the global index | |
*/ | |
function set(id, entity) { | |
entities[id] = entity; | |
} | |
/** | |
* Add entity to global index | |
* | |
* @private | |
* @method register | |
* @param {Surface} entity to add to global index | |
* @return {Number} new id | |
*/ | |
function register(entity) { | |
var id = entities.length; | |
set(id, entity); | |
return id; | |
} | |
/** | |
* Remove entity from global index | |
* | |
* @private | |
* @method unregister | |
* @param {Number} id entity registration id | |
*/ | |
function unregister(id) { | |
set(id, null); | |
} | |
var Entity = { | |
register: register, | |
unregister: unregister, | |
get: get, | |
set: set | |
}; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* | |
* This object translates the rendering instructions ("render specs") | |
* that renderable components generate into document update | |
* instructions ("update specs"). Private. | |
* | |
* @private | |
* @class SpecParser | |
* @constructor | |
*/ | |
function SpecParser() { | |
this.result = {}; | |
} | |
SpecParser._instance = new SpecParser(); | |
/** | |
* Convert a render spec coming from the context's render chain to an | |
* update spec for the update chain. This is the only major entry point | |
* for a consumer of this class. | |
* | |
* @method parse | |
* @static | |
* @private | |
* | |
* @param {renderSpec} spec input render spec | |
* @param {Object} context context to do the parse in | |
* @return {Object} the resulting update spec (if no callback | |
* specified, else none) | |
*/ | |
SpecParser.parse = function parse(spec, context) { | |
return SpecParser._instance.parse(spec, context); | |
}; | |
/** | |
* Convert a renderSpec coming from the context's render chain to an update | |
* spec for the update chain. This is the only major entrypoint for a | |
* consumer of this class. | |
* | |
* @method parse | |
* | |
* @private | |
* @param {renderSpec} spec input render spec | |
* @param {Context} context | |
* @return {updateSpec} the resulting update spec | |
*/ | |
SpecParser.prototype.parse = function parse(spec, context) { | |
this.reset(); | |
this._parseSpec(spec, context, Transform_1.identity); | |
return this.result; | |
}; | |
/** | |
* Prepare SpecParser for re-use (or first use) by setting internal state | |
* to blank. | |
* | |
* @private | |
* @method reset | |
*/ | |
SpecParser.prototype.reset = function reset() { | |
this.result = {}; | |
}; | |
// Multiply matrix M by vector v | |
function _vecInContext(v, m) { | |
return [ | |
v[0] * m[0] + v[1] * m[4] + v[2] * m[8], | |
v[0] * m[1] + v[1] * m[5] + v[2] * m[9], | |
v[0] * m[2] + v[1] * m[6] + v[2] * m[10] | |
]; | |
} | |
var _zeroZero = [0, 0]; | |
// From the provided renderSpec tree, recursively compose opacities, | |
// origins, transforms, and sizes corresponding to each surface id from | |
// the provided renderSpec tree structure. On completion, those | |
// properties of 'this' object should be ready to use to build an | |
// updateSpec. | |
SpecParser.prototype._parseSpec = function _parseSpec(spec, parentContext, sizeContext) { | |
var id; | |
var target; | |
var transform; | |
var opacity; | |
var origin; | |
var align; | |
var size; | |
if (typeof spec === 'number') { | |
id = spec; | |
transform = parentContext.transform; | |
align = parentContext.align || _zeroZero; | |
if (parentContext.size && align && (align[0] || align[1])) { | |
var alignAdjust = [align[0] * parentContext.size[0], align[1] * parentContext.size[1], 0]; | |
transform = Transform_1.thenMove(transform, _vecInContext(alignAdjust, sizeContext)); | |
} | |
this.result[id] = { | |
transform: transform, | |
opacity: parentContext.opacity, | |
origin: parentContext.origin || _zeroZero, | |
align: parentContext.align || _zeroZero, | |
size: parentContext.size | |
}; | |
} | |
else if (!spec) { // placed here so 0 will be cached earlier | |
return; | |
} | |
else if (spec instanceof Array) { | |
for (var i = 0; i < spec.length; i++) { | |
this._parseSpec(spec[i], parentContext, sizeContext); | |
} | |
} | |
else { | |
target = spec.target; | |
transform = parentContext.transform; | |
opacity = parentContext.opacity; | |
origin = parentContext.origin; | |
align = parentContext.align; | |
size = parentContext.size; | |
var nextSizeContext = sizeContext; | |
if (spec.opacity !== undefined) { opacity = parentContext.opacity * spec.opacity; } | |
if (spec.transform) { transform = Transform_1.multiply(parentContext.transform, spec.transform); } | |
if (spec.origin) { | |
origin = spec.origin; | |
nextSizeContext = parentContext.transform; | |
} | |
if (spec.align) { align = spec.align; } | |
if (spec.size || spec.proportions) { | |
var parentSize = size; | |
size = [size[0], size[1]]; | |
if (spec.size) { | |
if (spec.size[0] !== undefined) { size[0] = spec.size[0]; } | |
if (spec.size[1] !== undefined) { size[1] = spec.size[1]; } | |
} | |
if (spec.proportions) { | |
if (spec.proportions[0] !== undefined) { size[0] = size[0] * spec.proportions[0]; } | |
if (spec.proportions[1] !== undefined) { size[1] = size[1] * spec.proportions[1]; } | |
} | |
if (parentSize) { | |
if (align && (align[0] || align[1])) { transform = Transform_1.thenMove(transform, _vecInContext([align[0] * parentSize[0], align[1] * parentSize[1], 0], sizeContext)); } | |
if (origin && (origin[0] || origin[1])) { transform = Transform_1.moveThen([-origin[0] * size[0], -origin[1] * size[1], 0], transform); } | |
} | |
nextSizeContext = parentContext.transform; | |
origin = null; | |
align = null; | |
} | |
this._parseSpec(target, { | |
transform: transform, | |
opacity: opacity, | |
origin: origin, | |
align: align, | |
size: size | |
}, nextSizeContext); | |
} | |
}; | |
var SpecParser_1 = SpecParser; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* A wrapper for inserting a renderable component (like a Modifer or | |
* Surface) into the render tree. | |
* | |
* @class RenderNode | |
* @constructor | |
* | |
* @param {Object} object Target renderable component | |
*/ | |
function RenderNode(object) { | |
this._object = null; | |
this._child = null; | |
this._hasMultipleChildren = false; | |
this._isRenderable = false; | |
this._isModifier = false; | |
this._resultCache = {}; | |
this._prevResults = {}; | |
this._childResult = null; | |
if (object) { this.set(object); } | |
} | |
/** | |
* Append a renderable to the list of this node's children. | |
* This produces a new RenderNode in the tree. | |
* Note: Does not double-wrap if child is a RenderNode already. | |
* | |
* @method add | |
* @param {Object} child renderable object | |
* @return {RenderNode} new render node wrapping child | |
*/ | |
RenderNode.prototype.add = function add(child) { | |
var childNode = (child instanceof RenderNode) ? child : new RenderNode(child); | |
if (this._child instanceof Array) { this._child.push(childNode); } | |
else if (this._child) { | |
this._child = [this._child, childNode]; | |
this._hasMultipleChildren = true; | |
this._childResult = []; // to be used later | |
} | |
else { this._child = childNode; } | |
return childNode; | |
}; | |
/** | |
* Return the single wrapped object. Returns null if this node has multiple child nodes. | |
* | |
* @method get | |
* | |
* @return {Ojbect} contained renderable object | |
*/ | |
RenderNode.prototype.get = function get() { | |
return this._object || (this._hasMultipleChildren ? null : (this._child ? this._child.get() : null)); | |
}; | |
/** | |
* Overwrite the list of children to contain the single provided object | |
* | |
* @method set | |
* @param {Object} child renderable object | |
* @return {RenderNode} this render node, or child if it is a RenderNode | |
*/ | |
RenderNode.prototype.set = function set(child) { | |
this._childResult = null; | |
this._hasMultipleChildren = false; | |
this._isRenderable = child.render ? true : false; | |
this._isModifier = child.modify ? true : false; | |
this._object = child; | |
this._child = null; | |
if (child instanceof RenderNode) { return child; } | |
else { return this; } | |
}; | |
/** | |
* Get render size of contained object. | |
* | |
* @method getSize | |
* @return {Array.Number} size of this or size of single child. | |
*/ | |
RenderNode.prototype.getSize = function getSize() { | |
var result = null; | |
var target = this.get(); | |
if (target && target.getSize) { result = target.getSize(); } | |
if (!result && this._child && this._child.getSize) { result = this._child.getSize(); } | |
return result; | |
}; | |
// apply results of rendering this subtree to the document | |
function _applyCommit(spec, context, cacheStorage) { | |
var result = SpecParser_1.parse(spec, context); | |
var keys = Object.keys(result); | |
for (var i = 0; i < keys.length; i++) { | |
var id = keys[i]; | |
var childNode = Entity.get(id); | |
var commitParams = result[id]; | |
commitParams.allocator = context.allocator; | |
var commitResult = childNode.commit(commitParams); | |
if (commitResult) { _applyCommit(commitResult, context, cacheStorage); } | |
else { cacheStorage[id] = commitParams; } | |
} | |
} | |
/** | |
* Commit the content change from this node to the document. | |
* | |
* @private | |
* @method commit | |
* @param {Context} context render context | |
*/ | |
RenderNode.prototype.commit = function commit(context) { | |
// free up some divs from the last loop | |
var prevKeys = Object.keys(this._prevResults); | |
for (var i = 0; i < prevKeys.length; i++) { | |
var id = prevKeys[i]; | |
if (this._resultCache[id] === undefined) { | |
var object = Entity.get(id); | |
if (object.cleanup) { object.cleanup(context.allocator); } | |
} | |
} | |
this._prevResults = this._resultCache; | |
this._resultCache = {}; | |
_applyCommit(this.render(), context, this._resultCache); | |
}; | |
/** | |
* Generate a render spec from the contents of the wrapped component. | |
* | |
* @private | |
* @method render | |
* | |
* @return {Object} render specification for the component subtree | |
* only under this node. | |
*/ | |
RenderNode.prototype.render = function render() { | |
if (this._isRenderable) { return this._object.render(); } | |
var result = null; | |
if (this._hasMultipleChildren) { | |
result = this._childResult; | |
var children = this._child; | |
for (var i = 0; i < children.length; i++) { | |
result[i] = children[i].render(); | |
} | |
} | |
else if (this._child) { result = this._child.render(); } | |
return this._isModifier ? this._object.modify(result) : result; | |
}; | |
var RenderNode_1 = RenderNode; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* EventEmitter represents a channel for events. | |
* | |
* @class EventEmitter | |
* @constructor | |
*/ | |
function EventEmitter() { | |
this.listeners = {}; | |
this._owner = this; | |
} | |
/** | |
* Trigger an event, sending to all downstream handlers | |
* listening for provided 'type' key. | |
* | |
* @method emit | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {Object} event event data | |
* @return {EventHandler} this | |
*/ | |
EventEmitter.prototype.emit = function emit(type, event) { | |
var handlers = this.listeners[type]; | |
if (handlers) { | |
for (var i = 0; i < handlers.length; i++) { | |
handlers[i].call(this._owner, event); | |
} | |
} | |
return this; | |
}; | |
/** | |
* Bind a callback function to an event type handled by this object. | |
* | |
* @method "on" | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {function(string, Object)} handler callback | |
* @return {EventHandler} this | |
*/ | |
EventEmitter.prototype.on = function on(type, handler) { | |
if (!(type in this.listeners)) { this.listeners[type] = []; } | |
var index = this.listeners[type].indexOf(handler); | |
if (index < 0) { this.listeners[type].push(handler); } | |
return this; | |
}; | |
/** | |
* Alias for "on". | |
* @method addListener | |
*/ | |
EventEmitter.prototype.addListener = EventEmitter.prototype.on; | |
/** | |
* Unbind an event by type and handler. | |
* This undoes the work of "on". | |
* | |
* @method removeListener | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {function} handler function object to remove | |
* @return {EventEmitter} this | |
*/ | |
EventEmitter.prototype.removeListener = function removeListener(type, handler) { | |
var listener = this.listeners[type]; | |
if (listener !== undefined) { | |
var index = listener.indexOf(handler); | |
if (index >= 0) { listener.splice(index, 1); } | |
} | |
return this; | |
}; | |
/** | |
* Call event handlers with this set to owner. | |
* | |
* @method bindThis | |
* | |
* @param {Object} owner object this EventEmitter belongs to | |
*/ | |
EventEmitter.prototype.bindThis = function bindThis(owner) { | |
this._owner = owner; | |
}; | |
var EventEmitter_1 = EventEmitter; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* EventHandler forwards received events to a set of provided callback functions. | |
* It allows events to be captured, processed, and optionally piped through to other event handlers. | |
* | |
* @class EventHandler | |
* @extends EventEmitter | |
* @constructor | |
*/ | |
function EventHandler() { | |
EventEmitter_1.apply(this, arguments); | |
this.downstream = []; // downstream event handlers | |
this.downstreamFn = []; // downstream functions | |
this.upstream = []; // upstream event handlers | |
this.upstreamListeners = {}; // upstream listeners | |
} | |
EventHandler.prototype = Object.create(EventEmitter_1.prototype); | |
EventHandler.prototype.constructor = EventHandler; | |
/** | |
* Assign an event handler to receive an object's input events. | |
* | |
* @method setInputHandler | |
* @static | |
* | |
* @param {Object} object object to mix trigger, subscribe, and unsubscribe functions into | |
* @param {EventHandler} handler assigned event handler | |
*/ | |
EventHandler.setInputHandler = function setInputHandler(object, handler) { | |
object.trigger = handler.trigger.bind(handler); | |
if (handler.subscribe && handler.unsubscribe) { | |
object.subscribe = handler.subscribe.bind(handler); | |
object.unsubscribe = handler.unsubscribe.bind(handler); | |
} | |
}; | |
/** | |
* Assign an event handler to receive an object's output events. | |
* | |
* @method setOutputHandler | |
* @static | |
* | |
* @param {Object} object object to mix pipe, unpipe, on, addListener, and removeListener functions into | |
* @param {EventHandler} handler assigned event handler | |
*/ | |
EventHandler.setOutputHandler = function setOutputHandler(object, handler) { | |
if (handler instanceof EventHandler) { handler.bindThis(object); } | |
object.pipe = handler.pipe.bind(handler); | |
object.unpipe = handler.unpipe.bind(handler); | |
object.on = handler.on.bind(handler); | |
object.addListener = object.on; | |
object.removeListener = handler.removeListener.bind(handler); | |
}; | |
/** | |
* Trigger an event, sending to all downstream handlers | |
* listening for provided 'type' key. | |
* | |
* @method emit | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {Object} event event data | |
* @return {EventHandler} this | |
*/ | |
EventHandler.prototype.emit = function emit(type, event) { | |
EventEmitter_1.prototype.emit.apply(this, arguments); | |
var i = 0; | |
for (i = 0; i < this.downstream.length; i++) { | |
if (this.downstream[i].trigger) { this.downstream[i].trigger(type, event); } | |
} | |
for (i = 0; i < this.downstreamFn.length; i++) { | |
this.downstreamFn[i](type, event); | |
} | |
return this; | |
}; | |
/** | |
* Alias for emit | |
* @method addListener | |
*/ | |
EventHandler.prototype.trigger = EventHandler.prototype.emit; | |
/** | |
* Add event handler object to set of downstream handlers. | |
* | |
* @method pipe | |
* | |
* @param {EventHandler} target event handler target object | |
* @return {EventHandler} passed event handler | |
*/ | |
EventHandler.prototype.pipe = function pipe(target) { | |
if (target.subscribe instanceof Function) { return target.subscribe(this); } | |
var downstreamCtx = (target instanceof Function) ? this.downstreamFn : this.downstream; | |
var index = downstreamCtx.indexOf(target); | |
if (index < 0) { downstreamCtx.push(target); } | |
if (target instanceof Function) { target('pipe', null); } | |
else if (target.trigger) { target.trigger('pipe', null); } | |
return target; | |
}; | |
/** | |
* Remove handler object from set of downstream handlers. | |
* Undoes work of "pipe". | |
* | |
* @method unpipe | |
* | |
* @param {EventHandler} target target handler object | |
* @return {EventHandler} provided target | |
*/ | |
EventHandler.prototype.unpipe = function unpipe(target) { | |
if (target.unsubscribe instanceof Function) { return target.unsubscribe(this); } | |
var downstreamCtx = (target instanceof Function) ? this.downstreamFn : this.downstream; | |
var index = downstreamCtx.indexOf(target); | |
if (index >= 0) { | |
downstreamCtx.splice(index, 1); | |
if (target instanceof Function) { target('unpipe', null); } | |
else if (target.trigger) { target.trigger('unpipe', null); } | |
return target; | |
} | |
else { return false; } | |
}; | |
/** | |
* Bind a callback function to an event type handled by this object. | |
* | |
* @method "on" | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {function(string, Object)} handler callback | |
* @return {EventHandler} this | |
*/ | |
EventHandler.prototype.on = function on(type, handler) { | |
EventEmitter_1.prototype.on.apply(this, arguments); | |
if (!(type in this.upstreamListeners)) { | |
var upstreamListener = this.trigger.bind(this, type); | |
this.upstreamListeners[type] = upstreamListener; | |
for (var i = 0; i < this.upstream.length; i++) { | |
this.upstream[i].on(type, upstreamListener); | |
} | |
} | |
return this; | |
}; | |
/** | |
* Alias for "on" | |
* @method addListener | |
*/ | |
EventHandler.prototype.addListener = EventHandler.prototype.on; | |
/** | |
* Listen for events from an upstream event handler. | |
* | |
* @method subscribe | |
* | |
* @param {EventEmitter} source source emitter object | |
* @return {EventHandler} this | |
*/ | |
EventHandler.prototype.subscribe = function subscribe(source) { | |
var index = this.upstream.indexOf(source); | |
if (index < 0) { | |
this.upstream.push(source); | |
for (var type in this.upstreamListeners) { | |
source.on(type, this.upstreamListeners[type]); | |
} | |
} | |
return this; | |
}; | |
/** | |
* Stop listening to events from an upstream event handler. | |
* | |
* @method unsubscribe | |
* | |
* @param {EventEmitter} source source emitter object | |
* @return {EventHandler} this | |
*/ | |
EventHandler.prototype.unsubscribe = function unsubscribe(source) { | |
var index = this.upstream.indexOf(source); | |
if (index >= 0) { | |
this.upstream.splice(index, 1); | |
for (var type in this.upstreamListeners) { | |
source.removeListener(type, this.upstreamListeners[type]); | |
} | |
} | |
return this; | |
}; | |
var EventHandler_1 = EventHandler; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* Internal helper object to Context that handles the process of | |
* creating and allocating DOM elements within a managed div. | |
* Private. | |
* | |
* @class ElementAllocator | |
* @constructor | |
* @private | |
* @param {Node} container document element in which Famo.us content will be inserted | |
*/ | |
function ElementAllocator(container) { | |
if (!container) { container = document.createDocumentFragment(); } | |
this.container = container; | |
this.detachedNodes = {}; | |
this.nodeCount = 0; | |
} | |
/** | |
* Move the document elements from their original container to a new one. | |
* | |
* @private | |
* @method migrate | |
* | |
* @param {Node} container document element to which Famo.us content will be migrated | |
*/ | |
ElementAllocator.prototype.migrate = function migrate(container) { | |
var oldContainer = this.container; | |
if (container === oldContainer) { return; } | |
if (oldContainer instanceof DocumentFragment) { | |
container.appendChild(oldContainer); | |
} | |
else { | |
while (oldContainer.hasChildNodes()) { | |
container.appendChild(oldContainer.firstChild); | |
} | |
} | |
this.container = container; | |
}; | |
/** | |
* Allocate an element of specified type from the pool. | |
* | |
* @private | |
* @method allocate | |
* | |
* @param {string} type type of element, e.g. 'div' | |
* @return {Node} allocated document element | |
*/ | |
ElementAllocator.prototype.allocate = function allocate(type) { | |
type = type.toLowerCase(); | |
if (!(type in this.detachedNodes)) { this.detachedNodes[type] = []; } | |
var nodeStore = this.detachedNodes[type]; | |
var result; | |
if (nodeStore.length > 0) { | |
result = nodeStore.pop(); | |
} | |
else { | |
result = document.createElement(type); | |
this.container.appendChild(result); | |
} | |
this.nodeCount++; | |
return result; | |
}; | |
/** | |
* De-allocate an element of specified type to the pool. | |
* | |
* @private | |
* @method deallocate | |
* | |
* @param {Node} element document element to deallocate | |
*/ | |
ElementAllocator.prototype.deallocate = function deallocate(element) { | |
var nodeType = element.nodeName.toLowerCase(); | |
var nodeStore = this.detachedNodes[nodeType]; | |
nodeStore.push(element); | |
this.nodeCount--; | |
}; | |
/** | |
* Get count of total allocated nodes in the document. | |
* | |
* @private | |
* @method getNodeCount | |
* | |
* @return {Number} total node count | |
*/ | |
ElementAllocator.prototype.getNodeCount = function getNodeCount() { | |
return this.nodeCount; | |
}; | |
var ElementAllocator_1 = ElementAllocator; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
var _zeroZero$1 = [0, 0]; | |
var usePrefix = !('perspective' in document.documentElement.style); | |
function _getElementSize() { | |
var element = this.container; | |
return [element.clientWidth, element.clientHeight]; | |
} | |
var _setPerspective = usePrefix ? function(element, perspective) { | |
element.style.webkitPerspective = perspective ? perspective.toFixed() + 'px' : ''; | |
} : function(element, perspective) { | |
element.style.perspective = perspective ? perspective.toFixed() + 'px' : ''; | |
}; | |
/** | |
* The top-level container for a Famous-renderable piece of the document. | |
* It is directly updated by the process-wide Engine object, and manages one | |
* render tree root, which can contain other renderables. | |
* | |
* @class Context | |
* @constructor | |
* @private | |
* @param {Node} container Element in which content will be inserted | |
*/ | |
function Context(container) { | |
this.container = container; | |
this._allocator = new ElementAllocator_1(container); | |
this._node = new RenderNode_1(); | |
this._eventOutput = new EventHandler_1(); | |
this._size = _getElementSize.call(this); | |
this._perspectiveState = new Transitionable_1(0); | |
this._perspective = undefined; | |
this._nodeContext = { | |
allocator: this._allocator, | |
transform: Transform_1.identity, | |
opacity: 1, | |
origin: _zeroZero$1, | |
align: _zeroZero$1, | |
size: this._size | |
}; | |
this._eventOutput.on('resize', function() { | |
this.setSize(_getElementSize.call(this)); | |
}.bind(this)); | |
} | |
// Note: Unused | |
Context.prototype.getAllocator = function getAllocator() { | |
return this._allocator; | |
}; | |
/** | |
* Add renderables to this Context's render tree. | |
* | |
* @method add | |
* | |
* @param {Object} obj renderable object | |
* @return {RenderNode} RenderNode wrapping this object, if not already a RenderNode | |
*/ | |
Context.prototype.add = function add(obj) { | |
return this._node.add(obj); | |
}; | |
/** | |
* Move this Context to another containing document element. | |
* | |
* @method migrate | |
* | |
* @param {Node} container Element to which content will be migrated | |
*/ | |
Context.prototype.migrate = function migrate(container) { | |
if (container === this.container) { return; } | |
this.container = container; | |
this._allocator.migrate(container); | |
}; | |
/** | |
* Gets viewport size for Context. | |
* | |
* @method getSize | |
* | |
* @return {Array.Number} viewport size as [width, height] | |
*/ | |
Context.prototype.getSize = function getSize() { | |
return this._size; | |
}; | |
/** | |
* Sets viewport size for Context. | |
* | |
* @method setSize | |
* | |
* @param {Array.Number} size [width, height]. If unspecified, use size of root document element. | |
*/ | |
Context.prototype.setSize = function setSize(size) { | |
if (!size) { size = _getElementSize.call(this); } | |
this._size[0] = size[0]; | |
this._size[1] = size[1]; | |
}; | |
/** | |
* Commit this Context's content changes to the document. | |
* | |
* @private | |
* @method update | |
* @param {Object} contextParameters engine commit specification | |
*/ | |
Context.prototype.update = function update(contextParameters) { | |
if (contextParameters) { | |
if (contextParameters.transform) { this._nodeContext.transform = contextParameters.transform; } | |
if (contextParameters.opacity) { this._nodeContext.opacity = contextParameters.opacity; } | |
if (contextParameters.origin) { this._nodeContext.origin = contextParameters.origin; } | |
if (contextParameters.align) { this._nodeContext.align = contextParameters.align; } | |
if (contextParameters.size) { this._nodeContext.size = contextParameters.size; } | |
} | |
var perspective = this._perspectiveState.get(); | |
if (perspective !== this._perspective) { | |
_setPerspective(this.container, perspective); | |
this._perspective = perspective; | |
} | |
this._node.commit(this._nodeContext); | |
}; | |
/** | |
* Get current perspective of this context in pixels. | |
* | |
* @method getPerspective | |
* @return {Number} depth perspective in pixels | |
*/ | |
Context.prototype.getPerspective = function getPerspective() { | |
return this._perspectiveState.get(); | |
}; | |
/** | |
* Set current perspective of this context in pixels. | |
* | |
* @method setPerspective | |
* @param {Number} perspective in pixels | |
* @param {Object} [transition] Transitionable object for applying the change | |
* @param {function(Object)} callback function called on completion of transition | |
*/ | |
Context.prototype.setPerspective = function setPerspective(perspective, transition, callback) { | |
return this._perspectiveState.set(perspective, transition, callback); | |
}; | |
/** | |
* Trigger an event, sending to all downstream handlers | |
* listening for provided 'type' key. | |
* | |
* @method emit | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {Object} event event data | |
* @return {EventHandler} this | |
*/ | |
Context.prototype.emit = function emit(type, event) { | |
return this._eventOutput.emit(type, event); | |
}; | |
/** | |
* Bind a callback function to an event type handled by this object. | |
* | |
* @method "on" | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {function(string, Object)} handler callback | |
* @return {EventHandler} this | |
*/ | |
Context.prototype.on = function on(type, handler) { | |
return this._eventOutput.on(type, handler); | |
}; | |
/** | |
* Unbind an event by type and handler. | |
* This undoes the work of "on". | |
* | |
* @method removeListener | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {function} handler function object to remove | |
* @return {EventHandler} internal event handler object (for chaining) | |
*/ | |
Context.prototype.removeListener = function removeListener(type, handler) { | |
return this._eventOutput.removeListener(type, handler); | |
}; | |
/** | |
* Add event handler object to set of downstream handlers. | |
* | |
* @method pipe | |
* | |
* @param {EventHandler} target event handler target object | |
* @return {EventHandler} passed event handler | |
*/ | |
Context.prototype.pipe = function pipe(target) { | |
return this._eventOutput.pipe(target); | |
}; | |
/** | |
* Remove handler object from set of downstream handlers. | |
* Undoes work of "pipe". | |
* | |
* @method unpipe | |
* | |
* @param {EventHandler} target target handler object | |
* @return {EventHandler} provided target | |
*/ | |
Context.prototype.unpipe = function unpipe(target) { | |
return this._eventOutput.unpipe(target); | |
}; | |
var Context_1 = Context; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* A collection of methods for setting options which can be extended | |
* onto other classes. | |
* | |
* | |
* **** WARNING **** | |
* You can only pass through objects that will compile into valid JSON. | |
* | |
* Valid options: | |
* Strings, | |
* Arrays, | |
* Objects, | |
* Numbers, | |
* Nested Objects, | |
* Nested Arrays. | |
* | |
* This excludes: | |
* Document Fragments, | |
* Functions | |
* @class OptionsManager | |
* @constructor | |
* @param {Object} value options dictionary | |
*/ | |
function OptionsManager(value) { | |
this._value = value; | |
this.eventOutput = null; | |
} | |
/** | |
* Create options manager from source dictionary with arguments overriden by patch dictionary. | |
* | |
* @static | |
* @method OptionsManager.patch | |
* | |
* @param {Object} source source arguments | |
* @param {...Object} data argument additions and overwrites | |
* @return {Object} source object | |
*/ | |
OptionsManager.patch = function patchObject(source, data) { | |
var manager = new OptionsManager(source); | |
for (var i = 1; i < arguments.length; i++) { manager.patch(arguments[i]); } | |
return source; | |
}; | |
function _createEventOutput() { | |
this.eventOutput = new EventHandler_1(); | |
this.eventOutput.bindThis(this); | |
EventHandler_1.setOutputHandler(this, this.eventOutput); | |
} | |
/** | |
* Create OptionsManager from source with arguments overriden by patches. | |
* Triggers 'change' event on this object's event handler if the state of | |
* the OptionsManager changes as a result. | |
* | |
* @method patch | |
* | |
* @param {...Object} arguments list of patch objects | |
* @return {OptionsManager} this | |
*/ | |
OptionsManager.prototype.patch = function patch() { | |
var myState = this._value; | |
for (var i = 0; i < arguments.length; i++) { | |
var data = arguments[i]; | |
for (var k in data) { | |
if ((k in myState) && (data[k] && data[k].constructor === Object) && (myState[k] && myState[k].constructor === Object)) { | |
if (!myState.hasOwnProperty(k)) { myState[k] = Object.create(myState[k]); } | |
this.key(k).patch(data[k]); | |
if (this.eventOutput) { this.eventOutput.emit('change', {id: k, value: this.key(k).value()}); } | |
} | |
else { this.set(k, data[k]); } | |
} | |
} | |
return this; | |
}; | |
/** | |
* Alias for patch | |
* | |
* @method setOptions | |
* | |
*/ | |
OptionsManager.prototype.setOptions = OptionsManager.prototype.patch; | |
/** | |
* Return OptionsManager based on sub-object retrieved by key | |
* | |
* @method key | |
* | |
* @param {string} identifier key | |
* @return {OptionsManager} new options manager with the value | |
*/ | |
OptionsManager.prototype.key = function key(identifier) { | |
var result = new OptionsManager(this._value[identifier]); | |
if (!(result._value instanceof Object) || result._value instanceof Array) { result._value = {}; } | |
return result; | |
}; | |
/** | |
* Look up value by key or get the full options hash | |
* @method get | |
* | |
* @param {string} key key | |
* @return {Object} associated object or full options hash | |
*/ | |
OptionsManager.prototype.get = function get(key) { | |
return key ? this._value[key] : this._value; | |
}; | |
/** | |
* Alias for get | |
* @method getOptions | |
*/ | |
OptionsManager.prototype.getOptions = OptionsManager.prototype.get; | |
/** | |
* Set key to value. Outputs 'change' event if a value is overwritten. | |
* | |
* @method set | |
* | |
* @param {string} key key string | |
* @param {Object} value value object | |
* @return {OptionsManager} new options manager based on the value object | |
*/ | |
OptionsManager.prototype.set = function set(key, value) { | |
var originalValue = this.get(key); | |
this._value[key] = value; | |
if (this.eventOutput && value !== originalValue) { this.eventOutput.emit('change', {id: key, value: value}); } | |
return this; | |
}; | |
/** | |
* Bind a callback function to an event type handled by this object. | |
* | |
* @method "on" | |
* | |
* @param {string} type event type key (for example, 'change') | |
* @param {function(string, Object)} handler callback | |
* @return {EventHandler} this | |
*/ | |
OptionsManager.prototype.on = function on() { | |
_createEventOutput.call(this); | |
return this.on.apply(this, arguments); | |
}; | |
/** | |
* Unbind an event by type and handler. | |
* This undoes the work of "on". | |
* | |
* @method removeListener | |
* | |
* @param {string} type event type key (for example, 'change') | |
* @param {function} handler function object to remove | |
* @return {EventHandler} internal event handler object (for chaining) | |
*/ | |
OptionsManager.prototype.removeListener = function removeListener() { | |
_createEventOutput.call(this); | |
return this.removeListener.apply(this, arguments); | |
}; | |
/** | |
* Add event handler object to set of downstream handlers. | |
* | |
* @method pipe | |
* | |
* @param {EventHandler} target event handler target object | |
* @return {EventHandler} passed event handler | |
*/ | |
OptionsManager.prototype.pipe = function pipe() { | |
_createEventOutput.call(this); | |
return this.pipe.apply(this, arguments); | |
}; | |
/** | |
* Remove handler object from set of downstream handlers. | |
* Undoes work of "pipe" | |
* | |
* @method unpipe | |
* | |
* @param {EventHandler} target target handler object | |
* @return {EventHandler} provided target | |
*/ | |
OptionsManager.prototype.unpipe = function unpipe() { | |
_createEventOutput.call(this); | |
return this.unpipe.apply(this, arguments); | |
}; | |
var OptionsManager_1 = OptionsManager; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* The singleton object initiated upon process | |
* startup which manages all active Context instances, runs | |
* the render dispatch loop, and acts as a listener and dispatcher | |
* for events. All methods are therefore static. | |
* | |
* On static initialization, window.requestAnimationFrame is called with | |
* the event loop function. | |
* | |
* Note: Any window in which Engine runs will prevent default | |
* scrolling behavior on the 'touchmove' event. | |
* | |
* @static | |
* @class Engine | |
*/ | |
var Engine = {}; | |
var contexts = []; | |
var nextTickQueue = []; | |
var currentFrame = 0; | |
var nextTickFrame = 0; | |
var deferQueue = []; | |
var lastTime = Date.now(); | |
var frameTime; | |
var frameTimeLimit; | |
var loopEnabled = true; | |
var eventForwarders = {}; | |
var eventHandler = new EventHandler_1(); | |
var options = { | |
containerType: 'div', | |
containerClass: 'famous-container', | |
fpsCap: undefined, | |
runLoop: true, | |
appMode: true | |
}; | |
var optionsManager = new OptionsManager_1(options); | |
/** @const */ | |
var MAX_DEFER_FRAME_TIME = 10; | |
/** | |
* Inside requestAnimationFrame loop, step() is called, which: | |
* calculates current FPS (throttling loop if it is over limit set in setFPSCap), | |
* emits dataless 'prerender' event on start of loop, | |
* calls in order any one-shot functions registered by nextTick on last loop, | |
* calls Context.update on all Context objects registered, | |
* and emits dataless 'postrender' event on end of loop. | |
* | |
* @static | |
* @private | |
* @method step | |
*/ | |
Engine.step = function step() { | |
currentFrame++; | |
nextTickFrame = currentFrame; | |
var currentTime = Date.now(); | |
// skip frame if we're over our framerate cap | |
if (frameTimeLimit && currentTime - lastTime < frameTimeLimit) { return; } | |
var i = 0; | |
frameTime = currentTime - lastTime; | |
lastTime = currentTime; | |
eventHandler.emit('prerender'); | |
// empty the queue | |
var numFunctions = nextTickQueue.length; | |
while (numFunctions--) { (nextTickQueue.shift())(currentFrame); } | |
// limit total execution time for deferrable functions | |
while (deferQueue.length && (Date.now() - currentTime) < MAX_DEFER_FRAME_TIME) { | |
deferQueue.shift().call(this); | |
} | |
for (i = 0; i < contexts.length; i++) { contexts[i].update(); } | |
eventHandler.emit('postrender'); | |
}; | |
// engage requestAnimationFrame | |
function loop() { | |
if (options.runLoop) { | |
Engine.step(); | |
window.requestAnimationFrame(loop); | |
} | |
else { loopEnabled = false; } | |
} | |
window.requestAnimationFrame(loop); | |
// | |
// Upon main document window resize (unless on an "input" HTML element): | |
// scroll to the top left corner of the window, | |
// and for each managed Context: emit the 'resize' event and update its size. | |
// @param {Object=} event document event | |
// | |
function handleResize(event) { | |
for (var i = 0; i < contexts.length; i++) { | |
contexts[i].emit('resize'); | |
} | |
eventHandler.emit('resize'); | |
} | |
window.addEventListener('resize', handleResize, false); | |
handleResize(); | |
/** | |
* Initialize famous for app mode | |
* | |
* @static | |
* @private | |
* @method initialize | |
*/ | |
function initialize() { | |
// prevent scrolling via browser | |
window.addEventListener('touchmove', function(event) { | |
event.preventDefault(); | |
}, true); | |
addRootClasses(); | |
} | |
var initialized = false; | |
function addRootClasses() { | |
if (!document.body) { | |
Engine.nextTick(addRootClasses); | |
return; | |
} | |
document.body.classList.add('famous-root'); | |
document.documentElement.classList.add('famous-root'); | |
} | |
/** | |
* Add event handler object to set of downstream handlers. | |
* | |
* @method pipe | |
* | |
* @param {EventHandler} target event handler target object | |
* @return {EventHandler} passed event handler | |
*/ | |
Engine.pipe = function pipe(target) { | |
if (target.subscribe instanceof Function) { return target.subscribe(Engine); } | |
else { return eventHandler.pipe(target); } | |
}; | |
/** | |
* Remove handler object from set of downstream handlers. | |
* Undoes work of "pipe". | |
* | |
* @method unpipe | |
* | |
* @param {EventHandler} target target handler object | |
* @return {EventHandler} provided target | |
*/ | |
Engine.unpipe = function unpipe(target) { | |
if (target.unsubscribe instanceof Function) { return target.unsubscribe(Engine); } | |
else { return eventHandler.unpipe(target); } | |
}; | |
/** | |
* Bind a callback function to an event type handled by this object. | |
* | |
* @static | |
* @method "on" | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {function(string, Object)} handler callback | |
* @return {EventHandler} this | |
*/ | |
Engine.on = function on(type, handler) { | |
if (!(type in eventForwarders)) { | |
eventForwarders[type] = eventHandler.emit.bind(eventHandler, type); | |
addEngineListener(type, eventForwarders[type]); | |
} | |
return eventHandler.on(type, handler); | |
}; | |
function addEngineListener(type, forwarder) { | |
if (!document.body) { | |
Engine.nextTick(addEventListener.bind(this, type, forwarder)); | |
return; | |
} | |
document.body.addEventListener(type, forwarder); | |
} | |
/** | |
* Trigger an event, sending to all downstream handlers | |
* listening for provided 'type' key. | |
* | |
* @method emit | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {Object} event event data | |
* @return {EventHandler} this | |
*/ | |
Engine.emit = function emit(type, event) { | |
return eventHandler.emit(type, event); | |
}; | |
/** | |
* Unbind an event by type and handler. | |
* This undoes the work of "on". | |
* | |
* @static | |
* @method removeListener | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {function} handler function object to remove | |
* @return {EventHandler} internal event handler object (for chaining) | |
*/ | |
Engine.removeListener = function removeListener(type, handler) { | |
return eventHandler.removeListener(type, handler); | |
}; | |
/** | |
* Return the current calculated frames per second of the Engine. | |
* | |
* @static | |
* @method getFPS | |
* | |
* @return {Number} calculated fps | |
*/ | |
Engine.getFPS = function getFPS() { | |
return 1000 / frameTime; | |
}; | |
/** | |
* Set the maximum fps at which the system should run. If internal render | |
* loop is called at a greater frequency than this FPSCap, Engine will | |
* throttle render and update until this rate is achieved. | |
* | |
* @static | |
* @method setFPSCap | |
* | |
* @param {Number} fps maximum frames per second | |
*/ | |
Engine.setFPSCap = function setFPSCap(fps) { | |
frameTimeLimit = Math.floor(1000 / fps); | |
}; | |
/** | |
* Return engine options. | |
* | |
* @static | |
* @method getOptions | |
* @param {string} key | |
* @return {Object} engine options | |
*/ | |
Engine.getOptions = function getOptions(key) { | |
return optionsManager.getOptions(key); | |
}; | |
/** | |
* Set engine options | |
* | |
* @static | |
* @method setOptions | |
* | |
* @param {Object} [options] overrides of default options | |
* @param {Number} [options.fpsCap] maximum fps at which the system should run | |
* @param {boolean} [options.runLoop=true] whether the run loop should continue | |
* @param {string} [options.containerType="div"] type of container element. Defaults to 'div'. | |
* @param {string} [options.containerClass="famous-container"] type of container element. Defaults to 'famous-container'. | |
*/ | |
Engine.setOptions = function setOptions(options) { | |
return optionsManager.setOptions.apply(optionsManager, arguments); | |
}; | |
/** | |
* Creates a new Context for rendering and event handling with | |
* provided document element as top of each tree. This will be tracked by the | |
* process-wide Engine. | |
* | |
* @static | |
* @method createContext | |
* | |
* @param {Node} el will be top of Famo.us document element tree | |
* @return {Context} new Context within el | |
*/ | |
Engine.createContext = function createContext(el) { | |
if (!initialized && options.appMode) { Engine.nextTick(initialize); } | |
var needMountContainer = false; | |
if (!el) { | |
el = document.createElement(options.containerType); | |
el.classList.add(options.containerClass); | |
needMountContainer = true; | |
} | |
var context = new Context_1(el); | |
Engine.registerContext(context); | |
if (needMountContainer) { mount(context, el); } | |
return context; | |
}; | |
function mount(context, el) { | |
if (!document.body) { | |
Engine.nextTick(mount.bind(this, context, el)); | |
return; | |
} | |
document.body.appendChild(el); | |
context.emit('resize'); | |
} | |
/** | |
* Registers an existing context to be updated within the run loop. | |
* | |
* @static | |
* @method registerContext | |
* | |
* @param {Context} context Context to register | |
* @return {FamousContext} provided context | |
*/ | |
Engine.registerContext = function registerContext(context) { | |
contexts.push(context); | |
return context; | |
}; | |
/** | |
* Returns a list of all contexts. | |
* | |
* @static | |
* @method getContexts | |
* @return {Array} contexts that are updated on each tick | |
*/ | |
Engine.getContexts = function getContexts() { | |
return contexts; | |
}; | |
/** | |
* Removes a context from the run loop. Note: this does not do any | |
* cleanup. | |
* | |
* @static | |
* @method deregisterContext | |
* | |
* @param {Context} context Context to deregister | |
*/ | |
Engine.deregisterContext = function deregisterContext(context) { | |
var i = contexts.indexOf(context); | |
if (i >= 0) { contexts.splice(i, 1); } | |
}; | |
/** | |
* Queue a function to be executed on the next tick of the | |
* Engine. | |
* | |
* @static | |
* @method nextTick | |
* | |
* @param {function(Object)} fn function accepting window object | |
*/ | |
Engine.nextTick = function nextTick(fn) { | |
nextTickQueue.push(fn); | |
}; | |
/** | |
* Queue a function to be executed sometime soon, at a time that is | |
* unlikely to affect frame rate. | |
* | |
* @static | |
* @method defer | |
* | |
* @param {Function} fn | |
*/ | |
Engine.defer = function defer(fn) { | |
deferQueue.push(fn); | |
}; | |
optionsManager.on('change', function(data) { | |
if (data.id === 'fpsCap') { Engine.setFPSCap(data.value); } | |
else if (data.id === 'runLoop') { | |
// kick off the loop only if it was stopped | |
if (!loopEnabled && data.value) { | |
loopEnabled = true; | |
window.requestAnimationFrame(loop); | |
} | |
} | |
}); | |
var Engine_1 = Engine; | |
/* | |
* @overview Utility functions used by infamous. | |
* | |
* LICENSE | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
*/ | |
/** | |
* Creates a [famous/src/core/Context](#famous/src/core/Context) having the specified 3D perspective. | |
* | |
* @param {Number} perspective The integer amount of perspective to apply to the `Context`. | |
* @returns {module: famous/src/core/Context} The `Context` with the applied perspective. | |
*/ | |
function contextWithPerspective(perspective) { | |
const context = Engine_1.createContext(); | |
context.setPerspective(perspective); | |
return context; | |
} | |
function simpleExtend(object, ...others) { | |
others.forEach(function(other) { | |
for (const prop in other) { | |
object[prop] = other[prop]; | |
} | |
}); | |
} | |
var utils = Object.freeze({ | |
contextWithPerspective: contextWithPerspective, | |
simpleExtend: simpleExtend | |
}); | |
// Polyfill for Function.name on browsers that do not support it (IE): | |
// See: http://stackoverflow.com/questions/6903762/function-name-not-supported-in-ie | |
if (!(function f() {}).name) { | |
Object.defineProperty(Function.prototype, "name", { | |
get: function () { | |
var name = this.toString().match(/^\s*function\s*(\S*)\s*\(/)[1]; | |
// For better performance only parse once, and then cache the | |
// result through a new accessor for repeated access. | |
Object.defineProperty(this, "name", { value: name }); | |
return name; | |
} | |
}); | |
} | |
/* | |
* LICENSE | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
*/ | |
/** | |
* Molecules are the basic building blocks of all UI components. Molecules | |
* extend [famous/src/core/RenderNode](#famous/src/core/RenderNode), so they can be | |
* added to any `RenderNode` of a famo.us render tree, and by default they will | |
* also accept anything that a normal Famo.us `RenderNode` can accept via the | |
* `add` method. Classes that extend from `Molecule` might override | |
* `RenderNode.add` in order to accept things like arrays of renderables in | |
* stead of a single renderable. | |
* | |
* Molecules encapsulate the basic things you need for a component -- a | |
* [famous/src/transitions/TransitionableTransform](#famous/src/transitions/TransitionableTransform) | |
* for positioning things in space, and a [famous/src/core/EventHandler](#famous/src/core/EventHandler) | |
* for capturing user interaction -- exposing a unified API for working with these | |
* things. For now, [famous/src/core/Modifier](#famous/src/core/Modifier) is used as the interface | |
* for applying transforms and sizing, but this will change in Mixed Mode | |
* Famo.us. | |
* | |
* All components extend Molecule, but at the same time they can also use any | |
* number of Molecules internally to do nice things like create layouts and | |
* position multiple things in space. | |
* | |
* @class Molecule | |
* @extends {module: famous/src/core/RenderNode} | |
*/ | |
class Molecule extends RenderNode_1 { | |
/** | |
* Creates a new `Molecule` and applies `initialOptions` to it's internal | |
* `famous/src/core/Modifier`. See [famous/src/core/Modifier](#famous/src/core/Modifier) | |
* for details on what options you can pass. | |
* | |
* Note: Mixed Mode Famo.us does away with Modifiers, so this API will | |
* change slightly, but the change will be in such a way that APIs of | |
* higher level classes won't change because of this. One of the biggest | |
* changes in Mixed Mode will be that `size` will be set only on a | |
* per-Surface basis as far as a render tree is concerned. So if you | |
* normally put multiple `Surface` instances into a `Modifier` that has a | |
* size, then instead you'll have to explicitly assign a `size` to each | |
* `Surface`. This is a good thing, and makes for a cleaner and easier to | |
* use render tree with a separation of concerns from classes that can | |
* handle boundaries and group sizing. `Molecule` might then be an example | |
* of such a class with it's own size API. | |
* | |
* @constructor | |
* @param {Object} initialOptions The options to initialize this Molecule's `Modifier` with. | |
*/ | |
constructor(initialOptions) { | |
super(); | |
// "private" stuff. Not really, but regard it like so. For example, if | |
// you see something like obj._.someVariable then you're accessing | |
// internal stuff that wasn't designed to be accessed directly, and any | |
// problem you enounter with that is your own problem. :) | |
// | |
// TODO: Use a WeakMap to store these at some point. | |
this._ = { | |
options: {}, // set and get with this.options | |
defaultOptions: {} | |
}; | |
// Add default values for this Molecule | |
// TODO: Make default options static for the class. | |
simpleExtend(this._.defaultOptions, { | |
align: [0.5,0.5], | |
origin: [0.5,0.5], | |
transform: new TransitionableTransform_1, | |
handler: new EventHandler_1 | |
}); | |
// set the user's initial options. This automatically creates | |
// this.modifier, and adds it to this (don't forget, *this* is a | |
// RenderNode, so a Molecule can add things to itself). | |
// | |
// NOTE: this.options is a setter property. This statement applies all | |
// relevant properties to this.modifier. | |
this.options = initialOptions; | |
} | |
/** | |
* @property {Object} options The Molecule's options, which get applied to | |
* `this.modifier`. This may change with Mixed Mode. Setting this property | |
* overrides existing options. To extend existing options with new options, | |
* use `setOptions` instead. Unspecified options will be set to their default | |
* values. | |
* | |
* Note: Anytime `this.options` is assigned a new value, `this.modifier` is set | |
* to a new [famous/src/core/Modifier](#famous/src/core/Modifier). | |
*/ | |
set options(newOptions) { | |
this.resetOptions(); | |
this.setOptions(newOptions); | |
} | |
get options() { | |
return this._.options; | |
} | |
/** | |
* @property {module: famous/src/transitions/TransitionableTransform} transform | |
* The transform of this `Molecule`. The default is a | |
* [famous/src/transitions/TransitionableTransform](#famous/src/transitions/TransitionableTransform). | |
* Setting this property automatically puts the new transform into effect. | |
* See [famous/src/core/Modifier.transformFrom](#famous/src/core/Modifier.transformFrom). | |
*/ | |
set transform(newTransform) { | |
this.setOptions({transform: newTransform}); | |
} | |
get transform() { | |
return this.options.transform; | |
} | |
/** | |
* Compounds `newOptions` into the existing options, similar to extending an | |
* object and overriding only the desired properties. To override all | |
* options with a set of new options, set `this.options` directly. | |
* | |
* An example of setting just a single option without erasing other options: | |
* | |
* ```js | |
* const myMolecule = new Molecule() | |
* myMolecule.setOptions({ | |
* align: [0.2, 0.8] | |
* }) | |
* ``` | |
* | |
* @param {Object} newOptions An object containing the new options to apply to this `Molecule`. | |
*/ | |
setOptions(newOptions) { | |
if (typeof newOptions == 'undefined' || newOptions.constructor.name != "Object") | |
{ newOptions = {}; } | |
for (const prop in newOptions) { | |
// Subject to change when Famo.us API changes. | |
if (Modifier_1.prototype[''+prop+'From']) { | |
this.modifier[''+prop+'From'](newOptions[prop]); | |
} | |
this._.options[prop] = newOptions[prop]; | |
} | |
} | |
/** | |
* Sets all options back to their defaults. | |
* | |
* Note: Anytime this is called, `this.modifier` is set to a new | |
* [famous/src/core/Modifier](#famous/src/core/Modifier) having the default | |
* options. | |
*/ | |
resetOptions() { | |
this.modifier = new Modifier_1(); | |
this.set(this.modifier); | |
this.setOptions(this._.defaultOptions); | |
} | |
/** | |
* Forwards events from this Molecule's [famous/src/core/EventHandler](#famous/src/core/EventHandler) to the given | |
* target, which can be another `EventHandler` or `Molecule`. | |
* | |
* This method is equivalent to [famous/src/core/EventHandler.pipe](#famous/src/core/EventHandler.pipe), | |
* acting upon `this.handler`. | |
* | |
* TODO v0.1.0: Let this method accept a `Molecule`, then stop doing `pipe(this._.handler)` in other places | |
*/ | |
pipe() { | |
const args = Array.prototype.splice.call(arguments, 0); | |
return this.options.handler.pipe.apply(this.options.handler, args); | |
} | |
/** | |
* Stops events from this Molecule's [famous/src/core/EventHandler](#famous/src/core/EventHandler) | |
* from being sent to the given target. | |
* | |
* This method is equivalent to [famous/src/core/EventHandler.unpipe](#famous/src/core/EventHandler.unpipe), | |
* acting upon `this.handler`. | |
* | |
* TODO v0.1.0: Let this method accept a `Molecule`, then stop doing `pipe(this.options.handler)` in other places | |
*/ | |
unpipe() { | |
const args = Array.prototype.splice.call(arguments, 0); | |
return this.options.handler.unpipe.apply(this.options.handler, args); | |
} | |
/** | |
* Register an event handler for the specified event. | |
* See [famous/src/core/EventHandler.on](#famous/src/core/EventHandler.on). | |
*/ | |
on() { | |
const args = Array.prototype.splice.call(arguments, 0); | |
return this.options.handler.on.apply(this.options.handler, args); | |
} | |
/** | |
* Unregister an event handler for the specified event. | |
* See [famous/src/core/EventHandler.off](#famous/src/core/EventHandler.off). | |
*/ | |
off() { | |
const args = Array.prototype.splice.call(arguments, 0); | |
return this.options.handler.on.apply(this.options.handler, args); | |
} | |
} | |
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; | |
function unwrapExports (x) { | |
return x && x.__esModule ? x['default'] : x; | |
} | |
function createCommonjsModule(fn, module) { | |
return module = { exports: {} }, fn(module, module.exports), module.exports; | |
} | |
var forLength_1 = createCommonjsModule(function (module, exports) { | |
"use strict"; | |
// loop for a given length, performing action each loop iteration. action receives the index of the loop. | |
exports.forLength = forLength; | |
function forLength(length, action) { | |
for (var i = 0; i < length; i += 1) { | |
action(i); | |
} | |
} | |
exports["default"] = forLength; | |
exports.__esModule = true; | |
}); | |
var forLength = unwrapExports(forLength_1); | |
/* | |
* LICENSE | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
*/ | |
/** | |
* A scenegraph tree with a variable number of leaf node Modifiers (the grid | |
* cells) that are arranged in a grid. Add any [famous/src/core/RenderNode](#famous/src/core/RenderNode)-compatible | |
* item to each leafnode of the grid. | |
* | |
* TODO: Use Molecule instead of Modifier for the grid cells. | |
* TODO: Add an options parameter, that the Molecule constructor will handle. | |
* | |
* @class Grid | |
* @extends Molecule | |
*/ | |
class Grid extends Molecule { | |
/** | |
* Creates a new Grid having the specified number of columns, number of rows, | |
* and famo.us-style size. | |
* | |
* @constructor | |
* @param {Number} columns The integer number of columns. | |
* @param {Number} rows The integer number of rows. | |
* @param {Array} size A famo.us-style width/height size array. | |
*/ | |
constructor(columns, rows, size) { | |
super({size: size}); | |
this.columns = columns; | |
this.rows = rows; | |
this.cellNodes = []; | |
if (typeof this.options.size === 'undefined') { this.setOptions({size: [undefined, undefined]}); } | |
forLength(this.columns*this.rows, this._createGridCell.bind(this)); | |
} | |
/** | |
* Creates a grid cell at the given index. | |
* | |
* @private | |
* @param {Number} index The integer index of the grid cell. | |
*/ | |
_createGridCell(index) { | |
const column = index % this.columns; | |
const row = Math.floor(index / this.columns); | |
let cellSize = null; | |
if (typeof this.options.size[0] != 'undefined' && typeof this.options.size[1] != 'undefined') { | |
cellSize = []; | |
cellSize[0] = this.options.size[0]/this.columns; | |
cellSize[1] = this.options.size[1]/this.rows; | |
} | |
const mod = new Modifier_1({ | |
align: [0,0], | |
origin: [0,0], | |
size: cellSize? [cellSize[0], cellSize[1]]: [undefined, undefined], | |
transform: Transform_1.translate(column*cellSize[0],row*cellSize[1],0) | |
}); | |
const mod2 = new Modifier_1({ | |
//transform: Transform.rotateY(Math.PI/10), | |
align: [0.5,0.5], | |
origin: [0.5,0.5] | |
}); | |
// FIXME: ^^^ Why do I need an extra Modifier to align stuff in the middle of the grid cells????? | |
this.cellNodes.push(this.add(mod).add(mod2)); | |
} | |
/** | |
* Sets the items to be layed out in the grid. | |
* | |
* @param {Array} children An array of [famous/src/core/RenderNode](#famous/src/core/RenderNode)-compatible items. | |
*/ | |
setChildren(children) { | |
forLength(this.columns*this.rows, function(index) { | |
//this.cellNodes[index].set(null); // TODO: how do we erase previous children? | |
this.cellNodes[index].add(children[index]); | |
}.bind(this)); | |
return this; | |
} | |
} | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
var usePrefix$1 = !('transform' in document.documentElement.style); | |
var devicePixelRatio = window.devicePixelRatio || 1; | |
/** | |
* A base class for viewable content and event | |
* targets inside a Famo.us application, containing a renderable document | |
* fragment. Like an HTML div, it can accept internal markup, | |
* properties, classes, and handle events. | |
* | |
* @class ElementOutput | |
* @constructor | |
* | |
* @param {Node} element document parent of this container | |
*/ | |
function ElementOutput(element) { | |
this._matrix = null; | |
this._opacity = 1; | |
this._origin = null; | |
this._size = null; | |
this._eventOutput = new EventHandler_1(); | |
this._eventOutput.bindThis(this); | |
/** @ignore */ | |
this.eventForwarder = function eventForwarder(event) { | |
this._eventOutput.emit(event.type, event); | |
}.bind(this); | |
this.id = Entity.register(this); | |
this._element = null; | |
this._sizeDirty = false; | |
this._originDirty = false; | |
this._transformDirty = false; | |
this._invisible = false; | |
if (element) { this.attach(element); } | |
} | |
/** | |
* Bind a callback function to an event type handled by this object. | |
* | |
* @method "on" | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {function(string, Object)} fn handler callback | |
* @return {EventHandler} this | |
*/ | |
ElementOutput.prototype.on = function on(type, fn) { | |
if (this._element) { this._element.addEventListener(type, this.eventForwarder); } | |
this._eventOutput.on(type, fn); | |
}; | |
/** | |
* Unbind an event by type and handler. | |
* This undoes the work of "on" | |
* | |
* @method removeListener | |
* @param {string} type event type key (for example, 'click') | |
* @param {function(string, Object)} fn handler | |
*/ | |
ElementOutput.prototype.removeListener = function removeListener(type, fn) { | |
this._eventOutput.removeListener(type, fn); | |
}; | |
/** | |
* Trigger an event, sending to all downstream handlers | |
* listening for provided 'type' key. | |
* | |
* @method emit | |
* | |
* @param {string} type event type key (for example, 'click') | |
* @param {Object} [event] event data | |
* @return {EventHandler} this | |
*/ | |
ElementOutput.prototype.emit = function emit(type, event) { | |
if (event && !event.origin) { event.origin = this; } | |
var handled = this._eventOutput.emit(type, event); | |
if (handled && event && event.stopPropagation) { event.stopPropagation(); } | |
return handled; | |
}; | |
/** | |
* Add event handler object to set of downstream handlers. | |
* | |
* @method pipe | |
* | |
* @param {EventHandler} target event handler target object | |
* @return {EventHandler} passed event handler | |
*/ | |
ElementOutput.prototype.pipe = function pipe(target) { | |
return this._eventOutput.pipe(target); | |
}; | |
/** | |
* Remove handler object from set of downstream handlers. | |
* Undoes work of "pipe" | |
* | |
* @method unpipe | |
* | |
* @param {EventHandler} target target handler object | |
* @return {EventHandler} provided target | |
*/ | |
ElementOutput.prototype.unpipe = function unpipe(target) { | |
return this._eventOutput.unpipe(target); | |
}; | |
/** | |
* Return spec for this surface. Note that for a base surface, this is | |
* simply an id. | |
* | |
* @method render | |
* @private | |
* @return {Object} render spec for this surface (spec id) | |
*/ | |
ElementOutput.prototype.render = function render() { | |
return this.id; | |
}; | |
// Attach Famous event handling to document events emanating from target | |
// document element. This occurs just after attachment to the document. | |
// Calling this enables methods like #on and #pipe. | |
function _addEventListeners(target) { | |
for (var i in this._eventOutput.listeners) { | |
target.addEventListener(i, this.eventForwarder); | |
} | |
} | |
// Detach Famous event handling from document events emanating from target | |
// document element. This occurs just before detach from the document. | |
function _removeEventListeners(target) { | |
for (var i in this._eventOutput.listeners) { | |
target.removeEventListener(i, this.eventForwarder); | |
} | |
} | |
/** | |
* Return a Matrix's webkit css representation to be used with the | |
* CSS3 -webkit-transform style. | |
* Example: -webkit-transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,716,243,0,1) | |
* | |
* @method _formatCSSTransform | |
* @private | |
* @param {FamousMatrix} m matrix | |
* @return {string} matrix3d CSS style representation of the transform | |
*/ | |
function _formatCSSTransform(m) { | |
m[12] = Math.round(m[12] * devicePixelRatio) / devicePixelRatio; | |
m[13] = Math.round(m[13] * devicePixelRatio) / devicePixelRatio; | |
var result = 'matrix3d('; | |
for (var i = 0; i < 15; i++) { | |
result += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ','; | |
} | |
result += m[15] + ')'; | |
return result; | |
} | |
/** | |
* Directly apply given FamousMatrix to the document element as the | |
* appropriate webkit CSS style. | |
* | |
* @method setMatrix | |
* | |
* @static | |
* @private | |
* @param {Element} element document element | |
* @param {FamousMatrix} matrix | |
*/ | |
var _setMatrix; | |
if (usePrefix$1) { | |
_setMatrix = function(element, matrix) { | |
element.style.webkitTransform = _formatCSSTransform(matrix); | |
}; | |
} | |
else { | |
_setMatrix = function(element, matrix) { | |
element.style.transform = _formatCSSTransform(matrix); | |
}; | |
} | |
// format origin as CSS percentage string | |
function _formatCSSOrigin(origin) { | |
return (100 * origin[0]) + '% ' + (100 * origin[1]) + '%'; | |
} | |
// Directly apply given origin coordinates to the document element as the | |
// appropriate webkit CSS style. | |
var _setOrigin = usePrefix$1 ? function(element, origin) { | |
element.style.webkitTransformOrigin = _formatCSSOrigin(origin); | |
} : function(element, origin) { | |
element.style.transformOrigin = _formatCSSOrigin(origin); | |
}; | |
// Shrink given document element until it is effectively invisible. | |
var _setInvisible = usePrefix$1 ? function(element) { | |
element.style.webkitTransform = 'scale3d(0.0001,0.0001,0.0001)'; | |
element.style.opacity = 0; | |
} : function(element) { | |
element.style.transform = 'scale3d(0.0001,0.0001,0.0001)'; | |
element.style.opacity = 0; | |
}; | |
function _xyNotEquals$1(a, b) { | |
return (a && b) ? (a[0] !== b[0] || a[1] !== b[1]) : a !== b; | |
} | |
/** | |
* Apply changes from this component to the corresponding document element. | |
* This includes changes to classes, styles, size, content, opacity, origin, | |
* and matrix transforms. | |
* | |
* @private | |
* @method commit | |
* @param {Context} context commit context | |
*/ | |
ElementOutput.prototype.commit = function commit(context) { | |
var target = this._element; | |
if (!target) { return; } | |
var matrix = context.transform; | |
var opacity = context.opacity; | |
var origin = context.origin; | |
var size = context.size; | |
if (!matrix && this._matrix) { | |
this._matrix = null; | |
this._opacity = 0; | |
_setInvisible(target); | |
return; | |
} | |
if (_xyNotEquals$1(this._origin, origin)) { this._originDirty = true; } | |
if (Transform_1.notEquals(this._matrix, matrix)) { this._transformDirty = true; } | |
if (this._invisible) { | |
this._invisible = false; | |
this._element.style.display = ''; | |
} | |
if (this._opacity !== opacity) { | |
this._opacity = opacity; | |
target.style.opacity = (opacity >= 1) ? '0.999999' : opacity; | |
} | |
if (this._transformDirty || this._originDirty || this._sizeDirty) { | |
if (this._sizeDirty) { this._sizeDirty = false; } | |
if (this._originDirty) { | |
if (origin) { | |
if (!this._origin) { this._origin = [0, 0]; } | |
this._origin[0] = origin[0]; | |
this._origin[1] = origin[1]; | |
} | |
else { this._origin = null; } | |
_setOrigin(target, this._origin); | |
this._originDirty = false; | |
} | |
if (!matrix) { matrix = Transform_1.identity; } | |
this._matrix = matrix; | |
var aaMatrix = this._size ? Transform_1.thenMove(matrix, [-this._size[0]*origin[0], -this._size[1]*origin[1], 0]) : matrix; | |
_setMatrix(target, aaMatrix); | |
this._transformDirty = false; | |
} | |
}; | |
ElementOutput.prototype.cleanup = function cleanup() { | |
if (this._element) { | |
this._invisible = true; | |
this._element.style.display = 'none'; | |
} | |
}; | |
/** | |
* Place the document element that this component manages into the document. | |
* | |
* @private | |
* @method attach | |
* @param {Node} target document parent of this container | |
*/ | |
ElementOutput.prototype.attach = function attach(target) { | |
this._element = target; | |
_addEventListeners.call(this, target); | |
}; | |
/** | |
* Remove any contained document content associated with this surface | |
* from the actual document. | |
* | |
* @private | |
* @method detach | |
*/ | |
ElementOutput.prototype.detach = function detach() { | |
var target = this._element; | |
if (target) { | |
_removeEventListeners.call(this, target); | |
if (this._invisible) { | |
this._invisible = false; | |
this._element.style.display = ''; | |
} | |
} | |
this._element = null; | |
return target; | |
}; | |
var ElementOutput_1 = ElementOutput; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* A base class for viewable content and event | |
* targets inside a Famo.us application, containing a renderable document | |
* fragment. Like an HTML div, it can accept internal markup, | |
* properties, classes, and handle events. | |
* | |
* @class Surface | |
* @constructor | |
* | |
* @param {Object} [options] default option overrides | |
* @param {Array.Number} [options.size] [width, height] in pixels | |
* @param {Array.string} [options.classes] CSS classes to set on target div | |
* @param {Array} [options.properties] string dictionary of CSS properties to set on target div | |
* @param {Array} [options.attributes] string dictionary of HTML attributes to set on target div | |
* @param {string} [options.content] inner (HTML) content of surface | |
*/ | |
function Surface(options) { | |
ElementOutput_1.call(this); | |
this.options = {}; | |
this.properties = {}; | |
this.attributes = {}; | |
this.content = ''; | |
this.classList = []; | |
this.size = null; | |
this._classesDirty = true; | |
this._stylesDirty = true; | |
this._attributesDirty = true; | |
this._sizeDirty = true; | |
this._contentDirty = true; | |
this._trueSizeCheck = true; | |
this._dirtyClasses = []; | |
if (options) { this.setOptions(options); } | |
this._currentTarget = null; | |
} | |
Surface.prototype = Object.create(ElementOutput_1.prototype); | |
Surface.prototype.constructor = Surface; | |
Surface.prototype.elementType = 'div'; | |
Surface.prototype.elementClass = 'famous-surface'; | |
/** | |
* Set HTML attributes on this Surface. Note that this will cause | |
* dirtying and thus re-rendering, even if values do not change. | |
* | |
* @method setAttributes | |
* @param {Object} attributes property dictionary of "key" => "value" | |
*/ | |
Surface.prototype.setAttributes = function setAttributes(attributes) { | |
for (var n in attributes) { | |
if (n === 'style') { throw new Error('Cannot set styles via "setAttributes" as it will break Famo.us. Use "setProperties" instead.'); } | |
this.attributes[n] = attributes[n]; | |
} | |
this._attributesDirty = true; | |
}; | |
/** | |
* Get HTML attributes on this Surface. | |
* | |
* @method getAttributes | |
* | |
* @return {Object} Dictionary of this Surface's attributes. | |
*/ | |
Surface.prototype.getAttributes = function getAttributes() { | |
return this.attributes; | |
}; | |
/** | |
* Set CSS-style properties on this Surface. Note that this will cause | |
* dirtying and thus re-rendering, even if values do not change. | |
* | |
* @method setProperties | |
* @chainable | |
* @param {Object} properties property dictionary of "key" => "value" | |
*/ | |
Surface.prototype.setProperties = function setProperties(properties) { | |
for (var n in properties) { | |
this.properties[n] = properties[n]; | |
} | |
this._stylesDirty = true; | |
return this; | |
}; | |
/** | |
* Get CSS-style properties on this Surface. | |
* | |
* @method getProperties | |
* | |
* @return {Object} Dictionary of this Surface's properties. | |
*/ | |
Surface.prototype.getProperties = function getProperties() { | |
return this.properties; | |
}; | |
/** | |
* Add CSS-style class to the list of classes on this Surface. Note | |
* this will map directly to the HTML property of the actual | |
* corresponding rendered <div>. | |
* | |
* @method addClass | |
* @chainable | |
* @param {string} className name of class to add | |
*/ | |
Surface.prototype.addClass = function addClass(className) { | |
if (this.classList.indexOf(className) < 0) { | |
this.classList.push(className); | |
this._classesDirty = true; | |
} | |
return this; | |
}; | |
/** | |
* Remove CSS-style class from the list of classes on this Surface. | |
* Note this will map directly to the HTML property of the actual | |
* corresponding rendered <div>. | |
* | |
* @method removeClass | |
* @chainable | |
* @param {string} className name of class to remove | |
*/ | |
Surface.prototype.removeClass = function removeClass(className) { | |
var i = this.classList.indexOf(className); | |
if (i >= 0) { | |
this._dirtyClasses.push(this.classList.splice(i, 1)[0]); | |
this._classesDirty = true; | |
} | |
return this; | |
}; | |
/** | |
* Toggle CSS-style class from the list of classes on this Surface. | |
* Note this will map directly to the HTML property of the actual | |
* corresponding rendered <div>. | |
* | |
* @method toggleClass | |
* @param {string} className name of class to toggle | |
*/ | |
Surface.prototype.toggleClass = function toggleClass(className) { | |
var i = this.classList.indexOf(className); | |
if (i >= 0) { | |
this.removeClass(className); | |
} else { | |
this.addClass(className); | |
} | |
return this; | |
}; | |
/** | |
* Reset class list to provided dictionary. | |
* @method setClasses | |
* @chainable | |
* @param {Array.string} classList | |
*/ | |
Surface.prototype.setClasses = function setClasses(classList) { | |
var i = 0; | |
var removal = []; | |
for (i = 0; i < this.classList.length; i++) { | |
if (classList.indexOf(this.classList[i]) < 0) { removal.push(this.classList[i]); } | |
} | |
for (i = 0; i < removal.length; i++) { this.removeClass(removal[i]); } | |
// duplicates are already checked by addClass() | |
for (i = 0; i < classList.length; i++) { this.addClass(classList[i]); } | |
return this; | |
}; | |
/** | |
* Get array of CSS-style classes attached to this div. | |
* | |
* @method getClasslist | |
* @return {Array.string} array of class names | |
*/ | |
Surface.prototype.getClassList = function getClassList() { | |
return this.classList; | |
}; | |
/** | |
* Set or overwrite inner (HTML) content of this surface. Note that this | |
* causes a re-rendering if the content has changed. | |
* | |
* @method setContent | |
* @chainable | |
* @param {string|Document Fragment} content HTML content | |
*/ | |
Surface.prototype.setContent = function setContent(content) { | |
if (this.content !== content) { | |
this.content = content; | |
this._contentDirty = true; | |
} | |
return this; | |
}; | |
/** | |
* Return inner (HTML) content of this surface. | |
* | |
* @method getContent | |
* | |
* @return {string} inner (HTML) content | |
*/ | |
Surface.prototype.getContent = function getContent() { | |
return this.content; | |
}; | |
/** | |
* Set options for this surface | |
* | |
* @method setOptions | |
* @chainable | |
* @param {Object} [options] overrides for default options. See constructor. | |
*/ | |
Surface.prototype.setOptions = function setOptions(options) { | |
if (options.size) { this.setSize(options.size); } | |
if (options.classes) { this.setClasses(options.classes); } | |
if (options.properties) { this.setProperties(options.properties); } | |
if (options.attributes) { this.setAttributes(options.attributes); } | |
if (options.content) { this.setContent(options.content); } | |
return this; | |
}; | |
// Apply to document all changes from removeClass() since last setup(). | |
function _cleanupClasses(target) { | |
for (var i = 0; i < this._dirtyClasses.length; i++) { target.classList.remove(this._dirtyClasses[i]); } | |
this._dirtyClasses = []; | |
} | |
// Apply values of all Famous-managed styles to the document element. | |
// These will be deployed to the document on call to #setup(). | |
function _applyStyles(target) { | |
for (var n in this.properties) { | |
target.style[n] = this.properties[n]; | |
} | |
} | |
// Clear all Famous-managed styles from the document element. | |
// These will be deployed to the document on call to #setup(). | |
function _cleanupStyles(target) { | |
for (var n in this.properties) { | |
target.style[n] = ''; | |
} | |
} | |
// Apply values of all Famous-managed attributes to the document element. | |
// These will be deployed to the document on call to #setup(). | |
function _applyAttributes(target) { | |
for (var n in this.attributes) { | |
target.setAttribute(n, this.attributes[n]); | |
} | |
} | |
// Clear all Famous-managed attributes from the document element. | |
// These will be deployed to the document on call to #setup(). | |
function _cleanupAttributes(target) { | |
for (var n in this.attributes) { | |
target.removeAttribute(n); | |
} | |
} | |
function _xyNotEquals(a, b) { | |
return (a && b) ? (a[0] !== b[0] || a[1] !== b[1]) : a !== b; | |
} | |
/** | |
* One-time setup for an element to be ready for commits to document. | |
* | |
* @private | |
* @method setup | |
* | |
* @param {ElementAllocator} allocator document element pool for this context | |
*/ | |
Surface.prototype.setup = function setup(allocator) { | |
var target = allocator.allocate(this.elementType); | |
if (this.elementClass) { | |
if (this.elementClass instanceof Array) { | |
for (var i = 0; i < this.elementClass.length; i++) { | |
target.classList.add(this.elementClass[i]); | |
} | |
} | |
else { | |
target.classList.add(this.elementClass); | |
} | |
} | |
target.style.display = ''; | |
this.attach(target); | |
this._opacity = null; | |
this._currentTarget = target; | |
this._stylesDirty = true; | |
this._classesDirty = true; | |
this._attributesDirty = true; | |
this._sizeDirty = true; | |
this._contentDirty = true; | |
this._originDirty = true; | |
this._transformDirty = true; | |
}; | |
/** | |
* Apply changes from this component to the corresponding document element. | |
* This includes changes to classes, styles, size, content, opacity, origin, | |
* and matrix transforms. | |
* | |
* @private | |
* @method commit | |
* @param {Context} context commit context | |
*/ | |
Surface.prototype.commit = function commit(context) { | |
if (!this._currentTarget) { this.setup(context.allocator); } | |
var target = this._currentTarget; | |
var size = context.size; | |
if (this._classesDirty) { | |
_cleanupClasses.call(this, target); | |
var classList = this.getClassList(); | |
for (var i = 0; i < classList.length; i++) { target.classList.add(classList[i]); } | |
this._classesDirty = false; | |
this._trueSizeCheck = true; | |
} | |
if (this._stylesDirty) { | |
_applyStyles.call(this, target); | |
this._stylesDirty = false; | |
this._trueSizeCheck = true; | |
} | |
if (this._attributesDirty) { | |
_applyAttributes.call(this, target); | |
this._attributesDirty = false; | |
this._trueSizeCheck = true; | |
} | |
if (this.size) { | |
var origSize = context.size; | |
size = [this.size[0], this.size[1]]; | |
if (size[0] === undefined) { size[0] = origSize[0]; } | |
if (size[1] === undefined) { size[1] = origSize[1]; } | |
if (size[0] === true || size[1] === true) { | |
if (size[0] === true){ | |
if (this._trueSizeCheck || (this._size[0] === 0)) { | |
var width = target.offsetWidth; | |
if (this._size && this._size[0] !== width) { | |
this._size[0] = width; | |
this._sizeDirty = true; | |
} | |
size[0] = width; | |
} else { | |
if (this._size) { size[0] = this._size[0]; } | |
} | |
} | |
if (size[1] === true){ | |
if (this._trueSizeCheck || (this._size[1] === 0)) { | |
var height = target.offsetHeight; | |
if (this._size && this._size[1] !== height) { | |
this._size[1] = height; | |
this._sizeDirty = true; | |
} | |
size[1] = height; | |
} else { | |
if (this._size) { size[1] = this._size[1]; } | |
} | |
} | |
this._trueSizeCheck = false; | |
} | |
} | |
if (_xyNotEquals(this._size, size)) { | |
if (!this._size) { this._size = [0, 0]; } | |
this._size[0] = size[0]; | |
this._size[1] = size[1]; | |
this._sizeDirty = true; | |
} | |
if (this._sizeDirty) { | |
if (this._size) { | |
target.style.width = (this.size && this.size[0] === true) ? '' : this._size[0] + 'px'; | |
target.style.height = (this.size && this.size[1] === true) ? '' : this._size[1] + 'px'; | |
} | |
this._eventOutput.emit('resize'); | |
} | |
if (this._contentDirty) { | |
this.deploy(target); | |
this._eventOutput.emit('deploy'); | |
this._contentDirty = false; | |
this._trueSizeCheck = true; | |
} | |
ElementOutput_1.prototype.commit.call(this, context); | |
}; | |
/** | |
* Remove all Famous-relevant attributes from a document element. | |
* This is called by SurfaceManager's detach(). | |
* This is in some sense the reverse of .deploy(). | |
* | |
* @private | |
* @method cleanup | |
* @param {ElementAllocator} allocator | |
*/ | |
Surface.prototype.cleanup = function cleanup(allocator) { | |
var i = 0; | |
var target = this._currentTarget; | |
this._eventOutput.emit('recall'); | |
this.recall(target); | |
target.style.display = 'none'; | |
target.style.opacity = ''; | |
target.style.width = ''; | |
target.style.height = ''; | |
_cleanupStyles.call(this, target); | |
_cleanupAttributes.call(this, target); | |
var classList = this.getClassList(); | |
_cleanupClasses.call(this, target); | |
for (i = 0; i < classList.length; i++) { target.classList.remove(classList[i]); } | |
if (this.elementClass) { | |
if (this.elementClass instanceof Array) { | |
for (i = 0; i < this.elementClass.length; i++) { | |
target.classList.remove(this.elementClass[i]); | |
} | |
} | |
else { | |
target.classList.remove(this.elementClass); | |
} | |
} | |
this.detach(target); | |
this._currentTarget = null; | |
allocator.deallocate(target); | |
}; | |
/** | |
* Place the document element that this component manages into the document. | |
* | |
* @private | |
* @method deploy | |
* @param {Node} target document parent of this container | |
*/ | |
Surface.prototype.deploy = function deploy(target) { | |
var content = this.getContent(); | |
if (content instanceof Node) { | |
while (target.hasChildNodes()) { target.removeChild(target.firstChild); } | |
target.appendChild(content); | |
} | |
else { target.innerHTML = content; } | |
}; | |
/** | |
* Remove any contained document content associated with this surface | |
* from the actual document. | |
* | |
* @private | |
* @method recall | |
*/ | |
Surface.prototype.recall = function recall(target) { | |
var df = document.createDocumentFragment(); | |
while (target.hasChildNodes()) { df.appendChild(target.firstChild); } | |
this.setContent(df); | |
}; | |
/** | |
* Get the x and y dimensions of the surface. | |
* | |
* @method getSize | |
* @return {Array.Number} [x,y] size of surface | |
*/ | |
Surface.prototype.getSize = function getSize() { | |
return this._size ? this._size : this.size; | |
}; | |
/** | |
* Set x and y dimensions of the surface. | |
* | |
* @method setSize | |
* @chainable | |
* @param {Array.Number} size as [width, height] | |
*/ | |
Surface.prototype.setSize = function setSize(size) { | |
this.size = size ? [size[0], size[1]] : null; | |
this._sizeDirty = true; | |
return this; | |
}; | |
var Surface_1 = Surface; | |
/* | |
* LICENSE | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
*/ | |
/** | |
* Planes have the properties of [Molecules](#Molecule), plus they contain a | |
* [famous/src/core/Surface](#famous/src/core/Surface) so that they ultimately render | |
* onto the screen. A Surface's events are automatically piped to it's | |
* [famous/src/core/EventHandler](#famous/src/core/EventHandler), inherited from | |
* `Molecule`. | |
* | |
* @class Plane | |
* @extends Molecule | |
*/ | |
class Plane extends Molecule { | |
/** | |
* Creates a new `Plane`. Properties from the `initialOptions` parameter | |
* are applied to this Plane's [famous/src/core/Surface](#famous/src/core/Surface) as well as to | |
* to this Plane's [famous/src/core/Modifier](#famous/src/core/Modifier), hence the API of a Plane | |
* is currently the combination of the Famo.us `Modifier` and `Surface` APIs. | |
* | |
* @constructor | |
* @param {Object} initialOptions Options for the new Plane. | |
*/ | |
constructor(initialOptions) { | |
super(initialOptions); | |
this.surface = new Surface_1(this.options); | |
this.add(this.surface); | |
this.surface.pipe(this.options.handler); | |
} | |
/** | |
* Get the content of this Plane's [famous/src/core/Surface](#famous/src/core/Surface). | |
* See [famous/src/core/Surface.getContent](#famous/src/core/Surface.getContent). | |
*/ | |
getContent() { | |
const args = Array.prototype.splice.call(arguments, 0); | |
return this.surface.getContent.apply(this.surface, args); | |
} | |
/** | |
* Set the content of this Plane's [famous/src/core/Surface](#famous/src/core/Surface). | |
* See [famous/src/core/Surface.setContent](#famous/src/core/Surface.setContent). | |
*/ | |
setContent() { | |
const args = Array.prototype.splice.call(arguments, 0); | |
return this.surface.setContent.apply(this.surface, args); | |
} | |
} | |
/* | |
* LICENSE | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
*/ | |
/** | |
* A scenegraph tree who's two leaf nodes are [Plane](#Plane) instances facing | |
* opposite directions. For the purposes of these docs, in a brand new app with | |
* only a single `DoubleSidedPlane` added to the context, and having no | |
* rotation, "plane1" faces you and "plane2" faces away. | |
* | |
* @class DoubleSidedPlane | |
* @extends Molecule | |
*/ | |
class DoubleSidedPlane extends Molecule { | |
/** | |
* Creates a new `DoubleSidedPlane` who's `initialOptions` get passed to | |
* both [Plane](#Plane) instances, as well as this DoubleSidedPlane's parent | |
* [Molecule](#Molecule) constructor. | |
* | |
* @constructor | |
* @param {Object} initialOptions The options to initiate the `DoubleSidedPlane` with. | |
*/ | |
constructor(initialOptions) { | |
super(initialOptions); | |
this.children = []; | |
this.plane1 = new Plane(this.options); | |
this.plane1.transform.set(Transform_1.rotate(0,0,0)); | |
this.setOptions({properties: {background: 'orange'}}); | |
this.plane2 = new Plane(this.options); | |
this.plane2.transform.set(Transform_1.rotate(0,Math.PI,0)); | |
this.children.push(this.plane1); | |
this.children.push(this.plane2); | |
this.add(this.plane2); | |
this.add(this.plane1); | |
this.plane1.pipe(this.options.handler); | |
this.plane2.pipe(this.options.handler); | |
} | |
/** | |
* Get the content of the [famous/src/core/Surface](#famous/src/core/Surface) of each [Plane](#Plane). | |
* | |
* @returns {Array} An array containing two items, the content of each | |
* `Plane`. The first item is from "plane1". | |
*/ | |
getContent() { | |
return [this.plane1.getContent(), this.plane2.getContent()]; | |
} | |
/** | |
* Set the content of both [Plane](#Plane) instances. | |
* | |
* @param {Array} content An array of content, one item per `Plane`. The | |
* first item is for "plane1". | |
*/ | |
setContent(content) { | |
this.plane1.setContent(content[0]); | |
this.plane2.setContent(content[1]); | |
} | |
} | |
/* | |
* LICENSE | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
*/ | |
/** | |
* A calendar widget for selecting a date (WIP). | |
* | |
* @class Calendar | |
* @extends Molecule | |
*/ | |
class Calendar extends Molecule { | |
/** | |
* Create a new `Calendar` with the given Famo.us-style size array and | |
* transition. The transition is the type of animation used when switching | |
* between months. | |
* | |
* @constructor | |
* @param {Array} calendarSize A Famo.us-style width/height size array. | |
* @param {String} transition The name of the animation transition to use when switching months. | |
*/ | |
constructor(calendarSize, transition) { | |
super({size: calendarSize}); | |
this.transition = transition; | |
this.flipSide = 0; // 0 means the initial front faces are showing, 1 means the initial back faces are showing. | |
this.columnsRows = [7,6]; | |
this.planes = []; | |
this._initializeTransitions(); | |
this._createGrid(); | |
setTimeout( function() { | |
this.transitions[this.transition](); | |
setInterval(this.transitions[this.transition], 2000); | |
}.bind(this) , 800); | |
} | |
/** | |
* Creates the grid used for the layout of the day cells. | |
* | |
* @private | |
*/ | |
_createGrid() { | |
const grid = new Grid(this.columnsRows[0], this.columnsRows[1], this.options.size); | |
forLength(this.columnsRows[0]*this.columnsRows[1], function(i) { | |
const plane = new DoubleSidedPlane({ | |
properties: { | |
background: 'teal', | |
} | |
}); | |
this.planes.push(plane); | |
}.bind(this)); | |
grid.setChildren(this.planes); | |
this.add(grid); | |
} | |
/** | |
* Set up `this.transitions`, containing the available month-to-month | |
* transitions. | |
* | |
* @private | |
*/ | |
_initializeTransitions() { | |
this.transitions = { | |
flipDiagonal: function() { | |
this.flipSide = +!this.flipSide; | |
// determine which dimension of the grid is shorter and which is longer. | |
let shortest = 0; | |
let longest; | |
this.columnsRows.forEach(function(item, index) { | |
if (item < this.columnsRows[shortest]) | |
{ shortest = index; } | |
}.bind(this)); | |
longest = +!shortest; | |
// for each diagonal of the grid, flip those cells. | |
forLength(this.columnsRows[0]+this.columnsRows[1]-1, function(column) { | |
forLength(this.columnsRows[shortest], function(row) { | |
if (column-row >= 0 && column-row < this.columnsRows[longest]) { | |
const plane = this.planes[column-row + this.columnsRows[longest]*row]; | |
flipOne(plane, column); | |
} | |
}.bind(this)); | |
}.bind(this)); | |
function flipOne(item, column) { | |
if (typeof item.__targetRotation == 'undefined') { | |
item.__targetRotation = new Transitionable_1(0); | |
} | |
const rotation = new Transitionable_1(item.__targetRotation.get()); | |
item.__targetRotation.set(item.__targetRotation.get()+Math.PI); | |
//item.get().transformFrom(function() { | |
//return Transform.rotateY(rotation.get()); | |
//}); | |
item.children[0].get().transformFrom(function() { | |
return Transform_1.rotateY(rotation.get()); | |
}); | |
item.children[1].get().transformFrom(function() { | |
return Transform_1.rotateY(rotation.get()+Math.PI); | |
}); | |
setTimeout(function() { | |
rotation.set(item.__targetRotation.get(), { duration: 2000, curve: Easing_1.outExpo }); | |
}, 0+50*column); | |
} | |
}.bind(this) | |
}; | |
} | |
} | |
var utils$1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports.clone = clone; | |
exports.isEmptyObject = isEmptyObject; | |
exports.toCSS = toCSS; | |
var stringify = JSON.stringify; | |
var parse = JSON.parse; | |
/** | |
* Deeply clone object using serialization. | |
* Expects object to be plain and without cyclic dependencies. | |
* | |
* http://jsperf.com/lodash-deepclone-vs-jquery-extend-deep/6 | |
* | |
* @type {Object} obj | |
* @return {Object} | |
*/ | |
function clone(obj) { | |
return parse(stringify(obj)); | |
} | |
/** | |
* Determine whether an object is empty or not. | |
* More performant than a `Object.keys(obj).length > 0` | |
* | |
* @type {Object} obj | |
* @return {Boolean} | |
*/ | |
function isEmptyObject(obj) { | |
for (var key in obj) { | |
return false; | |
} // eslint-disable-line no-unused-vars | |
return true; | |
} | |
/** | |
* Simple very fast UID generation based on a global counter. | |
*/ | |
var uid = exports.uid = function () { | |
var globalReference = typeof window == 'undefined' ? commonjsGlobal : window; | |
var namespace = '__JSS_VERSION_COUNTER__'; | |
if (globalReference[namespace] == null) { globalReference[namespace] = 0; } | |
// In case we have more than one jss version. | |
var versionCounter = globalReference[namespace]++; | |
var ruleCounter = 0; | |
/** | |
* Returns a uid. | |
* Ensures uniqueness if more than 1 jss version is used. | |
* | |
* @api public | |
* @return {String} | |
*/ | |
function get() { | |
return 'jss-' + versionCounter + '-' + ruleCounter++; | |
} | |
/** | |
* Resets the counter. | |
* | |
* @api public | |
*/ | |
function reset() { | |
ruleCounter = 0; | |
} | |
return { get: get, reset: reset }; | |
}(); | |
/** | |
* Indent a string. | |
* | |
* http://jsperf.com/array-join-vs-for | |
* | |
* @param {Number} level | |
* @param {String} str | |
* @return {String} | |
*/ | |
function indent(level, str) { | |
var indentStr = ''; | |
for (var index = 0; index < level; index++) { | |
indentStr += ' '; | |
}return indentStr + str; | |
} | |
/** | |
* Converts a Rule to CSS string. | |
* | |
* Options: | |
* - `selector` use `false` to get a rule without selector | |
* - `indentationLevel` level of indentation | |
* | |
* @param {String} selector | |
* @param {Object} style | |
* @param {Object} options | |
* @return {String} | |
*/ | |
function toCSS(selector, style) { | |
var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; | |
var indentationLevel = options.indentationLevel || 0; | |
var str = ''; | |
if (options.selector !== false) { | |
str += indent(indentationLevel, selector + ' {'); | |
indentationLevel++; | |
} | |
for (var prop in style) { | |
var value = style[prop]; | |
// We want to generate multiple style with identical property names. | |
if (Array.isArray(value)) { | |
for (var index = 0; index < value.length; index++) { | |
str += '\n' + indent(indentationLevel, prop + ': ' + value[index] + ';'); | |
} | |
} else { str += '\n' + indent(indentationLevel, prop + ': ' + value + ';'); } | |
} | |
if (options.selector !== false) { str += '\n' + indent(--indentationLevel, '}'); } | |
return str; | |
} | |
/** | |
* Get class names from a selector. | |
* | |
* @param {String} selector | |
* @return {String} | |
*/ | |
var findClassNames = exports.findClassNames = function () { | |
var dotsRegExp = /[.]/g; | |
var classesRegExp = /[.][^ ,]+/g; | |
return function (selector) { | |
var classes = selector.match(classesRegExp); | |
if (!classes) { return ''; } | |
return classes.join(' ').replace(dotsRegExp, ''); | |
}; | |
}(); | |
}); | |
var Rule_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Regular rules. | |
* | |
* @api public | |
*/ | |
var Rule = function () { | |
function Rule(selector, style, options) { | |
_classCallCheck(this, Rule); | |
this.id = utils$1.uid.get(); | |
this.type = 'regular'; | |
this.options = options; | |
this.selectorText = selector || ''; | |
this.className = options.className || ''; | |
this.originalStyle = style; | |
// We expect style to be plain object. | |
this.style = (0, utils$1.clone)(style); | |
if (options.named) { | |
this.name = selector; | |
if (!this.className) { | |
this.className = this.name ? this.name + '--' + this.id : this.id; | |
} | |
this.selectorText = '.' + this.className; | |
} | |
} | |
/** | |
* Set selector string. | |
* Attenition: use this with caution. Most browser didn't implement selector | |
* text setter, so this will result in rerendering of entire style sheet. | |
* | |
* @param {String} selector | |
* @api public | |
*/ | |
_createClass(Rule, [{ | |
key: 'prop', | |
/** | |
* Get or set a style property. | |
* | |
* @param {String} name | |
* @param {String|Number} [value] | |
* @return {Rule|String|Number} | |
* @api public | |
*/ | |
value: function prop(name, value) { | |
var style = this.options.Renderer.style; | |
// Its a setter. | |
if (value != null) { | |
this.style[name] = value; | |
// Only defined if option linked is true. | |
if (this.renderable) { style(this.renderable, name, value); } | |
return this; | |
} | |
// Its a getter, read the value from the DOM if its not cached. | |
if (this.renderable && this.style[name] == null) { | |
// Cache the value after we have got it from the DOM once. | |
this.style[name] = style(this.renderable, name); | |
} | |
return this.style[name]; | |
} | |
/** | |
* Apply rule to an element inline. | |
* | |
* @param {Element} renderable | |
* @return {Rule} | |
* @api public | |
*/ | |
}, { | |
key: 'applyTo', | |
value: function applyTo(renderable) { | |
for (var prop in this.style) { | |
var value = this.style[prop]; | |
var style = this.options.Renderer.style; | |
if (Array.isArray(value)) { | |
for (var index = 0; index < value.length; index++) { | |
style(renderable, prop, value[index]); | |
} | |
} else { style(renderable, prop, value); } | |
} | |
return this; | |
} | |
/** | |
* Returns JSON representation of the rule. | |
* Array of values is not supported. | |
* | |
* @return {Object} | |
* @api public | |
*/ | |
}, { | |
key: 'toJSON', | |
value: function toJSON() { | |
var style = Object.create(null); | |
for (var prop in this.style) { | |
if (_typeof(this.style[prop]) != 'object') { | |
style[prop] = this.style[prop]; | |
} | |
} | |
return style; | |
} | |
/** | |
* Generates a CSS string. | |
* | |
* @see toCSS | |
* @api public | |
*/ | |
}, { | |
key: 'toString', | |
value: function toString(options) { | |
return (0, utils$1.toCSS)(this.selector, this.style, options); | |
} | |
}, { | |
key: 'selector', | |
set: function set() { | |
var selector = arguments.length <= 0 || arguments[0] === undefined ? '' : arguments[0]; | |
var _options = this.options; | |
var Renderer = _options.Renderer; | |
var sheet = _options.sheet; | |
// After we modify selector, ref by old selector needs to be removed. | |
if (sheet) { sheet.unregisterRule(this); } | |
this.selectorText = selector; | |
this.className = (0, utils$1.findClassNames)(selector); | |
if (!this.renderable) { | |
// Register the rule with new selector. | |
if (sheet) { sheet.registerRule(this); } | |
return; | |
} | |
var changed = Renderer.setSelector(this.renderable, selector); | |
if (changed) { | |
sheet.registerRule(this); | |
return; | |
} | |
// If selector setter is not implemented, rerender the sheet. | |
// We need to delete renderable from the rule, because when sheet.deploy() | |
// calls rule.toString, it will get the old selector. | |
delete this.renderable; | |
sheet.registerRule(this).deploy().link(); | |
} | |
/** | |
* Get selector string. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
, | |
get: function get() { | |
if (this.renderable) { | |
return this.options.Renderer.getSelector(this.renderable); | |
} | |
return this.selectorText; | |
} | |
}]); | |
return Rule; | |
}(); | |
exports.default = Rule; | |
}); | |
var SimpleRule_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Rule like @charset, @import, @namespace. | |
* | |
* @api public | |
*/ | |
var SimpleRule = function () { | |
function SimpleRule(name, value, options) { | |
_classCallCheck(this, SimpleRule); | |
this.id = utils$1.uid.get(); | |
this.type = 'simple'; | |
this.name = name; | |
this.value = value; | |
this.options = options; | |
} | |
/** | |
* Generates a CSS string. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
_createClass(SimpleRule, [{ | |
key: 'toString', | |
value: function toString() { | |
if (Array.isArray(this.value)) { | |
var str = ''; | |
for (var index = 0; index < this.value.length; index++) { | |
str += this.name + ' ' + this.value[index] + ';'; | |
if (this.value[index + 1]) { str += '\n'; } | |
} | |
return str; | |
} | |
return this.name + ' ' + this.value + ';'; | |
} | |
}]); | |
return SimpleRule; | |
}(); | |
exports.default = SimpleRule; | |
}); | |
var KeyframeRule_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Keyframe rule. | |
* | |
* @api private | |
*/ | |
var KeyframeRule = function () { | |
function KeyframeRule(selector, frames, options) { | |
_classCallCheck(this, KeyframeRule); | |
this.id = utils$1.uid.get(); | |
this.type = 'keyframe'; | |
this.selector = selector; | |
this.options = options; | |
this.frames = this.formatFrames(frames); | |
} | |
/** | |
* Creates formatted frames where every frame value is a rule instance. | |
* | |
* @api private | |
*/ | |
_createClass(KeyframeRule, [{ | |
key: 'formatFrames', | |
value: function formatFrames(frames) { | |
var newFrames = Object.create(null); | |
for (var name in frames) { | |
var options = _extends({}, this.options, { named: false, parent: this }); | |
newFrames[name] = this.options.jss.createRule(name, frames[name], options); | |
} | |
return newFrames; | |
} | |
/** | |
* Generates a CSS string. | |
* | |
* @return {String} | |
* @api private | |
*/ | |
}, { | |
key: 'toString', | |
value: function toString() { | |
var str = this.selector + ' {\n'; | |
var options = { indentationLevel: 1 }; | |
for (var name in this.frames) { | |
str += this.frames[name].toString(options) + '\n'; | |
} | |
str += '}'; | |
return str; | |
} | |
}]); | |
return KeyframeRule; | |
}(); | |
exports.default = KeyframeRule; | |
}); | |
var ConditionalRule_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Conditional rule for @media, @supports | |
* | |
* @api public | |
*/ | |
var ConditionalRule = function () { | |
function ConditionalRule(selector, styles, options) { | |
_classCallCheck(this, ConditionalRule); | |
this.id = utils$1.uid.get(); | |
this.type = 'conditional'; | |
this.selector = selector; | |
this.options = options; | |
this.rules = Object.create(null); | |
for (var name in styles) { | |
this.createRule(name, styles[name]); | |
} | |
} | |
/** | |
* A conditional rule always contains child rules. | |
* | |
* @param {Object} styles | |
* @return {Array} rules | |
* @api public | |
*/ | |
_createClass(ConditionalRule, [{ | |
key: 'createRule', | |
value: function createRule(name, style, options) { | |
var newOptions = _extends({}, this.options, { parent: this }); | |
var _newOptions = newOptions; | |
var sheet = _newOptions.sheet; | |
var jss = _newOptions.jss; | |
// We have already a rule in the current style sheet with this name, | |
// This new rule is supposed to overwrite the first one, for this we need | |
// to ensure it will have the same className/selector. | |
var existingRule = sheet && sheet.getRule(name); | |
var className = existingRule ? existingRule.className : null; | |
if (className || options) { | |
newOptions = _extends({}, newOptions, { className: className }, options); | |
} | |
var rule = (sheet || jss).createRule(name, style, newOptions); | |
this.rules[name] = rule; | |
return rule; | |
} | |
/** | |
* Generates a CSS string. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
}, { | |
key: 'toString', | |
value: function toString() { | |
var str = this.selector + ' {\n'; | |
for (var name in this.rules) { | |
var rule = this.rules[name]; | |
if (rule.style && (0, utils$1.isEmptyObject)(rule.style)) { | |
continue; | |
} | |
var ruleStr = rule.toString({ indentationLevel: 1 }); | |
str += ruleStr + '\n'; | |
} | |
str += '}'; | |
return str; | |
} | |
}]); | |
return ConditionalRule; | |
}(); | |
exports.default = ConditionalRule; | |
}); | |
var FontFaceRule = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Font-face rules. | |
* | |
* @api public | |
*/ | |
var Rule = function () { | |
function Rule(selector, style, options) { | |
_classCallCheck(this, Rule); | |
this.id = utils$1.uid.get(); | |
this.type = 'font-face'; | |
this.options = options; | |
this.selector = selector; | |
this.style = style; | |
} | |
/** | |
* Generates a CSS string. | |
* | |
* @see toCSS | |
* @api public | |
*/ | |
_createClass(Rule, [{ | |
key: 'toString', | |
value: function toString(options) { | |
if (Array.isArray(this.style)) { | |
var str = ''; | |
for (var index = 0; index < this.style.length; index++) { | |
str += (0, utils$1.toCSS)(this.selector, this.style[index], options); | |
if (this.style[index + 1]) { str += '\n'; } | |
} | |
return str; | |
} | |
return (0, utils$1.toCSS)(this.selector, this.style, options); | |
} | |
}]); | |
return Rule; | |
}(); | |
exports.default = Rule; | |
}); | |
var createRule_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports.default = createRule; | |
var _Rule2 = _interopRequireDefault(Rule_1); | |
var _SimpleRule2 = _interopRequireDefault(SimpleRule_1); | |
var _KeyframeRule2 = _interopRequireDefault(KeyframeRule_1); | |
var _ConditionalRule2 = _interopRequireDefault(ConditionalRule_1); | |
var _FontFaceRule2 = _interopRequireDefault(FontFaceRule); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
/** | |
* Map of at rules to corresponding implementation class. | |
* | |
* @type {Object} | |
*/ | |
var atRuleClassMap = { | |
'@charset': _SimpleRule2.default, | |
'@import': _SimpleRule2.default, | |
'@namespace': _SimpleRule2.default, | |
'@keyframes': _KeyframeRule2.default, | |
'@media': _ConditionalRule2.default, | |
'@supports': _ConditionalRule2.default, | |
'@font-face': _FontFaceRule2.default | |
}; | |
var atRuleNameRegExp = /^@[^ ]+/; | |
/** | |
* Create rule factory. | |
* | |
* @param {Object} [selector] if you don't pass selector - it will be generated | |
* @param {Object} [style] declarations block | |
* @param {Object} [options] rule options | |
* @return {Object} rule | |
* @api private | |
*/ | |
function createRule(selector) { | |
var style = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | |
var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; | |
// Is an at-rule. | |
if (selector && selector[0] === '@') { | |
var name = atRuleNameRegExp.exec(selector)[0]; | |
var AtRule = atRuleClassMap[name]; | |
return new AtRule(selector, style, options); | |
} | |
if (options.named == null) { options.named = true; } | |
return new _Rule2.default(selector, style, options); | |
} | |
}); | |
var DomRenderer_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* DOM rendering backend for StyleSheet. | |
* | |
* @api private | |
*/ | |
var DomRenderer = function () { | |
_createClass(DomRenderer, null, [{ | |
key: 'style', | |
value: function style(element, name, value) { | |
try { | |
if (value == null) { return element.style[name]; } | |
element.style[name] = value; | |
} catch (err) { | |
// IE8 may throw if property is unknown. | |
return false; | |
} | |
return true; | |
} | |
}, { | |
key: 'setSelector', | |
value: function setSelector(cssRule, selector) { | |
cssRule.selectorText = selector; | |
// Return false if setter was not successful. | |
// Currently works in chrome only. | |
return cssRule.selectorText === selector; | |
} | |
}, { | |
key: 'getSelector', | |
value: function getSelector(cssRule) { | |
return cssRule.selectorText; | |
} | |
}]); | |
function DomRenderer(options) { | |
_classCallCheck(this, DomRenderer); | |
this.head = document.head || document.getElementsByTagName('head')[0]; | |
this.element = options.element || document.createElement('style'); | |
// IE8 will not have `styleSheet` prop without `type and `styleSheet.cssText` | |
// is the only way to render on IE8. | |
this.element.type = 'text/css'; | |
if (options.media) { this.element.setAttribute('media', options.media); } | |
if (options.meta) { this.element.setAttribute('data-meta', options.meta); } | |
} | |
/** | |
* Insert style element into render tree. | |
* | |
* @api private | |
*/ | |
_createClass(DomRenderer, [{ | |
key: 'attach', | |
value: function attach() { | |
if (this.element.parendNode) { return; } | |
this.head.appendChild(this.element); | |
} | |
/** | |
* Remove style element from render tree. | |
* | |
* @api private | |
*/ | |
}, { | |
key: 'detach', | |
value: function detach() { | |
this.element.parentNode.removeChild(this.element); | |
} | |
/** | |
* Inject CSS string into element. | |
* | |
* @param {String} cssStr | |
* @api private | |
*/ | |
}, { | |
key: 'deploy', | |
value: function deploy(sheet) { | |
var css = '\n' + sheet.toString() + '\n'; | |
if ('sheet' in this.element) { this.element.innerHTML = css; } | |
// On IE8 the only way to render is `styleSheet.cssText`. | |
else if ('styleSheet' in this.element) { this.element.styleSheet.cssText = css; } | |
} | |
/** | |
* Insert a rule into element. | |
* | |
* @param {Rule} rule | |
* @return {CSSStyleRule} | |
* @api private | |
*/ | |
}, { | |
key: 'insertRule', | |
value: function insertRule(rule) { | |
// IE8 has only `styleSheet` and `styleSheet.rules` | |
var sheet = this.element.sheet || this.element.styleSheet; | |
var cssRules = sheet.cssRules || sheet.rules; | |
var nextIndex = cssRules.length; | |
if (sheet.insertRule) { sheet.insertRule(rule.toString(), nextIndex); }else { sheet.addRule(rule.selector, rule.toString({ selector: false }), nextIndex); } | |
return cssRules[nextIndex]; | |
} | |
/** | |
* Get all rules elements. | |
* | |
* @return {Object} rules map, where key is selector, CSSStyleRule is value. | |
* @api private | |
*/ | |
}, { | |
key: 'getRules', | |
value: function getRules() { | |
// IE8 has only `styleSheet` and `styleSheet.rules` | |
var sheet = this.element.sheet || this.element.styleSheet; | |
var cssRules = sheet.rules || sheet.cssRules; | |
var rules = Object.create(null); | |
for (var index = 0; index < cssRules.length; index++) { | |
var cssRule = cssRules[index]; | |
rules[cssRule.selectorText] = cssRule; | |
} | |
return rules; | |
} | |
}]); | |
return DomRenderer; | |
}(); | |
exports.default = DomRenderer; | |
}); | |
var VirtualRenderer_1 = createCommonjsModule(function (module, exports) { | |
"use strict"; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Rendering backend to do nothing in nodejs. | |
*/ | |
var VirtualRenderer = function () { | |
function VirtualRenderer() { | |
_classCallCheck(this, VirtualRenderer); | |
} | |
_createClass(VirtualRenderer, [{ | |
key: "attach", | |
value: function attach() {} | |
}, { | |
key: "detach", | |
value: function detach() {} | |
}, { | |
key: "deploy", | |
value: function deploy() {} | |
}, { | |
key: "insertRule", | |
value: function insertRule() {} | |
}, { | |
key: "getRules", | |
value: function getRules() { | |
return {}; | |
} | |
}], [{ | |
key: "style", | |
value: function style() {} | |
}, { | |
key: "setSelector", | |
value: function setSelector() {} | |
}, { | |
key: "getSelector", | |
value: function getSelector() {} | |
}]); | |
return VirtualRenderer; | |
}(); | |
exports.default = VirtualRenderer; | |
}); | |
var findRenderer_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports.default = findRenderer; | |
var _DomRenderer2 = _interopRequireDefault(DomRenderer_1); | |
var _VirtualRenderer2 = _interopRequireDefault(VirtualRenderer_1); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
/** | |
* Find proper renderer. | |
* Option `virtual` is used to force use of VirtualRenderer even if DOM is | |
* detected, used for testing only. | |
* | |
* @param {Object} options | |
* @return {Renderer} | |
* @api private | |
*/ | |
function findRenderer() { | |
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | |
if (options.Renderer) { return options.Renderer; } | |
return options.virtual || typeof document == 'undefined' ? _VirtualRenderer2.default : _DomRenderer2.default; | |
} | |
}); | |
var StyleSheet_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
var _createRule3 = _interopRequireDefault(createRule_1); | |
var _findRenderer2 = _interopRequireDefault(findRenderer_1); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* StyleSheet model. | |
* | |
* Options: | |
* | |
* - `media` media query - attribute of style element. | |
* - `meta` meta information about this style - attribute of style element, for e.g. you could pass | |
* component name for easier debugging. | |
* - `named` true by default - keys are names, selectors will be generated, if false - keys are | |
* global selectors. | |
* - `link` link jss `Rule` instances with DOM `CSSRule` instances so that styles, can be modified | |
* dynamically, false by default because it has some performance cost. | |
* - `element` style element, will create one by default | |
* | |
* @param {Object} [rules] object with selectors and declarations | |
* @param {Object} [options] | |
* @api public | |
*/ | |
var StyleSheet = function () { | |
function StyleSheet(rules, options) { | |
_classCallCheck(this, StyleSheet); | |
this.options = _extends({}, options); | |
if (this.options.named == null) { this.options.named = true; } | |
this.rules = Object.create(null); | |
this.classes = Object.create(null); | |
this.attached = false; | |
this.deployed = false; | |
this.linked = false; | |
var Renderer = (0, _findRenderer2.default)(this.options); | |
this.options.Renderer = Renderer; | |
this.renderer = new Renderer(this.options); | |
for (var name in rules) { | |
this.createRule(name, rules[name]); | |
} | |
} | |
/** | |
* Attach renderable to the render tree. | |
* | |
* @api public | |
* @return {StyleSheet} | |
*/ | |
_createClass(StyleSheet, [{ | |
key: 'attach', | |
value: function attach() { | |
if (this.attached) { return this; } | |
if (!this.deployed) { this.deploy(); } | |
this.renderer.attach(); | |
if (!this.linked && this.options.link) { this.link(); } | |
this.attached = true; | |
return this; | |
} | |
/** | |
* Remove renderable from render tree. | |
* | |
* @return {StyleSheet} | |
* @api public | |
*/ | |
}, { | |
key: 'detach', | |
value: function detach() { | |
if (!this.attached) { return this; } | |
this.renderer.detach(); | |
this.attached = false; | |
return this; | |
} | |
/** | |
* Add a rule to the current stylesheet. Will insert a rule also after the stylesheet | |
* has been rendered first time. | |
* | |
* @param {Object} [name] can be selector or name if ´options.named is true | |
* @param {Object} style property/value hash | |
* @return {Rule} | |
* @api public | |
*/ | |
}, { | |
key: 'addRule', | |
value: function addRule(name, style) { | |
var rule = this.createRule(name, style); | |
// Don't insert rule directly if there is no stringified version yet. | |
// It will be inserted all together when .attach is called. | |
if (this.deployed) { | |
var renderable = this.renderer.insertRule(rule); | |
if (this.options.link) { rule.renderable = renderable; } | |
} | |
return rule; | |
} | |
/** | |
* Create rules, will render also after stylesheet was rendered the first time. | |
* | |
* @param {Object} rules name:style hash. | |
* @return {Array} array of added rules | |
* @api public | |
*/ | |
}, { | |
key: 'addRules', | |
value: function addRules(rules) { | |
var added = []; | |
for (var name in rules) { | |
added.push(this.addRule(name, rules[name])); | |
} | |
return added; | |
} | |
/** | |
* Get a rule. | |
* | |
* @param {String} name can be selector or name if `named` option is true. | |
* @return {Rule} | |
* @api public | |
*/ | |
}, { | |
key: 'getRule', | |
value: function getRule(name) { | |
return this.rules[name]; | |
} | |
/** | |
* Convert rules to a CSS string. | |
* | |
* @param {Object} options | |
* @return {String} | |
* @api public | |
*/ | |
}, { | |
key: 'toString', | |
value: function toString(options) { | |
var rules = this.rules; | |
var stringified = Object.create(null); | |
var str = ''; | |
for (var name in rules) { | |
var rule = rules[name]; | |
// We have the same rule referenced twice if using named rules. | |
// By name and by selector. | |
if (stringified[rule.id]) { | |
continue; | |
} | |
if (rule.style && (0, utils$1.isEmptyObject)(rule.style)) { | |
continue; | |
} | |
if (rule.rules && (0, utils$1.isEmptyObject)(rule.rules)) { | |
continue; | |
} | |
if (str) { str += '\n'; } | |
str += rule.toString(options); | |
stringified[rule.id] = true; | |
} | |
return str; | |
} | |
/** | |
* Create a rule, will not render after stylesheet was rendered the first time. | |
* Will link the rule in `this.rules`. | |
* | |
* @see createRule | |
* @api private | |
*/ | |
}, { | |
key: 'createRule', | |
value: function createRule(name, style, options) { | |
options = _extends({}, options, { | |
sheet: this, | |
jss: this.options.jss, | |
Renderer: this.options.Renderer | |
}); | |
// Scope options overwrite instance options. | |
if (options.named == null) { options.named = this.options.named; } | |
var rule = (0, _createRule3.default)(name, style, options); | |
this.registerRule(rule); | |
options.jss.plugins.run(rule); | |
return rule; | |
} | |
/** | |
* Register a rule in `sheet.rules` and `sheet.classes` maps. | |
* | |
* @param {Rule} rule | |
* @api public | |
*/ | |
}, { | |
key: 'registerRule', | |
value: function registerRule(rule) { | |
// Children of container rules should not be registered. | |
if (rule.options.parent) { | |
// We need to register child rules of conditionals in classes, otherwise | |
// user can't access generated class name if it doesn't overrides | |
// a regular rule. | |
if (rule.name && rule.className) { | |
this.classes[rule.name] = rule.className; | |
} | |
return this; | |
} | |
if (rule.name) { | |
this.rules[rule.name] = rule; | |
if (rule.className) { this.classes[rule.name] = rule.className; } | |
} | |
if (rule.selector) { | |
this.rules[rule.selector] = rule; | |
} | |
return this; | |
} | |
/** | |
* Unregister a rule. | |
* | |
* @param {Rule} rule | |
* @api public | |
*/ | |
}, { | |
key: 'unregisterRule', | |
value: function unregisterRule(rule) { | |
// Children of a conditional rule are not registered. | |
if (!rule.options.parent) { | |
delete this.rules[rule.name]; | |
delete this.rules[rule.selector]; | |
} | |
delete this.classes[rule.name]; | |
return this; | |
} | |
/** | |
* Deploy pure CSS string to a renderable. | |
* | |
* @return {StyleSheet} | |
* @api private | |
*/ | |
}, { | |
key: 'deploy', | |
value: function deploy() { | |
this.renderer.deploy(this); | |
this.deployed = true; | |
return this; | |
} | |
/** | |
* Link renderable CSS rules with their corresponding models. | |
* | |
* @return {StyleSheet} | |
* @api private | |
*/ | |
}, { | |
key: 'link', | |
value: function link() { | |
var renderables = this.renderer.getRules(); | |
for (var selector in renderables) { | |
var rule = this.rules[selector]; | |
if (rule) { rule.renderable = renderables[selector]; } | |
} | |
this.linked = true; | |
return this; | |
} | |
}]); | |
return StyleSheet; | |
}(); | |
exports.default = StyleSheet; | |
}); | |
var PluginsRegistry_1 = createCommonjsModule(function (module, exports) { | |
"use strict"; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Register a plugin, run a plugin. | |
* | |
* @api public | |
*/ | |
var PluginsRegistry = function () { | |
function PluginsRegistry() { | |
_classCallCheck(this, PluginsRegistry); | |
this.registry = []; | |
} | |
/** | |
* Register plugin. Passed function will be invoked with a rule instance. | |
* | |
* @param {Function} fn | |
* @api public | |
*/ | |
_createClass(PluginsRegistry, [{ | |
key: "use", | |
value: function use(fn) { | |
this.registry.push(fn); | |
} | |
/** | |
* Execute all registered plugins. | |
* | |
* @param {Rule} rule | |
* @api private | |
*/ | |
}, { | |
key: "run", | |
value: function run(rule) { | |
for (var index = 0; index < this.registry.length; index++) { | |
this.registry[index](rule); | |
} | |
} | |
}]); | |
return PluginsRegistry; | |
}(); | |
exports.default = PluginsRegistry; | |
}); | |
var SheetsRegistry_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Sheets registry to access them all at one place. | |
* | |
* @api public | |
*/ | |
var SheetsRegistry = function () { | |
function SheetsRegistry() { | |
_classCallCheck(this, SheetsRegistry); | |
this.registry = []; | |
} | |
/** | |
* Register a style sheet. | |
* | |
* @param {StyleSheet} sheet | |
* @api public | |
*/ | |
_createClass(SheetsRegistry, [{ | |
key: 'add', | |
value: function add(sheet) { | |
this.registry.push(sheet); | |
} | |
/** | |
* Returns CSS string with all Style Sheets. | |
* | |
* @param {StyleSheet} sheet | |
* @api public | |
*/ | |
}, { | |
key: 'toString', | |
value: function toString(options) { | |
return this.registry.map(function (sheet) { | |
return sheet.toString(options); | |
}).join('\n'); | |
} | |
}]); | |
return SheetsRegistry; | |
}(); | |
exports.default = SheetsRegistry; | |
}); | |
var Jss_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | |
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); | |
var _StyleSheet2 = _interopRequireDefault(StyleSheet_1); | |
var _PluginsRegistry2 = _interopRequireDefault(PluginsRegistry_1); | |
var _SheetsRegistry2 = _interopRequireDefault(SheetsRegistry_1); | |
var _createRule3 = _interopRequireDefault(createRule_1); | |
var _findRenderer2 = _interopRequireDefault(findRenderer_1); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Main Jss class. | |
* | |
* @api public | |
*/ | |
var Jss = function () { | |
function Jss() { | |
_classCallCheck(this, Jss); | |
this.sheets = new _SheetsRegistry2.default(); | |
this.plugins = new _PluginsRegistry2.default(); | |
this.uid = utils$1.uid; | |
this.version = '3.11.1'; | |
} | |
/** | |
* Creates a new instance of Jss. | |
* | |
* @see Jss | |
* @api public | |
*/ | |
_createClass(Jss, [{ | |
key: 'create', | |
value: function create() { | |
return new Jss(); | |
} | |
/** | |
* Create a stylesheet. | |
* | |
* @see StyleSheet | |
* @api public | |
*/ | |
}, { | |
key: 'createStyleSheet', | |
value: function createStyleSheet(rules, options) { | |
var sheet = new _StyleSheet2.default(rules, _extends({}, options, { jss: this })); | |
this.sheets.add(sheet); | |
return sheet; | |
} | |
/** | |
* Create a rule. | |
* | |
* @see createRule | |
* @api public | |
*/ | |
}, { | |
key: 'createRule', | |
value: function createRule(selector, style, options) { | |
// Enable rule without selector. | |
if ((typeof selector === 'undefined' ? 'undefined' : _typeof(selector)) == 'object') { | |
options = style; | |
style = selector; | |
selector = null; | |
} | |
var rule = (0, _createRule3.default)(selector, style, _extends({}, options, { | |
jss: this, | |
Renderer: (0, _findRenderer2.default)(options) | |
})); | |
this.plugins.run(rule); | |
return rule; | |
} | |
/** | |
* Register plugin. Passed function will be invoked with a rule instance. | |
* | |
* @param {Function} plugins | |
* @api public | |
*/ | |
}, { | |
key: 'use', | |
value: function use() { | |
var _this = this; | |
for (var _len = arguments.length, plugins = Array(_len), _key = 0; _key < _len; _key++) { | |
plugins[_key] = arguments[_key]; | |
} | |
plugins.forEach(function (plugin) { | |
return _this.plugins.use(plugin); | |
}); | |
return this; | |
} | |
}]); | |
return Jss; | |
}(); | |
exports.default = Jss; | |
}); | |
var index = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports.Rule = exports.StyleSheet = exports.Jss = undefined; | |
var _Jss2 = _interopRequireDefault(Jss_1); | |
var _StyleSheet2 = _interopRequireDefault(StyleSheet_1); | |
var _Rule2 = _interopRequireDefault(Rule_1); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
var jss = new _Jss2.default(); | |
// Hotfix for babel 5 migration, will be removed in version 4.0.0 | |
/** | |
* StyleSheets written in javascript. | |
* | |
* @copyright Oleg Slobodskoi 2014-2016 | |
* @website https://github.com/jsstyles/jss | |
* @license MIT | |
*/ | |
module.exports = exports = jss; | |
// For testing only. | |
exports.Jss = _Jss2.default; | |
exports.StyleSheet = _StyleSheet2.default; | |
exports.Rule = _Rule2.default; | |
exports.default = jss; | |
}); | |
var Jss = unwrapExports(index); | |
var index$1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | |
exports.default = jssNested; | |
var regExp = /&/g; | |
/** | |
* Convert nested rules to separate, remove them from original styles. | |
* | |
* @param {Rule} rule | |
* @api public | |
*/ | |
function jssNested() { | |
return function (rule) { | |
if (rule.type !== 'regular') { return; } | |
var _rule$options = rule.options; | |
var sheet = _rule$options.sheet; | |
var jss = _rule$options.jss; | |
var parent = _rule$options.parent; | |
var container = sheet || jss; | |
var options = void 0; | |
if (parent && parent.type === 'conditional') { | |
container = parent; | |
} | |
for (var prop in rule.style) { | |
if (prop[0] === '&') { | |
if (!options) { options = _extends({}, rule.options, { named: false }); } | |
var name = prop.replace(regExp, rule.selector); | |
container.createRule(name, rule.style[prop], options); | |
delete rule.style[prop]; | |
} | |
} | |
}; | |
} | |
}); | |
var jssNested = unwrapExports(index$1); | |
var index$2 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports.default = jssExtend; | |
/** | |
* Handle `extend` property. | |
* | |
* @param {Rule} rule | |
* @api public | |
*/ | |
function jssExtend() { | |
function extend(rule, newStyle, style) { | |
if (typeof style.extend == 'string') { | |
if (rule.options && rule.options.sheet) { | |
var refRule = rule.options.sheet.getRule(style.extend); | |
if (refRule) { extend(rule, newStyle, refRule.originalStyle); } | |
} | |
} else if (Array.isArray(style.extend)) { | |
for (var index = 0; index < style.extend.length; index++) { | |
extend(rule, newStyle, style.extend[index]); | |
} | |
} else { | |
for (var prop in style.extend) { | |
if (prop === 'extend') { extend(rule, newStyle, style.extend.extend); }else { newStyle[prop] = style.extend[prop]; } | |
} | |
} | |
// Copy base style. | |
for (var _prop in style) { | |
if (_prop !== 'extend') { newStyle[_prop] = style[_prop]; } | |
} | |
return newStyle; | |
} | |
return function (rule) { | |
if (!rule.style || !rule.style.extend) { return; } | |
rule.style = extend(rule, {}, rule.style); | |
}; | |
} | |
}); | |
var jssExtend = unwrapExports(index$2); | |
var index$3 = createCommonjsModule(function (module, exports) { | |
// Don't automatically add 'px' to these possibly-unitless properties. | |
// Borrowed from jquery. | |
'use strict'; | |
exports.__esModule = true; | |
exports['default'] = jssPx; | |
var cssNumber = { | |
'animation-iteration-count': true, | |
'box-ordinal-group': true, | |
'column-count': true, | |
'fill-opacity': true, | |
'flex': true, | |
'flex-grow': true, | |
'flex-order': true, | |
'flex-shrink': true, | |
'font-weight': true, | |
'line-height': true, | |
'opacity': true, | |
'order': true, | |
'orphans': true, | |
'stop-opacity': true, | |
'tab-size': 1, | |
'widows': true, | |
'z-index': true, | |
'zoom': true | |
}; | |
/** | |
* Add px to numeric values. | |
* | |
* @param {Rule} rule | |
* @api public | |
*/ | |
function jssPx() { | |
return function (rule) { | |
var style = rule.style; | |
if (!style) { return; } | |
for (var prop in style) { | |
if (!cssNumber[prop] && typeof style[prop] == 'number') { | |
style[prop] += 'px'; | |
} | |
} | |
}; | |
} | |
module.exports = exports['default']; | |
}); | |
var jssPx = unwrapExports(index$3); | |
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | |
var isBrowser = (typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && (typeof document === "undefined" ? "undefined" : _typeof(document)) === 'object' && document.nodeType === 9; | |
var module$1 = Object.freeze({ | |
isBrowser: isBrowser, | |
default: isBrowser | |
}); | |
var _isInBrowser = ( module$1 && isBrowser ) || module$1; | |
var prefix = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _isInBrowser2 = _interopRequireDefault(_isInBrowser); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | |
var js = ''; /** | |
* Export javascript style and css style vendor prefixes. | |
* Based on "transform" support test. | |
*/ | |
var css = ''; | |
// We should not do anything if required serverside. | |
if (_isInBrowser2['default']) { | |
// Order matters. We need to check Webkit the last one because | |
// other vendors use to add Webkit prefixes to some properties | |
var jsCssMap = { | |
Moz: '-moz-', | |
// IE did it wrong again ... | |
ms: '-ms-', | |
O: '-o-', | |
Webkit: '-webkit-' | |
}; | |
var style = document.createElement('p').style; | |
var testProp = 'Transform'; | |
for (var key in jsCssMap) { | |
if (key + testProp in style) { | |
js = key; | |
css = jsCssMap[key]; | |
break; | |
} | |
} | |
} | |
/** | |
* Vendor prefix string for the current browser. | |
* | |
* @type {{js: String, css: String}} | |
* @api public | |
*/ | |
exports['default'] = { js: js, css: css }; | |
}); | |
var camelize_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports['default'] = camelize; | |
var regExp = /[-\s]+(.)?/g; | |
/** | |
* Convert dash separated strings to camel cased. | |
* | |
* @param {String} str | |
* @return {String} | |
*/ | |
function camelize(str) { | |
return str.replace(regExp, toUpper); | |
} | |
function toUpper(match, c) { | |
return c ? c.toUpperCase() : ''; | |
} | |
}); | |
var supportedProperty_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports['default'] = supportedProperty; | |
var _isInBrowser2 = _interopRequireDefault(_isInBrowser); | |
var _prefix2 = _interopRequireDefault(prefix); | |
var _camelize2 = _interopRequireDefault(camelize_1); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | |
var el = void 0; | |
var cache = {}; | |
if (_isInBrowser2['default']) { | |
el = document.createElement('p'); | |
/** | |
* We test every property on vendor prefix requirement. | |
* Once tested, result is cached. It gives us up to 70% perf boost. | |
* http://jsperf.com/element-style-object-access-vs-plain-object | |
* | |
* Prefill cache with known css properties to reduce amount of | |
* properties we need to feature test at runtime. | |
* http://davidwalsh.name/vendor-prefix | |
*/ | |
var computed = window.getComputedStyle(document.documentElement, ''); | |
for (var key in computed) { | |
if (!isNaN(key)) { cache[computed[key]] = computed[key]; } | |
} | |
} | |
/** | |
* Test if a property is supported, returns supported property with vendor | |
* prefix if required. Returns `false` if not supported. | |
* | |
* @param {String} prop dash separated | |
* @return {String|Boolean} | |
* @api public | |
*/ | |
function supportedProperty(prop) { | |
// For server-side rendering. | |
if (!el) { return prop; } | |
// We have not tested this prop yet, lets do the test. | |
if (cache[prop] != null) { return cache[prop]; } | |
// Camelization is required because we can't test using | |
// css syntax for e.g. in FF. | |
// Test if property is supported as it is. | |
if ((0, _camelize2['default'])(prop) in el.style) { | |
cache[prop] = prop; | |
} | |
// Test if property is supported with vendor prefix. | |
else if (_prefix2['default'].js + (0, _camelize2['default'])('-' + prop) in el.style) { | |
cache[prop] = _prefix2['default'].css + prop; | |
} else { | |
cache[prop] = false; | |
} | |
return cache[prop]; | |
} | |
}); | |
var supportedValue_1 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports['default'] = supportedValue; | |
var _isInBrowser2 = _interopRequireDefault(_isInBrowser); | |
var _prefix2 = _interopRequireDefault(prefix); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | |
var cache = {}; | |
var el = void 0; | |
if (_isInBrowser2['default']) { el = document.createElement('p'); } | |
/** | |
* Returns prefixed value if needed. Returns `false` if value is not supported. | |
* | |
* @param {String} property | |
* @param {String} value | |
* @return {String|Boolean} | |
* @api public | |
*/ | |
function supportedValue(property, value) { | |
// For server-side rendering. | |
if (!el) { return value; } | |
// It is a string or a number as a string like '1'. | |
// We want only prefixable values here. | |
if (typeof value !== 'string' || !isNaN(parseInt(value, 10))) { return value; } | |
var cacheKey = property + value; | |
if (cache[cacheKey] != null) { return cache[cacheKey]; } | |
// IE can even throw an error in some cases, for e.g. style.content = 'bar' | |
try { | |
// Test value as it is. | |
el.style[property] = value; | |
} catch (err) { | |
cache[cacheKey] = false; | |
return false; | |
} | |
// Value is supported as it is. | |
if (el.style[property] !== '') { | |
cache[cacheKey] = value; | |
} else { | |
// Test value with vendor prefix. | |
value = _prefix2['default'].css + value; | |
// Hardcode test to convert "flex" to "-ms-flexbox" for IE10. | |
if (value === '-ms-flex') { value = '-ms-flexbox'; } | |
el.style[property] = value; | |
// Value is supported with vendor prefix. | |
if (el.style[property] !== '') { cache[cacheKey] = value; } | |
} | |
if (!cache[cacheKey]) { cache[cacheKey] = false; } | |
// Reset style value. | |
el.style[property] = ''; | |
return cache[cacheKey]; | |
} | |
}); | |
var index$5 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports.supportedValue = exports.supportedProperty = exports.prefix = undefined; | |
var _prefix2 = _interopRequireDefault(prefix); | |
var _supportedProperty2 = _interopRequireDefault(supportedProperty_1); | |
var _supportedValue2 = _interopRequireDefault(supportedValue_1); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | |
exports['default'] = { | |
prefix: _prefix2['default'], | |
supportedProperty: _supportedProperty2['default'], | |
supportedValue: _supportedValue2['default'] | |
}; /** | |
* CSS Vendor prefix detection and property feature testing. | |
* | |
* @copyright Oleg Slobodskoi 2015 | |
* @website https://github.com/jsstyles/css-vendor | |
* @license MIT | |
*/ | |
exports.prefix = _prefix2['default']; | |
exports.supportedProperty = _supportedProperty2['default']; | |
exports.supportedValue = _supportedValue2['default']; | |
}); | |
var index$4 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
exports.__esModule = true; | |
exports['default'] = jssVendorPrefixer; | |
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj['default'] = obj; return newObj; } } | |
var vendor = _interopRequireWildcard(index$5); | |
/** | |
* Add vendor prefix to a property name when needed. | |
* | |
* @param {Rule} rule | |
* @api public | |
*/ | |
function jssVendorPrefixer() { | |
return function (rule) { | |
if (rule.type === 'keyframe') { | |
rule.selector = '@' + vendor.prefix.css + 'keyframes' + rule.selector.substr(10); | |
return; | |
} | |
if (rule.type !== 'regular') { return; } | |
for (var prop in rule.style) { | |
var value = rule.style[prop]; | |
var changeProp = false; | |
var supportedProp = vendor.supportedProperty(prop); | |
if (supportedProp && supportedProp !== prop) { changeProp = true; } | |
var changeValue = false; | |
var supportedValue = vendor.supportedValue(supportedProp, value); | |
if (supportedValue && supportedValue !== value) { changeValue = true; } | |
if (changeProp || changeValue) { | |
if (changeProp) { delete rule.style[prop]; } | |
rule.style[supportedProp || prop] = supportedValue || value; | |
} | |
} | |
}; | |
} | |
module.exports = exports['default']; | |
}); | |
var jssVendorPrefixer = unwrapExports(index$4); | |
var index$7 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var regExp = /([A-Z])/g; | |
/** | |
* Replace a string passed from String#replace. | |
* @param {String} str | |
* @return {String} | |
*/ | |
function replace(str) { | |
return '-' + str.toLowerCase(); | |
} | |
/** | |
* Convert camel cased properties of a single style to dasherized. | |
* | |
* @param {Object} style | |
* @return {Object} convertedStyle | |
*/ | |
function convertCase(style) { | |
var convertedStyle = {}; | |
for (var prop in style) { | |
var value = style[prop]; | |
prop = prop.replace(regExp, replace); | |
convertedStyle[prop] = value; | |
} | |
return convertedStyle; | |
} | |
/** | |
* Allow camel cased property names by converting them back to dasherized. | |
* | |
* @param {Rule} rule | |
*/ | |
exports.default = function () { | |
return function jssCamelCase(rule) { | |
var style = rule.style; | |
if (!style) { return; } | |
if (Array.isArray(style)) { | |
// Handle rules like @font-face, which can have multiple styles in an array | |
for (var index = 0; index < style.length; index++) { | |
style[index] = convertCase(style[index]); | |
} | |
} else { | |
rule.style = convertCase(style); | |
} | |
}; | |
}; | |
}); | |
var jssCamelCase = unwrapExports(index$7); | |
var index$8 = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports.default = jssPropsSort; | |
/** | |
* Sort props by length. | |
* | |
* @param {Rule} rule | |
* @api public | |
*/ | |
function jssPropsSort() { | |
function sort(prop0, prop1) { | |
return prop0.length > prop1.length; | |
} | |
return function (rule) { | |
var style = rule.style; | |
var type = rule.type; | |
if (!style || type !== 'regular') { return; } | |
var newStyle = {}; | |
var props = Object.keys(style).sort(sort); | |
for (var prop in props) { | |
newStyle[props[prop]] = style[props[prop]]; | |
} | |
rule.style = newStyle; | |
}; | |
} | |
}); | |
var jssPropsSort = unwrapExports(index$8); | |
const jss = Jss.create(); | |
jss.use(jssNested()); | |
jss.use(jssExtend()); | |
jss.use(jssPx()); | |
jss.use(jssVendorPrefixer()); | |
jss.use(jssCamelCase()); | |
jss.use(jssPropsSort()); | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
var _now = Date.now; | |
function _timestampTouch(touch, event, history) { | |
return { | |
x: touch.clientX, | |
y: touch.clientY, | |
identifier : touch.identifier, | |
origin: event.origin, | |
timestamp: _now(), | |
count: event.touches.length, | |
history: history | |
}; | |
} | |
function _handleStart$1(event) { | |
if (event.touches.length > this.touchLimit) { return; } | |
this.isTouched = true; | |
for (var i = 0; i < event.changedTouches.length; i++) { | |
var touch = event.changedTouches[i]; | |
var data = _timestampTouch(touch, event, null); | |
this.eventOutput.emit('trackstart', data); | |
if (!this.selective && !this.touchHistory[touch.identifier]) { this.track(data); } | |
} | |
} | |
function _handleMove$1(event) { | |
if (event.touches.length > this.touchLimit) { return; } | |
for (var i = 0; i < event.changedTouches.length; i++) { | |
var touch = event.changedTouches[i]; | |
var history = this.touchHistory[touch.identifier]; | |
if (history) { | |
var data = _timestampTouch(touch, event, history); | |
this.touchHistory[touch.identifier].push(data); | |
this.eventOutput.emit('trackmove', data); | |
} | |
} | |
} | |
function _handleEnd$1(event) { | |
if (!this.isTouched) { return; } | |
for (var i = 0; i < event.changedTouches.length; i++) { | |
var touch = event.changedTouches[i]; | |
var history = this.touchHistory[touch.identifier]; | |
if (history) { | |
var data = _timestampTouch(touch, event, history); | |
this.eventOutput.emit('trackend', data); | |
delete this.touchHistory[touch.identifier]; | |
} | |
} | |
this.isTouched = false; | |
} | |
function _handleUnpipe() { | |
for (var i in this.touchHistory) { | |
var history = this.touchHistory[i]; | |
this.eventOutput.emit('trackend', { | |
touch: history[history.length - 1].touch, | |
timestamp: Date.now(), | |
count: 0, | |
history: history | |
}); | |
delete this.touchHistory[i]; | |
} | |
} | |
/** | |
* Helper to TouchSync – tracks piped in touch events, organizes touch | |
* events by ID, and emits track events back to TouchSync. | |
* Emits 'trackstart', 'trackmove', and 'trackend' events upstream. | |
* | |
* @class TouchTracker | |
* @constructor | |
* @param {Object} options default options overrides | |
* @param [options.selective] {Boolean} selective if false, saves state for each touch | |
* @param [options.touchLimit] {Number} touchLimit upper bound for emitting events based on number of touches | |
*/ | |
function TouchTracker(options) { | |
this.selective = options.selective; | |
this.touchLimit = options.touchLimit || 1; | |
this.touchHistory = {}; | |
this.eventInput = new EventHandler_1(); | |
this.eventOutput = new EventHandler_1(); | |
EventHandler_1.setInputHandler(this, this.eventInput); | |
EventHandler_1.setOutputHandler(this, this.eventOutput); | |
this.eventInput.on('touchstart', _handleStart$1.bind(this)); | |
this.eventInput.on('touchmove', _handleMove$1.bind(this)); | |
this.eventInput.on('touchend', _handleEnd$1.bind(this)); | |
this.eventInput.on('touchcancel', _handleEnd$1.bind(this)); | |
this.eventInput.on('unpipe', _handleUnpipe.bind(this)); | |
this.isTouched = false; | |
} | |
/** | |
* Record touch data, if selective is false. | |
* @private | |
* @method track | |
* @param {Object} data touch data | |
*/ | |
TouchTracker.prototype.track = function track(data) { | |
this.touchHistory[data.identifier] = [data]; | |
}; | |
var TouchTracker_1 = TouchTracker; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* Handles piped in touch events. Emits 'start', 'update', and 'events' | |
* events with delta, position, velocity, acceleration, clientX, clientY, count, and touch id. | |
* Useful for dealing with inputs on touch devices. Designed to be used either as standalone, or | |
* included in a GenericSync. | |
* | |
* @class TouchSync | |
* @constructor | |
* | |
* @example | |
* var Surface = require('../core/Surface'); | |
* var TouchSync = require('../inputs/TouchSync'); | |
* | |
* var surface = new Surface({ size: [100, 100] }); | |
* var touchSync = new TouchSync(); | |
* surface.pipe(touchSync); | |
* | |
* touchSync.on('start', function (e) { // react to start }); | |
* touchSync.on('update', function (e) { // react to update }); | |
* touchSync.on('end', function (e) { // react to end });* | |
* | |
* @param [options] {Object} default options overrides | |
* @param [options.direction] {Number} read from a particular axis | |
* @param [options.rails] {Boolean} read from axis with greatest differential | |
* @param [options.velocitySampleLength] {Number} Number of previous frames to check velocity against. | |
* @param [options.scale] {Number} constant factor to scale velocity output | |
* @param [options.touchLimit] {Number} touchLimit upper bound for emitting events based on number of touches | |
*/ | |
function TouchSync(options) { | |
this.options = Object.create(TouchSync.DEFAULT_OPTIONS); | |
this._optionsManager = new OptionsManager_1(this.options); | |
if (options) { this.setOptions(options); } | |
this._eventOutput = new EventHandler_1(); | |
this._touchTracker = new TouchTracker_1({ | |
touchLimit: this.options.touchLimit | |
}); | |
EventHandler_1.setOutputHandler(this, this._eventOutput); | |
EventHandler_1.setInputHandler(this, this._touchTracker); | |
this._touchTracker.on('trackstart', _handleStart.bind(this)); | |
this._touchTracker.on('trackmove', _handleMove.bind(this)); | |
this._touchTracker.on('trackend', _handleEnd.bind(this)); | |
this._payload = { | |
delta : null, | |
position : null, | |
velocity : null, | |
clientX : undefined, | |
clientY : undefined, | |
count : 0, | |
touch : undefined | |
}; | |
this._position = null; // to be deprecated | |
} | |
TouchSync.DEFAULT_OPTIONS = { | |
direction: undefined, | |
rails: false, | |
touchLimit: 1, | |
velocitySampleLength: 10, | |
scale: 1 | |
}; | |
TouchSync.DIRECTION_X = 0; | |
TouchSync.DIRECTION_Y = 1; | |
var MINIMUM_TICK_TIME = 8; | |
/** | |
* Triggered by trackstart. | |
* @method _handleStart | |
* @private | |
*/ | |
function _handleStart(data) { | |
var velocity; | |
var delta; | |
if (this.options.direction !== undefined){ | |
this._position = 0; | |
velocity = 0; | |
delta = 0; | |
} | |
else { | |
this._position = [0, 0]; | |
velocity = [0, 0]; | |
delta = [0, 0]; | |
} | |
var payload = this._payload; | |
payload.delta = delta; | |
payload.position = this._position; | |
payload.velocity = velocity; | |
payload.clientX = data.x; | |
payload.clientY = data.y; | |
payload.count = data.count; | |
payload.touch = data.identifier; | |
this._eventOutput.emit('start', payload); | |
} | |
/** | |
* Triggered by trackmove. | |
* @method _handleMove | |
* @private | |
*/ | |
function _handleMove(data) { | |
var history = data.history; | |
var currHistory = history[history.length - 1]; | |
var prevHistory = history[history.length - 2]; | |
var distantHistory = history[history.length - this.options.velocitySampleLength] ? | |
history[history.length - this.options.velocitySampleLength] : | |
history[history.length - 2]; | |
var distantTime = distantHistory.timestamp; | |
var currTime = currHistory.timestamp; | |
var diffX = currHistory.x - prevHistory.x; | |
var diffY = currHistory.y - prevHistory.y; | |
var velDiffX = currHistory.x - distantHistory.x; | |
var velDiffY = currHistory.y - distantHistory.y; | |
if (this.options.rails) { | |
if (Math.abs(diffX) > Math.abs(diffY)) { diffY = 0; } | |
else { diffX = 0; } | |
if (Math.abs(velDiffX) > Math.abs(velDiffY)) { velDiffY = 0; } | |
else { velDiffX = 0; } | |
} | |
var diffTime = Math.max(currTime - distantTime, MINIMUM_TICK_TIME); | |
var velX = velDiffX / diffTime; | |
var velY = velDiffY / diffTime; | |
var scale = this.options.scale; | |
var nextVel; | |
var nextDelta; | |
if (this.options.direction === TouchSync.DIRECTION_X) { | |
nextDelta = scale * diffX; | |
nextVel = scale * velX; | |
this._position += nextDelta; | |
} | |
else if (this.options.direction === TouchSync.DIRECTION_Y) { | |
nextDelta = scale * diffY; | |
nextVel = scale * velY; | |
this._position += nextDelta; | |
} | |
else { | |
nextDelta = [scale * diffX, scale * diffY]; | |
nextVel = [scale * velX, scale * velY]; | |
this._position[0] += nextDelta[0]; | |
this._position[1] += nextDelta[1]; | |
} | |
var payload = this._payload; | |
payload.delta = nextDelta; | |
payload.velocity = nextVel; | |
payload.position = this._position; | |
payload.clientX = data.x; | |
payload.clientY = data.y; | |
payload.count = data.count; | |
payload.touch = data.identifier; | |
this._eventOutput.emit('update', payload); | |
} | |
/** | |
* Triggered by trackend. | |
* @method _handleEnd | |
* @private | |
*/ | |
function _handleEnd(data) { | |
this._payload.count = data.count; | |
this._eventOutput.emit('end', this._payload); | |
} | |
/** | |
* Set internal options, overriding any default options | |
* | |
* @method setOptions | |
* | |
* @param [options] {Object} default options overrides | |
* @param [options.direction] {Number} read from a particular axis | |
* @param [options.rails] {Boolean} read from axis with greatest differential | |
* @param [options.scale] {Number} constant factor to scale velocity output | |
*/ | |
TouchSync.prototype.setOptions = function setOptions(options) { | |
return this._optionsManager.setOptions(options); | |
}; | |
/** | |
* Return entire options dictionary, including defaults. | |
* | |
* @method getOptions | |
* @return {Object} configuration options | |
*/ | |
TouchSync.prototype.getOptions = function getOptions() { | |
return this.options; | |
}; | |
var TouchSync_1 = TouchSync; | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* Owner: mark@famo.us | |
* @license MPL 2.0 | |
* @copyright Famous Industries, Inc. 2015 | |
*/ | |
/** | |
* Combines multiple types of sync classes (e.g. mouse, touch, | |
* scrolling) into one standardized interface for inclusion in widgets. | |
* | |
* Sync classes are first registered with a key, and then can be accessed | |
* globally by key. | |
* | |
* Emits 'start', 'update' and 'end' events as a union of the sync class | |
* providers. | |
* | |
* @class GenericSync | |
* @constructor | |
* @param syncs {Object|Array} object with fields {sync key : sync options} | |
* or an array of registered sync keys | |
* @param [options] {Object|Array} options object to set on all syncs | |
*/ | |
function GenericSync(syncs, options) { | |
this._eventInput = new EventHandler_1(); | |
this._eventOutput = new EventHandler_1(); | |
EventHandler_1.setInputHandler(this, this._eventInput); | |
EventHandler_1.setOutputHandler(this, this._eventOutput); | |
this._syncs = {}; | |
if (syncs) { this.addSync(syncs); } | |
if (options) { this.setOptions(options); } | |
} | |
GenericSync.DIRECTION_X = 0; | |
GenericSync.DIRECTION_Y = 1; | |
GenericSync.DIRECTION_Z = 2; | |
// Global registry of sync classes. Append only. | |
var registry = {}; | |
/** | |
* Register a global sync class with an identifying key | |
* | |
* @static | |
* @method register | |
* | |
* @param syncObject {Object} an object of {sync key : sync options} fields | |
*/ | |
GenericSync.register = function register(syncObject) { | |
for (var key in syncObject){ | |
if (registry[key]){ // skip redundant registration | |
if (registry[key] !== syncObject[key]) // only if same registered class | |
{ throw new Error('Conflicting sync classes for key: ' + key); } | |
} | |
else { registry[key] = syncObject[key]; } | |
} | |
}; | |
/** | |
* Helper to set options on all sync instances | |
* | |
* @method setOptions | |
* @param options {Object} options object | |
*/ | |
GenericSync.prototype.setOptions = function(options) { | |
for (var key in this._syncs){ | |
this._syncs[key].setOptions(options); | |
} | |
}; | |
/** | |
* Pipe events to a sync class | |
* | |
* @method pipeSync | |
* @param key {String} identifier for sync class | |
*/ | |
GenericSync.prototype.pipeSync = function pipeToSync(key) { | |
var sync = this._syncs[key]; | |
this._eventInput.pipe(sync); | |
sync.pipe(this._eventOutput); | |
}; | |
/** | |
* Unpipe events from a sync class | |
* | |
* @method unpipeSync | |
* @param key {String} identifier for sync class | |
*/ | |
GenericSync.prototype.unpipeSync = function unpipeFromSync(key) { | |
var sync = this._syncs[key]; | |
this._eventInput.unpipe(sync); | |
sync.unpipe(this._eventOutput); | |
}; | |
function _addSingleSync(key, options) { | |
if (!registry[key]) { return; } | |
this._syncs[key] = new (registry[key])(options); | |
this.pipeSync(key); | |
} | |
/** | |
* Add a sync class to from the registered classes | |
* | |
* @method addSync | |
* @param syncs {Object|Array.String} an array of registered sync keys | |
* or an object with fields {sync key : sync options} | |
*/ | |
GenericSync.prototype.addSync = function addSync(syncs) { | |
if (syncs instanceof Array) | |
{ for (var i = 0; i < syncs.length; i++) | |
{ _addSingleSync.call(this, syncs[i]); } } | |
else if (syncs instanceof Object) | |
{ for (var key in syncs) | |
{ _addSingleSync.call(this, key, syncs[key]); } } | |
}; | |
var GenericSync_1 = GenericSync; | |
var callAfter_1 = createCommonjsModule(function (module, exports) { | |
"use strict"; | |
exports.callAfter = callAfter; | |
function callAfter(times, callback) { | |
var count = 0; | |
return function () { | |
if (++count == times) { | |
if (typeof callback == "function") { | |
callback.apply(this, arguments); | |
} | |
} | |
}; | |
} | |
exports["default"] = callAfter; | |
exports.__esModule = true; | |
}); | |
var callAfter = unwrapExports(callAfter_1); | |
/* | |
* LICENSE | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
*/ | |
/** | |
* A scenegraph with two Molecule leafnodes: the menu area and the content | |
* area. The menu area is hidden beyond the edge of the screen while the | |
* content area is visible. Swiping in from the edge of the screen reveals the | |
* menu, putting the content area out of focus. A mouse can also be used, and | |
* hovering near the edge of the screen also reveals the menu. | |
* | |
* Note: This layout is mostly useful if it exists at the root of a context so | |
* that the menu is clipped when it is closed (off to the side), otherwise the | |
* menu will be visible beyond the boundary of the container that contains the | |
* PushMenuLayout. | |
* | |
* Note: If you've called `openMenu` or `closeMenu` with a callback, the callback | |
* will be canceled if a drag or hover on the menu happens before the animation | |
* has completed. Please open an issue on GitHub if you have any opinion | |
* against this. :) Maybe we can add a boolean option for this behavior. | |
* | |
* TODO: Embed working example here. | |
* | |
* @class PushMenuLayout | |
* @extends Molecule | |
*/ | |
class PushMenuLayout extends Molecule { | |
/** | |
* Creates a new PushMenuLayout. | |
* | |
* @constructor | |
* @param {Object} options The options to instantiate a `PushMenuLayout` with. | |
* | |
* TODO v0.1.0: Handle `PushMenuLayout`-specific user options. Currently they | |
* just get passed into super() for the Molecule constructor to handle. | |
*/ | |
constructor(initialOptions) { | |
super(initialOptions); | |
// Add default values for this PushMenuLayout | |
// TODO: Make default options static for the class. | |
simpleExtend(this._.defaultOptions, { | |
menuSide: 'left', // left or right | |
menuWidth: 200, | |
menuHintSize: 10, // the amount of the menu that is visible before opening the menu. | |
pushAreaWidth: 40, // the area on the screen edge that the user can touch and drag to push out the menu. | |
animationDuration: 1000, | |
animationType: 'foldDown', // options: foldDown moveBack | |
// TODO: Background color for the whole layout should be the color that the fade fades to. | |
// TODO: Replace fade star/end colors with a fog color value and intensity. | |
fade: true, // when content recedes, it fades into the fog. | |
fadeStartColor: 'rgba(255,255,255,0)', | |
fadeEndColor: 'rgba(255,255,255,1)', | |
blur: false, // XXX: WIP, so false by default. | |
blurRadius: 5 | |
}); | |
// TODO: performance hit, this setter is invoked in the Molecule constructor, then here again. | |
this.options = initialOptions; | |
// TODO v0.1.0: Mark these as private. | |
// TODO v0.1.0: this.contentWidth should be the width of whatever is containing | |
// the layout, but we're just using it as a whole-page app for now. Get | |
// size from a commit? UPDATE: See the new famous/src/views/SizeAwareView | |
this.contentWidth = document.body.clientWidth - this.options.menuHintSize; | |
// Changing these values outside of an instance of PushMenuLayout might | |
// cause the layout to break. They are designed to be modified | |
// internally only. | |
this.isOpen = false; | |
this.isOpening = false; | |
this.isClosing = false; | |
this.isAnimating = false; // keep track of whether the menu is opening or closing. | |
this.isBeingDragged = false; // whether the user is dragging/pushing the menu or not. | |
this.transitionCallback = undefined; // holds the callback to the current open or close menu animation. | |
// Set the touch sync for pulling/pushing the menu open/closed. | |
GenericSync_1.register({ | |
touch: TouchSync_1 | |
}); | |
this._createComponents(); | |
this._initializeEvents(); | |
} | |
/** | |
* See Molecule.setOptions | |
* | |
* @override | |
*/ | |
setOptions(newOptions) { | |
super.setOptions(newOptions); | |
} | |
/** | |
* See Molecule.resetOptions | |
* | |
* @override | |
*/ | |
resetOptions() { | |
super.resetOptions(); | |
} | |
/** | |
* Creates the menu area, content area, `Plane` for the fade effect, etc. | |
* | |
* @private | |
*/ | |
_createComponents() { | |
var layout = this; | |
this.touchSync = new GenericSync_1(['touch']); | |
this.alignment = (this.options.menuSide == "left"? 0: 1); | |
this.animationTransition = new Transitionable_1(0); | |
this.mainMol = new Molecule(); | |
this.menuMol = new Molecule({ | |
size: [this.options.menuWidth,undefined] | |
}); | |
this.menuMol.oldTransform = this.menuMol.transform; | |
this.menuMol.transform = function() { // override | |
var currentPosition = layout.animationTransition.get(); | |
switch(layout.options.animationType) { | |
case "foldDown": | |
// XXX: this is depending on my modifications for TransitionableTransform. | |
this.oldTransform.setTranslateX( | |
layout.options.menuSide == 'left'? | |
currentPosition * (layout.options.menuWidth-layout.options.menuHintSize)/*range*/ - (layout.options.menuWidth-layout.options.menuHintSize)/*offset*/: | |
currentPosition * -(layout.options.menuWidth-layout.options.menuHintSize)/*range*/ + (layout.options.menuWidth-layout.options.menuHintSize)/*offset*/ | |
); | |
break; | |
case "moveBack": | |
// XXX: this is depending on my modifications for TransitionableTransform. | |
this.oldTransform.setTranslateX( | |
layout.options.menuSide == 'left'? | |
currentPosition * (layout.options.menuWidth-layout.options.menuHintSize)/*range*/ - (layout.options.menuWidth-layout.options.menuHintSize)/*offset*/: | |
currentPosition * -(layout.options.menuWidth-layout.options.menuHintSize)/*range*/ + (layout.options.menuWidth-layout.options.menuHintSize)/*offset*/ | |
); | |
break; | |
} | |
return this.oldTransform.get(); | |
}.bind(this.menuMol); | |
// contains the user's menu content. | |
this.menuContentMol = new Molecule(); | |
this.contentMol = new Molecule({ | |
size: [this.contentWidth,undefined] | |
}); | |
this.contentMol.oldTransform = this.contentMol.transform; | |
this.contentMol.transform = function() { // override | |
var currentPosition = layout.animationTransition.get(); | |
switch(layout.options.animationType) { | |
case "foldDown": | |
// XXX: this is depending on my modifications for TransitionableTransform. | |
this.oldTransform.setTranslateX( | |
layout.options.menuSide == 'left'? | |
currentPosition * (layout.options.menuWidth - layout.options.menuHintSize)/*range*/ + layout.options.menuHintSize/*offset*/: | |
currentPosition * -(layout.options.menuWidth - layout.options.menuHintSize)/*range*/ - layout.options.menuHintSize/*offset*/ | |
); | |
// XXX: this is depending on my modifications for TransitionableTransform. | |
this.oldTransform.setRotateY( | |
layout.options.menuSide == 'left'? | |
currentPosition * Math.PI/8: | |
currentPosition * -Math.PI/8 | |
); | |
break; | |
case "moveBack": | |
var depth = 100; | |
// XXX: this is depending on my modifications for TransitionableTransform. | |
this.oldTransform.setTranslateX( | |
layout.options.menuSide == 'left'? | |
layout.options.menuHintSize: | |
-layout.options.menuHintSize | |
); | |
this.oldTransform.setTranslateZ( | |
currentPosition * -depth | |
); | |
break; | |
} | |
return this.oldTransform.get(); | |
}.bind(this.contentMol); | |
this.menuTouchPlane = new Plane({ | |
size: [this.options.menuWidth + this.options.pushAreaWidth - this.options.menuHintSize, undefined], | |
properties: { | |
zIndex: '-1000' // below everything | |
} | |
}); | |
this.mainMol.setOptions({ | |
origin: [this.alignment, 0.5], | |
align: [this.alignment, 0.5] | |
}); | |
this.menuMol.setOptions({ | |
origin: [this.alignment, 0.5], | |
align: [this.alignment, 0.5] | |
}); | |
this.contentMol.setOptions({ | |
origin: [this.alignment, 0.5], | |
align: [this.alignment, 0.5] | |
}); | |
// FIXME: WHY THE EFF must I also set align and origin on menuTouchPlane | |
// when I've already set it on it's parent (this.menuMol)????? | |
this.menuTouchPlane.setOptions({ | |
origin: [this.alignment, 0.5], | |
align: [this.alignment, 0.5] | |
}); | |
// Bring the menu content molecule and touch plane forward just | |
// slightly so they're just above the content and content's fade plane, | |
// so touch and mouse interaction works. HTML, the bad parts. ;) | |
this.menuContentMol.transform.setTranslateZ(2); | |
this.menuTouchPlane.transform.setTranslateZ(2); | |
/* | |
* Styles for the fadePlane | |
*/ | |
// TODO: move this somewhere else . it's specific for each animation | |
this.updateStyles = function() { | |
var startColor; | |
var endColor; | |
switch(this.options.animationType) { | |
case "foldDown": | |
startColor = this.options.fadeStartColor; | |
endColor = this.options.fadeEndColor; | |
break; | |
case "moveBack": | |
startColor = endColor = this.options.fadeEndColor; | |
break; | |
} | |
var styles = { | |
'.infamous-fadeLeft': { | |
background: [ | |
endColor, | |
'-moz-linear-gradient(left, '+endColor+' 0%, '+startColor+' 100%)', | |
'-webkit-gradient(left top, right top, color-stop(0%, '+endColor+'), color-stop(100%, '+startColor+'))', | |
'-webkit-linear-gradient(left, '+endColor+' 0%, '+startColor+' 100%)', | |
'-o-linear-gradient(left, '+endColor+' 0%, '+startColor+' 100%)', | |
'-ms-linear-gradient(left, '+endColor+' 0%, '+startColor+' 100%)', | |
'linear-gradient(to right, '+endColor+' 0%, '+startColor+' 100%)' | |
], | |
filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#cc000000\', endColorstr=\'#4d000000\', GradientType=1 )' | |
}, | |
'.infamous-fadeRight': { | |
background: [ | |
startColor, | |
'-moz-linear-gradient(left, '+startColor+' 0%, '+endColor+' 100%)', | |
'-webkit-gradient(left top, right top, color-stop(0%, '+startColor+'), color-stop(100%, '+endColor+'))', | |
'-webkit-linear-gradient(left, '+startColor+' 0%, '+endColor+' 100%)', | |
'-o-linear-gradient(left, '+startColor+' 0%, '+endColor+' 100%)', | |
'-ms-linear-gradient(left, '+startColor+' 0%, '+endColor+' 100%)', | |
'linear-gradient(to right, '+startColor+' 0%, '+endColor+' 100%)' | |
], | |
filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#4d000000\', endColorstr=\'#cc000000\', GradientType=1 )' | |
} | |
}; | |
if (this.fadeStylesheet) { this.fadeStylesheet.detach(); } | |
this.fadeStylesheet = jss.createStyleSheet(styles); | |
this.fadeStylesheet.attach(); | |
}; | |
if (this.options.fade) { | |
this.updateStyles(); | |
this.fadePlane = new Plane({ | |
size: [undefined,undefined], | |
classes: [ | |
// TODO: switch to jss namespace. | |
(this.options.menuSide == 'left'? 'infamous-fadeRight': 'infamous-fadeLeft') | |
], | |
properties: { | |
zIndex: '1000', | |
pointerEvents: 'none' | |
} | |
}); | |
// FIXME: Why the EFF must I also set align and origin on fadePlane when | |
// I've already set it on it's parent (this.contentMol)????? | |
this.fadePlane.setOptions({ | |
origin: [this.alignment, 0.5], | |
align: [this.alignment, 0.5] | |
}); | |
// move the fadePlane forward by 1px so it doesn't glitch out. | |
// Chrome will make the fadePlane and the surface in the content | |
// area (if any) blink randomly when the two surfaces are in the | |
// same exact position together. | |
this.fadePlane.transform.setTranslateZ(1); | |
this.fadePlane.setOptions({ | |
opacity: this.animationTransition | |
}); | |
// TODO: Make fadePlane a sibling to menuMol and contentMol so that | |
// contentMol contains only the user;s content. This will affect | |
// the code in this.render(). | |
this.contentMol.add(this.fadePlane); | |
} | |
this.add(this.mainMol); | |
this.mainMol.add(this.contentMol); | |
this.menuMol.add(this.menuTouchPlane); | |
this.menuMol.add(this.menuContentMol); | |
this.mainMol.add(this.menuMol); | |
// TODO: Also create and add a background plane for the menu area so it will catch events that might fall through the menu content. | |
} | |
/** | |
* Sets up the events for the touch and mouse interaction that opens and | |
* closes the menu. | |
* | |
* @private | |
*/ | |
_initializeEvents() { | |
// move the menu, following the user's drag. Don't let the user drag the menu past the menu width. | |
this.options.handler.on('update', function(event) { // update == drag | |
this.isBeingDragged = true; | |
// stop the current transitions if any, along with the current callback if any. | |
this._haltAnimation(true); | |
var currentPosition = this.animationTransition.get(); | |
// TODO: handle the right-side menu. | |
switch(this.options.animationType) { | |
case "foldDown": | |
this.animationTransition.set(currentPosition + event.delta[0] / (this.options.menuWidth - this.options.menuHintSize)); | |
break; | |
case "moveBack": | |
this.animationTransition.set(currentPosition + event.delta[0] / (this.options.menuWidth - this.options.menuHintSize)); | |
break; | |
} | |
currentPosition = this.animationTransition.get(); | |
if (currentPosition > 1) { | |
this.animationTransition.set(1); | |
} | |
else if (currentPosition < 0) { | |
this.animationTransition.set(0); | |
} | |
}.bind(this)); | |
this.options.handler.on('end', function(event) { | |
this.isBeingDragged = false; | |
var currentPosition = this.animationTransition.get(); | |
if (currentPosition < 0.5) { | |
this.closeMenu(); | |
} | |
else { | |
this.openMenu(); | |
} | |
}.bind(this)); | |
// TODO v0.1.0: Use a SizeAwareView instead of relying on the body, since we | |
// might not be directly in the body. | |
window.addEventListener('resize', function(event) { | |
this.contentWidth = document.body.clientWidth - this.options.menuHintSize; | |
this.contentMol.setOptions({size: [this.contentWidth, undefined]}); | |
}.bind(this)); | |
/* | |
* Wire up events | |
* TODO: consolidate dup code here and in setMenu | |
*/ | |
this.menuTouchPlane.pipe(this.touchSync); | |
this.menuTouchPlane.on('mouseenter', function() { | |
if (!this.isOpening) { | |
this.openMenu(); | |
} | |
}.bind(this)); | |
this.menuTouchPlane.on('mouseleave', function() { | |
if (!this.isClosing) { | |
this.closeMenu(); | |
} | |
}.bind(this)); | |
this.touchSync.pipe(this.options.handler); | |
} | |
/** | |
* Add a scenegraph to the content area of the PushMenuLayout. You can put | |
* anything you want into the content area (magical 3D things for example), | |
* just be careful not to let them cover the menu or you'll block the user | |
* from interacting with the menu. | |
* | |
* @param {module: famous/src/core/RenderNode} node A scenegraph, i.e. a | |
* RenderNode with stuff in it. | |
* | |
* TODO: Accept plain renderables, f.e. Surfaces, etc. This change requires | |
* also modifying the code in this.render() to account for renderables. | |
* | |
* TODO: Make a sibling method to reset the content area. | |
*/ | |
setContent(node) { | |
this.contentMol.add(node); | |
} | |
/** | |
* Add a scenegraph to the menu area of the PushMenuLayout. If the object | |
* that you pass into setMenu is an infamous component, or a famo.us | |
* Surface, then it's events will be piped to this PushMenuLayout's input | |
* sync so that the user can open and close the menu with touch or mouse. | |
* General advice here would be to keep whatever you put into the menu | |
* contained within the boundaries of the menu or you might have touch and | |
* mouse interaction outside of the menu. | |
* | |
* @param {module: famous/src/core/RenderNode} node A scenegraph, i.e. a | |
* RenderNode with stuff in it. | |
* | |
* TODO: Accept plain renderables, f.e. Surfaces, etc. | |
* | |
* TODO: Remove old content before adding new content. | |
*/ | |
setMenu(node) { | |
this.menuContentMol.add(node); | |
if (node instanceof Molecule) { | |
node.pipe(this.touchSync); | |
node.on('mouseenter', function() { | |
if (!this.isOpening) { | |
this.openMenu(); | |
} | |
}.bind(this)); | |
node.on('mouseleave', function() { | |
if (!this.isClosing) { | |
this.closeMenu(); | |
} | |
}.bind(this)); | |
} | |
} | |
// TODO: replace menu easing with physics so the user can throw the menu, | |
// using initial velocity and drag to slow it down, and stop immediately | |
// when it hits the fully-open or fully-closed positions. | |
/** | |
* Opens the menu. | |
* | |
* @param {Function} callback The function to be called when the animation finishes. | |
* @param {boolean} [cancelPreviousCallback=false] This is optional. If | |
* true, then the callback of a previous open or close animation will be | |
* canceled if that animation was still inprogress when this method is | |
* called, otherwise the callback of the previous open or close animation | |
* will be fired immediately before the animation for this animation begins. | |
*/ | |
openMenu(callback, cancelPreviousCallback) { | |
this._haltAnimation(cancelPreviousCallback); | |
this.isClosing = false; | |
this.isOpening = true; | |
this._animate('open', callback); | |
} | |
/** | |
* Closes the menu. | |
* | |
* @param {Function} callback The function to be called when the animation finishes. | |
* @param {boolean} [cancelPreviousCallback=false] This is optional. If | |
* true, then the callback of a previous open or close animation will be | |
* canceled if that animation was still inprogress when this method is | |
* called, otherwise the callback of the previous open or close animation | |
* will be fired immediately before the animation for this animation begins. | |
*/ | |
closeMenu(callback, cancelPreviousCallback) { | |
this._haltAnimation(cancelPreviousCallback); | |
this.isClosing = true; | |
this.isOpening = false; | |
this._animate('close', callback); | |
} | |
/** | |
* Toggles the menu open or closed. If the menu is open or is opening, then it will now start | |
* closing, and vice versa. | |
* | |
* @param {Function} callback The function to be called when the animation finishes. | |
* @param {boolean} [cancelPreviousCallback=false] This is optional. If | |
* true, then the callback of a previous open or close animation will be | |
* canceled if that animation was still inprogress when this method is | |
* called, otherwise the callback of the previous open or close animation | |
* will be fired immediately before the animation for this animation begins. | |
*/ | |
toggleMenu(callback, cancelPreviousCallback) { | |
if (this.isOpen || this.isOpening) { | |
this.closeMenu(callback, cancelPreviousCallback); | |
} | |
else if (!this.isOpen || this.isClosing) { | |
this.openMenu(callback, cancelPreviousCallback); | |
} | |
} | |
/** | |
* Animates the menu to it's target state. | |
* | |
* @private | |
* @param {String} targetState The name of the state to animate to. | |
* @param {Function} callback The function to call after the animation completes. | |
*/ | |
_animate(targetState, callback) { | |
this.isAnimating = true; | |
this.transitionCallback = callback; | |
var _callback; | |
var self = this; | |
function setupCallback(numberOfTransitions) { | |
// Fire callback after numberOfTransitions calls, when the 4 transitions are complete. | |
_callback = callAfter(numberOfTransitions, function() { | |
self.isAnimating = self.isOpening = self.isClosing = false; | |
self.isOpen = targetState == 'open'? true: false; | |
if (typeof self.transitionCallback == 'function') { | |
self.transitionCallback(); | |
} | |
self.transitionCallback = undefined; | |
}.bind(self)); | |
} | |
setupCallback(1); | |
if (targetState == 'open') { | |
this.animationTransition.set(1, {duration: this.options.animationDuration, curve: Easing_1.outExpo}, _callback); | |
} | |
else if (targetState == 'close') { | |
this.animationTransition.set(0, {duration: this.options.animationDuration, curve: Easing_1.outExpo}, _callback); | |
} | |
} | |
/** | |
* Halts the current animation, if any. | |
* | |
* @private | |
* @param {boolean} [cancelCallback=false] Defaults to false. If true, the | |
* halted animation's callback won't fire, otherwise it will be fired. | |
*/ | |
_haltAnimation(cancelCallback) { | |
if (this.isAnimating) { | |
if (!cancelCallback && typeof this.transitionCallback == 'function') { | |
this.transitionCallback(); | |
} | |
this.transitionCallback = undefined; | |
this.animationTransition.halt(); | |
} | |
} | |
/** | |
* @override | |
*/ | |
render() { | |
// Blur the content if this.options.blur is true, and the animation is moveBack. | |
// | |
// TODO: Make the item to to be blur specifiable, perhaps with a method on | |
// this. | |
if (this.options.blur && this.options.fade && this.options.animationType == 'moveBack') { | |
let momentaryBlur = (this.animationTransition.get() * this.options.blurRadius); | |
let filter = { | |
"-webkit-filter": 'blur('+momentaryBlur+'px)', | |
"-moz-filter": 'blur('+momentaryBlur+'px)', | |
"-ms-filter": 'blur('+momentaryBlur+'px)', | |
"-o-filter": 'blur('+momentaryBlur+'px)', | |
filter: 'blur('+momentaryBlur+'px)' | |
}; | |
// TODO TODO TODO v0.1.0: Make fadePlane a sibling with menu and | |
// content molecules or the following breaks if fade is false. | |
// Then remove the check for this.options.fade in the previous if | |
// statement above. | |
if (this.contentMol._child[1].get() instanceof Surface_1) { | |
this.contentMol.get().setProperties(filter); | |
} | |
else if (this.contentMol._child[1] instanceof Plane) { | |
this.contentMol._child[1].surface.setProperties(filter); | |
} | |
} | |
return super.render() | |
} | |
} | |
// A reusable array, to avoid allocating new arrays during multiplication. | |
// in column-major order: | |
const scratch = [ | |
/*m11*/0, /*m12*/0, /*m13*/0, /*m14*/0, | |
/*m21*/0, /*m22*/0, /*m23*/0, /*m24*/0, | |
/*m31*/0, /*m32*/0, /*m33*/0, /*m34*/0, | |
/*m41*/0, /*m42*/0, /*m43*/0, /*m44*/0 ]; | |
function multiplyAndApply(A, B, target) { | |
//XXX: Are the following calculations faster hard coded (current), or as a loop? | |
scratch[0] = (A.m11 * B.m11) + (A.m21 * B.m12) + (A.m31 * B.m13) + (A.m41 * B.m14); | |
scratch[4] = (A.m11 * B.m21) + (A.m21 * B.m22) + (A.m31 * B.m23) + (A.m41 * B.m24); | |
scratch[8] = (A.m11 * B.m31) + (A.m21 * B.m32) + (A.m31 * B.m33) + (A.m41 * B.m34); | |
scratch[12] = (A.m11 * B.m41) + (A.m21 * B.m42) + (A.m31 * B.m43) + (A.m41 * B.m44); | |
scratch[1] = (A.m12 * B.m11) + (A.m22 * B.m12) + (A.m32 * B.m13) + (A.m42 * B.m14); | |
scratch[5] = (A.m12 * B.m21) + (A.m22 * B.m22) + (A.m32 * B.m23) + (A.m42 * B.m24); | |
scratch[9] = (A.m12 * B.m31) + (A.m22 * B.m32) + (A.m32 * B.m33) + (A.m42 * B.m34); | |
scratch[13] = (A.m12 * B.m41) + (A.m22 * B.m42) + (A.m32 * B.m43) + (A.m42 * B.m44); | |
scratch[2] = (A.m13 * B.m11) + (A.m23 * B.m12) + (A.m33 * B.m13) + (A.m43 * B.m14); | |
scratch[6] = (A.m13 * B.m21) + (A.m23 * B.m22) + (A.m33 * B.m23) + (A.m43 * B.m24); | |
scratch[10] = (A.m13 * B.m31) + (A.m23 * B.m32) + (A.m33 * B.m33) + (A.m43 * B.m34); | |
scratch[14] = (A.m13 * B.m41) + (A.m23 * B.m42) + (A.m33 * B.m43) + (A.m43 * B.m44); | |
scratch[3] = (A.m14 * B.m11) + (A.m24 * B.m12) + (A.m34 * B.m13) + (A.m44 * B.m14); | |
scratch[7] = (A.m14 * B.m21) + (A.m24 * B.m22) + (A.m34 * B.m23) + (A.m44 * B.m24); | |
scratch[11] = (A.m14 * B.m31) + (A.m24 * B.m32) + (A.m34 * B.m33) + (A.m44 * B.m34); | |
scratch[15] = (A.m14 * B.m41) + (A.m24 * B.m42) + (A.m34 * B.m43) + (A.m44 * B.m44); | |
applyArrayValuesToDOMMatrix(scratch, target); | |
} | |
function applyArrayValuesToDOMMatrix(array, matrix) { | |
const length = array.length; | |
if (length === 6) { | |
matrix.m11 = array[0]; | |
matrix.m12 = array[1]; | |
matrix.m21 = array[2]; | |
matrix.m22 = array[3]; | |
matrix.m41 = array[4]; | |
matrix.m42 = array[5]; | |
} | |
else if (length === 16) { | |
matrix.m11 = array[0]; | |
matrix.m12 = array[1]; | |
matrix.m13 = array[2]; | |
matrix.m14 = array[3]; | |
matrix.m21 = array[4]; | |
matrix.m22 = array[5]; | |
matrix.m23 = array[6]; | |
matrix.m24 = array[7]; | |
matrix.m31 = array[8]; | |
matrix.m32 = array[9]; | |
matrix.m33 = array[10]; | |
matrix.m34 = array[11]; | |
matrix.m41 = array[12]; | |
matrix.m42 = array[13]; | |
matrix.m43 = array[14]; | |
matrix.m44 = array[15]; | |
} | |
} | |
function rotateAxisAngleArray(x, y, z, angle) { | |
var sin = Math.sin; | |
var cos = Math.cos; | |
var pow = Math.pow; | |
const halfAngle = degreesToRadians(angle/2); | |
// TODO: should we provide a 6-item array here to signify 2D when the | |
// rotation is about the Z axis (for example when calling rotateSelf)? | |
// TODO: Performance can be improved by first detecting when x, y, or z of | |
// the axis are zero or 1, and using a pre-simplified version of the | |
// folowing math based on that condition. | |
// TODO: Performance can be improved by using different equations (use trig | |
// identities to find alternate formulas). | |
return [ | |
1-2*(y*y + z*z)*pow(sin(halfAngle), 2), 2*(x*y*pow(sin(halfAngle), 2) + z*sin(halfAngle)*cos(halfAngle)), 2*(x*z*pow(sin(halfAngle), 2) - y*sin(halfAngle)*cos(halfAngle)), 0, | |
2*(x*y*pow(sin(halfAngle), 2) - z*sin(halfAngle)*cos(halfAngle)), 1-2*(x*x + z*z)*pow(sin(halfAngle), 2), 2*(y*z*pow(sin(halfAngle), 2) + x*sin(halfAngle)*cos(halfAngle)), 0, | |
2*(x*z*pow(sin(halfAngle), 2) + y*sin(halfAngle)*cos(halfAngle)), 2*(y*z*pow(sin(halfAngle), 2) - x*sin(halfAngle)*cos(halfAngle)), 1-2*(x*x + y*y)*pow(sin(halfAngle), 2), 0, | |
0, 0, 0, 1 ] | |
} | |
function degreesToRadians(degrees) { | |
return Math.PI/180 * degrees | |
} | |
// This matrix is represented internally in row-major format so that it is easy | |
// to look at visually. In a pair of coordinates (as in "m23") the first number | |
// is the column and the second is the row (so "m23" means column 2 row 3). | |
const identity = [ | |
/*m11*/1, /*m21*/0, /*m31*/0, /*m41*/0, | |
/*m12*/0, /*m22*/1, /*m32*/0, /*m42*/0, | |
/*m13*/0, /*m23*/0, /*m33*/1, /*m43*/0, | |
/*m14*/0, /*m24*/0, /*m34*/0, /*m44*/1 ]; | |
var DOMMatrixReadOnly = null; | |
function initDOMMatrixReadOnly() { | |
if (DOMMatrixReadOnly) { return } | |
DOMMatrixReadOnly = (function () { | |
function DOMMatrixReadOnly(numberSequence) { | |
if ( numberSequence === void 0 ) { numberSequence = []; } | |
if (!(this instanceof DOMMatrix)) | |
{ throw new TypeError("DOMMatrixReadOnly can't be instantiated directly. Use DOMMatrix instead.") } | |
var length = numberSequence.length; | |
if (length === undefined || !(length === 6 || length === 16)) | |
{ throw new TypeError('DOMMatrix constructor argument "numberSequence" must be an array-like with 6 or 16 numbers.') } | |
this._matrix = new Float64Array(identity); | |
this._isIdentity = true; | |
this._is2D = length === 6 ? true : false; | |
applyArrayValuesToDOMMatrix(numberSequence, this); | |
} | |
var prototypeAccessors = { is2D: {},isIdentity: {},a: {},b: {},c: {},d: {},e: {},f: {},m11: {},m12: {},m13: {},m14: {},m21: {},m22: {},m23: {},m24: {},m31: {},m32: {},m33: {},m34: {},m41: {},m42: {},m43: {},m44: {} }; | |
// Immutable transform methods ------------------------------------------- | |
DOMMatrixReadOnly.prototype.translate = function translate (tx, ty, tz) { | |
if ( tz === void 0 ) { tz = 0; } | |
return new DOMMatrix(this).translateSelf(tx, ty, tz) | |
}; | |
DOMMatrixReadOnly.prototype.scale = function scale (scale$1, originX, originY) { | |
if ( originX === void 0 ) { originX = 0; } | |
if ( originY === void 0 ) { originY = 0; } | |
return new DOMMatrix(this).scaleSelf(scale$1, originX, originY) | |
}; | |
DOMMatrixReadOnly.prototype.scale3d = function scale3d (scale, originX, originY, originZ) { | |
if ( originX === void 0 ) { originX = 0; } | |
if ( originY === void 0 ) { originY = 0; } | |
if ( originZ === void 0 ) { originZ = 0; } | |
return new DOMMatrix(this).scale3dSelf(scale, originX, originY, originZ) | |
}; | |
DOMMatrixReadOnly.prototype.scaleNonUniform = function scaleNonUniform (scaleX, scaleY, scaleZ, originX, originY, originZ) { | |
if ( scaleY === void 0 ) { scaleY = 1; } | |
if ( scaleZ === void 0 ) { scaleZ = 1; } | |
if ( originX === void 0 ) { originX = 0; } | |
if ( originY === void 0 ) { originY = 0; } | |
if ( originZ === void 0 ) { originZ = 0; } | |
return new DOMMatrix(this).scaleNonUniformSelf(scaleX, scaleY, scaleZ, originX, originY, originZ) | |
}; | |
DOMMatrixReadOnly.prototype.rotate = function rotate (angle, originX, originY) { | |
if ( originX === void 0 ) { originX = 0; } | |
if ( originY === void 0 ) { originY = 0; } | |
return new DOMMatrix(this).rotateSelf(angle, originX, originY) | |
}; | |
// TODO | |
DOMMatrixReadOnly.prototype.rotateFromVector = function rotateFromVector (x, y) { | |
throw new Error('rotateFromVector is not implemented yet.') | |
}; | |
DOMMatrixReadOnly.prototype.rotateAxisAngle = function rotateAxisAngle (x, y, z, angle) { | |
return new DOMMatrix(this).rotateAxisAngleSelf(x, y, z, angle) | |
}; | |
DOMMatrixReadOnly.prototype.skewX = function skewX (sx) { | |
throw new Error('skewX is not implemented yet.') | |
}; | |
DOMMatrixReadOnly.prototype.skewY = function skewY (sy) { | |
throw new Error('skewY is not implemented yet.') | |
}; | |
DOMMatrixReadOnly.prototype.multiply = function multiply (other) { | |
return new DOMMatrix(this).multiplySelf(other) | |
}; | |
DOMMatrixReadOnly.prototype.flipX = function flipX () { | |
throw new Error('flipX is not implemented yet.') | |
}; | |
DOMMatrixReadOnly.prototype.flipY = function flipY () { | |
throw new Error('flipY is not implemented yet.') | |
}; | |
DOMMatrixReadOnly.prototype.inverse = function inverse () { | |
throw new Error('inverse is not implemented yet.') | |
}; | |
DOMMatrixReadOnly.prototype.transformPoint = function transformPoint (/*optional DOMPointInit*/ point) { | |
throw new Error('transformPoint is not implemented yet.') | |
}; | |
DOMMatrixReadOnly.prototype.toFloat32Array = function toFloat32Array () { | |
return Float32Array.from(this._matrix) | |
}; | |
DOMMatrixReadOnly.prototype.toFloat64Array = function toFloat64Array () { | |
return Float64Array.from(this._matrix) | |
}; | |
//stringifier() {} // What's this? | |
prototypeAccessors.is2D.get = function () { | |
return this._is2D | |
}; | |
/* | |
* TODO: make sure this matches the spec. | |
* TODO: Instead of calculating here, perhaps calculate and set | |
* this._isIdentity in other operations, and simply return the internal one | |
* here. | |
*/ | |
prototypeAccessors.isIdentity.get = function () { | |
var this$1 = this; | |
for (var i = 0, len = this._matrix.length; i < len; i+=1) { | |
if (this$1._matrix[i] != identity[i]) | |
{ return (this$1._isIdentity = false) } | |
} | |
return (this._isIdentity = true) | |
}; | |
prototypeAccessors.a.get = function () { return this.m11 }; | |
prototypeAccessors.b.get = function () { return this.m12 }; | |
prototypeAccessors.c.get = function () { return this.m21 }; | |
prototypeAccessors.d.get = function () { return this.m22 }; | |
prototypeAccessors.e.get = function () { return this.m41 }; | |
prototypeAccessors.f.get = function () { return this.m42 }; | |
prototypeAccessors.m11.get = function () { return this._matrix[0] }; | |
prototypeAccessors.m12.get = function () { return this._matrix[4] }; | |
prototypeAccessors.m13.get = function () { return this._matrix[8] }; | |
prototypeAccessors.m14.get = function () { return this._matrix[12] }; | |
prototypeAccessors.m21.get = function () { return this._matrix[1] }; | |
prototypeAccessors.m22.get = function () { return this._matrix[5] }; | |
prototypeAccessors.m23.get = function () { return this._matrix[9] }; | |
prototypeAccessors.m24.get = function () { return this._matrix[13] }; | |
prototypeAccessors.m31.get = function () { return this._matrix[2] }; | |
prototypeAccessors.m32.get = function () { return this._matrix[6] }; | |
prototypeAccessors.m33.get = function () { return this._matrix[10] }; | |
prototypeAccessors.m34.get = function () { return this._matrix[14] }; | |
prototypeAccessors.m41.get = function () { return this._matrix[3] }; | |
prototypeAccessors.m42.get = function () { return this._matrix[7] }; | |
prototypeAccessors.m43.get = function () { return this._matrix[11] }; | |
prototypeAccessors.m44.get = function () { return this._matrix[15] }; | |
Object.defineProperties( DOMMatrixReadOnly.prototype, prototypeAccessors ); | |
return DOMMatrixReadOnly; | |
}()); | |
} | |
initDOMMatrixReadOnly(); | |
initDOMMatrixReadOnly(); | |
var DOMMatrix = (function (DOMMatrixReadOnly$$1) { | |
function DOMMatrix(arg) { | |
const numArgs = arguments.length; | |
if (numArgs === 0) { | |
DOMMatrixReadOnly$$1.call(this, [1, 0, 0, 1, 0, 0]); | |
} | |
else if (numArgs === 1) { | |
if (typeof arg == 'string') { | |
throw new Error('CSS transformList arg not yet implemented.') | |
// TODO validate that syntax of transformList matches transform-list (http://www.w3.org/TR/css-transforms-1/#typedef-transform-list). | |
} | |
else if (arg instanceof DOMMatrix) { | |
DOMMatrixReadOnly$$1.call(this, arg._matrix); | |
} | |
else if (arg instanceof Float32Array || arg instanceof Float64Array || arg instanceof Array) { | |
DOMMatrixReadOnly$$1.call(this, arg); | |
} | |
} | |
else { | |
throw new Error('Wrong number of arguments to DOMMatrix constructor.') | |
} | |
} | |
if ( DOMMatrixReadOnly$$1 ) { DOMMatrix.__proto__ = DOMMatrixReadOnly$$1; } | |
DOMMatrix.prototype = Object.create( DOMMatrixReadOnly$$1 && DOMMatrixReadOnly$$1.prototype ); | |
DOMMatrix.prototype.constructor = DOMMatrix; | |
var prototypeAccessors = { a: {},b: {},c: {},d: {},e: {},f: {},m11: {},m12: {},m13: {},m14: {},m21: {},m22: {},m23: {},m24: {},m31: {},m32: {},m33: {},m34: {},m41: {},m42: {},m43: {},m44: {} }; | |
// Mutable transform methods | |
DOMMatrix.prototype.multiplySelf = function multiplySelf (other) { | |
if (!(other instanceof DOMMatrix)) | |
{ throw new Error('The argument to multiplySelf must be an instance of DOMMatrix') } | |
// TODO: avoid creating a new array, just apply values directly. | |
multiplyAndApply(this, other, this); | |
if (!other.is2D) { this._is2D = false; } | |
return this | |
}; | |
DOMMatrix.prototype.preMultiplySelf = function preMultiplySelf (other) { | |
if (!(other instanceof DOMMatrix)) | |
{ throw new Error('The argument to multiplySelf must be an instance of DOMMatrix') } | |
// TODO: avoid creating a new array, just apply values directly. | |
multiplyAndApply(other, this, this); | |
if (!other.is2D) { this._is2D = false; } | |
return this | |
}; | |
DOMMatrix.prototype.translateSelf = function translateSelf (tx, ty, tz) { | |
if ( tz === void 0 ) { tz = 0; } | |
// TODO: check args are numbers | |
if (arguments.length === 1) | |
{ throw new Error('The first two arguments (X and Y translation values) are required (the third, Z translation, is optional).') } | |
// http://www.w3.org/TR/2012/WD-css3-transforms-20120911/#Translate3dDefined | |
const translationMatrix = new DOMMatrix([ | |
// column-major: | |
1, 0, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 1, 0, | |
tx,ty,tz,1 ]); | |
this.multiplySelf(translationMatrix); | |
if (tz != 0) { | |
this._is2D = false; | |
} | |
return this | |
}; | |
DOMMatrix.prototype.scaleSelf = function scaleSelf (scale, originX, originY) { | |
if ( originX === void 0 ) { originX = 0; } | |
if ( originY === void 0 ) { originY = 0; } | |
this.translateSelf(originX, originY); | |
this.multiplySelf(new DOMMatrix([ | |
// 2D: | |
/*a*/scale, /*b*/0, | |
/*c*/0, /*d*/scale, | |
/*e*/0, /*f*/0 ])); | |
this.translateSelf(-originX, -originY); | |
return this | |
}; | |
DOMMatrix.prototype.scale3dSelf = function scale3dSelf (scale, originX, originY, originZ) { | |
if ( originX === void 0 ) { originX = 0; } | |
if ( originY === void 0 ) { originY = 0; } | |
if ( originZ === void 0 ) { originZ = 0; } | |
this.translateSelf(originX, originY, originZ); | |
this.multiplySelf(new DOMMatrix([ | |
// 3D | |
scale, 0, 0, 0, | |
0, scale, 0, 0, | |
0, 0, scale, 0, | |
0, 0, 0, 1 ])); | |
this.translateSelf(-originX, -originY, -originZ); | |
return this | |
}; | |
DOMMatrix.prototype.scaleNonUniformSelf = function scaleNonUniformSelf (scaleX, scaleY, scaleZ, originX, originY, originZ) { | |
if ( scaleY === void 0 ) { scaleY = 1; } | |
if ( scaleZ === void 0 ) { scaleZ = 1; } | |
if ( originX === void 0 ) { originX = 0; } | |
if ( originY === void 0 ) { originY = 0; } | |
if ( originZ === void 0 ) { originZ = 0; } | |
this.translateSelf(originX, originY, originZ); | |
this.multiplySelf(new DOMMatrix([ | |
// 3D | |
scaleX, 0, 0, 0, | |
0, scaleY, 0, 0, | |
0, 0, scaleZ, 0, | |
0, 0, 0, 1 ])); | |
this.translateSelf(-originX, -originY, -originZ); | |
if (scaleZ !== 1 || originZ !== 0) { this._is2D = false; } | |
return this | |
}; | |
DOMMatrix.prototype.rotateSelf = function rotateSelf (angle, originX, originY) { | |
if ( originX === void 0 ) { originX = 0; } | |
if ( originY === void 0 ) { originY = 0; } | |
this.translateSelf(originX, originY); | |
// axis of rotation | |
var ref = [0,0,1]; | |
var x = ref[0]; | |
var y = ref[1]; | |
var z = ref[2]; // We're rotating around the Z axis. | |
this.rotateAxisAngleSelf(x, y, z, angle); | |
this.translateSelf(-originX, -originY); | |
return this | |
}; | |
// TODO | |
DOMMatrix.prototype.rotateFromVectorSelf = function rotateFromVectorSelf (x, y) { | |
throw new Error('rotateFromVectorSelf is not implemented yet.') | |
}; | |
DOMMatrix.prototype.rotateAxisAngleSelf = function rotateAxisAngleSelf (x, y, z, angle) { | |
const rotationMatrix = new DOMMatrix(rotateAxisAngleArray(x,y,z,angle)); | |
this.multiplySelf(rotationMatrix); | |
return this | |
}; | |
DOMMatrix.prototype.skewXSelf = function skewXSelf (sx) { | |
throw new Error('skewXSelf is not implemented yet.') | |
}; | |
DOMMatrix.prototype.skewYSelf = function skewYSelf (sy) { | |
throw new Error('skewYSelf is not implemented yet.') | |
}; | |
DOMMatrix.prototype.invertSelf = function invertSelf () { | |
throw new Error('invertSelf is not implemented yet.') | |
}; | |
DOMMatrix.prototype.setMatrixValue = function setMatrixValue (/*DOMString*/ transformList) { | |
throw new Error('setMatrixValue is not implemented yet.') | |
}; | |
prototypeAccessors.a.get = function () { return this.m11 }; | |
prototypeAccessors.b.get = function () { return this.m12 }; | |
prototypeAccessors.c.get = function () { return this.m21 }; | |
prototypeAccessors.d.get = function () { return this.m22 }; | |
prototypeAccessors.e.get = function () { return this.m41 }; | |
prototypeAccessors.f.get = function () { return this.m42 }; | |
prototypeAccessors.m11.get = function () { return this._matrix[0] }; | |
prototypeAccessors.m12.get = function () { return this._matrix[4] }; | |
prototypeAccessors.m13.get = function () { return this._matrix[8] }; | |
prototypeAccessors.m14.get = function () { return this._matrix[12] }; | |
prototypeAccessors.m21.get = function () { return this._matrix[1] }; | |
prototypeAccessors.m22.get = function () { return this._matrix[5] }; | |
prototypeAccessors.m23.get = function () { return this._matrix[9] }; | |
prototypeAccessors.m24.get = function () { return this._matrix[13] }; | |
prototypeAccessors.m31.get = function () { return this._matrix[2] }; | |
prototypeAccessors.m32.get = function () { return this._matrix[6] }; | |
prototypeAccessors.m33.get = function () { return this._matrix[10] }; | |
prototypeAccessors.m34.get = function () { return this._matrix[14] }; | |
prototypeAccessors.m41.get = function () { return this._matrix[3] }; | |
prototypeAccessors.m42.get = function () { return this._matrix[7] }; | |
prototypeAccessors.m43.get = function () { return this._matrix[11] }; | |
prototypeAccessors.m44.get = function () { return this._matrix[15] }; | |
prototypeAccessors.a.set = function (value) { this.m11 = value; }; | |
prototypeAccessors.b.set = function (value) { this.m12 = value; }; | |
prototypeAccessors.c.set = function (value) { this.m21 = value; }; | |
prototypeAccessors.d.set = function (value) { this.m22 = value; }; | |
prototypeAccessors.e.set = function (value) { this.m41 = value; }; | |
prototypeAccessors.f.set = function (value) { this.m42 = value; }; | |
prototypeAccessors.m11.set = function (value) { this._matrix[0] = value; }; | |
prototypeAccessors.m12.set = function (value) { this._matrix[4] = value; }; | |
prototypeAccessors.m13.set = function (value) { this._matrix[8] = value; }; | |
prototypeAccessors.m14.set = function (value) { this._matrix[12] = value; }; | |
prototypeAccessors.m21.set = function (value) { this._matrix[1] = value; }; | |
prototypeAccessors.m22.set = function (value) { this._matrix[5] = value; }; | |
prototypeAccessors.m23.set = function (value) { this._matrix[9] = value; }; | |
prototypeAccessors.m24.set = function (value) { this._matrix[13] = value; }; | |
prototypeAccessors.m31.set = function (value) { this._matrix[2] = value; }; | |
prototypeAccessors.m32.set = function (value) { this._matrix[6] = value; }; | |
prototypeAccessors.m33.set = function (value) { this._matrix[10] = value; }; | |
prototypeAccessors.m34.set = function (value) { this._matrix[14] = value; }; | |
prototypeAccessors.m41.set = function (value) { this._matrix[3] = value; }; | |
prototypeAccessors.m42.set = function (value) { this._matrix[7] = value; }; | |
prototypeAccessors.m43.set = function (value) { this._matrix[11] = value; }; | |
prototypeAccessors.m44.set = function (value) { this._matrix[15] = value; }; | |
Object.defineProperties( DOMMatrix.prototype, prototypeAccessors ); | |
return DOMMatrix; | |
}(DOMMatrixReadOnly)); | |
let privatesMap; | |
const _ = function (o) { | |
if (!privatesMap) { | |
privatesMap = new WeakMap; | |
let privates = {}; | |
privatesMap.set(o, privates); | |
return privates | |
} | |
else { | |
let privates = privatesMap.get(o); | |
if (privates === undefined) { | |
privates = {}; | |
privatesMap.set(o, privates); | |
} | |
return privates | |
} | |
}; | |
var DOMPointReadOnly = function DOMPointReadOnly(x,y,z,w) { | |
if (arguments.length === 1) { | |
if (!isDOMPointInit(x)) | |
{ throw new TypeError('Expected an object with x, y, z, and w properties') } | |
_(this).x = x.x; | |
_(this).y = x.y; | |
_(this).z = x.z; | |
_(this).w = x.w; | |
} | |
else if (arguments.length === 4) { | |
_(this).x = x || 0; | |
_(this).y = y || 0; | |
_(this).z = z || 0; | |
_(this).w = w || 0; | |
} | |
else { | |
throw new TypeError('Expected 1 or 4 arguments') | |
} | |
}; | |
var prototypeAccessors = { x: {},y: {},z: {},w: {} }; | |
prototypeAccessors.x.get = function () { return _(this).x }; | |
prototypeAccessors.y.get = function () { return _(this).y }; | |
prototypeAccessors.z.get = function () { return _(this).z }; | |
prototypeAccessors.w.get = function () { return _(this).w }; | |
DOMPointReadOnly.prototype.matrixTransform = function matrixTransform (matrix) { | |
let result = new this.constructor(this); | |
// TODO | |
//const x | |
//const y | |
//const z | |
//const w | |
return result | |
}; | |
DOMPointReadOnly.fromPoint = function fromPoint (other) { | |
return new this(other) | |
}; | |
Object.defineProperties( DOMPointReadOnly.prototype, prototypeAccessors ); | |
var DOMPoint = (function (DOMPointReadOnly) { | |
function DOMPoint () { | |
DOMPointReadOnly.apply(this, arguments); | |
} | |
if ( DOMPointReadOnly ) { DOMPoint.__proto__ = DOMPointReadOnly; } | |
DOMPoint.prototype = Object.create( DOMPointReadOnly && DOMPointReadOnly.prototype ); | |
DOMPoint.prototype.constructor = DOMPoint; | |
var prototypeAccessors$1 = { x: {},y: {},z: {},w: {} }; | |
prototypeAccessors$1.x.set = function (value) { _(this).x = value; }; | |
prototypeAccessors$1.y.set = function (value) { _(this).y = value; }; | |
prototypeAccessors$1.z.set = function (value) { _(this).z = value; }; | |
prototypeAccessors$1.w.set = function (value) { _(this).w = value; }; | |
Object.defineProperties( DOMPoint.prototype, prototypeAccessors$1 ); | |
return DOMPoint; | |
}(DOMPointReadOnly)); | |
function isDOMPointInit(o) { | |
if (typeof o != 'object') { return false } | |
if ( | |
'x' in o && | |
'y' in o && | |
'z' in o && | |
'w' in o | |
) { return true } | |
return false | |
} | |
let _global = null; | |
// browser | |
if (typeof window != 'undefined') { | |
_global = window; | |
} | |
else if (typeof global != 'undefined') { | |
_global = global; | |
} | |
if (_global) { | |
_global.DOMMatrix = DOMMatrix; | |
_global.DOMMatrixReadOnly = DOMMatrixReadOnly; | |
_global.DOMPoint = DOMPoint; | |
_global.DOMPointReadOnly = DOMPointReadOnly; | |
} | |
const instanceofSymbol$2 = Symbol('instanceofSymbol'); | |
const ObservableMixin = base => { | |
class Observable extends base { | |
constructor(options) { | |
if ( options === void 0 ) options = {}; | |
super(options); | |
} | |
on(eventName, callback) { | |
if (!this._eventMap) | |
{ this._eventMap = new Map; } | |
if (!this._eventMap.has(eventName)) | |
{ this._eventMap.set(eventName, []); } | |
if (typeof callback == 'function') | |
{ this._eventMap.get(eventName).push(callback); } | |
else | |
{ throw new Error('Expected a function in callback argument of Observable#on.') } | |
} | |
off(eventName, callback) { | |
if (!this._eventMap || !this._eventMap.has(eventName)) { return } | |
const callbacks = this._eventMap.get(eventName); | |
if (callbacks.indexOf(callback) === -1) { return } | |
callbacks.splice(callbacks.indexOf(callback), 1); | |
if (callbacks.length === 0) { this._eventMap.delete(eventName); } | |
if (this._eventMap.size === 0) { this._eventMap = null; } | |
} | |
triggerEvent(eventName, data) { | |
if (!this._eventMap || !this._eventMap.has(eventName)) { return } | |
const callbacks = this._eventMap.get(eventName); | |
for (let i=0, len=callbacks.length; i<len; i+=1) { | |
callbacks[i](data); | |
} | |
} | |
} | |
Object.defineProperty(Observable, Symbol.hasInstance, { | |
value: function(obj) { | |
if (this !== Observable) { return Object.getPrototypeOf(Observable)[Symbol.hasInstance].call(this, obj) } | |
let currentProto = obj; | |
while(currentProto) { | |
const desc = Object.getOwnPropertyDescriptor(currentProto, "constructor"); | |
if (desc && desc.value && desc.value.hasOwnProperty(instanceofSymbol$2)) | |
{ return true } | |
currentProto = Object.getPrototypeOf(currentProto); | |
} | |
return false | |
} | |
}); | |
Observable[instanceofSymbol$2] = true; | |
return Observable | |
}; | |
const Observable = ObservableMixin(class{}); | |
Observable.mixin = ObservableMixin; | |
/** | |
* Represents a set of values for the X, Y, and Z axes. For example, the | |
* position of an object can be described using a set of 3 numbers, one for each | |
* axis in 3D space: {x:10, y:10, z:10}. | |
* | |
* The value don't have to be numerical. For example, | |
* {x:'foo', y:'bar', z:'baz'} | |
*/ | |
class XYZValues extends Observable { | |
constructor(x, y, z) { | |
if ( x === void 0 ) x = 0; | |
if ( y === void 0 ) y = 0; | |
if ( z === void 0 ) z = 0; | |
super(); | |
this._x = x; | |
this._y = y; | |
this._z = z; | |
} | |
set x(value) { | |
this._x = value; | |
this.triggerEvent('valuechanged', {x: value}); | |
} | |
get x() { return this._x } | |
set y(value) { | |
this._y = value; | |
this.triggerEvent('valuechanged', {y: value}); | |
} | |
get y() { return this._y } | |
set z(value) { | |
this._z = value; | |
this.triggerEvent('valuechanged', {z: value}); | |
} | |
get z() { return this._z } | |
} | |
function epsilon(value) { | |
return Math.abs(value) < 0.000001 ? 0 : value; | |
} | |
function applyCSSLabel(value, label) { | |
if (value === 0) { | |
return '0px' | |
} else if (label === '%') { | |
return value * 100 + '%'; | |
} else if (label === 'px') { | |
return value + 'px' | |
} | |
} | |
function animationFrame() { | |
let resolve = null; | |
const promise = new Promise(r => resolve = r); | |
window.requestAnimationFrame(resolve); | |
return promise | |
} | |
// Create lowercase versions of each setter property. | |
// we care only about the setters, for now. | |
function makeLowercaseSetterAliases(object) { | |
const props = Object.getOwnPropertyNames(object); | |
for (let l=props.length, i=0; i<l; i+=1) { | |
const prop = props[i]; | |
const lowercaseProp = prop.toLowerCase(); | |
if (lowercaseProp != prop) { | |
const descriptor = Object.getOwnPropertyDescriptor(object, prop); | |
if (typeof descriptor.set != 'undefined') { | |
Object.defineProperty(object, lowercaseProp, descriptor); | |
} | |
} | |
} | |
} | |
let childObservationHandlers = null; | |
let childObserver = null; | |
function observeChildren(ctx, onConnect, onDisconnect) { | |
if (!childObservationHandlers) { childObservationHandlers = new Map; } | |
if (!childObserver) { childObserver = createChildObserver(); } | |
childObservationHandlers.set(ctx, {onConnect, onDisconnect}); | |
childObserver.observe(ctx, { childList: true }); | |
return true | |
} | |
// NOTE: If a child is disconnected then connected to the same parent in the | |
// same turn, then the onConnect and onDisconnect callbacks won't be called | |
// because the DOM tree will be back in the exact state as before (this is | |
// possible thanks to the logic associated with weightsPerTarget). | |
function createChildObserver() { | |
return new MutationObserver(changes => { | |
const weightsPerTarget = new Map; | |
// We're just counting how many times each child node was added and | |
// removed from the parent we're observing. | |
for (let i=0, l=changes.length; i<l; i+=1) { | |
const change = changes[i]; | |
if (change.type != 'childList') { continue } | |
if (!weightsPerTarget.has(change.target)) | |
{ weightsPerTarget.set(change.target, new Map); } | |
const weights = weightsPerTarget.get(change.target); | |
var addedNodes = change.addedNodes; | |
for (let l=addedNodes.length, i=0; i<l; i+=1) | |
{ weights.set(addedNodes[i], (weights.get(addedNodes[i]) || 0) + 1); } | |
var removedNodes = change.removedNodes; | |
for (let l=removedNodes.length, i=0; i<l; i+=1) | |
{ weights.set(removedNodes[i], (weights.get(removedNodes[i]) || 0) - 1); } | |
} | |
// TODO PERFORMANCE: Can these for..of loops be converted to regular for loops? | |
for (var i = 0, list = weightsPerTarget; i < list.length; i += 1) { | |
const ref = list[i]; | |
var target = ref[0]; | |
var weights = ref[1]; | |
var ref$1 = childObservationHandlers.get(target); | |
var onConnect = ref$1.onConnect; | |
var onDisconnect = ref$1.onDisconnect; | |
for (var i$1 = 0, list$1 = weights; i$1 < list$1.length; i$1 += 1) { | |
// If the number of times a child was added is greater than the | |
// number of times it was removed, then the net result is that | |
// it was added, so we call onConnect just once. | |
const ref$2 = list$1[i$1]; | |
var node = ref$2[0]; | |
var weight = ref$2[1]; | |
if (weight > 0 && typeof onConnect == 'function') | |
{ onConnect.call(target, node); } | |
// If the number of times a child was added is less than the | |
// number of times it was removed, then the net result is that | |
// it was removed, so we call onConnect just once. | |
else if (weight < 0 && typeof onDisconnect == 'function') | |
{ onDisconnect.call(target, node); } | |
// If the number of times a child was added is equal to the | |
// number of times it was removed, then it was essentially left | |
// in place, so we don't call anything. | |
} | |
} | |
}) | |
} | |
const hasShadowDomV0 = | |
typeof Element.prototype.createShadowRoot == 'function' | |
&& typeof HTMLContentElement == 'function' | |
? true : false; | |
const hasShadowDomV1 = | |
typeof Element.prototype.attachShadow == 'function' | |
&& typeof HTMLSlotElement == 'function' | |
? true : false; | |
function getShadowRootVersion(shadowRoot) { | |
console.log('getShadowRootVersion'); | |
if (!shadowRoot) { return null } | |
const slot = document.createElement('slot'); | |
shadowRoot.appendChild(slot); | |
slot.appendChild(document.createElement('div')); | |
const assignedNodes = slot.assignedNodes({ flatten: true }); | |
slot.remove(); | |
console.log('hmm', assignedNodes.length, assignedNodes.length > 0 ? 'v1' : 'v0'); | |
return assignedNodes.length > 0 ? 'v1' : 'v0' | |
} | |
function getAncestorShadowRoot(node) { | |
let current = node; | |
while (current && !(current instanceof ShadowRoot)) { | |
current = current.parentNode; | |
} | |
return current | |
} | |
// in the future, the user will be able to toggle the HTML API. | |
const hasHtmlApi = true; | |
// Traverses a tree while considering ShadowDOM disribution. | |
function traverse(node, isShadowChild) { | |
console.log(isShadowChild ? 'distributedNode:' : 'node:', node); | |
var children = node.children; | |
for (let l=children.length, i=0; i<l; i+=1) { | |
// skip nodes that are possiblyDistributed, i.e. they have a parent | |
// that has a ShadowRoot. | |
if (!hasHtmlApi || !children[i]._elementManager.element._isPossiblyDistributed) | |
{ traverse(children[i]); } | |
} | |
const shadowChildren = node._elementManager.element._shadowChildren; | |
if (hasHtmlApi && shadowChildren) { | |
for (let l=shadowChildren.length, i=0; i<l; i+=1) | |
{ traverse(shadowChildren[i].imperativeCounterpart, true); } | |
} | |
} | |
// helper function to use instead of instanceof for classes that implement the | |
// static Symbol.hasInstance method, because the behavior of instanceof isn't | |
// polyfillable. | |
function isInstanceof(lhs, rhs) { | |
if (typeof rhs == 'function' && rhs[Symbol.hasInstance]) | |
{ return rhs[Symbol.hasInstance](lhs) } | |
else { return lhs instanceof rhs } | |
} | |
var Utility$2 = Object.freeze({ | |
epsilon: epsilon, | |
applyCSSLabel: applyCSSLabel, | |
animationFrame: animationFrame, | |
makeLowercaseSetterAliases: makeLowercaseSetterAliases, | |
observeChildren: observeChildren, | |
getShadowRootVersion: getShadowRootVersion, | |
hasShadowDomV0: hasShadowDomV0, | |
hasShadowDomV1: hasShadowDomV1, | |
getAncestorShadowRoot: getAncestorShadowRoot, | |
traverse: traverse, | |
isInstanceof: isInstanceof | |
}); | |
const instanceofSymbol$4 = Symbol('instanceofSymbol'); | |
const TreeNodeMixin = base => { | |
class TreeNode extends base { | |
constructor(options) { | |
if ( options === void 0 ) options = {}; | |
super(options); | |
this._parent = null; // default to no parent. | |
this._children = []; | |
} | |
/** | |
* this._parent is protected (node's can access other node._parent). | |
* The user should use the add() method, which automatically handles | |
* setting a parent. | |
* | |
* @readonly | |
*/ | |
get parent() { | |
return this._parent | |
} | |
/** | |
* @readonly | |
*/ | |
get children() { | |
// return a new array, so that the user modifying it doesn't affect | |
// this node's actual children. | |
return [...this._children] | |
} | |
/** | |
* Add a child node to this TreeNode. | |
* | |
* @param {TreeNode} childNode The child node to add. | |
*/ | |
add(childNode) { | |
if (! isInstanceof(childNode, TreeNode)) | |
{ throw new TypeError('TreeNode.add() expects the childNode argument to be a TreeNode instance.') } | |
if (childNode._parent === this) | |
{ throw new ReferenceError('childNode is already a child of this parent.') } | |
if (childNode._parent) | |
{ childNode._parent.remove(childNode); } | |
childNode._parent = this; | |
this._children.push(childNode); | |
return this | |
} | |
/** | |
* Add all the child nodes in the given array to this node. | |
* | |
* @param {Array.TreeNode} nodes The nodes to add. | |
*/ | |
addChildren(nodes) { | |
nodes.forEach(node => this.add()(node)); | |
return this | |
} | |
/** | |
* Remove a child node from this node. | |
* | |
* @param {TreeNode} childNode The node to remove. | |
*/ | |
remove(childNode) { | |
if (! isInstanceof(childNode, TreeNode)) | |
{ throw new Error(` | |
TreeNode.remove expects the childNode argument to be an | |
instance of TreeNode. There should only be TreeNodes in the | |
tree. | |
`) } | |
if (childNode._parent !== this) | |
{ throw new ReferenceError('childNode is not a child of this parent.') } | |
childNode._parent = null; | |
this._children.splice(this._children.indexOf(childNode), 1); | |
return this | |
} | |
/** | |
* Remove all the child nodes in the given array from this node. | |
* | |
* @param {Array.TreeNode} nodes The nodes to remove. | |
*/ | |
removeChildren(nodes) { | |
nodes.forEach(node => this.remove(node)); | |
return this | |
} | |
/** | |
* Shortcut to remove all children. | |
*/ | |
removeAllChildren() { | |
this.removeChildren(this._children); | |
return this | |
} | |
/** | |
* @readonly | |
* @return {number} How many children this TreeNode has. | |
*/ | |
get childCount() { | |
return this._children.length | |
} | |
} | |
Object.defineProperty(TreeNode, Symbol.hasInstance, { | |
value: function(obj) { | |
if (this !== TreeNode) { return Object.getPrototypeOf(TreeNode)[Symbol.hasInstance].call(this, obj) } | |
let currentProto = obj; | |
while(currentProto) { | |
const desc = Object.getOwnPropertyDescriptor(currentProto, "constructor"); | |
if (desc && desc.value && desc.value.hasOwnProperty(instanceofSymbol$4)) | |
{ return true } | |
currentProto = Object.getPrototypeOf(currentProto); | |
} | |
return false | |
} | |
}); | |
TreeNode[instanceofSymbol$4] = true; | |
return TreeNode | |
}; | |
const TreeNode = TreeNodeMixin(class{}); | |
TreeNode.mixin = TreeNodeMixin; | |
var runtime = createCommonjsModule(function (module) { | |
/** | |
* Copyright (c) 2014, Facebook, Inc. | |
* All rights reserved. | |
* | |
* This source code is licensed under the BSD-style license found in the | |
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An | |
* additional grant of patent rights can be found in the PATENTS file in | |
* the same directory. | |
*/ | |
!(function(global) { | |
"use strict"; | |
var Op = Object.prototype; | |
var hasOwn = Op.hasOwnProperty; | |
var undefined; // More compressible than void 0. | |
var $Symbol = typeof Symbol === "function" ? Symbol : {}; | |
var iteratorSymbol = $Symbol.iterator || "@@iterator"; | |
var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator"; | |
var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; | |
var inModule = 'object' === "object"; | |
var runtime = global.regeneratorRuntime; | |
if (runtime) { | |
if (inModule) { | |
// If regeneratorRuntime is defined globally and we're in a module, | |
// make the exports object identical to regeneratorRuntime. | |
module.exports = runtime; | |
} | |
// Don't bother evaluating the rest of this file if the runtime was | |
// already defined globally. | |
return; | |
} | |
// Define the runtime globally (as expected by generated code) as either | |
// module.exports (if we're in a module) or a new, empty object. | |
runtime = global.regeneratorRuntime = inModule ? module.exports : {}; | |
function wrap(innerFn, outerFn, self, tryLocsList) { | |
// If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator. | |
var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator; | |
var generator = Object.create(protoGenerator.prototype); | |
var context = new Context(tryLocsList || []); | |
// The ._invoke method unifies the implementations of the .next, | |
// .throw, and .return methods. | |
generator._invoke = makeInvokeMethod(innerFn, self, context); | |
return generator; | |
} | |
runtime.wrap = wrap; | |
// Try/catch helper to minimize deoptimizations. Returns a completion | |
// record like context.tryEntries[i].completion. This interface could | |
// have been (and was previously) designed to take a closure to be | |
// invoked without arguments, but in all the cases we care about we | |
// already have an existing method we want to call, so there's no need | |
// to create a new function object. We can even get away with assuming | |
// the method takes exactly one argument, since that happens to be true | |
// in every case, so we don't have to touch the arguments object. The | |
// only additional allocation required is the completion record, which | |
// has a stable shape and so hopefully should be cheap to allocate. | |
function tryCatch(fn, obj, arg) { | |
try { | |
return { type: "normal", arg: fn.call(obj, arg) }; | |
} catch (err) { | |
return { type: "throw", arg: err }; | |
} | |
} | |
var GenStateSuspendedStart = "suspendedStart"; | |
var GenStateSuspendedYield = "suspendedYield"; | |
var GenStateExecuting = "executing"; | |
var GenStateCompleted = "completed"; | |
// Returning this object from the innerFn has the same effect as | |
// breaking out of the dispatch switch statement. | |
var ContinueSentinel = {}; | |
// Dummy constructor functions that we use as the .constructor and | |
// .constructor.prototype properties for functions that return Generator | |
// objects. For full spec compliance, you may wish to configure your | |
// minifier not to mangle the names of these two functions. | |
function Generator() {} | |
function GeneratorFunction() {} | |
function GeneratorFunctionPrototype() {} | |
// This is a polyfill for %IteratorPrototype% for environments that | |
// don't natively support it. | |
var IteratorPrototype = {}; | |
IteratorPrototype[iteratorSymbol] = function () { | |
return this; | |
}; | |
var getProto = Object.getPrototypeOf; | |
var NativeIteratorPrototype = getProto && getProto(getProto(values([]))); | |
if (NativeIteratorPrototype && | |
NativeIteratorPrototype !== Op && | |
hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) { | |
// This environment has a native %IteratorPrototype%; use it instead | |
// of the polyfill. | |
IteratorPrototype = NativeIteratorPrototype; | |
} | |
var Gp = GeneratorFunctionPrototype.prototype = | |
Generator.prototype = Object.create(IteratorPrototype); | |
GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype; | |
GeneratorFunctionPrototype.constructor = GeneratorFunction; | |
GeneratorFunctionPrototype[toStringTagSymbol] = | |
GeneratorFunction.displayName = "GeneratorFunction"; | |
// Helper for defining the .next, .throw, and .return methods of the | |
// Iterator interface in terms of a single ._invoke method. | |
function defineIteratorMethods(prototype) { | |
["next", "throw", "return"].forEach(function(method) { | |
prototype[method] = function(arg) { | |
return this._invoke(method, arg); | |
}; | |
}); | |
} | |
runtime.isGeneratorFunction = function(genFun) { | |
var ctor = typeof genFun === "function" && genFun.constructor; | |
return ctor | |
? ctor === GeneratorFunction || | |
// For the native GeneratorFunction constructor, the best we can | |
// do is to check its .name property. | |
(ctor.displayName || ctor.name) === "GeneratorFunction" | |
: false; | |
}; | |
runtime.mark = function(genFun) { | |
if (Object.setPrototypeOf) { | |
Object.setPrototypeOf(genFun, GeneratorFunctionPrototype); | |
} else { | |
genFun.__proto__ = GeneratorFunctionPrototype; | |
if (!(toStringTagSymbol in genFun)) { | |
genFun[toStringTagSymbol] = "GeneratorFunction"; | |
} | |
} | |
genFun.prototype = Object.create(Gp); | |
return genFun; | |
}; | |
// Within the body of any async function, `await x` is transformed to | |
// `yield regeneratorRuntime.awrap(x)`, so that the runtime can test | |
// `hasOwn.call(value, "__await")` to determine if the yielded value is | |
// meant to be awaited. | |
runtime.awrap = function(arg) { | |
return { __await: arg }; | |
}; | |
function AsyncIterator(generator) { | |
function invoke(method, arg, resolve, reject) { | |
var record = tryCatch(generator[method], generator, arg); | |
if (record.type === "throw") { | |
reject(record.arg); | |
} else { | |
var result = record.arg; | |
var value = result.value; | |
if (value && | |
typeof value === "object" && | |
hasOwn.call(value, "__await")) { | |
return Promise.resolve(value.__await).then(function(value) { | |
invoke("next", value, resolve, reject); | |
}, function(err) { | |
invoke("throw", err, resolve, reject); | |
}); | |
} | |
return Promise.resolve(value).then(function(unwrapped) { | |
// When a yielded Promise is resolved, its final value becomes | |
// the .value of the Promise<{value,done}> result for the | |
// current iteration. If the Promise is rejected, however, the | |
// result for this iteration will be rejected with the same | |
// reason. Note that rejections of yielded Promises are not | |
// thrown back into the generator function, as is the case | |
// when an awaited Promise is rejected. This difference in | |
// behavior between yield and await is important, because it | |
// allows the consumer to decide what to do with the yielded | |
// rejection (swallow it and continue, manually .throw it back | |
// into the generator, abandon iteration, whatever). With | |
// await, by contrast, there is no opportunity to examine the | |
// rejection reason outside the generator function, so the | |
// only option is to throw it from the await expression, and | |
// let the generator function handle the exception. | |
result.value = unwrapped; | |
resolve(result); | |
}, reject); | |
} | |
} | |
if (typeof global.process === "object" && global.process.domain) { | |
invoke = global.process.domain.bind(invoke); | |
} | |
var previousPromise; | |
function enqueue(method, arg) { | |
function callInvokeWithMethodAndArg() { | |
return new Promise(function(resolve, reject) { | |
invoke(method, arg, resolve, reject); | |
}); | |
} | |
return previousPromise = | |
// If enqueue has been called before, then we want to wait until | |
// all previous Promises have been resolved before calling invoke, | |
// so that results are always delivered in the correct order. If | |
// enqueue has not been called before, then it is important to | |
// call invoke immediately, without waiting on a callback to fire, | |
// so that the async generator function has the opportunity to do | |
// any necessary setup in a predictable way. This predictability | |
// is why the Promise constructor synchronously invokes its | |
// executor callback, and why async functions synchronously | |
// execute code before the first await. Since we implement simple | |
// async functions in terms of async generators, it is especially | |
// important to get this right, even though it requires care. | |
previousPromise ? previousPromise.then( | |
callInvokeWithMethodAndArg, | |
// Avoid propagating failures to Promises returned by later | |
// invocations of the iterator. | |
callInvokeWithMethodAndArg | |
) : callInvokeWithMethodAndArg(); | |
} | |
// Define the unified helper method that is used to implement .next, | |
// .throw, and .return (see defineIteratorMethods). | |
this._invoke = enqueue; | |
} | |
defineIteratorMethods(AsyncIterator.prototype); | |
AsyncIterator.prototype[asyncIteratorSymbol] = function () { | |
return this; | |
}; | |
runtime.AsyncIterator = AsyncIterator; | |
// Note that simple async functions are implemented on top of | |
// AsyncIterator objects; they just return a Promise for the value of | |
// the final result produced by the iterator. | |
runtime.async = function(innerFn, outerFn, self, tryLocsList) { | |
var iter = new AsyncIterator( | |
wrap(innerFn, outerFn, self, tryLocsList) | |
); | |
return runtime.isGeneratorFunction(outerFn) | |
? iter // If outerFn is a generator, return the full iterator. | |
: iter.next().then(function(result) { | |
return result.done ? result.value : iter.next(); | |
}); | |
}; | |
function makeInvokeMethod(innerFn, self, context) { | |
var state = GenStateSuspendedStart; | |
return function invoke(method, arg) { | |
if (state === GenStateExecuting) { | |
throw new Error("Generator is already running"); | |
} | |
if (state === GenStateCompleted) { | |
if (method === "throw") { | |
throw arg; | |
} | |
// Be forgiving, per 25.3.3.3.3 of the spec: | |
// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume | |
return doneResult(); | |
} | |
context.method = method; | |
context.arg = arg; | |
while (true) { | |
var delegate = context.delegate; | |
if (delegate) { | |
var delegateResult = maybeInvokeDelegate(delegate, context); | |
if (delegateResult) { | |
if (delegateResult === ContinueSentinel) { continue; } | |
return delegateResult; | |
} | |
} | |
if (context.method === "next") { | |
// Setting context._sent for legacy support of Babel's | |
// function.sent implementation. | |
context.sent = context._sent = context.arg; | |
} else if (context.method === "throw") { | |
if (state === GenStateSuspendedStart) { | |
state = GenStateCompleted; | |
throw context.arg; | |
} | |
context.dispatchException(context.arg); | |
} else if (context.method === "return") { | |
context.abrupt("return", context.arg); | |
} | |
state = GenStateExecuting; | |
var record = tryCatch(innerFn, self, context); | |
if (record.type === "normal") { | |
// If an exception is thrown from innerFn, we leave state === | |
// GenStateExecuting and loop back for another invocation. | |
state = context.done | |
? GenStateCompleted | |
: GenStateSuspendedYield; | |
if (record.arg === ContinueSentinel) { | |
continue; | |
} | |
return { | |
value: record.arg, | |
done: context.done | |
}; | |
} else if (record.type === "throw") { | |
state = GenStateCompleted; | |
// Dispatch the exception by looping back around to the | |
// context.dispatchException(context.arg) call above. | |
context.method = "throw"; | |
context.arg = record.arg; | |
} | |
} | |
}; | |
} | |
// Call delegate.iterator[context.method](context.arg) and handle the | |
// result, either by returning a { value, done } result from the | |
// delegate iterator, or by modifying context.method and context.arg, | |
// setting context.delegate to null, and returning the ContinueSentinel. | |
function maybeInvokeDelegate(delegate, context) { | |
var method = delegate.iterator[context.method]; | |
if (method === undefined) { | |
// A .throw or .return when the delegate iterator has no .throw | |
// method always terminates the yield* loop. | |
context.delegate = null; | |
if (context.method === "throw") { | |
if (delegate.iterator.return) { | |
// If the delegate iterator has a return method, give it a | |
// chance to clean up. | |
context.method = "return"; | |
context.arg = undefined; | |
maybeInvokeDelegate(delegate, context); | |
if (context.method === "throw") { | |
// If maybeInvokeDelegate(context) changed context.method from | |
// "return" to "throw", let that override the TypeError below. | |
return ContinueSentinel; | |
} | |
} | |
context.method = "throw"; | |
context.arg = new TypeError( | |
"The iterator does not provide a 'throw' method"); | |
} | |
return ContinueSentinel; | |
} | |
var record = tryCatch(method, delegate.iterator, context.arg); | |
if (record.type === "throw") { | |
context.method = "throw"; | |
context.arg = record.arg; | |
context.delegate = null; | |
return ContinueSentinel; | |
} | |
var info = record.arg; | |
if (! info) { | |
context.method = "throw"; | |
context.arg = new TypeError("iterator result is not an object"); | |
context.delegate = null; | |
return ContinueSentinel; | |
} | |
if (info.done) { | |
// Assign the result of the finished delegate to the temporary | |
// variable specified by delegate.resultName (see delegateYield). | |
context[delegate.resultName] = info.value; | |
// Resume execution at the desired location (see delegateYield). | |
context.next = delegate.nextLoc; | |
// If context.method was "throw" but the delegate handled the | |
// exception, let the outer generator proceed normally. If | |
// context.method was "next", forget context.arg since it has been | |
// "consumed" by the delegate iterator. If context.method was | |
// "return", allow the original .return call to continue in the | |
// outer generator. | |
if (context.method !== "return") { | |
context.method = "next"; | |
context.arg = undefined; | |
} | |
} else { | |
// Re-yield the result returned by the delegate method. | |
return info; | |
} | |
// The delegate iterator is finished, so forget it and continue with | |
// the outer generator. | |
context.delegate = null; | |
return ContinueSentinel; | |
} | |
// Define Generator.prototype.{next,throw,return} in terms of the | |
// unified ._invoke helper method. | |
defineIteratorMethods(Gp); | |
Gp[toStringTagSymbol] = "Generator"; | |
// A Generator should always return itself as the iterator object when the | |
// @@iterator function is called on it. Some browsers' implementations of the | |
// iterator prototype chain incorrectly implement this, causing the Generator | |
// object to not be returned from this call. This ensures that doesn't happen. | |
// See https://github.com/facebook/regenerator/issues/274 for more details. | |
Gp[iteratorSymbol] = function() { | |
return this; | |
}; | |
Gp.toString = function() { | |
return "[object Generator]"; | |
}; | |
function pushTryEntry(locs) { | |
var entry = { tryLoc: locs[0] }; | |
if (1 in locs) { | |
entry.catchLoc = locs[1]; | |
} | |
if (2 in locs) { | |
entry.finallyLoc = locs[2]; | |
entry.afterLoc = locs[3]; | |
} | |
this.tryEntries.push(entry); | |
} | |
function resetTryEntry(entry) { | |
var record = entry.completion || {}; | |
record.type = "normal"; | |
delete record.arg; | |
entry.completion = record; | |
} | |
function Context(tryLocsList) { | |
// The root entry object (effectively a try statement without a catch | |
// or a finally block) gives us a place to store values thrown from | |
// locations where there is no enclosing try statement. | |
this.tryEntries = [{ tryLoc: "root" }]; | |
tryLocsList.forEach(pushTryEntry, this); | |
this.reset(true); | |
} | |
runtime.keys = function(object) { | |
var keys = []; | |
for (var key in object) { | |
keys.push(key); | |
} | |
keys.reverse(); | |
// Rather than returning an object with a next method, we keep | |
// things simple and return the next function itself. | |
return function next() { | |
while (keys.length) { | |
var key = keys.pop(); | |
if (key in object) { | |
next.value = key; | |
next.done = false; | |
return next; | |
} | |
} | |
// To avoid creating an additional object, we just hang the .value | |
// and .done properties off the next function object itself. This | |
// also ensures that the minifier will not anonymize the function. | |
next.done = true; | |
return next; | |
}; | |
}; | |
function values(iterable) { | |
if (iterable) { | |
var iteratorMethod = iterable[iteratorSymbol]; | |
if (iteratorMethod) { | |
return iteratorMethod.call(iterable); | |
} | |
if (typeof iterable.next === "function") { | |
return iterable; | |
} | |
if (!isNaN(iterable.length)) { | |
var i = -1, next = function next() { | |
while (++i < iterable.length) { | |
if (hasOwn.call(iterable, i)) { | |
next.value = iterable[i]; | |
next.done = false; | |
return next; | |
} | |
} | |
next.value = undefined; | |
next.done = true; | |
return next; | |
}; | |
return next.next = next; | |
} | |
} | |
// Return an iterator with no values. | |
return { next: doneResult }; | |
} | |
runtime.values = values; | |
function doneResult() { | |
return { value: undefined, done: true }; | |
} | |
Context.prototype = { | |
constructor: Context, | |
reset: function(skipTempReset) { | |
this.prev = 0; | |
this.next = 0; | |
// Resetting context._sent for legacy support of Babel's | |
// function.sent implementation. | |
this.sent = this._sent = undefined; | |
this.done = false; | |
this.delegate = null; | |
this.method = "next"; | |
this.arg = undefined; | |
this.tryEntries.forEach(resetTryEntry); | |
if (!skipTempReset) { | |
for (var name in this) { | |
// Not sure about the optimal order of these conditions: | |
if (name.charAt(0) === "t" && | |
hasOwn.call(this, name) && | |
!isNaN(+name.slice(1))) { | |
this[name] = undefined; | |
} | |
} | |
} | |
}, | |
stop: function() { | |
this.done = true; | |
var rootEntry = this.tryEntries[0]; | |
var rootRecord = rootEntry.completion; | |
if (rootRecord.type === "throw") { | |
throw rootRecord.arg; | |
} | |
return this.rval; | |
}, | |
dispatchException: function(exception) { | |
if (this.done) { | |
throw exception; | |
} | |
var context = this; | |
function handle(loc, caught) { | |
record.type = "throw"; | |
record.arg = exception; | |
context.next = loc; | |
if (caught) { | |
// If the dispatched exception was caught by a catch block, | |
// then let that catch block handle the exception normally. | |
context.method = "next"; | |
context.arg = undefined; | |
} | |
return !! caught; | |
} | |
for (var i = this.tryEntries.length - 1; i >= 0; --i) { | |
var entry = this.tryEntries[i]; | |
var record = entry.completion; | |
if (entry.tryLoc === "root") { | |
// Exception thrown outside of any try block that could handle | |
// it, so set the completion value of the entire function to | |
// throw the exception. | |
return handle("end"); | |
} | |
if (entry.tryLoc <= this.prev) { | |
var hasCatch = hasOwn.call(entry, "catchLoc"); | |
var hasFinally = hasOwn.call(entry, "finallyLoc"); | |
if (hasCatch && hasFinally) { | |
if (this.prev < entry.catchLoc) { | |
return handle(entry.catchLoc, true); | |
} else if (this.prev < entry.finallyLoc) { | |
return handle(entry.finallyLoc); | |
} | |
} else if (hasCatch) { | |
if (this.prev < entry.catchLoc) { | |
return handle(entry.catchLoc, true); | |
} | |
} else if (hasFinally) { | |
if (this.prev < entry.finallyLoc) { | |
return handle(entry.finallyLoc); | |
} | |
} else { | |
throw new Error("try statement without catch or finally"); | |
} | |
} | |
} | |
}, | |
abrupt: function(type, arg) { | |
for (var i = this.tryEntries.length - 1; i >= 0; --i) { | |
var entry = this.tryEntries[i]; | |
if (entry.tryLoc <= this.prev && | |
hasOwn.call(entry, "finallyLoc") && | |
this.prev < entry.finallyLoc) { | |
var finallyEntry = entry; | |
break; | |
} | |
} | |
if (finallyEntry && | |
(type === "break" || | |
type === "continue") && | |
finallyEntry.tryLoc <= arg && | |
arg <= finallyEntry.finallyLoc) { | |
// Ignore the finally entry if control is not jumping to a | |
// location outside the try/catch block. | |
finallyEntry = null; | |
} | |
var record = finallyEntry ? finallyEntry.completion : {}; | |
record.type = type; | |
record.arg = arg; | |
if (finallyEntry) { | |
this.method = "next"; | |
this.next = finallyEntry.finallyLoc; | |
return ContinueSentinel; | |
} | |
return this.complete(record); | |
}, | |
complete: function(record, afterLoc) { | |
if (record.type === "throw") { | |
throw record.arg; | |
} | |
if (record.type === "break" || | |
record.type === "continue") { | |
this.next = record.arg; | |
} else if (record.type === "return") { | |
this.rval = this.arg = record.arg; | |
this.method = "return"; | |
this.next = "end"; | |
} else if (record.type === "normal" && afterLoc) { | |
this.next = afterLoc; | |
} | |
return ContinueSentinel; | |
}, | |
finish: function(finallyLoc) { | |
for (var i = this.tryEntries.length - 1; i >= 0; --i) { | |
var entry = this.tryEntries[i]; | |
if (entry.finallyLoc === finallyLoc) { | |
this.complete(entry.completion, entry.afterLoc); | |
resetTryEntry(entry); | |
return ContinueSentinel; | |
} | |
} | |
}, | |
"catch": function(tryLoc) { | |
for (var i = this.tryEntries.length - 1; i >= 0; --i) { | |
var entry = this.tryEntries[i]; | |
if (entry.tryLoc === tryLoc) { | |
var record = entry.completion; | |
if (record.type === "throw") { | |
var thrown = record.arg; | |
resetTryEntry(entry); | |
} | |
return thrown; | |
} | |
} | |
// The context.catch method must only be called with a location | |
// argument that corresponds to a known catch block. | |
throw new Error("illegal catch attempt"); | |
}, | |
delegateYield: function(iterable, resultName, nextLoc) { | |
this.delegate = { | |
iterator: values(iterable), | |
resultName: resultName, | |
nextLoc: nextLoc | |
}; | |
if (this.method === "next") { | |
// Deliberately forget the last sent value so that we don't | |
// accidentally pass it on to the delegate. | |
this.arg = undefined; | |
} | |
return ContinueSentinel; | |
} | |
}; | |
})( | |
// Among the various tricks for obtaining a reference to the global | |
// object, this seems to be the most reliable technique that does not | |
// use indirect eval (which violates Content Security Policy). | |
typeof commonjsGlobal === "object" ? commonjsGlobal : | |
typeof window === "object" ? window : | |
typeof self === "object" ? self : commonjsGlobal | |
); | |
}); | |
// This method of obtaining a reference to the global object needs to be | |
// kept identical to the way it is obtained in runtime.js | |
var g = | |
typeof commonjsGlobal === "object" ? commonjsGlobal : | |
typeof window === "object" ? window : | |
typeof self === "object" ? self : commonjsGlobal; | |
// Use `getOwnPropertyNames` because not all browsers support calling | |
// `hasOwnProperty` on the global `self` object in a worker. See #183. | |
var hadRuntime = g.regeneratorRuntime && | |
Object.getOwnPropertyNames(g).indexOf("regeneratorRuntime") >= 0; | |
// Save the old regeneratorRuntime in case it needs to be restored later. | |
var oldRuntime = hadRuntime && g.regeneratorRuntime; | |
// Force reevalutation of runtime.js. | |
g.regeneratorRuntime = undefined; | |
var runtimeModule = runtime; | |
if (hadRuntime) { | |
// Restore the original runtime. | |
g.regeneratorRuntime = oldRuntime; | |
} else { | |
// Remove the global property added by runtime.js. | |
try { | |
delete g.regeneratorRuntime; | |
} catch(e) { | |
g.regeneratorRuntime = undefined; | |
} | |
} | |
var index$10 = runtimeModule; | |
// 7.1.4 ToInteger | |
var ceil = Math.ceil; | |
var floor = Math.floor; | |
var _toInteger = function (it) { | |
return isNaN(it = +it) ? 0 : (it > 0 ? floor : ceil)(it); | |
}; | |
// 7.2.1 RequireObjectCoercible(argument) | |
var _defined = function (it) { | |
if (it == undefined) { throw TypeError("Can't call method on " + it); } | |
return it; | |
}; | |
// true -> String#at | |
// false -> String#codePointAt | |
var _stringAt = function (TO_STRING) { | |
return function (that, pos) { | |
var s = String(_defined(that)); | |
var i = _toInteger(pos); | |
var l = s.length; | |
var a, b; | |
if (i < 0 || i >= l) { return TO_STRING ? '' : undefined; } | |
a = s.charCodeAt(i); | |
return a < 0xd800 || a > 0xdbff || i + 1 === l || (b = s.charCodeAt(i + 1)) < 0xdc00 || b > 0xdfff | |
? TO_STRING ? s.charAt(i) : a | |
: TO_STRING ? s.slice(i, i + 2) : (a - 0xd800 << 10) + (b - 0xdc00) + 0x10000; | |
}; | |
}; | |
var _library = true; | |
var _global$1 = createCommonjsModule(function (module) { | |
// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 | |
var global = module.exports = typeof window != 'undefined' && window.Math == Math | |
? window : typeof self != 'undefined' && self.Math == Math ? self | |
// eslint-disable-next-line no-new-func | |
: Function('return this')(); | |
if (typeof __g == 'number') { __g = global; } // eslint-disable-line no-undef | |
}); | |
var _core = createCommonjsModule(function (module) { | |
var core = module.exports = { version: '2.5.0' }; | |
if (typeof __e == 'number') { __e = core; } // eslint-disable-line no-undef | |
}); | |
var _aFunction = function (it) { | |
if (typeof it != 'function') { throw TypeError(it + ' is not a function!'); } | |
return it; | |
}; | |
// optional / simple context binding | |
var _ctx = function (fn, that, length) { | |
_aFunction(fn); | |
if (that === undefined) { return fn; } | |
switch (length) { | |
case 1: return function (a) { | |
return fn.call(that, a); | |
}; | |
case 2: return function (a, b) { | |
return fn.call(that, a, b); | |
}; | |
case 3: return function (a, b, c) { | |
return fn.call(that, a, b, c); | |
}; | |
} | |
return function (/* ...args */) { | |
return fn.apply(that, arguments); | |
}; | |
}; | |
var _isObject = function (it) { | |
return typeof it === 'object' ? it !== null : typeof it === 'function'; | |
}; | |
var _anObject = function (it) { | |
if (!_isObject(it)) { throw TypeError(it + ' is not an object!'); } | |
return it; | |
}; | |
var _fails = function (exec) { | |
try { | |
return !!exec(); | |
} catch (e) { | |
return true; | |
} | |
}; | |
// Thank's IE8 for his funny defineProperty | |
var _descriptors = !_fails(function () { | |
return Object.defineProperty({}, 'a', { get: function () { return 7; } }).a != 7; | |
}); | |
var document$1 = _global$1.document; | |
// typeof document.createElement is 'object' in old IE | |
var is = _isObject(document$1) && _isObject(document$1.createElement); | |
var _domCreate = function (it) { | |
return is ? document$1.createElement(it) : {}; | |
}; | |
var _ie8DomDefine = !_descriptors && !_fails(function () { | |
return Object.defineProperty(_domCreate('div'), 'a', { get: function () { return 7; } }).a != 7; | |
}); | |
// 7.1.1 ToPrimitive(input [, PreferredType]) | |
// instead of the ES6 spec version, we didn't implement @@toPrimitive case | |
// and the second argument - flag - preferred type is a string | |
var _toPrimitive = function (it, S) { | |
if (!_isObject(it)) { return it; } | |
var fn, val; | |
if (S && typeof (fn = it.toString) == 'function' && !_isObject(val = fn.call(it))) { return val; } | |
if (typeof (fn = it.valueOf) == 'function' && !_isObject(val = fn.call(it))) { return val; } | |
if (!S && typeof (fn = it.toString) == 'function' && !_isObject(val = fn.call(it))) { return val; } | |
throw TypeError("Can't convert object to primitive value"); | |
}; | |
var dP = Object.defineProperty; | |
var f = _descriptors ? Object.defineProperty : function defineProperty(O, P, Attributes) { | |
_anObject(O); | |
P = _toPrimitive(P, true); | |
_anObject(Attributes); | |
if (_ie8DomDefine) { try { | |
return dP(O, P, Attributes); | |
} catch (e) { /* empty */ } } | |
if ('get' in Attributes || 'set' in Attributes) { throw TypeError('Accessors not supported!'); } | |
if ('value' in Attributes) { O[P] = Attributes.value; } | |
return O; | |
}; | |
var _objectDp = { | |
f: f | |
}; | |
var _propertyDesc = function (bitmap, value) { | |
return { | |
enumerable: !(bitmap & 1), | |
configurable: !(bitmap & 2), | |
writable: !(bitmap & 4), | |
value: value | |
}; | |
}; | |
var _hide = _descriptors ? function (object, key, value) { | |
return _objectDp.f(object, key, _propertyDesc(1, value)); | |
} : function (object, key, value) { | |
object[key] = value; | |
return object; | |
}; | |
var PROTOTYPE = 'prototype'; | |
var $export = function (type, name, source) { | |
var IS_FORCED = type & $export.F; | |
var IS_GLOBAL = type & $export.G; | |
var IS_STATIC = type & $export.S; | |
var IS_PROTO = type & $export.P; | |
var IS_BIND = type & $export.B; | |
var IS_WRAP = type & $export.W; | |
var exports = IS_GLOBAL ? _core : _core[name] || (_core[name] = {}); | |
var expProto = exports[PROTOTYPE]; | |
var target = IS_GLOBAL ? _global$1 : IS_STATIC ? _global$1[name] : (_global$1[name] || {})[PROTOTYPE]; | |
var key, own, out; | |
if (IS_GLOBAL) { source = name; } | |
for (key in source) { | |
// contains in native | |
own = !IS_FORCED && target && target[key] !== undefined; | |
if (own && key in exports) { continue; } | |
// export native or passed | |
out = own ? target[key] : source[key]; | |
// prevent global pollution for namespaces | |
exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key] | |
// bind timers to global for call from export context | |
: IS_BIND && own ? _ctx(out, _global$1) | |
// wrap global constructors for prevent change them in library | |
: IS_WRAP && target[key] == out ? (function (C) { | |
var F = function (a, b, c) { | |
if (this instanceof C) { | |
switch (arguments.length) { | |
case 0: return new C(); | |
case 1: return new C(a); | |
case 2: return new C(a, b); | |
} return new C(a, b, c); | |
} return C.apply(this, arguments); | |
}; | |
F[PROTOTYPE] = C[PROTOTYPE]; | |
return F; | |
// make static versions for prototype methods | |
})(out) : IS_PROTO && typeof out == 'function' ? _ctx(Function.call, out) : out; | |
// export proto methods to core.%CONSTRUCTOR%.methods.%NAME% | |
if (IS_PROTO) { | |
(exports.virtual || (exports.virtual = {}))[key] = out; | |
// export proto methods to core.%CONSTRUCTOR%.prototype.%NAME% | |
if (type & $export.R && expProto && !expProto[key]) { _hide(expProto, key, out); } | |
} | |
} | |
}; | |
// type bitmap | |
$export.F = 1; // forced | |
$export.G = 2; // global | |
$export.S = 4; // static | |
$export.P = 8; // proto | |
$export.B = 16; // bind | |
$export.W = 32; // wrap | |
$export.U = 64; // safe | |
$export.R = 128; // real proto method for `library` | |
var _export = $export; | |
var _redefine = _hide; | |
var hasOwnProperty = {}.hasOwnProperty; | |
var _has = function (it, key) { | |
return hasOwnProperty.call(it, key); | |
}; | |
var _iterators = {}; | |
var toString = {}.toString; | |
var _cof = function (it) { | |
return toString.call(it).slice(8, -1); | |
}; | |
// fallback for non-array-like ES3 and non-enumerable old V8 strings | |
// eslint-disable-next-line no-prototype-builtins | |
var _iobject = Object('z').propertyIsEnumerable(0) ? Object : function (it) { | |
return _cof(it) == 'String' ? it.split('') : Object(it); | |
}; | |
// to indexed object, toObject with fallback for non-array-like ES3 strings | |
var _toIobject = function (it) { | |
return _iobject(_defined(it)); | |
}; | |
// 7.1.15 ToLength | |
var min = Math.min; | |
var _toLength = function (it) { | |
return it > 0 ? min(_toInteger(it), 0x1fffffffffffff) : 0; // pow(2, 53) - 1 == 9007199254740991 | |
}; | |
var max = Math.max; | |
var min$1 = Math.min; | |
var _toAbsoluteIndex = function (index, length) { | |
index = _toInteger(index); | |
return index < 0 ? max(index + length, 0) : min$1(index, length); | |
}; | |
// false -> Array#indexOf | |
// true -> Array#includes | |
var _arrayIncludes = function (IS_INCLUDES) { | |
return function ($this, el, fromIndex) { | |
var O = _toIobject($this); | |
var length = _toLength(O.length); | |
var index = _toAbsoluteIndex(fromIndex, length); | |
var value; | |
// Array#includes uses SameValueZero equality algorithm | |
// eslint-disable-next-line no-self-compare | |
if (IS_INCLUDES && el != el) { while (length > index) { | |
value = O[index++]; | |
// eslint-disable-next-line no-self-compare | |
if (value != value) { return true; } | |
// Array#indexOf ignores holes, Array#includes - not | |
} } else { for (;length > index; index++) { if (IS_INCLUDES || index in O) { | |
if (O[index] === el) { return IS_INCLUDES || index || 0; } | |
} } } return !IS_INCLUDES && -1; | |
}; | |
}; | |
var SHARED = '__core-js_shared__'; | |
var store = _global$1[SHARED] || (_global$1[SHARED] = {}); | |
var _shared = function (key) { | |
return store[key] || (store[key] = {}); | |
}; | |
var id = 0; | |
var px = Math.random(); | |
var _uid = function (key) { | |
return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36)); | |
}; | |
var shared = _shared('keys'); | |
var _sharedKey = function (key) { | |
return shared[key] || (shared[key] = _uid(key)); | |
}; | |
var arrayIndexOf = _arrayIncludes(false); | |
var IE_PROTO$1 = _sharedKey('IE_PROTO'); | |
var _objectKeysInternal = function (object, names) { | |
var O = _toIobject(object); | |
var i = 0; | |
var result = []; | |
var key; | |
for (key in O) { if (key != IE_PROTO$1) { _has(O, key) && result.push(key); } } | |
// Don't enum bug & hidden keys | |
while (names.length > i) { if (_has(O, key = names[i++])) { | |
~arrayIndexOf(result, key) || result.push(key); | |
} } | |
return result; | |
}; | |
// IE 8- don't enum bug keys | |
var _enumBugKeys = ( | |
'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf' | |
).split(','); | |
// 19.1.2.14 / 15.2.3.14 Object.keys(O) | |
var _objectKeys = Object.keys || function keys(O) { | |
return _objectKeysInternal(O, _enumBugKeys); | |
}; | |
var _objectDps = _descriptors ? Object.defineProperties : function defineProperties(O, Properties) { | |
_anObject(O); | |
var keys = _objectKeys(Properties); | |
var length = keys.length; | |
var i = 0; | |
var P; | |
while (length > i) { _objectDp.f(O, P = keys[i++], Properties[P]); } | |
return O; | |
}; | |
var document$2 = _global$1.document; | |
var _html = document$2 && document$2.documentElement; | |
// 19.1.2.2 / 15.2.3.5 Object.create(O [, Properties]) | |
var IE_PROTO = _sharedKey('IE_PROTO'); | |
var Empty = function () { /* empty */ }; | |
var PROTOTYPE$1 = 'prototype'; | |
// Create object with fake `null` prototype: use iframe Object with cleared prototype | |
var createDict = function () { | |
// Thrash, waste and sodomy: IE GC bug | |
var iframe = _domCreate('iframe'); | |
var i = _enumBugKeys.length; | |
var lt = '<'; | |
var gt = '>'; | |
var iframeDocument; | |
iframe.style.display = 'none'; | |
_html.appendChild(iframe); | |
iframe.src = 'javascript:'; // eslint-disable-line no-script-url | |
// createDict = iframe.contentWindow.Object; | |
// html.removeChild(iframe); | |
iframeDocument = iframe.contentWindow.document; | |
iframeDocument.open(); | |
iframeDocument.write(lt + 'script' + gt + 'document.F=Object' + lt + '/script' + gt); | |
iframeDocument.close(); | |
createDict = iframeDocument.F; | |
while (i--) { delete createDict[PROTOTYPE$1][_enumBugKeys[i]]; } | |
return createDict(); | |
}; | |
var _objectCreate = Object.create || function create(O, Properties) { | |
var result; | |
if (O !== null) { | |
Empty[PROTOTYPE$1] = _anObject(O); | |
result = new Empty(); | |
Empty[PROTOTYPE$1] = null; | |
// add "__proto__" for Object.getPrototypeOf polyfill | |
result[IE_PROTO] = O; | |
} else { result = createDict(); } | |
return Properties === undefined ? result : _objectDps(result, Properties); | |
}; | |
var _wks = createCommonjsModule(function (module) { | |
var store = _shared('wks'); | |
var Symbol = _global$1.Symbol; | |
var USE_SYMBOL = typeof Symbol == 'function'; | |
var $exports = module.exports = function (name) { | |
return store[name] || (store[name] = | |
USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : _uid)('Symbol.' + name)); | |
}; | |
$exports.store = store; | |
}); | |
var def = _objectDp.f; | |
var TAG = _wks('toStringTag'); | |
var _setToStringTag = function (it, tag, stat) { | |
if (it && !_has(it = stat ? it : it.prototype, TAG)) { def(it, TAG, { configurable: true, value: tag }); } | |
}; | |
var IteratorPrototype = {}; | |
// 25.1.2.1.1 %IteratorPrototype%[@@iterator]() | |
_hide(IteratorPrototype, _wks('iterator'), function () { return this; }); | |
var _iterCreate = function (Constructor, NAME, next) { | |
Constructor.prototype = _objectCreate(IteratorPrototype, { next: _propertyDesc(1, next) }); | |
_setToStringTag(Constructor, NAME + ' Iterator'); | |
}; | |
// 7.1.13 ToObject(argument) | |
var _toObject = function (it) { | |
return Object(_defined(it)); | |
}; | |
// 19.1.2.9 / 15.2.3.2 Object.getPrototypeOf(O) | |
var IE_PROTO$2 = _sharedKey('IE_PROTO'); | |
var ObjectProto = Object.prototype; | |
var _objectGpo = Object.getPrototypeOf || function (O) { | |
O = _toObject(O); | |
if (_has(O, IE_PROTO$2)) { return O[IE_PROTO$2]; } | |
if (typeof O.constructor == 'function' && O instanceof O.constructor) { | |
return O.constructor.prototype; | |
} return O instanceof Object ? ObjectProto : null; | |
}; | |
var ITERATOR = _wks('iterator'); | |
var BUGGY = !([].keys && 'next' in [].keys()); // Safari has buggy iterators w/o `next` | |
var FF_ITERATOR = '@@iterator'; | |
var KEYS = 'keys'; | |
var VALUES = 'values'; | |
var returnThis = function () { return this; }; | |
var _iterDefine = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED) { | |
_iterCreate(Constructor, NAME, next); | |
var getMethod = function (kind) { | |
if (!BUGGY && kind in proto) { return proto[kind]; } | |
switch (kind) { | |
case KEYS: return function keys() { return new Constructor(this, kind); }; | |
case VALUES: return function values() { return new Constructor(this, kind); }; | |
} return function entries() { return new Constructor(this, kind); }; | |
}; | |
var TAG = NAME + ' Iterator'; | |
var DEF_VALUES = DEFAULT == VALUES; | |
var VALUES_BUG = false; | |
var proto = Base.prototype; | |
var $native = proto[ITERATOR] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT]; | |
var $default = $native || getMethod(DEFAULT); | |
var $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined; | |
var $anyNative = NAME == 'Array' ? proto.entries || $native : $native; | |
var methods, key, IteratorPrototype; | |
// Fix native | |
if ($anyNative) { | |
IteratorPrototype = _objectGpo($anyNative.call(new Base())); | |
if (IteratorPrototype !== Object.prototype && IteratorPrototype.next) { | |
// Set @@toStringTag to native iterators | |
_setToStringTag(IteratorPrototype, TAG, true); | |
// fix for some old engines | |
if (!_library && !_has(IteratorPrototype, ITERATOR)) { _hide(IteratorPrototype, ITERATOR, returnThis); } | |
} | |
} | |
// fix Array#{values, @@iterator}.name in V8 / FF | |
if (DEF_VALUES && $native && $native.name !== VALUES) { | |
VALUES_BUG = true; | |
$default = function values() { return $native.call(this); }; | |
} | |
// Define iterator | |
if ((!_library || FORCED) && (BUGGY || VALUES_BUG || !proto[ITERATOR])) { | |
_hide(proto, ITERATOR, $default); | |
} | |
// Plug for library | |
_iterators[NAME] = $default; | |
_iterators[TAG] = returnThis; | |
if (DEFAULT) { | |
methods = { | |
values: DEF_VALUES ? $default : getMethod(VALUES), | |
keys: IS_SET ? $default : getMethod(KEYS), | |
entries: $entries | |
}; | |
if (FORCED) { for (key in methods) { | |
if (!(key in proto)) { _redefine(proto, key, methods[key]); } | |
} } else { _export(_export.P + _export.F * (BUGGY || VALUES_BUG), NAME, methods); } | |
} | |
return methods; | |
}; | |
var $at = _stringAt(true); | |
// 21.1.3.27 String.prototype[@@iterator]() | |
_iterDefine(String, 'String', function (iterated) { | |
this._t = String(iterated); // target | |
this._i = 0; // next index | |
// 21.1.5.2.1 %StringIteratorPrototype%.next() | |
}, function () { | |
var O = this._t; | |
var index = this._i; | |
var point; | |
if (index >= O.length) { return { value: undefined, done: true }; } | |
point = $at(O, index); | |
this._i += point.length; | |
return { value: point, done: false }; | |
}); | |
var _addToUnscopables = function () { /* empty */ }; | |
var _iterStep = function (done, value) { | |
return { value: value, done: !!done }; | |
}; | |
// 22.1.3.4 Array.prototype.entries() | |
// 22.1.3.13 Array.prototype.keys() | |
// 22.1.3.29 Array.prototype.values() | |
// 22.1.3.30 Array.prototype[@@iterator]() | |
var es6_array_iterator = _iterDefine(Array, 'Array', function (iterated, kind) { | |
this._t = _toIobject(iterated); // target | |
this._i = 0; // next index | |
this._k = kind; // kind | |
// 22.1.5.2.1 %ArrayIteratorPrototype%.next() | |
}, function () { | |
var O = this._t; | |
var kind = this._k; | |
var index = this._i++; | |
if (!O || index >= O.length) { | |
this._t = undefined; | |
return _iterStep(1); | |
} | |
if (kind == 'keys') { return _iterStep(0, index); } | |
if (kind == 'values') { return _iterStep(0, O[index]); } | |
return _iterStep(0, [index, O[index]]); | |
}, 'values'); | |
// argumentsList[@@iterator] is %ArrayProto_values% (9.4.4.6, 9.4.4.7) | |
_iterators.Arguments = _iterators.Array; | |
_addToUnscopables('keys'); | |
_addToUnscopables('values'); | |
_addToUnscopables('entries'); | |
var TO_STRING_TAG = _wks('toStringTag'); | |
var DOMIterables = ('CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,' + | |
'DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,' + | |
'MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,' + | |
'SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,' + | |
'TextTrackList,TouchList').split(','); | |
for (var i = 0; i < DOMIterables.length; i++) { | |
var NAME = DOMIterables[i]; | |
var Collection = _global$1[NAME]; | |
var proto = Collection && Collection.prototype; | |
if (proto && !proto[TO_STRING_TAG]) { _hide(proto, TO_STRING_TAG, NAME); } | |
_iterators[NAME] = _iterators.Array; | |
} | |
// getting tag from 19.1.3.6 Object.prototype.toString() | |
var TAG$1 = _wks('toStringTag'); | |
// ES3 wrong here | |
var ARG = _cof(function () { return arguments; }()) == 'Arguments'; | |
// fallback for IE11 Script Access Denied error | |
var tryGet = function (it, key) { | |
try { | |
return it[key]; | |
} catch (e) { /* empty */ } | |
}; | |
var _classof = function (it) { | |
var O, T, B; | |
return it === undefined ? 'Undefined' : it === null ? 'Null' | |
// @@toStringTag case | |
: typeof (T = tryGet(O = Object(it), TAG$1)) == 'string' ? T | |
// builtinTag case | |
: ARG ? _cof(O) | |
// ES3 arguments fallback | |
: (B = _cof(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : B; | |
}; | |
var _anInstance = function (it, Constructor, name, forbiddenField) { | |
if (!(it instanceof Constructor) || (forbiddenField !== undefined && forbiddenField in it)) { | |
throw TypeError(name + ': incorrect invocation!'); | |
} return it; | |
}; | |
// call something on iterator step with safe closing on error | |
var _iterCall = function (iterator, fn, value, entries) { | |
try { | |
return entries ? fn(_anObject(value)[0], value[1]) : fn(value); | |
// 7.4.6 IteratorClose(iterator, completion) | |
} catch (e) { | |
var ret = iterator['return']; | |
if (ret !== undefined) { _anObject(ret.call(iterator)); } | |
throw e; | |
} | |
}; | |
// check on default Array iterator | |
var ITERATOR$1 = _wks('iterator'); | |
var ArrayProto = Array.prototype; | |
var _isArrayIter = function (it) { | |
return it !== undefined && (_iterators.Array === it || ArrayProto[ITERATOR$1] === it); | |
}; | |
var ITERATOR$2 = _wks('iterator'); | |
var core_getIteratorMethod = _core.getIteratorMethod = function (it) { | |
if (it != undefined) { return it[ITERATOR$2] | |
|| it['@@iterator'] | |
|| _iterators[_classof(it)]; } | |
}; | |
var _forOf = createCommonjsModule(function (module) { | |
var BREAK = {}; | |
var RETURN = {}; | |
var exports = module.exports = function (iterable, entries, fn, that, ITERATOR) { | |
var iterFn = ITERATOR ? function () { return iterable; } : core_getIteratorMethod(iterable); | |
var f = _ctx(fn, that, entries ? 2 : 1); | |
var index = 0; | |
var length, step, iterator, result; | |
if (typeof iterFn != 'function') { throw TypeError(iterable + ' is not iterable!'); } | |
// fast case for arrays with default iterator | |
if (_isArrayIter(iterFn)) { for (length = _toLength(iterable.length); length > index; index++) { | |
result = entries ? f(_anObject(step = iterable[index])[0], step[1]) : f(iterable[index]); | |
if (result === BREAK || result === RETURN) { return result; } | |
} } else { for (iterator = iterFn.call(iterable); !(step = iterator.next()).done;) { | |
result = _iterCall(iterator, f, step.value, entries); | |
if (result === BREAK || result === RETURN) { return result; } | |
} } | |
}; | |
exports.BREAK = BREAK; | |
exports.RETURN = RETURN; | |
}); | |
// 7.3.20 SpeciesConstructor(O, defaultConstructor) | |
var SPECIES = _wks('species'); | |
var _speciesConstructor = function (O, D) { | |
var C = _anObject(O).constructor; | |
var S; | |
return C === undefined || (S = _anObject(C)[SPECIES]) == undefined ? D : _aFunction(S); | |
}; | |
// fast apply, http://jsperf.lnkit.com/fast-apply/5 | |
var _invoke = function (fn, args, that) { | |
var un = that === undefined; | |
switch (args.length) { | |
case 0: return un ? fn() | |
: fn.call(that); | |
case 1: return un ? fn(args[0]) | |
: fn.call(that, args[0]); | |
case 2: return un ? fn(args[0], args[1]) | |
: fn.call(that, args[0], args[1]); | |
case 3: return un ? fn(args[0], args[1], args[2]) | |
: fn.call(that, args[0], args[1], args[2]); | |
case 4: return un ? fn(args[0], args[1], args[2], args[3]) | |
: fn.call(that, args[0], args[1], args[2], args[3]); | |
} return fn.apply(that, args); | |
}; | |
var process$1 = _global$1.process; | |
var setTask = _global$1.setImmediate; | |
var clearTask = _global$1.clearImmediate; | |
var MessageChannel = _global$1.MessageChannel; | |
var Dispatch = _global$1.Dispatch; | |
var counter = 0; | |
var queue = {}; | |
var ONREADYSTATECHANGE = 'onreadystatechange'; | |
var defer; | |
var channel; | |
var port; | |
var run = function () { | |
var id = +this; | |
// eslint-disable-next-line no-prototype-builtins | |
if (queue.hasOwnProperty(id)) { | |
var fn = queue[id]; | |
delete queue[id]; | |
fn(); | |
} | |
}; | |
var listener = function (event) { | |
run.call(event.data); | |
}; | |
// Node.js 0.9+ & IE10+ has setImmediate, otherwise: | |
if (!setTask || !clearTask) { | |
setTask = function setImmediate(fn) { | |
var args = []; | |
var i = 1; | |
while (arguments.length > i) { args.push(arguments[i++]); } | |
queue[++counter] = function () { | |
// eslint-disable-next-line no-new-func | |
_invoke(typeof fn == 'function' ? fn : Function(fn), args); | |
}; | |
defer(counter); | |
return counter; | |
}; | |
clearTask = function clearImmediate(id) { | |
delete queue[id]; | |
}; | |
// Node.js 0.8- | |
if (_cof(process$1) == 'process') { | |
defer = function (id) { | |
process$1.nextTick(_ctx(run, id, 1)); | |
}; | |
// Sphere (JS game engine) Dispatch API | |
} else if (Dispatch && Dispatch.now) { | |
defer = function (id) { | |
Dispatch.now(_ctx(run, id, 1)); | |
}; | |
// Browsers with MessageChannel, includes WebWorkers | |
} else if (MessageChannel) { | |
channel = new MessageChannel(); | |
port = channel.port2; | |
channel.port1.onmessage = listener; | |
defer = _ctx(port.postMessage, port, 1); | |
// Browsers with postMessage, skip WebWorkers | |
// IE8 has postMessage, but it's sync & typeof its postMessage is 'object' | |
} else if (_global$1.addEventListener && typeof postMessage == 'function' && !_global$1.importScripts) { | |
defer = function (id) { | |
_global$1.postMessage(id + '', '*'); | |
}; | |
_global$1.addEventListener('message', listener, false); | |
// IE8- | |
} else if (ONREADYSTATECHANGE in _domCreate('script')) { | |
defer = function (id) { | |
_html.appendChild(_domCreate('script'))[ONREADYSTATECHANGE] = function () { | |
_html.removeChild(this); | |
run.call(id); | |
}; | |
}; | |
// Rest old browsers | |
} else { | |
defer = function (id) { | |
setTimeout(_ctx(run, id, 1), 0); | |
}; | |
} | |
} | |
var _task = { | |
set: setTask, | |
clear: clearTask | |
}; | |
var macrotask = _task.set; | |
var Observer = _global$1.MutationObserver || _global$1.WebKitMutationObserver; | |
var process$2 = _global$1.process; | |
var Promise$1 = _global$1.Promise; | |
var isNode$1 = _cof(process$2) == 'process'; | |
var _microtask = function () { | |
var head, last, notify; | |
var flush = function () { | |
var parent, fn; | |
if (isNode$1 && (parent = process$2.domain)) { parent.exit(); } | |
while (head) { | |
fn = head.fn; | |
head = head.next; | |
try { | |
fn(); | |
} catch (e) { | |
if (head) { notify(); } | |
else { last = undefined; } | |
throw e; | |
} | |
} last = undefined; | |
if (parent) { parent.enter(); } | |
}; | |
// Node.js | |
if (isNode$1) { | |
notify = function () { | |
process$2.nextTick(flush); | |
}; | |
// browsers with MutationObserver | |
} else if (Observer) { | |
var toggle = true; | |
var node = document.createTextNode(''); | |
new Observer(flush).observe(node, { characterData: true }); // eslint-disable-line no-new | |
notify = function () { | |
node.data = toggle = !toggle; | |
}; | |
// environments with maybe non-completely correct, but existent Promise | |
} else if (Promise$1 && Promise$1.resolve) { | |
var promise = Promise$1.resolve(); | |
notify = function () { | |
promise.then(flush); | |
}; | |
// for other environments - macrotask based on: | |
// - setImmediate | |
// - MessageChannel | |
// - window.postMessag | |
// - onreadystatechange | |
// - setTimeout | |
} else { | |
notify = function () { | |
// strange IE + webpack dev server bug - use .call(global) | |
macrotask.call(_global$1, flush); | |
}; | |
} | |
return function (fn) { | |
var task = { fn: fn, next: undefined }; | |
if (last) { last.next = task; } | |
if (!head) { | |
head = task; | |
notify(); | |
} last = task; | |
}; | |
}; | |
// 25.4.1.5 NewPromiseCapability(C) | |
function PromiseCapability(C) { | |
var resolve, reject; | |
this.promise = new C(function ($$resolve, $$reject) { | |
if (resolve !== undefined || reject !== undefined) { throw TypeError('Bad Promise constructor'); } | |
resolve = $$resolve; | |
reject = $$reject; | |
}); | |
this.resolve = _aFunction(resolve); | |
this.reject = _aFunction(reject); | |
} | |
var f$1 = function (C) { | |
return new PromiseCapability(C); | |
}; | |
var _newPromiseCapability = { | |
f: f$1 | |
}; | |
var _perform = function (exec) { | |
try { | |
return { e: false, v: exec() }; | |
} catch (e) { | |
return { e: true, v: e }; | |
} | |
}; | |
var _promiseResolve = function (C, x) { | |
var promiseCapability = _newPromiseCapability.f(C); | |
var resolve = promiseCapability.resolve; | |
resolve(x); | |
return promiseCapability.promise; | |
}; | |
var _redefineAll = function (target, src, safe) { | |
for (var key in src) { | |
if (safe && target[key]) { target[key] = src[key]; } | |
else { _hide(target, key, src[key]); } | |
} return target; | |
}; | |
var SPECIES$1 = _wks('species'); | |
var _setSpecies = function (KEY) { | |
var C = typeof _core[KEY] == 'function' ? _core[KEY] : _global$1[KEY]; | |
if (_descriptors && C && !C[SPECIES$1]) { _objectDp.f(C, SPECIES$1, { | |
configurable: true, | |
get: function () { return this; } | |
}); } | |
}; | |
var ITERATOR$3 = _wks('iterator'); | |
var SAFE_CLOSING = false; | |
try { | |
var riter = [7][ITERATOR$3](); | |
riter['return'] = function () { SAFE_CLOSING = true; }; | |
// eslint-disable-next-line no-throw-literal | |
Array.from(riter, function () { throw 2; }); | |
} catch (e) { /* empty */ } | |
var _iterDetect = function (exec, skipClosing) { | |
if (!skipClosing && !SAFE_CLOSING) { return false; } | |
var safe = false; | |
try { | |
var arr = [7]; | |
var iter = arr[ITERATOR$3](); | |
iter.next = function () { return { done: safe = true }; }; | |
arr[ITERATOR$3] = function () { return iter; }; | |
exec(arr); | |
} catch (e) { /* empty */ } | |
return safe; | |
}; | |
var task = _task.set; | |
var microtask = _microtask(); | |
var PROMISE = 'Promise'; | |
var TypeError$1 = _global$1.TypeError; | |
var process = _global$1.process; | |
var $Promise = _global$1[PROMISE]; | |
var isNode = _classof(process) == 'process'; | |
var empty = function () { /* empty */ }; | |
var Internal; | |
var newGenericPromiseCapability; | |
var OwnPromiseCapability; | |
var Wrapper; | |
var newPromiseCapability = newGenericPromiseCapability = _newPromiseCapability.f; | |
var USE_NATIVE = !!function () { | |
try { | |
// correct subclassing with @@species support | |
var promise = $Promise.resolve(1); | |
var FakePromise = (promise.constructor = {})[_wks('species')] = function (exec) { | |
exec(empty, empty); | |
}; | |
// unhandled rejections tracking support, NodeJS Promise without it fails @@species test | |
return (isNode || typeof PromiseRejectionEvent == 'function') && promise.then(empty) instanceof FakePromise; | |
} catch (e) { /* empty */ } | |
}(); | |
// helpers | |
var sameConstructor = _library ? function (a, b) { | |
// with library wrapper special case | |
return a === b || a === $Promise && b === Wrapper; | |
} : function (a, b) { | |
return a === b; | |
}; | |
var isThenable = function (it) { | |
var then; | |
return _isObject(it) && typeof (then = it.then) == 'function' ? then : false; | |
}; | |
var notify = function (promise, isReject) { | |
if (promise._n) { return; } | |
promise._n = true; | |
var chain = promise._c; | |
microtask(function () { | |
var value = promise._v; | |
var ok = promise._s == 1; | |
var i = 0; | |
var run = function (reaction) { | |
var handler = ok ? reaction.ok : reaction.fail; | |
var resolve = reaction.resolve; | |
var reject = reaction.reject; | |
var domain = reaction.domain; | |
var result, then; | |
try { | |
if (handler) { | |
if (!ok) { | |
if (promise._h == 2) { onHandleUnhandled(promise); } | |
promise._h = 1; | |
} | |
if (handler === true) { result = value; } | |
else { | |
if (domain) { domain.enter(); } | |
result = handler(value); | |
if (domain) { domain.exit(); } | |
} | |
if (result === reaction.promise) { | |
reject(TypeError$1('Promise-chain cycle')); | |
} else if (then = isThenable(result)) { | |
then.call(result, resolve, reject); | |
} else { resolve(result); } | |
} else { reject(value); } | |
} catch (e) { | |
reject(e); | |
} | |
}; | |
while (chain.length > i) { run(chain[i++]); } // variable length - can't use forEach | |
promise._c = []; | |
promise._n = false; | |
if (isReject && !promise._h) { onUnhandled(promise); } | |
}); | |
}; | |
var onUnhandled = function (promise) { | |
task.call(_global$1, function () { | |
var value = promise._v; | |
var unhandled = isUnhandled(promise); | |
var result, handler, console; | |
if (unhandled) { | |
result = _perform(function () { | |
if (isNode) { | |
process.emit('unhandledRejection', value, promise); | |
} else if (handler = _global$1.onunhandledrejection) { | |
handler({ promise: promise, reason: value }); | |
} else if ((console = _global$1.console) && console.error) { | |
console.error('Unhandled promise rejection', value); | |
} | |
}); | |
// Browsers should not trigger `rejectionHandled` event if it was handled here, NodeJS - should | |
promise._h = isNode || isUnhandled(promise) ? 2 : 1; | |
} promise._a = undefined; | |
if (unhandled && result.e) { throw result.v; } | |
}); | |
}; | |
var isUnhandled = function (promise) { | |
if (promise._h == 1) { return false; } | |
var chain = promise._a || promise._c; | |
var i = 0; | |
var reaction; | |
while (chain.length > i) { | |
reaction = chain[i++]; | |
if (reaction.fail || !isUnhandled(reaction.promise)) { return false; } | |
} return true; | |
}; | |
var onHandleUnhandled = function (promise) { | |
task.call(_global$1, function () { | |
var handler; | |
if (isNode) { | |
process.emit('rejectionHandled', promise); | |
} else if (handler = _global$1.onrejectionhandled) { | |
handler({ promise: promise, reason: promise._v }); | |
} | |
}); | |
}; | |
var $reject = function (value) { | |
var promise = this; | |
if (promise._d) { return; } | |
promise._d = true; | |
promise = promise._w || promise; // unwrap | |
promise._v = value; | |
promise._s = 2; | |
if (!promise._a) { promise._a = promise._c.slice(); } | |
notify(promise, true); | |
}; | |
var $resolve = function (value) { | |
var promise = this; | |
var then; | |
if (promise._d) { return; } | |
promise._d = true; | |
promise = promise._w || promise; // unwrap | |
try { | |
if (promise === value) { throw TypeError$1("Promise can't be resolved itself"); } | |
if (then = isThenable(value)) { | |
microtask(function () { | |
var wrapper = { _w: promise, _d: false }; // wrap | |
try { | |
then.call(value, _ctx($resolve, wrapper, 1), _ctx($reject, wrapper, 1)); | |
} catch (e) { | |
$reject.call(wrapper, e); | |
} | |
}); | |
} else { | |
promise._v = value; | |
promise._s = 1; | |
notify(promise, false); | |
} | |
} catch (e) { | |
$reject.call({ _w: promise, _d: false }, e); // wrap | |
} | |
}; | |
// constructor polyfill | |
if (!USE_NATIVE) { | |
// 25.4.3.1 Promise(executor) | |
$Promise = function Promise(executor) { | |
_anInstance(this, $Promise, PROMISE, '_h'); | |
_aFunction(executor); | |
Internal.call(this); | |
try { | |
executor(_ctx($resolve, this, 1), _ctx($reject, this, 1)); | |
} catch (err) { | |
$reject.call(this, err); | |
} | |
}; | |
// eslint-disable-next-line no-unused-vars | |
Internal = function Promise(executor) { | |
this._c = []; // <- awaiting reactions | |
this._a = undefined; // <- checked in isUnhandled reactions | |
this._s = 0; // <- state | |
this._d = false; // <- done | |
this._v = undefined; // <- value | |
this._h = 0; // <- rejection state, 0 - default, 1 - handled, 2 - unhandled | |
this._n = false; // <- notify | |
}; | |
Internal.prototype = _redefineAll($Promise.prototype, { | |
// 25.4.5.3 Promise.prototype.then(onFulfilled, onRejected) | |
then: function then(onFulfilled, onRejected) { | |
var reaction = newPromiseCapability(_speciesConstructor(this, $Promise)); | |
reaction.ok = typeof onFulfilled == 'function' ? onFulfilled : true; | |
reaction.fail = typeof onRejected == 'function' && onRejected; | |
reaction.domain = isNode ? process.domain : undefined; | |
this._c.push(reaction); | |
if (this._a) { this._a.push(reaction); } | |
if (this._s) { notify(this, false); } | |
return reaction.promise; | |
}, | |
// 25.4.5.1 Promise.prototype.catch(onRejected) | |
'catch': function (onRejected) { | |
return this.then(undefined, onRejected); | |
} | |
}); | |
OwnPromiseCapability = function () { | |
var promise = new Internal(); | |
this.promise = promise; | |
this.resolve = _ctx($resolve, promise, 1); | |
this.reject = _ctx($reject, promise, 1); | |
}; | |
_newPromiseCapability.f = newPromiseCapability = function (C) { | |
return sameConstructor($Promise, C) | |
? new OwnPromiseCapability(C) | |
: newGenericPromiseCapability(C); | |
}; | |
} | |
_export(_export.G + _export.W + _export.F * !USE_NATIVE, { Promise: $Promise }); | |
_setToStringTag($Promise, PROMISE); | |
_setSpecies(PROMISE); | |
Wrapper = _core[PROMISE]; | |
// statics | |
_export(_export.S + _export.F * !USE_NATIVE, PROMISE, { | |
// 25.4.4.5 Promise.reject(r) | |
reject: function reject(r) { | |
var capability = newPromiseCapability(this); | |
var $$reject = capability.reject; | |
$$reject(r); | |
return capability.promise; | |
} | |
}); | |
_export(_export.S + _export.F * (_library || !USE_NATIVE), PROMISE, { | |
// 25.4.4.6 Promise.resolve(x) | |
resolve: function resolve(x) { | |
// instanceof instead of internal slot check because we should fix it without replacement native Promise core | |
if (x instanceof $Promise && sameConstructor(x.constructor, this)) { return x; } | |
return _promiseResolve(this, x); | |
} | |
}); | |
_export(_export.S + _export.F * !(USE_NATIVE && _iterDetect(function (iter) { | |
$Promise.all(iter)['catch'](empty); | |
})), PROMISE, { | |
// 25.4.4.1 Promise.all(iterable) | |
all: function all(iterable) { | |
var C = this; | |
var capability = newPromiseCapability(C); | |
var resolve = capability.resolve; | |
var reject = capability.reject; | |
var result = _perform(function () { | |
var values = []; | |
var index = 0; | |
var remaining = 1; | |
_forOf(iterable, false, function (promise) { | |
var $index = index++; | |
var alreadyCalled = false; | |
values.push(undefined); | |
remaining++; | |
C.resolve(promise).then(function (value) { | |
if (alreadyCalled) { return; } | |
alreadyCalled = true; | |
values[$index] = value; | |
--remaining || resolve(values); | |
}, reject); | |
}); | |
--remaining || resolve(values); | |
}); | |
if (result.e) { reject(result.v); } | |
return capability.promise; | |
}, | |
// 25.4.4.4 Promise.race(iterable) | |
race: function race(iterable) { | |
var C = this; | |
var capability = newPromiseCapability(C); | |
var reject = capability.reject; | |
var result = _perform(function () { | |
_forOf(iterable, false, function (promise) { | |
C.resolve(promise).then(capability.resolve, reject); | |
}); | |
}); | |
if (result.e) { reject(result.v); } | |
return capability.promise; | |
} | |
}); | |
_export(_export.P + _export.R, 'Promise', { 'finally': function (onFinally) { | |
var C = _speciesConstructor(this, _core.Promise || _global$1.Promise); | |
var isFunction = typeof onFinally == 'function'; | |
return this.then( | |
isFunction ? function (x) { | |
return _promiseResolve(C, onFinally()).then(function () { return x; }); | |
} : onFinally, | |
isFunction ? function (e) { | |
return _promiseResolve(C, onFinally()).then(function () { throw e; }); | |
} : onFinally | |
); | |
} }); | |
// https://github.com/tc39/proposal-promise-try | |
_export(_export.S, 'Promise', { 'try': function (callbackfn) { | |
var promiseCapability = _newPromiseCapability.f(this); | |
var result = _perform(callbackfn); | |
(result.e ? promiseCapability.reject : promiseCapability.resolve)(result.v); | |
return promiseCapability.promise; | |
} }); | |
var promise$2 = _core.Promise; | |
var promise = createCommonjsModule(function (module) { | |
module.exports = { "default": promise$2, __esModule: true }; | |
}); | |
var asyncToGenerator = createCommonjsModule(function (module, exports) { | |
"use strict"; | |
exports.__esModule = true; | |
var _promise2 = _interopRequireDefault(promise); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
exports.default = function (fn) { | |
return function () { | |
var gen = fn.apply(this, arguments); | |
return new _promise2.default(function (resolve, reject) { | |
function step(key, arg) { | |
try { | |
var info = gen[key](arg); | |
var value = info.value; | |
} catch (error) { | |
reject(error); | |
return; | |
} | |
if (info.done) { | |
resolve(value); | |
} else { | |
return _promise2.default.resolve(value).then(function (value) { | |
step("next", value); | |
}, function (err) { | |
step("throw", err); | |
}); | |
} | |
} | |
return step("next"); | |
}); | |
}; | |
}; | |
}); | |
var documentReady = createCommonjsModule(function (module, exports) { | |
'use strict'; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _regenerator2 = _interopRequireDefault(index$10); | |
var _promise2 = _interopRequireDefault(promise); | |
var _asyncToGenerator3 = _interopRequireDefault(asyncToGenerator); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
/** | |
* Await for this to run code after the DOM has been parsed and loaded (but not | |
* sub-resources like images, scripts, etc). | |
* | |
* @example | |
* ```js | |
* async function main() { | |
* await documentReady() | |
* console.log('Document ready!') | |
* } | |
* main() | |
* ``` | |
*/ | |
exports.default = function () { | |
var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { | |
return _regenerator2.default.wrap(function _callee$(_context) { | |
while (1) { | |
switch (_context.prev = _context.next) { | |
case 0: | |
if (!(document.readyState === 'loading')) { | |
_context.next = 3; | |
break; | |
} | |
_context.next = 3; | |
return new _promise2.default(function (resolve) { | |
document.addEventListener('DOMContentLoaded', resolve); | |
}); | |
case 3: | |
case 'end': | |
return _context.stop(); | |
} | |
} | |
}, _callee, this); | |
})); | |
function documentReady() { | |
return _ref.apply(this, arguments); | |
} | |
return documentReady; | |
}(); | |
}); | |
var documentReady$1 = unwrapExports(documentReady); | |
// TODO: | |
// - Finish lookAt from the camera tutorial. | |
let targetContextMap = new WeakMap; | |
function createWebGLContext(target, version) { | |
const canvas = createCanvas('100%', '100%'); | |
const gl = getGl(canvas, version); | |
if (gl) { | |
if (targetContextMap.has(target)) { removeWebGLContext(target); } | |
target.appendChild(canvas); | |
targetContextMap.set(target, gl); | |
} | |
return gl | |
} | |
function removeWebGLContext(target) { | |
const gl = targetContextMap.get(target); | |
target.removeChild(gl.canvas); | |
} | |
function createCanvas(width, height) { | |
const canvas = document.createElement('canvas'); | |
setCanvasCSSSize(canvas, width, height); | |
return canvas | |
} | |
function setCanvasCSSSize(canvas, width, height) { | |
canvas.style.width = width; | |
canvas.style.height = height; | |
} | |
function setGlResolution(gl, width, height) { | |
setCanvasRenderSize(gl.canvas, width, height); | |
gl.viewport(0, 0, width, height); | |
} | |
function setCanvasRenderSize(canvas, width, height) { | |
canvas.width = width; | |
canvas.height = height; | |
} | |
function getGl(canvasOrSelector, version) { | |
let canvas; | |
if (canvasOrSelector instanceof HTMLCanvasElement) | |
{ canvas = canvasOrSelector; } | |
if (!canvas) | |
{ canvas = document.querySelector(canvasOrSelector); } | |
if (!(canvas instanceof HTMLCanvasElement)) { return false } | |
if (version == 1 || version == undefined) { version = ''; } | |
else if (version == 2) { version = '2'; } | |
else { throw new Error('Invalid WebGL version.') } | |
return canvas.getContext('webgl'+version) | |
} | |
function createShader(gl, type, source) { | |
// Create a vertex shader object | |
const shader = gl.createShader(type); | |
// Attach vertex shader source code | |
gl.shaderSource(shader, source); | |
// Compile the vertex shader | |
gl.compileShader(shader); | |
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); | |
if (success) { return shader } | |
const error = new Error("*** Error compiling shader '" + shader + "':" + gl.getShaderInfoLog(shader)); | |
gl.deleteShader(shader); | |
throw error | |
} | |
function createProgram(gl, vertexShader, fragmentShader) { | |
// Create a shader program object to store | |
// the combined shader program | |
const program = gl.createProgram(); | |
// Attach a vertex shader | |
gl.attachShader(program, vertexShader); | |
// Attach a fragment shader | |
gl.attachShader(program, fragmentShader); | |
// Link both programs | |
gl.linkProgram(program); | |
const success = gl.getProgramParameter(program, gl.LINK_STATUS); | |
if (success) { | |
return program | |
} | |
console.log(' --- Error making program. GL Program Info Log:', gl.getProgramInfoLog(program)); | |
gl.deleteProgram(program); | |
} | |
const v3 = { | |
cross(a, b) { | |
return [ | |
a[1] * b[2] - a[2] * b[1], | |
a[2] * b[0] - a[0] * b[2], | |
a[0] * b[1] - a[1] * b[0], | |
] | |
}, | |
subtract(a, b) { | |
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]] | |
}, | |
add(a, b) { | |
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]] | |
}, | |
normalize(v) { | |
const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); | |
// make sure we don't divide by 0. | |
if (length > 0.00001) { | |
return [v[0] / length, v[1] / length, v[2] / length] | |
} else { | |
return [0, 0, 0] | |
} | |
}, | |
}; | |
class Geometry { | |
constructor(...args) { | |
this._init(...args); | |
} | |
_init() { | |
this.verts = null; // Float32Array | |
this.normals = null; // Float32Array | |
this._colors = null; // Float32Array | |
this._color = null; | |
this._calcVerts(); | |
this.color = [ 0.5, 0.5, 0.5 ]; | |
} | |
// TODO handle CSS color strings with tinycolor2 from NPM | |
// @param {Array.number} value - array of four color values r, g, b, and a. | |
// TODO: don't use accept values for color alpha, use node's opacity. | |
set color(value) { | |
if (!value) { return } | |
this._color = value; | |
let color = null; | |
if (typeof value == 'string') | |
{ color = value.trim().split(' ').map(rgbPart => parseFloat(rgbPart)); } | |
else { color = value; } | |
// length of _colors array, considering it is four numbers per color, | |
// for each vertex. | |
// TODO: use a uniform instead of attributes that are all the same | |
// value. | |
const l = this.verts.length; | |
const _colorsLength = l + l/3; | |
const _colors = this._colors = new Float32Array(_colorsLength); | |
for (let i=0; i<_colorsLength; i+=4) { // 4 color parts per vertex | |
_colors[i+0] = color[0]; // r | |
_colors[i+1] = color[1]; // g | |
_colors[i+2] = color[2]; // b | |
_colors[i+3] = typeof color[3] == 'undefined' ? 1 : color[3]; // a | |
} | |
} | |
get color() { | |
return this._color | |
} | |
} | |
class IsoscelesTriangle extends Geometry { | |
_init(width, height) { | |
this.width = width; // number | |
this.height = height; // number | |
super._init(); | |
} | |
_calcVerts() { | |
var ref = this; | |
var width = ref.width; | |
var height = ref.height; | |
const verts = this.verts = new Float32Array([ | |
-width/2, 0, 0, | |
width/2, 0, 0, | |
0, height, 0, | |
]); | |
const normal = [0,0,1]; // pointing along Z | |
const normals = this.normals = new Float32Array(verts.length); | |
for (let i=0, l=verts.length; i<l; i+=3) { // 3 numbers per vertex | |
normals[i+0] = normal[0]; | |
normals[i+1] = normal[1]; | |
normals[i+2] = normal[2]; | |
} | |
} | |
} | |
class SymmetricTrapezoid extends Geometry { // extends from TwoDeePolygon, which has same normals code. | |
_init(baseWidth, topWidth, height) { | |
this.baseWidth = baseWidth; // number | |
this.topWidth = topWidth; // number | |
this.height = height; // number | |
super._init(); | |
} | |
_calcVerts() { | |
var ref = this; | |
var baseWidth = ref.baseWidth; | |
var topWidth = ref.topWidth; | |
var height = ref.height; | |
const verts = this.verts = new Float32Array([ | |
-baseWidth/2, 0, 0, | |
baseWidth/2, 0, 0, | |
topWidth/2, height, 0, | |
topWidth/2, height, 0, | |
-topWidth/2, height, 0, | |
-baseWidth/2, 0, 0, | |
]); | |
const normal = [0,0,1]; // pointing along Z | |
const normals = this.normals = new Float32Array(verts.length); | |
for (let i=0, l=verts.length; i<l; i+=3) { // 3 numbers per vertex | |
normals[i+0] = normal[0]; | |
normals[i+1] = normal[1]; | |
normals[i+2] = normal[2]; | |
} | |
} | |
} | |
class Quad extends Geometry { | |
_init(width, height) { | |
this.width = width; // number | |
this.height = height; // number | |
super._init(); | |
} | |
_calcVerts() { | |
var ref = this; | |
var width = ref.width; | |
var height = ref.height; | |
const verts = this.verts = new Float32Array([ | |
-width/2, -height/2, 0, | |
width/2, -height/2, 0, | |
width/2, height/2, 0, | |
width/2, height/2, 0, | |
-width/2, height/2, 0, | |
-width/2, -height/2, 0, | |
]); | |
const normal = [0,0,1]; // pointing along Z | |
const normals = this.normals = new Float32Array(verts.length); | |
for (let i=0, l=verts.length; i<l; i+=3) { // 3 numbers per vertex | |
normals[i+0] = normal[0]; | |
normals[i+1] = normal[1]; | |
normals[i+2] = normal[2]; | |
} | |
} | |
} | |
class Cube extends Geometry { | |
_init(x, y, width) { | |
// the top front left corner | |
this.x = x; // number | |
this.y = y; // number | |
this.width = width; // number | |
super._init(); | |
} | |
_calcVerts() { | |
var ref = this; | |
var x = ref.x; | |
var y = ref.y; | |
var width = ref.width; | |
const x2 = x + width; | |
const y2 = y + width; | |
const verts = this.verts = new Float32Array([ | |
// front face | |
x, y, 0, | |
x2, y, 0, | |
x2, y2, 0, | |
x2, y2, 0, | |
x, y2, 0, | |
x, y, 0, | |
// left face | |
x, y, 0, | |
x, y, -width, | |
x, y2, -width, | |
x, y2, -width, | |
x, y2, 0, | |
x, y, 0, | |
// right face | |
x2, y, 0, | |
x2, y, -width, | |
x2, y2, -width, | |
x2, y2, -width, | |
x2, y2, 0, | |
x2, y, 0, | |
// back face | |
x, y, -width, | |
x2, y, -width, | |
x2, y2, -width, | |
x2, y2, -width, | |
x, y2, -width, | |
x, y, -width, | |
// top face | |
x, y, 0, | |
x, y, -width, | |
x2, y, -width, | |
x2, y, -width, | |
x2, y, 0, | |
x, y, 0, | |
// bottom face | |
x, y2, 0, | |
x, y2, -width, | |
x2, y2, -width, | |
x2, y2, -width, | |
x2, y2, 0, | |
x, y2, 0, | |
]); | |
const faceNormals = [ | |
[0,0,1, ], // front face | |
[-1,0,0, ], // left face | |
[1,0,0,], // right face | |
[0,0,-1,], // back face | |
[0,-1,0, ], // top face | |
[0,1,0,], // bottom face | |
]; | |
const normals = this.normals = new Float32Array(verts.length); | |
for (let side=0, i=0, l=verts.length; i<l; i+=6*3, side+=1) { // 6 vertices per side, 3 numbers per vertex normal | |
// first vertex | |
normals[i+0] = faceNormals[side][0]; | |
normals[i+1] = faceNormals[side][1]; | |
normals[i+2] = faceNormals[side][2]; | |
// second vertex | |
normals[i+3] = faceNormals[side][0]; | |
normals[i+4] = faceNormals[side][1]; | |
normals[i+5] = faceNormals[side][2]; | |
// third vertex | |
normals[i+6] = faceNormals[side][0]; | |
normals[i+7] = faceNormals[side][1]; | |
normals[i+8] = faceNormals[side][2]; | |
// fourth vertex | |
normals[i+9] = faceNormals[side][0]; | |
normals[i+10] = faceNormals[side][1]; | |
normals[i+11] = faceNormals[side][2]; | |
// fifth vertex | |
normals[i+12] = faceNormals[side][0]; | |
normals[i+13] = faceNormals[side][1]; | |
normals[i+14] = faceNormals[side][2]; | |
// sixth vertex | |
normals[i+15] = faceNormals[side][0]; | |
normals[i+16] = faceNormals[side][1]; | |
normals[i+17] = faceNormals[side][2]; | |
} | |
} | |
} | |
class FourSidedPyramid extends Geometry { | |
//_init(base, height) { | |
//this.base = base | |
//this.height = height | |
//super._init() | |
//} | |
//_calcVerts() { | |
//const {base, height} = this | |
//// base is hypotenuse in following calculations | |
//// TODO: this can be replaced with a loop that can make any-sided | |
//// pyramid. | |
//const verts = this.verts = new Float32Array([ | |
//// base | |
//0, 0, 0, // bottom front left | |
//base, 0, 0, // bottom front right | |
//base, 0, -base, // bottom back right | |
//base, 0, -base, // bottom back right | |
//0, 0, -base, // bottom back left | |
//0, 0, 0, // bottom front left | |
//// front | |
//0, 0, 0, // bottom front left | |
//base, 0, 0, // bottom front right | |
//base/2, height, -base/2, // tip top | |
//// right | |
//base, 0, 0, // bottom front right | |
//base, 0, -base, // bottom back right | |
//base/2, height, -base/2, // tip top | |
//// back | |
//base, 0, -base, // bottom back right | |
//0, 0, -base, // bottom back left | |
//base/2, height, -base/2, // tip top | |
//// left | |
//0, 0, -base, // bottom back left | |
//0, 0, 0, // bottom front left | |
//base/2, height, -base/2, // tip top | |
//]) | |
//const faceNormals = [ | |
//// bottom | |
//[0, -1, 0], | |
//// front | |
//v3.cross( | |
//[base, 0, 0], // bottom front right | |
//[base/2, height, -base/2] // tip top | |
//), | |
//// right | |
//v3.cross( | |
//[base, 0, -base], // bottom back right | |
//[-base/2, height, -base/2] // tip top (-x) | |
//), | |
//// left | |
//v3.cross( | |
//[-base, 0, 0], // bottom front right (-x) | |
//[-base/2, height, base/2] // tip top (-x, +z) | |
//), | |
//// right | |
//v3.cross( | |
//[0, 0, base], // bottom back left (+z) | |
//[base/2, height, base/2] // tip top (+z) | |
//), | |
//] | |
//const normals = this.normals = new Float32Array(verts.length) | |
//// bottom (6 verts) | |
//for (let side=0, i=0, l=6*3; i<l; i+=6*3, side+=1) { // 6 vertices per side, 3 numbers per vertex normal | |
//// first vertex | |
//normals[i+0] = faceNormals[side][0] | |
//normals[i+1] = faceNormals[side][1] | |
//normals[i+2] = faceNormals[side][2] | |
//// second vertex | |
//normals[i+3] = faceNormals[side][0] | |
//normals[i+4] = faceNormals[side][1] | |
//normals[i+5] = faceNormals[side][2] | |
//// third vertex | |
//normals[i+6] = faceNormals[side][0] | |
//normals[i+7] = faceNormals[side][1] | |
//normals[i+8] = faceNormals[side][2] | |
//// fourth vertex | |
//normals[i+9] = faceNormals[side][0] | |
//normals[i+10] = faceNormals[side][1] | |
//normals[i+11] = faceNormals[side][2] | |
//// fifth vertex | |
//normals[i+12] = faceNormals[side][0] | |
//normals[i+13] = faceNormals[side][1] | |
//normals[i+14] = faceNormals[side][2] | |
//// sixth vertex | |
//normals[i+15] = faceNormals[side][0] | |
//normals[i+16] = faceNormals[side][1] | |
//normals[i+17] = faceNormals[side][2] | |
//} | |
//// sides (3 verts each) | |
//for (let side=0+1, i=0, l=verts.length - 6*3; i<l; i+=3*3, side+=1) { // 3 vertices per side, 3 numbers per vertex normal | |
//// first vertex | |
//normals[i+0] = faceNormals[side][0] | |
//normals[i+1] = faceNormals[side][1] | |
//normals[i+2] = faceNormals[side][2] | |
//// second vertex | |
//normals[i+3] = faceNormals[side][0] | |
//normals[i+4] = faceNormals[side][1] | |
//normals[i+5] = faceNormals[side][2] | |
//// third vertex | |
//normals[i+6] = faceNormals[side][0] | |
//normals[i+7] = faceNormals[side][1] | |
//normals[i+8] = faceNormals[side][2] | |
//} | |
//} | |
_init() { | |
super._init(); | |
} | |
_calcVerts() { | |
this.verts = new Float32Array([ | |
-100 ,0.087303 ,-100 | |
,100 ,0.087303 ,-100 | |
,100 ,0.087303 ,100 | |
,-100 ,0.087303 ,100 | |
,100 ,0.087303 ,100 | |
,100 ,0.087303 ,-100 | |
,0 ,200.087 ,0 | |
,100 ,0.087303 ,-100 | |
,-100 ,0.087303 ,-100 | |
,0 ,200.087 ,0 | |
,-100 ,0.087303 ,-100 | |
,-100 ,0.087303 ,100 | |
,0 ,200.087 ,0 | |
,-100 ,0.087303 ,100 | |
,100 ,0.087303 ,100 | |
,0 ,200.087 ,0 | |
]); | |
this.normals = new Float32Array([ | |
0 ,-1 ,0 | |
,0 ,-1 ,0 | |
,0 ,-1 ,0 | |
,0 ,-1 ,0 | |
,0.894427 ,0.447214 ,0 | |
,0.894427 ,0.447214 ,0 | |
,0.894427 ,0.447214 ,0 | |
,0 ,0.447214 ,-0.894427 | |
,0 ,0.447214 ,-0.894427 | |
,0 ,0.447214 ,-0.894427 | |
,-0.894427 ,0.447214 ,0 | |
,-0.894427 ,0.447214 ,0 | |
,-0.894427 ,0.447214 ,0 | |
,0 ,0.447214 ,0.894427 | |
,0 ,0.447214 ,0.894427 | |
,0 ,0.447214 ,0.894427 | |
]); | |
} | |
} | |
const m3 = { | |
identity: Object.freeze([ | |
1, 0, 0, | |
0, 1, 0, | |
0, 0, 1, | |
]), | |
translation(tx, ty) { | |
return [ | |
1, 0, 0, | |
0, 1, 0, | |
tx, ty, 1, | |
] | |
}, | |
rotation(angleInRadians) { | |
const c = Math.cos(angleInRadians); | |
const s = Math.sin(angleInRadians); | |
return [ | |
c,-s, 0, | |
s, c, 0, | |
0, 0, 1, | |
] | |
}, | |
scaling(sx, sy) { | |
return [ | |
sx, 0, 0, | |
0, sy, 0, | |
0, 0, 1, | |
] | |
}, | |
// Note: This matrix flips the Y axis so that 0 is at the top. | |
projection(width, height) { | |
// longer version, multiple matrices | |
let matrix = m3.identity; | |
matrix = m3.multiply(m3.scaling(1/width, 1/height), matrix); // get the portion of clip space | |
matrix = m3.multiply(m3.scaling(2, 2), matrix); // convert to clip space units | |
matrix = m3.multiply(m3.translation(-1, -1), matrix); // Move from the center to bottom left | |
matrix = m3.multiply(m3.scaling(1, -1), matrix); // move to the top left like DOM | |
return matrix | |
// shorter version, manual result of the longer version | |
//return [ | |
//2 / width, 0, 0, | |
//0, -2 / height, 0, | |
//-1, 1, 1 | |
//] | |
}, | |
multiply(a, b) { | |
const a00 = a[0]; | |
const a01 = a[1]; | |
const a02 = a[2]; | |
const a10 = a[3]; | |
const a11 = a[4]; | |
const a12 = a[5]; | |
const a20 = a[6]; | |
const a21 = a[7]; | |
const a22 = a[8]; | |
const b00 = b[0]; | |
const b01 = b[1]; | |
const b02 = b[2]; | |
const b10 = b[3]; | |
const b11 = b[4]; | |
const b12 = b[5]; | |
const b20 = b[6]; | |
const b21 = b[7]; | |
const b22 = b[8]; | |
return [ | |
b00 * a00 + b01 * a10 + b02 * a20, | |
b00 * a01 + b01 * a11 + b02 * a21, | |
b00 * a02 + b01 * a12 + b02 * a22, | |
b10 * a00 + b11 * a10 + b12 * a20, | |
b10 * a01 + b11 * a11 + b12 * a21, | |
b10 * a02 + b11 * a12 + b12 * a22, | |
b20 * a00 + b21 * a10 + b22 * a20, | |
b20 * a01 + b21 * a11 + b22 * a21, | |
b20 * a02 + b21 * a12 + b22 * a22, | |
] | |
}, | |
}; | |
const m4 = { | |
identity: Object.freeze([ | |
1, 0, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 1, 0, | |
0, 0, 0, 1, | |
]), | |
translation(tx, ty, tz) { | |
return [ | |
1, 0, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 1, 0, | |
tx, ty, tz, 1, | |
] | |
}, | |
xRotation(degrees) { | |
const radians = degToRad(degrees); | |
const c = Math.cos(radians); | |
const s = Math.sin(radians); | |
return [ | |
1, 0, 0, 0, | |
0, c, s, 0, | |
0, -s, c, 0, | |
0, 0, 0, 1, | |
] | |
}, | |
yRotation(degrees) { | |
const radians = degToRad(degrees); | |
const c = Math.cos(radians); | |
const s = Math.sin(radians); | |
return [ | |
c, 0, -s, 0, | |
0, 1, 0, 0, | |
s, 0, c, 0, | |
0, 0, 0, 1, | |
] | |
}, | |
zRotation(degrees) { | |
const radians = degToRad(degrees); | |
const c = Math.cos(radians); | |
const s = Math.sin(radians); | |
return [ | |
c,-s, 0, 0, | |
s, c, 0, 0, | |
0, 0, 1, 0, | |
0, 0, 0, 1, | |
] | |
}, | |
scaling(sx, sy, sz) { | |
return [ | |
sx, 0, 0, 0, | |
0, sy, 0, 0, | |
0, 0, sz, 0, | |
0, 0, 0, 1, | |
] | |
}, | |
inverse(m) { | |
const m00 = m[0 * 4 + 0]; | |
const m01 = m[0 * 4 + 1]; | |
const m02 = m[0 * 4 + 2]; | |
const m03 = m[0 * 4 + 3]; | |
const m10 = m[1 * 4 + 0]; | |
const m11 = m[1 * 4 + 1]; | |
const m12 = m[1 * 4 + 2]; | |
const m13 = m[1 * 4 + 3]; | |
const m20 = m[2 * 4 + 0]; | |
const m21 = m[2 * 4 + 1]; | |
const m22 = m[2 * 4 + 2]; | |
const m23 = m[2 * 4 + 3]; | |
const m30 = m[3 * 4 + 0]; | |
const m31 = m[3 * 4 + 1]; | |
const m32 = m[3 * 4 + 2]; | |
const m33 = m[3 * 4 + 3]; | |
const tmp_0 = m22 * m33; | |
const tmp_1 = m32 * m23; | |
const tmp_2 = m12 * m33; | |
const tmp_3 = m32 * m13; | |
const tmp_4 = m12 * m23; | |
const tmp_5 = m22 * m13; | |
const tmp_6 = m02 * m33; | |
const tmp_7 = m32 * m03; | |
const tmp_8 = m02 * m23; | |
const tmp_9 = m22 * m03; | |
const tmp_10 = m02 * m13; | |
const tmp_11 = m12 * m03; | |
const tmp_12 = m20 * m31; | |
const tmp_13 = m30 * m21; | |
const tmp_14 = m10 * m31; | |
const tmp_15 = m30 * m11; | |
const tmp_16 = m10 * m21; | |
const tmp_17 = m20 * m11; | |
const tmp_18 = m00 * m31; | |
const tmp_19 = m30 * m01; | |
const tmp_20 = m00 * m21; | |
const tmp_21 = m20 * m01; | |
const tmp_22 = m00 * m11; | |
const tmp_23 = m10 * m01; | |
const t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - | |
(tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31); | |
const t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - | |
(tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31); | |
const t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - | |
(tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31); | |
const t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - | |
(tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21); | |
const d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3); | |
return [ | |
d * t0, | |
d * t1, | |
d * t2, | |
d * t3, | |
d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - | |
(tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)), | |
d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - | |
(tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)), | |
d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - | |
(tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)), | |
d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - | |
(tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)), | |
d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - | |
(tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)), | |
d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - | |
(tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)), | |
d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - | |
(tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)), | |
d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - | |
(tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)), | |
d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - | |
(tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)), | |
d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - | |
(tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)), | |
d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - | |
(tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)), | |
d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - | |
(tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)) | |
] | |
}, | |
transpose(m) { | |
return [ | |
m[0], m[4], m[8], m[12], | |
m[1], m[5], m[9], m[13], | |
m[2], m[6], m[10], m[14], | |
m[3], m[7], m[11], m[15], | |
] | |
}, | |
// Note: This matrix flips the Y axis so that 0 is at the top. | |
projection(width, height, depth) { | |
// longer version, multiple matrices | |
//let matrix = m4.identity | |
//matrix = m4.multiply(m4.scaling(1/width, 1/height, 1/depth), matrix) // get the portion of clip space | |
//matrix = m4.multiply(m4.scaling(2, 2, 2), matrix) // convert to clip space units | |
//matrix = m4.multiply(m4.translation(-1, -1, 0), matrix) // Move from the center to bottom left | |
//matrix = m4.multiply(m4.scaling(1, -1, 1), matrix) // move to the top left like DOM | |
//return matrix | |
// shorter version, manual result of the longer version | |
return [ | |
2 / width, 0, 0, 0, | |
0, -2 / height, 0, 0, | |
0, 0, 2 / depth, 0, | |
-1, 1, 0, 1, | |
] | |
}, | |
// Note: This matrix flips the Y axis so that 0 is at the top. | |
orthographic(left, right, top, bottom, near, far) { | |
return [ | |
2 / (right - left), 0, 0, 0, | |
0, 2 / (top - bottom), 0, 0, | |
0, 0, 2 / (near - far), 0, | |
(left + right) / (left - right), | |
(bottom + top) / (bottom - top), | |
(near + far) / (near - far), | |
1, | |
] | |
}, | |
perspective(fieldOfViewInDegrees, aspect, near, far) { | |
const fieldOfViewInRadians = degToRad(fieldOfViewInDegrees); | |
const f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians); | |
const rangeInv = 1.0 / (near - far); | |
return [ | |
f / aspect, 0, 0, 0, | |
0, f, 0, 0, | |
0, 0, (near + far) * rangeInv, -1, | |
0, 0, near * far * rangeInv * 2, 0 | |
] | |
}, | |
lookAt(cameraPosition, target, up) { | |
const zAxis = v3.normalize(v3.subtract(cameraPosition, target)); | |
const xAxis = v3.cross(up, zAxis); | |
const yAxis = v3.cross(zAxis, xAxis); | |
return [ | |
xAxis[0], xAxis[1], xAxis[2], 0, | |
yAxis[0], yAxis[1], yAxis[2], 0, | |
zAxis[0], zAxis[1], zAxis[2], 0, | |
cameraPosition[0], cameraPosition[1], cameraPosition[2], 1, | |
]; | |
}, | |
multiply(a, b) { | |
const a00 = a[0 * 4 + 0]; | |
const a01 = a[0 * 4 + 1]; | |
const a02 = a[0 * 4 + 2]; | |
const a03 = a[0 * 4 + 3]; | |
const a10 = a[1 * 4 + 0]; | |
const a11 = a[1 * 4 + 1]; | |
const a12 = a[1 * 4 + 2]; | |
const a13 = a[1 * 4 + 3]; | |
const a20 = a[2 * 4 + 0]; | |
const a21 = a[2 * 4 + 1]; | |
const a22 = a[2 * 4 + 2]; | |
const a23 = a[2 * 4 + 3]; | |
const a30 = a[3 * 4 + 0]; | |
const a31 = a[3 * 4 + 1]; | |
const a32 = a[3 * 4 + 2]; | |
const a33 = a[3 * 4 + 3]; | |
const b00 = b[0 * 4 + 0]; | |
const b01 = b[0 * 4 + 1]; | |
const b02 = b[0 * 4 + 2]; | |
const b03 = b[0 * 4 + 3]; | |
const b10 = b[1 * 4 + 0]; | |
const b11 = b[1 * 4 + 1]; | |
const b12 = b[1 * 4 + 2]; | |
const b13 = b[1 * 4 + 3]; | |
const b20 = b[2 * 4 + 0]; | |
const b21 = b[2 * 4 + 1]; | |
const b22 = b[2 * 4 + 2]; | |
const b23 = b[2 * 4 + 3]; | |
const b30 = b[3 * 4 + 0]; | |
const b31 = b[3 * 4 + 1]; | |
const b32 = b[3 * 4 + 2]; | |
const b33 = b[3 * 4 + 3]; | |
return [ | |
b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, | |
b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, | |
b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, | |
b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, | |
b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, | |
b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, | |
b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, | |
b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, | |
b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, | |
b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, | |
b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, | |
b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, | |
b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, | |
b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, | |
b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, | |
b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33, | |
] | |
}, | |
}; | |
function degToRad(degrees) { | |
return degrees * Math.PI / 180 | |
} | |
const vertShaderSource = ` | |
attribute vec4 a_vertexPosition; | |
uniform mat4 u_worldViewProjectionMatrix; | |
// TODO: awaiting on transpose() method for DOMMatrix | |
//uniform mat4 u_worldInverseTransposeMatrix; // used for correct lighting normals | |
attribute vec4 a_color; | |
varying vec4 v_fragColor; | |
attribute vec3 a_normal; | |
varying vec3 v_vertNormal; | |
uniform mat4 u_worldMatrix; | |
uniform vec3 u_lightWorldPosition; | |
varying vec3 v_surfaceToLightVector; | |
uniform vec3 u_cameraWorldPosition; | |
varying vec3 v_surfaceToCameraVector; | |
attribute vec2 a_textureCoordinate; | |
varying vec2 v_textureCoordinate; | |
void main() { | |
vec3 surfaceWorldPosition = (u_worldMatrix * a_vertexPosition).xyz; | |
// compute the vector of the surface to the pointLight | |
// and pass it to the fragment shader | |
v_surfaceToLightVector = u_lightWorldPosition - surfaceWorldPosition; | |
// compute the vector of the surface to the camera | |
// and pass it to the fragment shader | |
v_surfaceToCameraVector = u_cameraWorldPosition - surfaceWorldPosition; | |
gl_Position = u_worldViewProjectionMatrix * a_vertexPosition; | |
v_fragColor = a_color; | |
//v_fragColor = gl_Position * 0.5 + 0.5; | |
// orient the normals and pass to the fragment shader | |
//v_vertNormal = mat3(u_worldInverseTransposeMatrix) * a_normal; // TODO waiting for transpose() method on DOMMatrix | |
//alternate: v_vertNormal = (u_worldInverseTransposeMatrix * vec4(a_normal, 0)).xyz; | |
v_vertNormal = mat3(u_worldMatrix) * a_normal; | |
v_textureCoordinate = a_textureCoordinate; | |
} | |
`; | |
const fragShaderSource = ` | |
// TODO: detect highp support, see | |
// https://github.com/greggman/webgl-fundamentals/issues/80#issuecomment-306746556 | |
//precision mediump float; | |
precision highp float; | |
varying vec4 v_fragColor; | |
varying vec3 v_vertNormal; | |
varying vec3 v_surfaceToLightVector; | |
//// TODO: use this for directional lighting (f.e. sunlight or moonlight). | |
//uniform vec3 reverseLightDirection; | |
varying vec3 v_surfaceToCameraVector; | |
uniform float u_shininess; | |
uniform vec3 u_lightColor; | |
uniform vec3 u_specularColor; | |
varying vec2 v_textureCoordinate; | |
uniform sampler2D u_texture; | |
uniform bool u_hasTexture; | |
void main(void) { | |
// because v_vertNormal is a varying it's interpolated | |
// so it will not be a unit vector. Normalizing it | |
// will make it a unit vector again. | |
vec3 normal = normalize(v_vertNormal); | |
vec3 surfaceToCameraDirection = normalize(v_surfaceToCameraVector); | |
vec3 surfaceToLightDirection = normalize(v_surfaceToLightVector); | |
// represents the unit vector oriented at half of the angle between | |
// surfaceToLightDirection and surfaceToCameraDirection. | |
vec3 halfVector = normalize(surfaceToLightDirection + surfaceToCameraDirection); | |
float pointLight = dot(normal, surfaceToLightDirection); | |
float pointLightIntensity = 1.0; // TODO make configurable | |
//float directionalLight = dot(normal, reverseLightDirection); // TODO make configurable | |
//float specular = dot(normal, halfVector); | |
float specular = 0.0; | |
if (pointLight > 0.0) { | |
specular = pow(dot(normal, halfVector), u_shininess); | |
} | |
// TODO make configurable | |
//vec3 ambientLight = vec3(0.361, 0.184, 0.737); // teal | |
vec3 ambientLight = vec3(1.0, 1.0, 1.0); // white | |
float ambientLightIntensity = 0.3; | |
// TODO: user can choose color or texture, default to a color if no texture, etc. | |
// TODO: blend texture on top of color, if texture has alpha. | |
gl_FragColor = v_fragColor; | |
if (u_hasTexture) { | |
gl_FragColor = texture2D(u_texture, v_textureCoordinate); | |
} | |
// Lets multiply just the color portion (not the alpha) of | |
// gl_FragColor by the pointLight + directionalLight | |
//gl_FragColor.rgb *= pointLight * u_lightColor; // point light only. | |
//gl_FragColor.rgb *= directionalLight; // directional light only. | |
//gl_FragColor.rgb *= ambientLight; // ambient light only. | |
gl_FragColor.rgb *= | |
//clamp(directionalLight, 0.0, 1.0) + | |
clamp(pointLight, 0.0, 1.0) * u_lightColor * pointLightIntensity + | |
ambientLight * ambientLightIntensity; | |
// Just add in the specular | |
gl_FragColor.rgb += specular * u_specularColor; | |
//gl_FragColor.a = 0.5; | |
} | |
`; | |
//import * as PIXI from 'pixi.js' // also sets the PIXI global. | |
//import SVG from 'pixi-svg' // uses the PIXI global, won't work if you don't import the main `pixi.js module`. | |
//import Two from 'two.js/build/two' | |
const updateResolution = state => { | |
const resolution = [ | |
parseFloat(getComputedStyle(state.gl.canvas).width) * window.devicePixelRatio, | |
parseFloat(getComputedStyle(state.gl.canvas).height) * window.devicePixelRatio, | |
1000, | |
]; | |
setGlResolution(state.gl, ...resolution); | |
state.projectionMatrix = m4.perspective(45, resolution[0] / resolution[1], 1, 2000); | |
}; | |
class WebGlRenderer { | |
/** | |
* Creates the WebGL program for the given scene. | |
*/ | |
initGl(scene) { | |
const gl = createWebGLContext(scene); | |
const state = scene.webGlRendererState; | |
state.gl = gl; | |
if (!gl) { console.log('You need WebGL.'); } | |
const vertShader = createShader(gl, gl.VERTEX_SHADER, vertShaderSource); | |
const fragShader = createShader(gl, gl.FRAGMENT_SHADER, fragShaderSource); | |
const program = createProgram(gl, vertShader, fragShader); | |
gl.useProgram(program); | |
state.colorsBuffer = gl.createBuffer(); | |
state.colorAttributeLocation = gl.getAttribLocation(program, 'a_color'); | |
gl.enableVertexAttribArray(state.colorAttributeLocation); | |
state.vertexBuffer = gl.createBuffer(); | |
state.vertexAttributeLocation = gl.getAttribLocation(program, "a_vertexPosition"); | |
gl.enableVertexAttribArray(state.vertexAttributeLocation); | |
state.normalsBuffer = gl.createBuffer(); | |
state.normalAttributeLocation = gl.getAttribLocation(program, 'a_normal'); | |
gl.enableVertexAttribArray(state.normalAttributeLocation); | |
state.textureCoordinatesBuffer = gl.createBuffer(); | |
state.textureCoordinateLocation = gl.getAttribLocation(program, 'a_textureCoordinate'); | |
// cull_face doesn't work, because I've drawn my vertices in the wrong | |
// order. They should be clockwise to be front facing (I seem to have done | |
// them counter-clockwise). See "CULL_FACE" at | |
// https://webglfundamentals.org/webgl/lessons/webgl-3d-orthographic.html | |
//gl.enable(gl.CULL_FACE) | |
// enables depth sorting, so pixels aren't drawn in order of appearance, but order only if they are visible (on top of other pixels). | |
gl.enable(gl.DEPTH_TEST); | |
// enable alpha blending (transparency) | |
// XXX: For blending (transparency) to work, we have to disable depth testing. | |
// TODO: Maybe we have to selectively enable depth testing and disable | |
// blending, or vice versa, depending on the object we want to draw... | |
// ...Or perhaps we must draw things in a certain order, from back to front, | |
// so we can have depth testing AND blending at the same time. | |
gl.blendFunc(gl.SRC_ALPHA, gl.ONE); | |
gl.enable(gl.BLEND); | |
//gl.disable(gl.DEPTH_TEST) | |
state.projectionMatrix = m4.identity; | |
updateResolution(state); | |
scene.on('parentsizechange', () => updateResolution(state)); | |
state.worldViewProjectionMatrixLocation = gl.getUniformLocation(program, 'u_worldViewProjectionMatrix'); | |
//const worldInverseTransposeMatrixLocation = gl.getUniformLocation(program, 'u_worldInverseTransposeMatrix') | |
state.worldMatrixLocation = gl.getUniformLocation(program, 'u_worldMatrix'); | |
//const reverseLightDirectionLocation = gl.getUniformLocation(program, 'reverseLightDirection') | |
//gl.uniform3fv(reverseLightDirectionLocation, v3.normalize([0.5, 0.7, 1])) | |
state.lightWorldPositionLocation = gl.getUniformLocation(program, 'u_lightWorldPosition'); | |
state.cameraWorldPositionLocation = gl.getUniformLocation(program, 'u_cameraWorldPosition'); | |
const shininessLocation = gl.getUniformLocation(program, 'u_shininess'); | |
const lightColorLocation = gl.getUniformLocation(program, 'u_lightColor'); | |
const specularColorLocation = gl.getUniformLocation(program, 'u_specularColor'); | |
state.textureLocation = gl.getUniformLocation(program, 'u_texture'); | |
state.hasTextureLocation = gl.getUniformLocation(program, 'u_hasTexture'); | |
let shininess = 200; | |
gl.uniform1f(shininessLocation, shininess); | |
const red = [1, 0.6, 0.6]; | |
const white = [1, 1, 1]; | |
let lightColor = white; | |
gl.uniform3fv(lightColorLocation, v3.normalize(lightColor)); | |
let specularColor = white; | |
gl.uniform3fv(specularColorLocation, v3.normalize(specularColor)); | |
state.lightAnimParam = 0; | |
state.lightWorldPosition = [20,30,50]; | |
state.cameraAngle = 0; | |
state.cameraRadius = 200; | |
} | |
drawScene(scene) { | |
const state = scene.webGlRendererState; | |
var gl = state.gl; | |
// TODO: light does not affect the back side of polygons?... | |
state.lightAnimParam += 0.05; | |
state.lightWorldPosition = [ | |
300*Math.sin(state.lightAnimParam), | |
300*Math.sin(state.lightAnimParam*2), | |
Math.abs(300*Math.cos(state.lightAnimParam)) | |
//300 | |
]; | |
gl.uniform3fv(state.lightWorldPositionLocation, state.lightWorldPosition); | |
let backgroundColor = scene.getAttribute('background'); | |
if (typeof backgroundColor == 'string') | |
{ backgroundColor = backgroundColor.split(' ').map(rgbPart => parseFloat(rgbPart)); } | |
else | |
{ backgroundColor = [0, 0, 0, 0]; } | |
gl.clearColor(...backgroundColor); | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // why do we need to do this? | |
//state.cameraAngle++ | |
let cameraMatrix = m4.identity; | |
cameraMatrix = m4.multiply(cameraMatrix, m4.yRotation(state.cameraAngle)); | |
cameraMatrix = m4.multiply(cameraMatrix, m4.translation(0, 0, state.cameraRadius * 1.5)); | |
const viewMatrix = m4.inverse(cameraMatrix); | |
state.viewProjectionMatrix = m4.multiply(state.projectionMatrix, viewMatrix); | |
const cameraWorldPosition = [cameraMatrix[12], cameraMatrix[13], cameraMatrix[14]]; | |
gl.uniform3fv(state.cameraWorldPositionLocation, cameraWorldPosition); | |
// TODO: we need to use the traversal that takes into consideration ShadowDOM. | |
const children = scene.imperativeCounterpart._children; | |
for (let i=0, l=children.length; i<l; i+=1) { | |
this.drawNodeAndRecurse(state, children[i]); | |
} | |
} | |
drawNodeAndRecurse(state, node) { | |
var gl = state.gl; | |
const meshAttr = node.element.getAttribute('mesh'); | |
if (meshAttr) { | |
const size = node._calculatedSize; | |
const svgElement = Array.from(node.element.children) | |
.find(child => child instanceof SVGSVGElement); | |
const hasTexture = !!svgElement; | |
if (meshAttr == 'cube') { | |
if (!(node.__shape instanceof Cube)) | |
{ node.__shape = new Cube(0, 0, size.x); } | |
// TODO else, like quad or symtrap | |
} | |
else if (meshAttr == 'quad') { | |
if (!(node.__shape instanceof Quad)) | |
{ node.__shape = new Quad(size.x, size.y); } | |
else { | |
node.__shape.width = size.x; | |
node.__shape.height = size.y; | |
node.__shape._calcVerts(); | |
} | |
if (hasTexture) { | |
// TODO we would create one per Geometry (and eventually multiple per | |
// geometry), but for now just one texture for all quads to get it working. | |
// TODO Make the texture only once, not each tick. | |
if (!node.__texture) { | |
// XXX this will eventually be set with a texture map feature | |
// TODO: for now, we should at least set default | |
// coordinates for each geometry, even if that's not | |
// ideal; it's more ideal than nothing. | |
node.__shape.textureCoordinates = new Float32Array([ | |
0, 0, | |
1, 0, | |
1, 1, | |
1, 1, | |
0, 1, | |
0, 0, | |
]); | |
node.__texture = gl.createTexture(); | |
} | |
///// SVG TEXTURE FROM TWO.JS { | |
if (!node.__two) { | |
node.__two = new Two({ | |
type: Two.Types.webgl, | |
fullscreen: false, | |
autostart: false, | |
}); | |
node.__two.interpret(svgElement); | |
} | |
node.__two.update(); | |
const image = node.__two.renderer.domElement; | |
const isPowerOf2 = value => (value & (value - 1)) == 0; | |
// copy the pixi canvas image to the texture. | |
gl.bindTexture(gl.TEXTURE_2D, node.__texture); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); | |
// TODO: unbind from buffers and textures when done | |
// using them, to prevent modification from outside | |
// Mip maps can only be generated on images whose width and height are a power of 2. | |
if (isPowerOf2(image.width) && isPowerOf2(image.height)) { | |
gl.generateMipmap(gl.TEXTURE_2D); | |
// TODO make filters configurable? | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); | |
// Using just NEAREST or LINEAR only can increase performance, for example. | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) | |
} | |
else { | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
// TODO make filters configurable? | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
} | |
///// } | |
///// SVG TEXTURE FROM PIXI-SVG { | |
//if (!node.__pixiRenderer) { | |
//node.__pixiRenderer = PIXI.autoDetectRenderer({ | |
//width: node._calculatedSize.x * window.devicePixelRatio, | |
//height: node._calculatedSize.y * window.devicePixelRatio, | |
////width: 300 * window.devicePixelRatio, | |
////height: 300 * window.devicePixelRatio, | |
//resolution: window.devicePixelRatio, | |
//}); | |
//node.__pixiStage = new PIXI.Container() | |
//window.stage = node.__pixiStage | |
//} | |
//node.__pixiStage.removeChild(node.__svgGraphic) | |
//node.__svgGraphic = new SVG(svgElement) | |
//node.__pixiStage.addChild(node.__svgGraphic) | |
//node.__pixiRenderer.render(node.__pixiStage); | |
//const image = node.__pixiRenderer.view | |
//const isPowerOf2 = value => (value & (value - 1)) == 0 | |
//// copy the pixi canvas image to the texture. | |
//gl.bindTexture(gl.TEXTURE_2D, node.__texture) | |
//gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image) | |
//// TODO: unbind from buffers and textures when done | |
//// using them, to prevent modification from outside | |
//// Mip maps can only be generated on images whose width and height are a power of 2. | |
//if (isPowerOf2(image.width) && isPowerOf2(image.height)) { | |
//gl.generateMipmap(gl.TEXTURE_2D) | |
//// TODO make filters configurable? | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) | |
//// Using just NEAREST or LINEAR only can increase performance, for example. | |
////gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) | |
////gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) | |
//} | |
//else { | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) | |
//// TODO make filters configurable? | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) | |
//} | |
///// } | |
///// PRE-DEFINED TEXTURE FROM IMAGE { | |
//// set a temporary solid color texture for the meantime | |
//// while the following texture loads. | |
//gl.bindTexture(gl.TEXTURE_2D, node.__texture) | |
//// Fill the texture with a 1x1 blue pixel to start with. | |
//gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])) | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
//const image = new Image | |
//const isPowerOf2 = value => (value & (value - 1)) == 0 | |
//image.addEventListener('load', () => { | |
//// Now that the image has loaded copy it to the texture. | |
//gl.bindTexture(gl.TEXTURE_2D, node.__texture) | |
//gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image) | |
//// TODO: unbind from buffers and textures when done | |
//// using them, to prevent modification from outside | |
//// Mip maps can only be generated on images whose width and height are a power of 2. | |
//if (isPowerOf2(image.width) && isPowerOf2(image.height)) { | |
//gl.generateMipmap(gl.TEXTURE_2D) | |
//// TODO make filters configurable? | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) | |
//// Using just NEAREST or LINEAR only can increase performance, for example. | |
////gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) | |
////gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) | |
//} | |
//else { | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) | |
//// TODO make filters configurable? | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) | |
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) | |
//} | |
//}) | |
//image.src = imageUrl // imageUrl should be a data URL | |
//// } | |
} | |
} | |
else if (meshAttr == 'isotriangle') { | |
if (!(node.__shape instanceof IsoscelesTriangle)) | |
{ node.__shape = new IsoscelesTriangle(size.x, size.y); } | |
// TODO else, like quad or symtrap | |
} | |
else if (meshAttr == 'pyramid4') { | |
if (!(node.__shape instanceof FourSidedPyramid)) | |
{ node.__shape = new FourSidedPyramid(size.x, size.y); } | |
// TODO else, like quad or symtrap | |
} | |
else if (meshAttr == 'symtrap') { | |
if (!(node.__shape instanceof SymmetricTrapezoid)) | |
{ node.__shape = new SymmetricTrapezoid(size.x/2, size.x, size.y); } | |
else { | |
node.__shape.baseWidth = size.x/2; | |
node.__shape.topWidth = size.x; | |
node.__shape.height = size.y; | |
node.__shape._calcVerts(); | |
} | |
} | |
//else node.__shape = null | |
else { | |
if (!(node.__shape instanceof Quad)) | |
{ node.__shape = new Quad(size.x, size.y); } | |
else { | |
node.__shape.width = size.x; | |
node.__shape.height = size.y; | |
node.__shape._calcVerts(); | |
} | |
// TODO this will eventually be set with a texture map feature | |
if (hasTexture) { | |
node.__shape.textureCoordinates = new Float32Array([ | |
0, 0, | |
1, 0, | |
1, 1, | |
1, 1, | |
0, 1, | |
0, 0, | |
]); | |
} | |
} | |
if (node.__shape) { | |
// COLORS ///////////////////////////////// | |
node.__shape.color = node.element.getAttribute('color'); | |
gl.bindBuffer(gl.ARRAY_BUFFER, state.colorsBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, node.__shape._colors, gl.STATIC_DRAW); | |
// Tell the attribute how to get data out of vertexBuffer (ARRAY_BUFFER) | |
const colorSize = 4; // components per iteration | |
const colorType = gl.FLOAT; | |
const normalizeColorData = false; // don't normalize the data | |
const colorStride = 0; // 0 = move forward colorSize * sizeof(colorType) each iteration to get the next vertex | |
const colorOffset = 0; // start at the beginning of the buffer | |
gl.vertexAttribPointer( | |
state.colorAttributeLocation, colorSize, colorType, normalizeColorData, colorStride, colorOffset); | |
// VERTICES ///////////////////////////////// | |
gl.bindBuffer(gl.ARRAY_BUFFER, state.vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, node.__shape.verts, gl.STATIC_DRAW); | |
// Tell the attribute how to get data out of vertexBuffer (ARRAY_BUFFER) | |
const vertexSize = 3; // components per iteration | |
const type = gl.FLOAT; | |
const normalizeVertexData = false; // don't normalize the data | |
const stride = 0; // 0 = move forward vertexSize * sizeof(type) each iteration to get the next vertex | |
const offset = 0; // start at the beginning of the buffer | |
gl.vertexAttribPointer( | |
state.vertexAttributeLocation, vertexSize, type, normalizeVertexData, stride, offset); | |
// NORMALS ///////////////////////////////// | |
gl.bindBuffer(gl.ARRAY_BUFFER, state.normalsBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, node.__shape.normals, gl.STATIC_DRAW); | |
// Tell the attribute how to get data out of vertexBuffer (ARRAY_BUFFER) | |
const normalSize = 3; // components per iteration | |
const normalType = gl.FLOAT; | |
const normalizeNormalsData = false; // don't normalize the data | |
const normalStride = 0; // 0 = move forward normalSize * sizeof(normalType) each iteration to get the next vertex | |
const normalOffset = 0; // start at the beginning of the buffer | |
gl.vertexAttribPointer( | |
state.normalAttributeLocation, normalSize, normalType, normalizeNormalsData, normalStride, normalOffset); | |
// TEXTURE COORDINATES ///////////////////////////////// | |
if (hasTexture) { | |
gl.uniform1i(state.hasTextureLocation, +true); | |
gl.bindBuffer(gl.ARRAY_BUFFER, state.textureCoordinatesBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, node.__shape.textureCoordinates, gl.STATIC_DRAW); | |
// Tell the attribute how to get data out of vertexBuffer (ARRAY_BUFFER) | |
const textureCoordinateSize = 2; // components per iteration | |
const textureCoordinateType = gl.FLOAT; | |
const normalizeTextureCoordinateData = false; // don't normalize the data | |
const textureCoordinateStride = 0; // 0 = move forward textureCoordinateSize * sizeof(textureCoordinateType) each iteration to get the next vertex | |
const textureCoordinateOffset = 0; // start at the beginning of the buffer | |
gl.enableVertexAttribArray(state.textureCoordinateLocation); | |
gl.vertexAttribPointer( | |
state.textureCoordinateLocation, textureCoordinateSize, textureCoordinateType, normalizeTextureCoordinateData, textureCoordinateStride, textureCoordinateOffset); | |
// Tell the shader to use texture unit 0 for u_texture | |
// TODO: Get index of the node's texture, but right now there's only one texture. | |
gl.uniform1i(state.textureLocation, 0); | |
} | |
else { | |
gl.uniform1i(state.hasTextureLocation, +false); | |
gl.disableVertexAttribArray(state.textureCoordinateLocation); | |
} | |
// TRANFORMS ///////////////////////////////// | |
gl.uniformMatrix4fv(state.worldMatrixLocation, false, node._worldMatrix.toFloat32Array()); | |
// for correct lighting normals | |
// TODO: waiting for transpose() method on DOMMatrix | |
//const worldInverseTransposeMatrix = m4.transpose(m4.inverse(node._worldMatrix)) | |
//gl.uniformMatrix4fv(worldInverseTransposeMatrixLocation, false, worldInverseTransposeMatrix) | |
const worldViewProjectionMatrix = m4.multiply(state.viewProjectionMatrix, node._worldMatrix.toFloat32Array()); | |
gl.uniformMatrix4fv(state.worldViewProjectionMatrixLocation, false, worldViewProjectionMatrix); | |
const count = node.__shape.verts.length / 3; | |
gl.drawArrays(gl.TRIANGLES, offset, count); | |
} | |
} | |
const children = node._children; | |
for (let i=0, l=children.length; i<l; i+=1) { | |
this.drawNodeAndRecurse(state, children[i]); | |
} | |
} | |
} | |
let instance = null; | |
function getWebGlRenderer() { | |
if (instance) { return instance } | |
else { return instance = new WebGlRenderer } | |
} | |
let documentIsReady = false; | |
let webGLRenderer = null; | |
class Motor { | |
constructor() { | |
this._inFrame = false; // true when inside a requested animation frame. | |
this._rAF = null; // the current animation frame, or null. | |
this._animationLoopStarted = false; | |
this._allRenderTasks = []; | |
this._taskIterationIndex = 0; | |
this._numberOfTasks = 0; | |
this._nodesToBeRendered = []; | |
this._modifiedScenes = []; | |
// A set of nodes that are the root nodes of subtrees where all nodes | |
// in each subtree need to have their world matrices updated. | |
this._worldMatrixRootNodes = []; | |
} | |
/** | |
* Starts an rAF loop and runs the render tasks in the _renderTasks stack. | |
* As long as there are tasks in the stack, the loop continues. When the | |
* stack becomes empty due to removal of tasks, the rAF stops and the app | |
* sits there doing nothing -- silence, crickets. | |
*/ | |
_startAnimationLoop() { | |
if (this._animationLoopStarted) { return Promise.resolve() } | |
this._animationLoopStarted = true; | |
const logic = () => { | |
// DIRECT ANIMATION LOOP /////////////////////////////////// | |
// So now we can render after the scene is mounted. | |
const motorLoop = timestamp => { | |
this._inFrame = true; | |
this._runRenderTasks(timestamp); | |
this._renderNodes(timestamp); | |
// If any tasks are left to run, continue the animation loop. | |
if (this._allRenderTasks.length) | |
{ this._rAF = requestAnimationFrame(motorLoop); } | |
else { | |
this._rAF = null; | |
this._animationLoopStarted = false; | |
} | |
this._inFrame = false; | |
}; | |
this._rAF = requestAnimationFrame(motorLoop); | |
}; | |
if (!documentIsReady) { | |
return documentReady$1().then(() => { | |
documentIsReady = true; | |
logic(); | |
}) | |
} | |
logic(); | |
return Promise.resolve() | |
} | |
//async _startAnimationLoop() { | |
//if (this._animationLoopStarted) return | |
//this._animationLoopStarted = true | |
//if (!documentIsReady) { | |
//await documentReady() | |
//documentIsReady = true | |
//} | |
//// DIRECT ANIMATION LOOP /////////////////////////////////// | |
//// So now we can render after the scene is mounted. | |
//const motorLoop = timestamp => { | |
//this._inFrame = true | |
//this._runRenderTasks(timestamp) | |
//this._renderNodes(timestamp) | |
//// If any tasks are left to run, continue the animation loop. | |
//if (this._allRenderTasks.length) | |
//this._rAF = requestAnimationFrame(motorLoop) | |
//else { | |
//this._rAF = null | |
//this._animationLoopStarted = false | |
//} | |
//this._inFrame = false | |
//} | |
//this._rAF = requestAnimationFrame(motorLoop) | |
//// ANIMATION LOOP USING WHILE AND AWAIT /////////////////////////////////// | |
////this._rAF = true | |
////let timestamp = null | |
////while (this._rAF) { | |
////timestamp = await animationFrame() | |
////this._inFrame = true | |
////this._runRenderTasks(timestamp) | |
////this._renderNodes(timestamp) | |
////// If any tasks are left to run, continue the animation loop. | |
////if (!this._allRenderTasks.length) { | |
////this._rAF = null | |
////this._animationLoopStarted = false | |
////} | |
////this._inFrame = false | |
////} | |
//} | |
/** | |
* When a render tasks is added a new rAF loop will be started if there | |
* isn't one currently. | |
* | |
* A render task is simply a function that will be called over and over | |
* again, in the Motor's animation loop. That's all, nothing special. | |
* However, if a Node setter is used inside of a render task, then the Node | |
* will tell Motor that it needs to be re-rendered, which will happen at | |
* the end of the current frame. If a Node setter is used outside of a | |
* render task (i.e. outside of the Motor's animation loop), then the Node | |
* tells Motor to re-render the Node on the next animation loop tick. | |
* Basically, regardless of where the Node's setters are used (inside or | |
* outside of the Motor's animation loop), rendering always happens inside | |
* the loop. | |
* | |
* @param {Function} fn The render task to add. | |
* @return {Function} A reference to the render task. Useful for saving to | |
* a variable so that it can later be passed to Motor.removeRenderTask(). | |
*/ | |
addRenderTask(fn) { | |
if (typeof fn != 'function') | |
{ throw new Error('Render task must be a function.') } | |
if (this._allRenderTasks.includes(fn)) { return } | |
this._allRenderTasks.push(fn); | |
this._numberOfTasks += 1; | |
// If the render loop isn't started, start it. | |
if (!this._animationLoopStarted) | |
{ this._startAnimationLoop(); } | |
return fn | |
} | |
removeRenderTask(fn) { | |
const taskIndex = this._allRenderTasks.indexOf(fn); | |
if (taskIndex == -1) { return } | |
this._allRenderTasks.splice(taskIndex, 1); | |
this._numberOfTasks -= 1; | |
this._taskIterationIndex -= 1; | |
} | |
_runRenderTasks(timestamp) { | |
for (this._taskIterationIndex = 0; this._taskIterationIndex < this._numberOfTasks; this._taskIterationIndex += 1) { | |
const task = this._allRenderTasks[this._taskIterationIndex]; | |
if (task(timestamp) === false) | |
{ this.removeRenderTask(task); } | |
} | |
} | |
_setNodeToBeRendered(node) { | |
if (this._nodesToBeRendered.includes(node)) { return } | |
this._nodesToBeRendered.push(node); | |
if (!this._inFrame) { this._startAnimationLoop(); } | |
} | |
_renderNodes(timestamp) { | |
if (!this._nodesToBeRendered.length) { return } | |
for (let i=0, l=this._nodesToBeRendered.length; i<l; i+=1) { | |
const node = this._nodesToBeRendered[i]; | |
node._render(timestamp); | |
// If the node is root of a subtree containing updated nodes and | |
// has no ancestors that were modified, then add it to the | |
// _worldMatrixRootNodes set so we can update the world matrices of | |
// all the nodes in the root node's subtree. | |
if ( | |
// a node could be a Scene, which is not Transformable | |
isInstanceof(node, Transformable) && | |
// and if ancestor is not instanceof Transformable, f.e. | |
// `false` if there is no ancestor to be rendered, or Sizeable | |
// if the Scene is returned. | |
!isInstanceof(node._getAncestorToBeRendered(), Transformable) && | |
// and the node isn't already added. | |
!this._worldMatrixRootNodes.includes(node) | |
) { | |
this._worldMatrixRootNodes.push(node); | |
} | |
// keep track of which scenes are modified so we can render webgl | |
// only for those scenes. | |
// TODO FIXME: at this point, a node should always have a scene, | |
// otherwise it should not ever be rendered here, but turns out | |
// some nodes are getting into this queue without a scene. We | |
// shouldn't need the conditional check for node._scene, and it | |
// will save CPU by not allowing the code to get here in that case. | |
if (node._scene && !this._modifiedScenes.includes(node._scene)) | |
{ this._modifiedScenes.push(node._scene); } | |
} | |
// Update world matrices of the subtrees. | |
const worldMatrixRootNodes = this._worldMatrixRootNodes; | |
for (let i=0, l=worldMatrixRootNodes.length; i<l; i+=1) { | |
const subtreeRoot = worldMatrixRootNodes[i]; | |
subtreeRoot._calculateWorldMatricesInSubtree(); | |
} | |
worldMatrixRootNodes.length = 0; | |
// render webgl of modified scenes. | |
const modifiedScenes = this._modifiedScenes; | |
// TODO PERFORMANCE: store a list of webgl-enabled modified scenes, and | |
// iterate only through those so we don't iterate over non-webgl | |
// scenes. | |
for (let i=0, l=modifiedScenes.length; i<l; i+=1) { | |
const sceneElement = modifiedScenes[i].element; | |
// TODO we're temporarily storing stuff on the .element, but we | |
// don't want that, we will move it to WebGLRenderer. | |
if ( | |
sceneElement.webglEnabled && | |
( webGLRenderer || (webGLRenderer = getWebGlRenderer()) ) // only ever call getWebGlRenderer once | |
) | |
{ webGLRenderer.drawScene(sceneElement); } | |
} | |
modifiedScenes.length = 0; | |
const nodesToBeRendered = this._nodesToBeRendered; | |
for (let i=0, l=nodesToBeRendered.length; i<l; i+=1) { | |
nodesToBeRendered[i]._willBeRendered = false; | |
} | |
nodesToBeRendered.length = 0; | |
} | |
} | |
// export a singleton instance rather than the class directly. | |
var Motor$1 = new Motor; | |
// fallback to experimental CSS transform if browser doesn't have it (fix for Safari 9) | |
if (typeof document.createElement('div').style.transform == 'undefined') { | |
Object.defineProperty(CSSStyleDeclaration.prototype, 'transform', { | |
set(value) { | |
this.webkitTransform = value; | |
}, | |
get() { | |
return this.webkitTransform | |
}, | |
enumerable: true, | |
}); | |
} | |
class XYZNonNegativeValues extends XYZValues { | |
constructor(x, y, z) { | |
if ( x === void 0 ) x = 0; | |
if ( y === void 0 ) y = 0; | |
if ( z === void 0 ) z = 0; | |
super(x, y, z); | |
} | |
_checkForNegative(axisName, value) { | |
if(value < 0) { | |
throw new Error(axisName + " value was " + value + ". Size values cannot be negative.") | |
} | |
} | |
set x(value) { | |
this._checkForNegative("X", value); | |
super.x = value; | |
} | |
set y(value) { | |
this._checkForNegative("Y", value); | |
super.y = value; | |
} | |
set z(value) { | |
this._checkForNegative("Z", value); | |
super.z = value; | |
} | |
} | |
const instanceofSymbol$3 = Symbol('instanceofSymbol'); | |
const SizeableMixin = base => { | |
// Sizeable extends TreeNode because Sizeable knows about its _parent when | |
// calculating proportionalSize. Also Transformable knows about it's parent | |
// in order to calculate it's world matrix based on it's parent's. | |
class Sizeable extends TreeNode.mixin(Observable.mixin(base)) { | |
constructor(options) { | |
if ( options === void 0 ) options = {}; | |
super(options); | |
this._propertyFunctions = null; | |
this._calculatedSize = { x:0, y:0, z:0 }; | |
this._properties = {}; | |
this._setDefaultProperties(); | |
this._setPropertyObservers(); | |
this.properties = options; | |
} | |
_setDefaultProperties() { | |
Object.assign(this._properties, { | |
sizeMode: new XYZValues('absolute', 'absolute', 'absolute'), | |
absoluteSize: new XYZNonNegativeValues(0, 0, 0), | |
proportionalSize: new XYZNonNegativeValues(1, 1, 1), | |
}); | |
} | |
_setPropertyObservers() { | |
this._properties.sizeMode.on('valuechanged', | |
() => this.triggerEvent('propertychange', 'sizeMode')); | |
this._properties.absoluteSize.on('valuechanged', | |
() => this.triggerEvent('propertychange', 'absoluteSize')); | |
this._properties.proportionalSize.on('valuechanged', | |
() => this.triggerEvent('propertychange', 'proportionalSize')); | |
} | |
_calcSize() { | |
const calculatedSize = this._calculatedSize; | |
const previousSize = Object.assign({}, calculatedSize); | |
const props = this._properties; | |
const parentSize = this._getParentSize(); | |
if (props.sizeMode._x == 'absolute') { | |
calculatedSize.x = props.absoluteSize._x; | |
} | |
else { // proportional | |
calculatedSize.x = parentSize.x * props.proportionalSize._x; | |
} | |
if (props.sizeMode._y == 'absolute') { | |
calculatedSize.y = props.absoluteSize._y; | |
} | |
else { // proportional | |
calculatedSize.y = parentSize.y * props.proportionalSize._y; | |
} | |
if (props.sizeMode._z == 'absolute') { | |
calculatedSize.z = props.absoluteSize._z; | |
} | |
else { // proportional | |
calculatedSize.z = parentSize.z * props.proportionalSize._z; | |
} | |
if ( | |
previousSize.x !== calculatedSize.x | |
|| previousSize.y !== calculatedSize.y | |
|| previousSize.z !== calculatedSize.z | |
) { | |
this.triggerEvent('sizechange', Object.assign({}, calculatedSize)); | |
} | |
} | |
_getParentSize() { | |
return this._parent ? this._parent._calculatedSize : {x:0,y:0,z:0} | |
} | |
_setPropertyXYZ(Class, name, newValue) { | |
if (newValue instanceof Function) { | |
// remove previous task if any. | |
if (!this._propertyFunctions) { this._propertyFunctions = new Map; } | |
if (this._propertyFunctions.has(name)) | |
{ Motor$1.removeRenderTask(this._propertyFunctions.get(name)); } | |
this._propertyFunctions.set(name, | |
Motor$1.addRenderTask(time => { | |
const result = newValue( | |
this._properties[name].x, | |
this._properties[name].y, | |
this._properties[name].z, | |
time | |
); | |
if (result === false) { | |
this._propertyFunctions.delete(name); | |
return false | |
} | |
this[name] = result; | |
}) | |
); | |
} | |
else if (newValue instanceof Array) { | |
if (typeof newValue[0] != 'undefined') { this._properties[name].x = newValue[0]; } | |
if (typeof newValue[1] != 'undefined') { this._properties[name].y = newValue[1]; } | |
if (typeof newValue[2] != 'undefined') { this._properties[name].z = newValue[2]; } | |
} | |
else if (newValue instanceof Object) { | |
if (typeof newValue.x != 'undefined') { this._properties[name].x = newValue.x; } | |
if (typeof newValue.y != 'undefined') { this._properties[name].y = newValue.y; } | |
if (typeof newValue.z != 'undefined') { this._properties[name].z = newValue.z; } | |
} | |
else { | |
throw new TypeError(`Invalid value for ${Class.name}#${name}.`) | |
} | |
} | |
_setPropertySingle(Class, name, newValue, type) { | |
if (!(typeof newValue == type || newValue instanceof Function)) | |
{ throw new TypeError(`Invalid value for ${Class.name}#${name}.`) } | |
if (newValue instanceof Function) { | |
// remove previous task if any. | |
Motor$1.addRenderTask(time => { | |
const result = newValue( | |
this._properties[name], | |
time | |
); | |
if (result === false) { return false } | |
this[name] = result; | |
}); | |
} | |
else { | |
this._properties[name] = newValue; | |
this.triggerEvent('propertychange', name); | |
} | |
} | |
_render() { | |
// nothing yet, but needed because ImperativeBase calls | |
// `super._render()`, which will call either Transformable's | |
// _render or Sizeable's _render for Node and Scene classes, | |
// respectively. | |
} | |
/** | |
* Set the size mode for each axis. Possible size modes are "absolute" and "proportional". | |
* | |
* @param {Object} newValue | |
* @param {number} [newValue.x] The x-axis sizeMode to apply. | |
* @param {number} [newValue.y] The y-axis sizeMode to apply. | |
* @param {number} [newValue.z] The z-axis sizeMode to apply. | |
*/ | |
set sizeMode(newValue) { | |
this._setPropertyXYZ(Sizeable, 'sizeMode', newValue); | |
} | |
get sizeMode() { | |
return this._properties.sizeMode | |
} | |
/** | |
* @param {Object} newValue | |
* @param {number} [newValue.x] The x-axis absoluteSize to apply. | |
* @param {number} [newValue.y] The y-axis absoluteSize to apply. | |
* @param {number} [newValue.z] The z-axis absoluteSize to apply. | |
*/ | |
set absoluteSize(newValue) { | |
this._setPropertyXYZ(Sizeable, 'absoluteSize', newValue); | |
} | |
get absoluteSize() { | |
return this._properties.absoluteSize | |
} | |
/** | |
* Get the actual size of the Node. This can be useful when size is | |
* proportional, as the actual size of the Node depends on the size of | |
* it's parent. | |
* | |
* @readonly | |
* | |
* @return {Array.number} An Oject with x, y, and z properties, each | |
* property representing the computed size of the x, y, and z axes | |
* respectively. | |
*/ | |
get actualSize() { | |
var ref = this._calculatedSize; | |
var x = ref.x; | |
var y = ref.y; | |
var z = ref.z; | |
return {x,y,z} | |
} | |
/** | |
* Set the size of a Node proportional to the size of it's parent Node. The | |
* values are a real number between 0 and 1 inclusive where 0 means 0% of | |
* the parent size and 1 means 100% of the parent size. | |
* | |
* @param {Object} newValue | |
* @param {number} [newValue.x] The x-axis proportionalSize to apply. | |
* @param {number} [newValue.y] The y-axis proportionalSize to apply. | |
* @param {number} [newValue.z] The z-axis proportionalSize to apply. | |
*/ | |
set proportionalSize(newValue) { | |
this._setPropertyXYZ(Sizeable, 'proportionalSize', newValue); | |
} | |
get proportionalSize() { | |
return this._properties.proportionalSize | |
} | |
/** | |
* Set all properties of a Sizeable in one method. | |
* | |
* @param {Object} properties Properties object - see example | |
* | |
* @example | |
* node.properties = { | |
* sizeMode: {x:'absolute', y:'proportional', z:'absolute'}, | |
* absoluteSize: {x:300, y:100, z:200}, | |
* proportionalSize: {x:1, z:0.5} | |
* } | |
*/ | |
set properties(properties) { | |
if ( properties === void 0 ) properties = {}; | |
if (properties.sizeMode) | |
{ this.sizeMode = properties.sizeMode; } | |
if (properties.absoluteSize) | |
{ this.absoluteSize = properties.absoluteSize; } | |
if (properties.proportionalSize) | |
{ this.proportionalSize = properties.proportionalSize; } | |
} | |
// no need for a properties getter? | |
} | |
// for use by MotorHTML, convenient since HTMLElement attributes are all | |
// converted to lowercase by default, so if we don't do this then we won't be | |
// able to map attributes to Node setters as easily. | |
makeLowercaseSetterAliases(Sizeable.prototype); | |
Object.defineProperty(Sizeable, Symbol.hasInstance, { | |
value: function(obj) { | |
if (this !== Sizeable) { return Object.getPrototypeOf(Sizeable)[Symbol.hasInstance].call(this, obj) } | |
let currentProto = obj; | |
while (currentProto) { | |
const desc = Object.getOwnPropertyDescriptor(currentProto, "constructor"); | |
if (desc && desc.value && desc.value.hasOwnProperty(instanceofSymbol$3)) | |
{ return true } | |
currentProto = Object.getPrototypeOf(currentProto); | |
} | |
return false | |
} | |
}); | |
Sizeable[instanceofSymbol$3] = true; | |
return Sizeable | |
}; | |
const Sizeable = SizeableMixin(class{}); | |
Sizeable.mixin = SizeableMixin; | |
const instanceofSymbol$1 = Symbol('instanceofSymbol'); | |
const TransformableMixin = base => { | |
// Transformable extends TreeNode (indirectly through Sizeable) because it | |
// needs to be aware of its _parent when calculating align adjustments. | |
const ParentClass = Sizeable.mixin(base); | |
class Transformable extends ParentClass { | |
constructor(options) { | |
if ( options === void 0 ) options = {}; | |
super(options); | |
this._worldMatrix = null; | |
} | |
_setDefaultProperties() { | |
super._setDefaultProperties(); | |
Object.assign(this._properties, { | |
position: new XYZValues(0, 0, 0), | |
rotation: new XYZValues(0, 0, 0), | |
scale: new XYZValues(1, 1, 1), | |
origin: new XYZValues(0.5, 0.5, 0.5), | |
align: new XYZValues(0, 0, 0), | |
mountPoint: new XYZValues(0, 0, 0), | |
opacity: 1, | |
transform: new window.DOMMatrix, | |
}); | |
} | |
_setPropertyObservers() { | |
super._setPropertyObservers(); | |
this._properties.position.on('valuechanged', | |
() => this.triggerEvent('propertychange', 'position')); | |
this._properties.rotation.on('valuechanged', | |
() => this.triggerEvent('propertychange', 'rotation')); | |
this._properties.scale.on('valuechanged', | |
() => this.triggerEvent('propertychange', 'scale')); | |
this._properties.origin.on('valuechanged', | |
() => this.triggerEvent('propertychange', 'origin')); | |
this._properties.align.on('valuechanged', | |
() => this.triggerEvent('propertychange', 'align')); | |
this._properties.mountPoint.on('valuechanged', | |
() => this.triggerEvent('propertychange', 'mountPoint')); | |
} | |
/** | |
* Takes all the current component values (position, rotation, etc) and | |
* calculates a transformation DOMMatrix from them. See "W3C Geometry | |
* Interfaces" to learn about DOMMatrix. | |
* | |
* @method | |
* @private | |
* @memberOf Node | |
*/ | |
_calculateMatrix () { | |
const matrix = new window.DOMMatrix; | |
const properties = this._properties; | |
const alignAdjustment = [0,0,0]; | |
if (this._parent) { // The root Scene doesn't have a parent, for example. | |
const parentSize = this._parent._calculatedSize; | |
var align = properties.align; | |
alignAdjustment[0] = parentSize.x * align.x; | |
alignAdjustment[1] = parentSize.y * align.y; | |
alignAdjustment[2] = parentSize.z * align.z; | |
} | |
const mountPointAdjustment = [0,0,0]; | |
const thisSize = this._calculatedSize; | |
var mountPoint = properties.mountPoint; | |
mountPointAdjustment[0] = thisSize.x * mountPoint.x; | |
mountPointAdjustment[1] = thisSize.y * mountPoint.y; | |
mountPointAdjustment[2] = thisSize.z * mountPoint.z; | |
const appliedPosition = []; | |
var position = properties.position; | |
appliedPosition[0] = position.x + alignAdjustment[0] - mountPointAdjustment[0]; | |
appliedPosition[1] = position.y + alignAdjustment[1] - mountPointAdjustment[1]; | |
appliedPosition[2] = position.z + alignAdjustment[2] - mountPointAdjustment[2]; | |
matrix.translateSelf(appliedPosition[0], appliedPosition[1], appliedPosition[2]); | |
// origin calculation will go here: | |
// - move by negative origin before rotating. | |
// apply each axis rotation, in the x,y,z order. | |
var rotation = properties.rotation; | |
matrix.rotateAxisAngleSelf(1,0,0, rotation.x); | |
matrix.rotateAxisAngleSelf(0,1,0, rotation.y); | |
matrix.rotateAxisAngleSelf(0,0,1, rotation.z); | |
// origin calculation will go here: | |
// - move by positive origin after rotating. | |
return matrix | |
} | |
// TODO: fix _isIdentity in DOMMatrix, it is returning true even if false. | |
_calculateWorldMatricesInSubtree() { | |
this._calculateWorldMatrixFromParent(); | |
const children = this._children; | |
for (let i=0, l=children.length; i<l; i+=1) { | |
children[i]._calculateWorldMatricesInSubtree(); | |
} | |
} | |
_calculateWorldMatrixFromParent() { | |
const parent = this._parent; | |
if (isInstanceof(parent, Transformable)) | |
//this._worldMatrix = parent._worldMatrix.multiply(this._properties.transform) | |
{ this._worldMatrix = this._properties.transform.multiply(parent._worldMatrix); } | |
else // otherwise parent is the Scene, which is Sizeable, not Transformable | |
{ this._worldMatrix = this._properties.transform; } | |
} | |
_render() { | |
super._render(); | |
// TODO: only run this when necessary (f.e. not if only opacity | |
// changed) | |
this._properties.transform = this._calculateMatrix(); | |
} | |
/** | |
* Set the position of the Transformable. | |
* | |
* @param {Object} newValue | |
* @param {number} [newValue.x] The x-axis position to apply. | |
* @param {number} [newValue.y] The y-axis position to apply. | |
* @param {number} [newValue.z] The z-axis position to apply. | |
*/ | |
set position(newValue) { | |
this._setPropertyXYZ(Transformable, 'position', newValue); | |
} | |
get position() { | |
return this._properties.position | |
} | |
/** | |
* @param {Object} newValue | |
* @param {number} [newValue.x] The x-axis rotation to apply. | |
* @param {number} [newValue.y] The y-axis rotation to apply. | |
* @param {number} [newValue.z] The z-axis rotation to apply. | |
*/ | |
set rotation(newValue) { | |
this._setPropertyXYZ(Transformable, 'rotation', newValue); | |
} | |
get rotation() { | |
return this._properties.rotation | |
} | |
/** | |
* @param {Object} newValue | |
* @param {number} [newValue.x] The x-axis scale to apply. | |
* @param {number} [newValue.y] The y-axis scale to apply. | |
* @param {number} [newValue.z] The z-axis scale to apply. | |
*/ | |
set scale(newValue) { | |
this._setPropertyXYZ(Transformable, 'scale', newValue); | |
} | |
get scale() { | |
return this._properties.scale | |
} | |
/** | |
* Set this Node's opacity. | |
* | |
* @param {number} opacity A floating point number between 0 and 1 | |
* (inclusive). 0 is fully transparent, 1 is fully opaque. | |
*/ | |
set opacity(newValue) { | |
if (!isRealNumber(newValue)) { newValue = undefined; } | |
this._setPropertySingle(Transformable, 'opacity', newValue, 'number'); | |
} | |
get opacity() { | |
return this._properties.opacity | |
} | |
/** | |
* Set the alignment of the Node. This determines at which point in this | |
* Node's parent that this Node is mounted. | |
* | |
* @param {Object} newValue | |
* @param {number} [newValue.x] The x-axis align to apply. | |
* @param {number} [newValue.y] The y-axis align to apply. | |
* @param {number} [newValue.z] The z-axis align to apply. | |
*/ | |
set align(newValue) { | |
this._setPropertyXYZ(Transformable, 'align', newValue); | |
} | |
get align() { | |
return this._properties.align | |
} | |
/** | |
* Set the mount point of the Node. | |
* | |
* @param {Object} newValue | |
* @param {number} [newValue.x] The x-axis mountPoint to apply. | |
* @param {number} [newValue.y] The y-axis mountPoint to apply. | |
* @param {number} [newValue.z] The z-axis mountPoint to apply. | |
*/ | |
set mountPoint(newValue) { | |
this._setPropertyXYZ(Transformable, 'mountPoint', newValue); | |
} | |
get mountPoint() { | |
return this._properties.mountPoint | |
} | |
/** | |
* Set all properties of a Transformable in one method. | |
* | |
* @param {Object} properties Properties object - see example. | |
* | |
* @example | |
* node.properties = { | |
* position: {x:200, y:300, z:100}, | |
* rotation: {z:35}, | |
* scale: {y:2}, | |
* opacity: .9, | |
* } | |
*/ | |
set properties(properties) { | |
if ( properties === void 0 ) properties = {}; | |
super.properties = properties; | |
if (properties.position) | |
{ this.position = properties.position; } | |
if (properties.rotation) | |
{ this.rotation = properties.rotation; } | |
if (properties.scale) | |
{ this.scale = properties.scale; } | |
if (properties.origin) | |
{ this.origin = properties.origin; } | |
if (properties.align) | |
{ this.align = properties.align; } | |
if (properties.mountPoint) | |
{ this.mountPoint = properties.mountPoint; } | |
if (properties.opacity) | |
{ this.opacity = properties.opacity; } | |
} | |
// no need for a properties getter? | |
} | |
// for use by MotorHTML, convenient since HTMLElement attributes are all | |
// converted to lowercase by default, so if we don't do this then we won't be | |
// able to map attributes to Node setters as easily. | |
makeLowercaseSetterAliases(Transformable.prototype); | |
Object.defineProperty(Transformable, Symbol.hasInstance, { | |
value: function(obj) { | |
if (this !== Transformable) { return Object.getPrototypeOf(Transformable)[Symbol.hasInstance].call(this, obj) } | |
let currentProto = obj; | |
while(currentProto) { | |
const desc = Object.getOwnPropertyDescriptor(currentProto, "constructor"); | |
if (desc && desc.value && desc.value.hasOwnProperty(instanceofSymbol$1)) | |
{ return true } | |
currentProto = Object.getPrototypeOf(currentProto); | |
} | |
return false | |
} | |
}); | |
Transformable[instanceofSymbol$1] = true; | |
return Transformable | |
}; | |
function isRealNumber(num) { | |
if ( | |
typeof num != 'number' | |
|| Object.is(num, NaN) | |
|| Object.is(num, Infinity) | |
) { return false } | |
return true | |
} | |
const Transformable = TransformableMixin(class{}); | |
Transformable.mixin = TransformableMixin; | |
var styles$1 = { | |
// all items of the scene graph are hidden until they are mounted in a | |
// scene (this changes to `display:block`). | |
display: 'none', | |
boxSizing: 'border-box', | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
// Defaults to [0.5,0.5,0.5] (the Z axis doesn't apply for DOM elements, | |
// but will for 3D objects in WebGL.) | |
transformOrigin: '50% 50% 0', // default | |
transformStyle: 'preserve-3d', | |
}; | |
var styles = Object.assign({}, styles$1, | |
{position: 'relative', | |
overflow: 'hidden', | |
width: '100%', | |
height: '100%', | |
// Constant perspective for now. | |
perspective: 1000}); | |
/* global customElements */ | |
// Very very stupid hack needed for Safari in order for us to be able to extend | |
// the HTMLElement class. See: | |
// https://github.com/google/traceur-compiler/issues/1709 | |
if (typeof window.HTMLElement != 'function') { | |
const _HTMLElement = function HTMLElement(){}; | |
_HTMLElement.prototype = window.HTMLElement.prototype; | |
window.HTMLElement = _HTMLElement; | |
} | |
const classCache = new Map; | |
function classExtendsHTMLElement(constructor) { | |
if (!constructor) { return false } | |
if (constructor === HTMLElement) { return true } | |
else { return classExtendsHTMLElement(constructor.prototype.__proto__ ? constructor.prototype.__proto__.constructor : null) } | |
} | |
/** | |
* Creates a WebComponent base class dynamically, depending on which | |
* HTMLElement class you want it to extend from. Extend from WebComponent when | |
* making a new Custom Element class. | |
* | |
* @example | |
* const WebComponent = WebComponentMixin(HTMLButtonElement) | |
* class AwesomeButton extends WebComponent { ... } | |
* | |
* @param {Function} elementClass The class that the generated WebComponent | |
* base class will extend from. | |
*/ | |
function WebComponentMixin(elementClass) { | |
if (!elementClass) { elementClass = HTMLElement; } | |
if (!classExtendsHTMLElement(elementClass)) { | |
throw new TypeError( | |
'The argument to WebComponentMixin must be a constructor that extends from or is HTMLElement.' | |
) | |
} | |
// if a base class that extends the given `elementClass` has already been | |
// created, return it. | |
if (classCache.has(elementClass)) | |
{ return classCache.get(elementClass) } | |
// otherwise, create it. | |
class WebComponent extends elementClass { | |
constructor() { | |
super(); | |
// Throw an error if no Custom Elements v1 API exists. | |
if (!('customElements' in window)) { | |
throw new Error(` | |
Your browser does not support the Custom Elements API. You'll | |
need to install a polyfill. See how at http://.... | |
`) | |
} | |
this._connected = false; | |
this._initialized = false; | |
this._initialAttributeChange = false; | |
this._childObserver = null; | |
this._style = null; | |
} | |
// Subclasses can implement these. | |
childConnectedCallback(child) { } | |
childDisconnectedCallback(child) { } | |
connectedCallback() { | |
if (super.connectedCallback) { super.connectedCallback(); } | |
this._connected = true; | |
if (!this._initialized) { | |
this.init(); | |
this._initialized = true; | |
} | |
} | |
_createStyles() { | |
const rule = jss.createRule(this.getStyles()); | |
rule.applyTo(this); | |
return rule | |
} | |
disconnectedCallback() { | |
if (super.disconnectedCallback) { super.disconnectedCallback(); } | |
this._connected = false; | |
// Deferr to the next tick before cleaning up in case the | |
// element is actually being re-attached somewhere else within this | |
// same tick (detaching and attaching is synchronous, so by | |
// deferring to the next tick we'll be able to know if the element | |
// was re-attached or not in order to clean up or not). Note that | |
// appendChild can be used to move an element to another parent | |
// element, in which case connectedCallback and disconnectedCallback | |
// both get called, and in which case we don't necessarily want to | |
// clean up. If the element gets re-attached before the next tick | |
// (for example, gets moved), then we want to preserve the | |
// stuff that would be cleaned up by an extending class' deinit | |
// method by not running the following this.deinit() call. | |
Promise.resolve().then(() => { // deferr to the next tick. | |
// As mentioned in the previous comment, if the element was not | |
// re-attached in the last tick (for example, it was moved to | |
// another element), then clean up. | |
if (!this._connected && this._initialized) { | |
this.deinit(); | |
} | |
}); | |
} | |
//async disconnectedCallback() { | |
//if (super.disconnectedCallback) super.disconnectedCallback() | |
//this._connected = false | |
//// Deferr to the next tick before cleaning up in case the | |
//// element is actually being re-attached somewhere else within this | |
//// same tick (detaching and attaching is synchronous, so by | |
//// deferring to the next tick we'll be able to know if the element | |
//// was re-attached or not in order to clean up or not). Note that | |
//// appendChild can be used to move an element to another parent | |
//// element, in which case connectedCallback and disconnectedCallback | |
//// both get called, and in which case we don't necessarily want to | |
//// clean up. If the element gets re-attached before the next tick | |
//// (for example, gets moved), then we want to preserve the | |
//// stuff that would be cleaned up by an extending class' deinit | |
//// method by not running the following this.deinit() call. | |
//await Promise.resolve() // deferr to the next tick. | |
//// As mentioned in the previous comment, if the element was not | |
//// re-attached in the last tick (for example, it was moved to | |
//// another element), then clean up. | |
//if (!this._connected && this._initialized) { | |
//this.deinit() | |
//} | |
//} | |
/** | |
* This method can be overridden by extending classes, it should return | |
* JSS-compatible styling. See http://github.com/cssinjs/jss for | |
* documentation. | |
* @abstract | |
*/ | |
getStyles() { | |
return {} | |
} | |
/** | |
* Init is called exactly once, the first time this element is | |
* connected into the DOM. When an element is disconnected then | |
* connected right away within the same synchronous tick, init() is not | |
* fired again. However, if an element is disconnected and the current | |
* tick completes before the element is connected again, then deinit() | |
* will be called (i.e. the element was not simply moved to a new | |
* location, it was actually removed), then the next time that the | |
* element is connected back into DOM init() will be called again. | |
* | |
* This is in contrast to connectedCallback and disconnectedCallback: | |
* connectedCallback is guaranteed to always fire even if the elemet | |
* was previously disconnected in the same synchronous tick. | |
* | |
* For example, ... | |
* | |
* Subclasses should extend this to add such logic. | |
*/ | |
init() { | |
if (!this._style) { this._style = this._createStyles(); } | |
// Handle any nodes that may have been connected before `this` node | |
// was created (f.e. child nodes that were connected before the | |
// custom elements were registered and which would therefore not be | |
// detected by the following MutationObserver). | |
if (!this._childObserver) { | |
const children = this.childNodes; | |
if (children.length) { | |
// Timeout needed in case the Custom Element classes are | |
// registered after the elements are already defined in the | |
// DOM but not yet upgraded. This means that the `node` arg | |
// might be a `<motor-node>` but if it isn't upgraded then | |
// its API won't be available to the logic inside the | |
// childConnectedCallback. The reason this happens is | |
// because parents are upgraded first and their | |
// connectedCallbacks fired before their children are | |
// upgraded. | |
// | |
// TODO FIXME PERFORMANCE: This causes a possibly "buggy" effect where | |
// elements in a tree will appear in intervals of 5 | |
// milliseconds. We want elements to be rendered instantly, | |
// in the first frame that they are present in the scene | |
// graph. | |
// How can we fix this? Maybe we can switch to a Promise microtask. | |
setTimeout(() => { | |
for (let l=children.length, i=0; i<l; i+=1) { | |
this.childConnectedCallback(children[i]); | |
} | |
}, 5); | |
} | |
this._childObserver = observeChildren(this, this.childConnectedCallback, this.childDisconnectedCallback); | |
} | |
// fire this.attributeChangedCallback in case some attributes have | |
// existed before the custom element was upgraded. | |
if (!this._initialAttributeChange && this.hasAttributes()) { | |
// HTMLElement#attributes is a NamedNodeMap which is not an | |
// iterable, so we use Array.from. See: | |
// https://github.com/zloirock/core-js/issues/234 | |
var ref = this; | |
var attributes = ref.attributes; | |
for (let l=attributes.length, i=0; i<l; i+=1) | |
{ this.attributeChangedCallback(attributes[i].name, null, attributes[i].value); } | |
} | |
} | |
static get observedAttributes() { | |
console.warn(`WebComponent: Your custom element (${ this.name }) should specify observed attributes or attributeChangedCallback won't be called`); | |
} | |
attributeChangedCallback(...args) { | |
//console.log(' --- attributeChangedCallback', typeof args[2]) | |
if (super.attributeChangedCallback) { super.attributeChangedCallback(...args); } | |
this._initialAttributeChange = true; | |
} | |
/** | |
* This is the reciprocal of init(). It will be called when an element | |
* has been disconnected but not re-connected within the same tick. | |
* | |
* The reason that init() and deinit() exist is so that if an element is | |
* moved from one place to another within the same synchronous tick, | |
* that deinit and init logic will not fire unnecessarily. If logic is | |
* needed in that case, then connectedCallback and disconnectedCallback | |
* can be used directly instead. | |
*/ | |
deinit() { | |
// Nothing much at the moment, but extending classes can extend | |
// this to add deintialization logic. | |
this._initialized = false; | |
} | |
} | |
classCache.set(elementClass, WebComponent); | |
return WebComponent | |
} | |
initDeclarativeBase(); | |
class HTMLNode extends DeclarativeBase { | |
getStyles() { | |
return styles$1 | |
} | |
// TODO: get these from somewhere dynamically, and do same for | |
// proxyGettersSetters and _updateNodeProperty | |
static get observedAttributes() { return [ | |
'sizeMode', | |
'absoluteSize', | |
'proportionalSize', | |
'align', | |
'mountPoint', | |
'rotation', | |
'position', | |
'scale', | |
'origin', | |
'skew', | |
'opacity', | |
].map(a => a.toLowerCase())} | |
attributeChangedCallback(...args) { | |
super.attributeChangedCallback(...args); | |
this._updateNodeProperty(...args); | |
} | |
//async attributeChangedCallback(...args) { | |
//super.attributeChangedCallback(...args) | |
//this._updateNodeProperty(...args) | |
//} | |
_updateNodeProperty(attribute, oldValue, newValue) { | |
// attributes on our HTML elements are the same name as those on | |
// the Node class (the setters). | |
if (newValue !== oldValue) { | |
if (attribute.match(/opacity/i)) | |
{ this.imperativeCounterpart[attribute] = window.parseFloat(newValue); } | |
else if (attribute.match(/sizeMode/i)) | |
{ this.imperativeCounterpart[attribute] = parseStringArray(newValue); } | |
else if ( | |
attribute.match(/rotation/i) | |
|| attribute.match(/scale/i) | |
|| attribute.match(/position/i) | |
|| attribute.match(/absoluteSize/i) | |
|| attribute.match(/proportionalSize/i) | |
|| attribute.match(/align/i) | |
|| attribute.match(/mountPoint/i) | |
|| attribute.match(/origin/i) | |
|| attribute.match(/skew/i) | |
) { | |
this.imperativeCounterpart[attribute] = parseNumberArray(newValue); | |
} | |
else { | |
/* nothing, ignore other attributes */ | |
} | |
} | |
} | |
} | |
// This associates the Transformable getters/setters with the HTML-API classes, | |
// so that the same getters/setters can be called from HTML side of the API. | |
proxyGettersSetters(Transformable, HTMLNode); | |
proxyGettersSetters(Sizeable, HTMLNode); | |
function parseNumberArray(str) { | |
checkIsNumberArrayString(str); | |
const numbers = str.trim().split(/(?:\s*,\s*)|(?:\s+)/g); | |
const length = numbers.length; | |
if (length > 0) { numbers[0] = window.parseFloat(numbers[0]); } | |
if (length > 1) { numbers[1] = window.parseFloat(numbers[1]); } | |
if (length > 2) { numbers[2] = window.parseFloat(numbers[2]); } | |
return numbers | |
} | |
function parseStringArray(str) { | |
checkIsSizeArrayString(str); | |
const strings = str.trim().toLowerCase().split(/(?:\s*,\s*)|(?:\s+)/g); | |
const length = strings.length; | |
if (length > 0) { strings[0] = strings[0]; } | |
if (length > 1) { strings[1] = strings[1]; } | |
if (length > 2) { strings[2] = strings[2]; } | |
return strings | |
} | |
function checkIsNumberArrayString(str) { | |
if (!str.match(/^\s*(((\s*(-|\+)?((\.\d+)|(\d+\.\d+)|(\d+)|(\d+(\.\d+)?e(-|\+)?(\d+)))\s*,){0,2}(\s*(-|\+)?((\.\d+)|(\d+\.\d+)|(\d+)|(\d+(\.\d+)?e(-|\+)?(\d+)))))|((\s*(-|\+)?((\.\d+)|(\d+\.\d+)|(\d+)|(\d+(\.\d+)?e(-|\+)?(\d+)))\s){0,2}(\s*(-|\+)?((\.\d+)|(\d+\.\d+)|(\d+)|(\d+(\.\d+)?e(-|\+)?(\d+))))))\s*$/g)) | |
{ throw new Error(`Attribute must be a comma- or space-separated sequence of up to three numbers, for example "1 2.5 3". Yours was "${str}".`) } | |
} | |
function checkIsSizeArrayString(str) { | |
if (!str.match(/^\s*(((\s*([a-zA-Z]+)\s*,){0,2}(\s*([a-zA-Z]+)))|((\s*([a-zA-Z]+)\s*){1,3}))\s*$/g)) | |
{ throw new Error(`Attribute must be a comma- or space-separated sequence of up to three strings, for example "absolute absolute". Yours was "${str}".`) } | |
} | |
/* global HTMLSlotElement */ | |
var DeclarativeBase; | |
// We use this to Override HTMLElement.prototype.attachShadow in v1, and | |
// HTMLElement.prototype.createShadowRoot in v0, so that we can make the | |
// connection between parent and child on the iperative side when the HTML side | |
// is using shadow roots. | |
const observers = new WeakMap; | |
function hijack(original) { | |
return function(...args) { | |
// In v0, shadow roots can be replaced, but in v1 calling attachShadow | |
// on an element that already has a root throws. So, we can set this to | |
// true, and if the try-catch passes then we know we have a v0 root and | |
// that the root was just replaced. | |
const oldRoot = this.shadowRoot; | |
let root = null; | |
try { | |
root = original.call(this, ...args); | |
} | |
catch (e) { throw e } | |
if (this instanceof DeclarativeBase) { | |
this._hasShadowRoot = true; | |
if (oldRoot) { | |
onV0ShadowRootReplaced.call(this, oldRoot); | |
} | |
const observer = observeChildren(root, shadowRootChildAdded.bind(this), shadowRootChildRemoved.bind(this)); | |
observers.set(root, observer); | |
var ref = this; | |
var children = ref.children; | |
for (let l=children.length, i=0; i<l; i+=1) { | |
if (!(children[i] instanceof DeclarativeBase)) { continue } | |
children[i]._isPossiblyDistributed = true; | |
} | |
} | |
return root | |
} | |
} | |
function shadowRootChildAdded(child) { | |
// NOTE Logic here is similar to childConnectedCallback | |
if (child instanceof DeclarativeBase) { | |
this.imperativeCounterpart.add(child.imperativeCounterpart); | |
} | |
else if ( | |
hasShadowDomV0 | |
&& child instanceof HTMLContentElement | |
) { | |
// observe <content> elements. | |
} | |
else if ( | |
hasShadowDomV1 | |
&& child instanceof HTMLSlotElement | |
) { | |
child.addEventListener('slotchange', this); | |
this._handleDistributedChildren(child); | |
} | |
} | |
function shadowRootChildRemoved(child) { | |
// NOTE Logic here is similar to childDisconnectedCallback | |
if (child instanceof DeclarativeBase) { | |
this.imperativeCounterpart.remove(child.imperativeCounterpart); | |
} | |
else if ( | |
hasShadowDomV0 | |
&& child instanceof HTMLContentElement | |
) { | |
// unobserve <content> element | |
} | |
else if ( | |
hasShadowDomV1 | |
&& child instanceof HTMLSlotElement | |
) { | |
child.removeEventListener('slotchange', this); | |
this._handleDistributedChildren(child); | |
this._slotElementsAssignedNodes.delete(child); | |
} | |
} | |
function onV0ShadowRootReplaced(oldRoot) { | |
observers.get(oldRoot).disconnect(); | |
observers.delete(oldRoot); | |
var childNodes = oldRoot.childNodes; | |
for (let l=childNodes.length, i=0; i<l; i+=1) { | |
const child = childNodes[i]; | |
if (!(child instanceof DeclarativeBase)) { continue } | |
// We should disconnect the imperative connection (f.e. so it is not | |
// rendered in WebGL) | |
this.imperativeCounterpart.remove(child.imperativeCounterpart, true); | |
} | |
} | |
if (HTMLElement.prototype.createShadowRoot instanceof Function) | |
{ HTMLElement.prototype.createShadowRoot = hijack(HTMLElement.prototype.createShadowRoot); } | |
if (HTMLElement.prototype.attachShadow instanceof Function) | |
{ HTMLElement.prototype.attachShadow = hijack(HTMLElement.prototype.attachShadow); } | |
initDeclarativeBase(); | |
function initDeclarativeBase() { | |
if (DeclarativeBase) { return } | |
/** | |
* @implements {EventListener} | |
*/ | |
DeclarativeBase = class DeclarativeBase extends WebComponentMixin(window.HTMLElement) { | |
constructor() { | |
super(); | |
this.imperativeCounterpart = null; // to hold the imperative API Node instance. | |
// true if this node has a shadow root (even if it is "closed", see | |
// hijack function above). Once true always true because shadow | |
// roots cannot be removed. | |
this._hasShadowRoot = false; | |
// True when this node has a parent that has a shadow root. When | |
// using the HTML API, Imperative API can look at this to determine | |
// whether to render this node or not, in the case of WebGL. | |
this._isPossiblyDistributed = false; | |
// A map of the slot elements that are children of this node and | |
// their last-known assigned nodes. When a slotchange happens while | |
// this node is in a shadow root and has a slot child, we can | |
// detect what the difference is between the last known and the new | |
// assignments, and notate the new distribution of child nodes. See | |
// issue #40 for background on why we do this. | |
this._slotElementsAssignedNodes = new WeakMap; | |
// If this node is distributed into a shadow tree, this will | |
// reference the parent of the <slot> or <content> element. | |
// Basically, this node will render as a child of that parent node | |
// in the flat tree. | |
this._shadowParent = null; | |
// If this element has a child <slot> or <content> element while in | |
// a shadow root, then this will be a Set of the nodes distributed | |
// into the <slot> or <content>, and those nodes render relatively | |
// to this node in the flat tree. We instantiate this later, only | |
// when/if needed. | |
this._shadowChildren = null; | |
this._associateImperativeNode(); | |
} | |
/** | |
* This method creates the association between this HTMLNode instance | |
* and the imperative Node instance. | |
* | |
* This method may get called by this.init, but can also be called by | |
* the Node class if Node is used imperatively. See Node#constructor. | |
* | |
* @private | |
* | |
* @param {Object} imperativeCounterpart The imperative counterpart to | |
* associate with this MotorHTML element. This parameter is only used in the | |
* imperative API constructors, and this happens when using the imperative | |
* form of infamous instead of the HTML interface to infamous. When the HTML | |
* interface is used, this gets called first without an | |
* imperativeCounterpart argument and the call to this in an imperative | |
* constructor will be a noop. Basically, either this gets called first by a | |
* MotorHTML element, or first by an imperative instance, depending on which | |
* API is used first. | |
*/ | |
_associateImperativeNode(imperativeCounterpart) { | |
// if the association is made already, noop | |
if (this.imperativeCounterpart) { return } | |
this.imperativeCounterpart = this; | |
} | |
childConnectedCallback(child) { | |
// mirror the DOM connections in the imperative API's virtual scene graph. | |
if (child instanceof HTMLNode) { | |
if (this._hasShadowRoot) { child._isPossiblyDistributed = true; } | |
// If ImperativeBase#add was called first, child's | |
// _parent will already be set, so prevent recursion. | |
if (child.imperativeCounterpart._parent) { return } | |
this.imperativeCounterpart.add(child.imperativeCounterpart); | |
} | |
else if ( | |
hasShadowDomV0 | |
&& child instanceof HTMLContentElement | |
&& | |
//getShadowRootVersion( | |
getAncestorShadowRoot(this) | |
//) == 'v0' | |
) { | |
// observe <content> elements. | |
} | |
else if ( | |
hasShadowDomV1 | |
&& child instanceof HTMLSlotElement | |
&& | |
//getShadowRootVersion( | |
getAncestorShadowRoot(this) | |
//) == 'v1' | |
) { | |
child.addEventListener('slotchange', this); | |
this._handleDistributedChildren(child); | |
} | |
} | |
// This method is part of the EventListener interface. | |
handleEvent(event) { | |
if (event.type == 'slotchange') { | |
const slot = event.target; | |
this._handleDistributedChildren(slot); | |
} | |
} | |
_handleDistributedChildren(slot) { | |
const diff = this._getDistributedChildDifference(slot); | |
var added = diff.added; | |
for (let l=added.length, i=0; i<l; i+=1) { | |
const addedNode = added[i]; | |
if (!(addedNode instanceof DeclarativeBase)) { continue } | |
// We do this because if the given slot is assigned to another | |
// slot, then this logic will run again for the next slot on | |
// that next slot's slotchange, so we remove the distributed | |
// node from the previous shadowParent and add it to the next | |
// one. If we don't do this, then the distributed node will | |
// exist in multiple shadowChildren lists when there is a | |
// chain of assigned slots. For more info, see | |
// https://github.com/w3c/webcomponents/issues/611 | |
const shadowParent = addedNode._shadowParent; | |
if (shadowParent && shadowParent._shadowChildren) { | |
const shadowChildren = shadowParent._shadowChildren; | |
shadowChildren.splice(shadowChildren.indexOf(addedNode), 1); | |
if (!shadowChildren.length) | |
{ shadowParent._shadowChildren = null; } | |
} | |
addedNode._shadowParent = this; | |
if (!this._shadowChildren) { this._shadowChildren = []; } | |
this._shadowChildren.add(addedNode); | |
} | |
var removed = diff.removed; | |
for (let l=removed.length, i=0; i<l; i+=1) { | |
const removedNode = removed[i]; | |
if (!(removedNode instanceof DeclarativeBase)) { continue } | |
removedNode._shadowParent = null; | |
this._shadowChildren.delete(removedNode); | |
if (!this._shadowChildren.size) { this._shadowChildren = null; } | |
} | |
} | |
_getDistributedChildDifference(slot) { | |
let previousNodes; | |
if (this._slotElementsAssignedNodes.has(slot)) | |
{ previousNodes = this._slotElementsAssignedNodes.get(slot); } | |
else | |
{ previousNodes = []; } | |
const newNodes = slot.assignedNodes({flatten: true}); | |
// save the newNodes to be used as the previousNodes for next time. | |
this._slotElementsAssignedNodes.set(slot, newNodes); | |
const diff = { | |
removed: [], | |
}; | |
for (let i=0, l=previousNodes.length; i<l; i+=1) { | |
const oldNode = previousNodes[i]; | |
const newIndex = newNodes.indexOf(oldNode); | |
// if it exists in the previousNodes but not the newNodes, then | |
// the node was removed. | |
if (!(newIndex >= 0)) { | |
diff.removed.push(oldNode); | |
} | |
// otherwise the node wasn't added or removed. | |
else { | |
newNodes.splice(i, 1); | |
} | |
} | |
// Remaining nodes in newNodes must have been added. | |
diff.added = newNodes; | |
return diff | |
} | |
childDisconnectedCallback(child) { | |
// mirror the connection in the imperative API's virtual scene graph. | |
if (child instanceof HTMLNode) { | |
child._isPossiblyDistributed = false; | |
// If ImperativeBase#remove was called first, child's | |
// _parent will already be null, so prevent recursion. | |
if (!child.imperativeCounterpart._parent) { return } | |
this.imperativeCounterpart.remove(child.imperativeCounterpart); | |
} | |
else if ( | |
hasShadowDomV0 | |
&& child instanceof HTMLContentElement | |
&& | |
//getShadowRootVersion( | |
getAncestorShadowRoot(this) | |
//) == 'v0' | |
) { | |
// unobserve <content> element | |
} | |
else if ( | |
hasShadowDomV1 | |
&& child instanceof HTMLSlotElement | |
&& | |
//getShadowRootVersion( | |
getAncestorShadowRoot(this) | |
//) == 'v1' | |
) { | |
child.removeEventListener('slotchange', this); | |
this._handleDistributedChildren(child); | |
this._slotElementsAssignedNodes.delete(child); | |
} | |
} | |
setAttribute(attr, value) { | |
//if (this.tagName.toLowerCase() == 'motor-scene') | |
//console.log('setting attribute', arguments[1]) | |
super.setAttribute(attr, value); | |
} | |
}; | |
} | |
// Creates setters/getters on the TargetClass which proxy to the | |
// setters/getters on SourceClass. | |
function proxyGettersSetters(SourceClass, TargetClass) { | |
// Node methods not to proxy (private underscored methods are also detected and | |
// ignored). | |
const methodProxyBlacklist = [ | |
'constructor', | |
'parent', | |
'children', // proxying this one would really break stuff (f.e. React) | |
'element', | |
'scene', | |
'add', | |
'addChildren', | |
'remove', | |
'removeChildren', | |
]; | |
const props = Object.getOwnPropertyNames(SourceClass.prototype); | |
for (let l=props.length, i=0; i<l; i+=1) { | |
const prop = props[i]; | |
if ( | |
// skip the blacklisted properties | |
methodProxyBlacklist.indexOf(prop) >= 0 | |
// skip the private underscored properties | |
|| prop.indexOf('_') == 0 | |
// skip properties that are already defined. | |
|| TargetClass.prototype.hasOwnProperty(prop) | |
) { continue } | |
const targetDescriptor = {}; | |
const sourceDescriptor = Object.getOwnPropertyDescriptor(SourceClass.prototype, prop); | |
// if the property has a setter | |
if (sourceDescriptor.set) { | |
Object.assign(targetDescriptor, { | |
set(value) { | |
this.imperativeCounterpart[prop] = value; | |
} | |
}); | |
} | |
// if the property has a getter | |
if (sourceDescriptor.get) { | |
Object.assign(targetDescriptor, { | |
get() { | |
return this.imperativeCounterpart[prop] | |
} | |
}); | |
} | |
Object.defineProperty(TargetClass.prototype, prop, targetDescriptor); | |
} | |
} | |
var sleep_1 = createCommonjsModule(function (module, exports) { | |
"use strict"; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
var _promise2 = _interopRequireDefault(promise); | |
exports.default = sleep; | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
/** | |
* Await for a certain amount of time. | |
* | |
* @example | |
* ```js | |
* async function main() { | |
* await sleep(10000) | |
* console.log('Logged after 10000 milliseconds!') | |
* } | |
* main() | |
* ``` | |
*/ | |
function sleep(duration) { | |
var resolve = null; | |
var promise$$2 = new _promise2.default(function (r) { | |
return resolve = r; | |
}); | |
setTimeout(resolve, duration); | |
return promise$$2; | |
} | |
}); | |
initDeclarativeBase(); | |
class HTMLScene extends Observable.mixin(DeclarativeBase) { | |
static define(name) { | |
customElements.define(name || 'i-scene', HTMLScene); | |
} | |
constructor() { | |
super(); | |
this._sizePollTask = null; | |
this._parentSize = {x:0, y:0, z:0}; | |
// If the scene is already in the DOM, make it be "mounted". | |
if (!this.imperativeCounterpart._mounted && this.parentNode) | |
{ this.imperativeCounterpart.mount(this.parentNode); } | |
} | |
__startSizePolling() { | |
// NOTE Polling is currently required because there's no other way to do this | |
// reliably, not even with MutationObserver. ResizeObserver hasn't | |
// landed in browsers yet. | |
if (!this._sizePollTask) | |
{ this._sizePollTask = Motor$1.addRenderTask(this._checkSize.bind(this)); } | |
} | |
// NOTE, the Z dimension of a scene doesn't matter, it's a flat plane, so | |
// we haven't taken that into consideration here. | |
_checkSize() { | |
// The scene has a parent by the time this is called (see | |
// src/core/Scene#mount where _startSizePolling is called) | |
const parent = this.parentNode; | |
const parentSize = this._parentSize; | |
const style = getComputedStyle(parent); | |
const width = parseFloat(style.width); | |
const height = parseFloat(style.height); | |
// if we have a size change, trigger parentsizechange | |
if (parentSize.x != width || parentSize.y != height) { | |
parentSize.x = width; | |
parentSize.y = height; | |
this.triggerEvent('parentsizechange', Object.assign({}, parentSize)); | |
} | |
} | |
/** @override */ | |
getStyles() { | |
return styles | |
} | |
deinit() { | |
super.deinit(); | |
this.imperativeCounterpart.unmount(); | |
} | |
__stopSizePolling() { | |
Motor$1.removeRenderTask(this._sizePollTask); | |
this._sizePollTask = null; | |
} | |
connectedCallback() { | |
super.connectedCallback(); | |
// When the HTMLScene gets addded to the DOM, make it be "mounted". | |
if (!this.imperativeCounterpart._mounted) | |
{ this.imperativeCounterpart.mount(this.parentNode); } | |
} | |
} | |
// This associates the Transformable getters/setters with the HTML-API classes, | |
// so that the same getters/setters can be called from HTML side of the API. | |
proxyGettersSetters(Sizeable, HTMLScene); | |
// although Transformable is not used in this file, importing it first prevents | |
// a cyclical dependeny problem when an app entrypoint imports ./Scene.js | |
// before ./Node.js (Sizeable imports Motor which imports Transformable which | |
// imports Sizeable). See: | |
// https://esdiscuss.org/topic/how-to-solve-this-basic-es6-module-circular-dependency-problem | |
// TODO: write a test that imports public interfaces in every possible | |
// permutation to detect circular dependency errors. | |
// | |
// Transformable is not used in this file, but importing here solves the | |
// circular dependency problem. | |
// Sizeable is used in this file. | |
initImperativeBase(); | |
const instanceofSymbol$5 = Symbol('instanceofSymbol'); | |
let Scene = null; | |
const SceneMixin = base => { | |
// Scene is Sizeable, which is currently a subset of Transformable. | |
class _Scene extends ImperativeBase.mixin(Sizeable.mixin(base)) { | |
static define(name) { | |
customElements.define(name || 'i-scene', Scene); | |
} | |
constructor(options) { | |
if ( options === void 0 ) options = {}; | |
super(options); | |
// NOTE: z size is always 0, since native DOM elements are always flat. | |
this._elementParentSize = {x:0, y:0, z:0}; | |
this._onElementParentSizeChange = (newSize) => { | |
this._elementParentSize = newSize; | |
this._calcSize(); | |
this._needsToBeRendered(); | |
}; | |
this._calcSize(); | |
this._needsToBeRendered(); | |
// For now, use the same program (with shaders) for all objects. | |
// Basically it has position, frag colors, point light, directional | |
// light, and ambient light. | |
// TODO: maybe call this in `init()`, and destroy webgl stuff in | |
// `deinit()`. | |
// TODO: The user might enable this by setting the attribute later, so | |
// we can't simply rely on having it in constructor, we need a | |
// getter/setter like node properties. | |
this.initWebGl(); | |
} | |
// TODO: we need to deinit webgl too. | |
initWebGl() { | |
// TODO: this needs to be cancelable too, search other codes for | |
// "mountcancel" to see. | |
this.mountPromise.then(() => { | |
this.webglEnabled = !!this.getAttribute('webglenabled'); | |
if (!this.webglEnabled) { return } | |
this.webGlRendererState = {}; | |
getWebGlRenderer().initGl(this); | |
}); | |
} | |
//async initWebGl() { | |
//// TODO: this needs to be cancelable too, search other codes for | |
//// "mountcancel" to see. | |
//await this.mountPromise | |
//this.webglEnabled = !!this.getAttribute('webglenabled') | |
//if (!this.webglEnabled) return | |
//this.webGlRendererState = {} | |
//getWebGlRenderer().initGl(this) | |
//} | |
_setDefaultProperties() { | |
super._setDefaultProperties(); | |
Object.assign(this._properties, { | |
sizeMode: new XYZValues('proportional', 'proportional', 'absolute'), | |
}); | |
} | |
_startOrStopSizePolling() { | |
if ( | |
this._mounted && | |
(this._properties.sizeMode.x == 'proportional' | |
|| this._properties.sizeMode.y == 'proportional' | |
|| this._properties.sizeMode.z == 'proportional') | |
) { | |
this._startSizePolling(); | |
} | |
else { | |
this._stopSizePolling(); | |
} | |
} | |
// observe size changes on the scene element. | |
_startSizePolling() { | |
if (!this._elementManager) { return } | |
this._elementManager.element.__startSizePolling(); | |
this._elementManager.element.on('parentsizechange', this._onElementParentSizeChange); | |
} | |
// Don't observe size changes on the scene element. | |
_stopSizePolling() { | |
if (!this._elementManager) { return } | |
this._elementManager.element.off('parentsizechange', this._onElementParentSizeChange); | |
this._elementManager.element.__stopSizePolling(); | |
} | |
/** @override */ | |
_getParentSize() { | |
return this._mounted ? this._elementParentSize : {x:0,y:0,z:0} | |
} | |
/** | |
* Mount the scene into the given target. | |
* Resolves the Scene's mountPromise, which can be use to do something once | |
* the scene is mounted. | |
* | |
* @param {string|HTMLElement} [mountPoint=document.body] If a string selector is provided, | |
* the mount point will be selected from the DOM. If an HTMLElement is | |
* provided, that will be the mount point. If no mount point is provided, | |
* the scene will be mounted into document.body. | |
*/ | |
mount(mountPoint) { | |
const mountLogic = () => { | |
// if no mountPoint was provided, just mount onto the <body> element. | |
if (mountPoint === undefined) { mountPoint = document.body; } | |
// if the user supplied a selector, mount there. | |
else if (typeof mountPoint === 'string') | |
{ mountPoint = document.querySelector(mountPoint); } | |
// if we have an actual mount point (the user may have supplied one) | |
if (!(mountPoint instanceof window.HTMLElement)) | |
{ throw new Error('Invalid mount point specified in Scene.mount() call. Pass a selector, an actual HTMLElement, or don\'t pass anything to mount to <body>.') } | |
if (this._mounted) { this.unmount(); } | |
if (mountPoint !== this._elementManager.element.parentNode) | |
{ mountPoint.appendChild(this._elementManager.element); } | |
this._mounted = true; | |
if (this._mountPromise) { this._resolveMountPromise(); } | |
this._elementManager.shouldRender(); | |
this._startOrStopSizePolling(); | |
}; | |
// Wait for the document to be ready before mounting, otherwise the | |
// target mount point might not exist yet when this function is called. | |
if (document.readyState == 'loading') { return documentReady$1().then(mountLogic) } | |
else { | |
mountLogic(); | |
return Promise.resolve() | |
} | |
} | |
//async mount(mountPoint) { | |
//// Wait for the document to be ready before mounting, otherwise the | |
//// target mount point might not exist yet when this function is called. | |
//if (document.readyState == 'loading') await documentReady() | |
//// if no mountPoint was provided, just mount onto the <body> element. | |
//if (mountPoint === undefined) mountPoint = document.body | |
//// if the user supplied a selector, mount there. | |
//else if (typeof mountPoint === 'string') | |
//mountPoint = document.querySelector(mountPoint) | |
//// if we have an actual mount point (the user may have supplied one) | |
//if (!(mountPoint instanceof window.HTMLElement)) | |
//throw new Error('Invalid mount point specified in Scene.mount() call. Pass a selector, an actual HTMLElement, or don\'t pass anything to mount to <body>.') | |
//if (this._mounted) this.unmount() | |
//if (mountPoint !== this._elementManager.element.parentNode) | |
//mountPoint.appendChild(this._elementManager.element) | |
//this._mounted = true | |
//if (this._mountPromise) this._resolveMountPromise() | |
//this._elementManager.shouldRender() | |
//this._startOrStopSizePolling() | |
//} | |
/** | |
* Unmount the scene from it's mount point. Resets the Scene's | |
* mountPromise. | |
*/ | |
unmount() { | |
if (!this._mounted) { return } | |
this._elementManager.shouldNotRender(); | |
this._stopSizePolling(); | |
if (this._elementManager.element.parentNode) | |
{ this._elementManager.element.parentNode.removeChild(this._elementManager.element); } | |
if (this._mountPromise) { this._rejectMountPromise('mountcancel'); } | |
this._resetMountPromise(); | |
} | |
set sizeMode(value) { | |
super.sizeMode = value; | |
this._startOrStopSizePolling(); | |
} | |
} | |
Object.defineProperty(_Scene, Symbol.hasInstance, { | |
value: function(obj) { | |
if (this !== _Scene) { return Object.getPrototypeOf(_Scene)[Symbol.hasInstance].call(this, obj) } | |
let currentProto = obj; | |
while(currentProto) { | |
const desc = Object.getOwnPropertyDescriptor(currentProto, "constructor"); | |
if (desc && desc.value && desc.value.hasOwnProperty(instanceofSymbol$5)) | |
{ return true } | |
currentProto = Object.getPrototypeOf(currentProto); | |
} | |
return false | |
} | |
}); | |
_Scene[instanceofSymbol$5] = true; | |
return _Scene | |
}; | |
Scene = SceneMixin(class{}); | |
Scene.mixin = SceneMixin; | |
// for now, hard-mixin the HTMLScene class. We'll do this automatically later. | |
Scene = Scene.mixin(HTMLScene); | |
// We explicitly use `var` instead of `let` here because it is hoisted for the | |
// Node and Scene modules. This, along with the following initImperativeBase | |
// function, allows the circular dependency between this module and the Node and | |
// Scene modules to work. For details on why, see | |
// https://esdiscuss.org/topic/how-to-solve-this-basic-es6-module-circular-dependency-problem. | |
var ImperativeBase; | |
// Here we wrap the definition of the ImperativeBase class with this function in | |
// order to solve the circular depdendency problem caused by the | |
// Node<->ImperativeBase and Scene<->ImperativeBase circles. The Node and Scene | |
// modules call initImperativeBase to ensure that the ImperativeBase declaration | |
// happens first, and then those modules can use the live binding in their | |
// declarations. | |
initImperativeBase(); | |
function initImperativeBase() { | |
if (ImperativeBase) { return } | |
const instanceofSymbol = Symbol('instanceofSymbol'); | |
/** | |
* The ImperativeBase class is the base class for the Imperative version of the | |
* API, for people who chose to take the all-JavaScript approach and who will | |
* not use the HTML-based API (infamous/motor-html). | |
* | |
* In the future when there is an option to disable the HTML-DOM rendering (and | |
* render only WebGL, for example) then the imperative API will be the only API | |
* available since the HTML API will be turned off as a result of disabling | |
* HTML rendering. Disabling both WebGL and HTML won't make sense, as we'll need | |
* at least one of those to render with. | |
*/ | |
const ImperativeBaseMixin = base => { | |
const ParentClass = base; | |
class ImperativeBase extends ParentClass { | |
constructor(options) { | |
if ( options === void 0 ) options = {}; | |
super(options); | |
this._willBeRendered = false; | |
// Here we create the DOM HTMLElement associated with this | |
// Imperative-API Node. | |
this._elementManager = new ElementManager(this); | |
this._elementManager.element._associateImperativeNode(this); | |
// For Nodes, true when this Node is added to a parent AND it | |
// has an anancestor Scene that is mounted into DOM. For | |
// Scenes, true when mounted into DOM. | |
this._mounted = false; | |
// For Nodes, a promise that resolves when this Node is | |
// attached to a tree that has a root Scene TreeNode *and* when | |
// that root Scene has been mounted into the DOM. For Scenes, | |
// resolves when mounted into DOM. | |
this._mountPromise = null; | |
this._resolveMountPromise = null; | |
this._rejectMountPromise = null; | |
this._awaitingMountPromiseToRender = false; | |
this._waitingForMountConditions = false; | |
// See Transformable/Sizeable propertychange event. | |
this.on('propertychange', prop => { | |
if ( | |
prop == 'sizeMode' || | |
prop == 'absoluteSize' || | |
prop == 'proportionalSize' | |
) { | |
this._calcSize(); | |
} | |
this._needsToBeRendered(); | |
}); | |
} | |
/** | |
* Subclasses are required to override this. It should return the HTML-API | |
* counterpart for this Imperative-API instance. See Node or Scene classes | |
* for example. | |
* | |
* @private | |
*/ | |
_makeElement() { | |
throw new Error('Subclasses need to override ImperativeBase#_makeElement.') | |
} | |
/** | |
* @readonly | |
*/ | |
get mountPromise() { | |
if (!this._mountPromise) { | |
this._mountPromise = new Promise((resolve, reject) => { | |
this._resolveMountPromise = resolve; | |
this._rejectMountPromise = reject; | |
}); | |
} | |
if (!this._mounted) | |
{ this._waitForMountThenResolveMountPromise(); } | |
else if (this._mounted) | |
{ this._resolveMountPromise(); } | |
return this._mountPromise | |
} | |
_waitForMountThenResolveMountPromise() { | |
// extended in Node or Scene to await for anything that mount | |
// depends on. | |
} | |
/** | |
* @readonly | |
*/ | |
get element() { | |
return this._elementManager.element | |
} | |
/** | |
* @override | |
*/ | |
add(childNode) { | |
if (!isInstanceof(childNode, ImperativeBase)) { return } | |
// We cannot add Scenes to Nodes, for now. | |
if (childNode instanceof Scene) { | |
throw new Error(` | |
A Scene cannot be added to another Node or Scene (at | |
least for now). To place a Scene in a Node, just mount | |
a new Scene onto a MotorHTMLNode with Scene.mount(). | |
`) | |
} | |
super.add(childNode); | |
// Pass this parent node's Scene reference (if any, checking this cache | |
// first) to the new child and the child's children. | |
if (childNode._scene || childNode.scene) { | |
if (childNode._resolveScenePromise) | |
{ childNode._resolveScenePromise(childNode._scene); } | |
childNode._giveSceneRefToChildren(); | |
} | |
// Calculate sizing because proportional size might depend on | |
// the new parent. | |
childNode._calcSize(); | |
childNode._needsToBeRendered(); | |
// child should watch the parent for size changes. | |
this.on('sizechange', childNode._onParentSizeChange); | |
this._elementManager.connectChildElement(childNode); | |
return this | |
} | |
remove(childNode, /*private use*/leaveInDom) { | |
if (!(childNode instanceof Node$1)) { return } | |
super.remove(childNode); | |
this.off('sizechange', childNode._onParentSizeChange); | |
childNode._resetSceneRef(); | |
if (childNode._mountPromise) { childNode._rejectMountPromise('mountcancel'); } | |
if (childNode._mounted) { childNode._elementManager.shouldNotRender(); } | |
childNode._resetMountPromise(); | |
if (!leaveInDom) | |
{ this._elementManager.disconnectChildElement(childNode); } | |
} | |
_resetMountPromise() { | |
this._mounted = false; | |
this._mountPromise = null; | |
this._resolveMountPromise = null; | |
this._rejectMountPromise = null; | |
const children = this._children; | |
for (let i=0, l=children.length; i<l; i+=1) { | |
children[i]._resetMountPromise(); | |
} | |
} | |
_needsToBeRendered() { | |
if (this._awaitingMountPromiseToRender) { return Promise.resolve() } | |
const logic = () => { | |
this._willBeRendered = true; | |
Motor$1._setNodeToBeRendered(this); | |
}; | |
if (!this._mounted) { | |
this._awaitingMountPromiseToRender = true; | |
let possibleError = undefined; | |
// try | |
return this.mountPromise | |
.then(logic) | |
// catch | |
.catch(() => { | |
if (e == 'mountcancel') { return } | |
else { possibleError = e; } | |
}) | |
// finally | |
.then(() => { | |
this._awaitingMountPromiseToRender = false; | |
if (possibleError) { throw possibleError } | |
}) | |
} | |
logic(); | |
return Promise.resolve() | |
} | |
//async _needsToBeRendered() { | |
//if (this._awaitingMountPromiseToRender) return | |
//if (!this._mounted) { | |
//try { | |
//this._awaitingMountPromiseToRender = true | |
//await this.mountPromise | |
//} catch(e) { | |
//if (e == 'mountcancel') return | |
//else throw e | |
//} finally { | |
//this._awaitingMountPromiseToRender = false | |
//} | |
//} | |
//this._willBeRendered = true | |
//Motor._setNodeToBeRendered(this) | |
//} | |
// This method is used by Motor._renderNodes(). | |
_getAncestorToBeRendered() { | |
let parent = this._parent; | |
while (parent) { | |
if (parent._willBeRendered) { return parent } | |
parent = parent._parent; | |
} | |
return false | |
} | |
_render(timestamp) { | |
super._render(); | |
// applies the transform matrix to the element's style property. | |
this._elementManager.applyImperativeNodeProperties(this); | |
} | |
/** | |
* Set all properties of an ImperativeBase instance in one method. | |
* | |
* @param {Object} properties Properties object - see example. | |
* | |
* @example | |
* node.properties = { | |
* classes: ['open', 'big'], | |
* } | |
*/ | |
set properties(properties) { | |
if ( properties === void 0 ) properties = {}; | |
super.properties = properties; | |
if (properties.classes) | |
{ this._elementManager.setClasses(...properties.classes); } | |
} | |
} | |
Object.defineProperty(ImperativeBase, Symbol.hasInstance, { | |
value: function(obj) { | |
if (this !== ImperativeBase) { return Object.getPrototypeOf(ImperativeBase)[Symbol.hasInstance].call(this, obj) } | |
let currentProto = obj; | |
while(currentProto) { | |
const desc = Object.getOwnPropertyDescriptor(currentProto, "constructor"); | |
if (desc && desc.value && desc.value.hasOwnProperty(instanceofSymbol)) | |
{ return true } | |
currentProto = Object.getPrototypeOf(currentProto); | |
} | |
return false | |
} | |
}); | |
ImperativeBase[instanceofSymbol] = true; | |
return ImperativeBase | |
}; | |
ImperativeBase = ImperativeBaseMixin(Sizeable); | |
ImperativeBase.mixin = ImperativeBaseMixin; | |
} | |
initImperativeBase(); | |
const instanceofSymbol = Symbol('instanceofSymbol'); | |
let Node$1 = null; | |
const NodeMixin = base => { | |
class _Node extends ImperativeBase.mixin(Transformable.mixin(base)) { | |
static define(name) { | |
customElements.define(name || 'i-node', Node$1); | |
} | |
/** | |
* @constructor | |
* | |
* @param {Object} options Initial properties that the node will | |
* have. This can be used when creating a node, alternatively to using the | |
* setters/getters for position, rotation, etc. | |
* | |
* @example | |
* var node = new Node({ | |
* absoluteSize: {x:100, y:100, z:100}, | |
* rotation: {x:30, y:20, z:25} | |
* }) | |
*/ | |
constructor (options) { | |
if ( options === void 0 ) options = {}; | |
super(options); | |
// This was when using my `multiple()` implementation, we could call | |
// specific constructors using specific arguments. But, we're using | |
// class-factory style mixins for now, so we don't have control over the | |
// specific arguments we can pass to the constructors, so we're just | |
// using a single `options` parameter in all the constructors. | |
//this.callSuperConstructor(Transformable, options) | |
//this.callSuperConstructor(TreeNode) | |
//this.callSuperConstructor(ImperativeBase) | |
this._scene = null; // stores a ref to this Node's root Scene. | |
// This is an internal promise that resolves when this Node is added to | |
// to a scene graph that has a root Scene TreeNode. The resolved value | |
// is the root Scene. | |
this._scenePromise = null; | |
this._resolveScenePromise = null; | |
/** | |
* @private | |
* This method is defined here in the consructor as an arrow function | |
* because parent Nodes pass it to Observable#on and Observable#off. If | |
* it were a prototype method, then it would need to be bound when | |
* passed to Observable#on, which would require keeping track of the | |
* bound function reference in order to be able to pass it to | |
* Observable#off later. See ImperativeBase#add and | |
* ImperativeBase#remove. | |
*/ | |
this._onParentSizeChange = () => { | |
// We only need to recalculate sizing and matrices if this node has | |
// properties that depend on parent sizing (proportional size, | |
// align, and mountPoint). mountPoint isn't obvious: if this node | |
// is proportionally sized, then the mountPoint will depend on the | |
// size of this element which depends on the size of this element's | |
// parent. | |
if ( | |
this._properties.sizeMode.x === "proportional" | |
|| this._properties.sizeMode.y === "proportional" | |
|| this._properties.sizeMode.z === "proportional" | |
|| this._properties.align.x !== 0 | |
|| this._properties.align.y !== 0 | |
|| this._properties.align.z !== 0 | |
) { | |
this._calcSize(); | |
this._needsToBeRendered(); | |
} | |
}; | |
this._calcSize(); | |
this._needsToBeRendered(); | |
} | |
/** | |
* @private | |
*/ | |
_waitForMountThenResolveMountPromise() { | |
if (this._awaitingScenePromise) { return Promise.resolve() } | |
const logic = () => { | |
this._mounted = true; | |
this._resolveMountPromise(); | |
this._elementManager.shouldRender(); | |
}; | |
this._awaitingScenePromise = true; | |
let possibleError = undefined; | |
// try | |
return this._getScenePromise() | |
.then(() => this._scene.mountPromise) | |
.then(logic) | |
// catch | |
.catch(() => { | |
if (e == 'mountcancel') { return } | |
else { possibleError = e; } | |
}) | |
// finally | |
.then(() => { | |
this._awaitingScenePromise = false; | |
if (possibleError) { throw possibleError } | |
}) | |
} | |
//async _waitForMountThenResolveMountPromise() { | |
//if (this._awaitingScenePromise) return | |
//try { | |
//this._awaitingScenePromise = true | |
//await this._getScenePromise() | |
//await this._scene.mountPromise | |
//} catch (e) { | |
//if (e == 'mountcancel') return | |
//else throw e | |
//} finally { | |
//this._awaitingScenePromise = false | |
//} | |
//this._mounted = true | |
//this._resolveMountPromise() | |
//this._elementManager.shouldRender() | |
//} | |
/** | |
* @private | |
* Get a promise for the node's eventual scene. | |
*/ | |
_getScenePromise() { | |
if (!this._scenePromise) { | |
this._scenePromise = new Promise((a, b) => { | |
this._resolveScenePromise = a; | |
}); | |
} | |
if (this._scene) | |
{ this._resolveScenePromise(); } | |
return this._scenePromise | |
} | |
/** | |
* Get the Scene that this Node is in, null if no Scene. This is recursive | |
* at first, then cached. | |
* | |
* This traverses up the scene graph tree starting at this Node and finds | |
* the root Scene, if any. It caches the value for performance. If this | |
* Node is removed from a parent node with parent.remove(), then the | |
* cache is invalidated so the traversal can happen again when this Node is | |
* eventually added to a new tree. This way, if the scene is cached on a | |
* parent Node that we're adding this Node to then we can get that cached | |
* value instead of traversing the tree. | |
* | |
* @readonly | |
*/ | |
get scene() { | |
// NOTE: this._scene is initally null, created in the constructor. | |
// if already cached, return it. Or if no parent, return it (it'll be null). | |
if (this._scene || !this._parent) { return this._scene } | |
// if the parent node already has a ref to the scene, use that. | |
if (this._parent._scene) { | |
this._scene = this._parent._scene; | |
} | |
else if (this._parent instanceof Scene) { | |
this._scene = this._parent; | |
} | |
// otherwise call the scene getter on the parent, which triggers | |
// traversal up the scene graph in order to find the root scene (null | |
// if none). | |
else { | |
this._scene = this._parent.scene; | |
} | |
return this._scene | |
} | |
/** | |
* @private | |
* This method to be called only when this Node has this.scene. | |
* Resolves the _scenePromise for all children of the tree of this Node. | |
*/ | |
_giveSceneRefToChildren() { | |
const children = this._children; | |
for (let i=0, l=children.length; i<l; i+=1) { | |
const childNode = children[i]; | |
childNode._scene = this._scene; | |
if (childNode._resolveScenePromise) | |
{ childNode._resolveScenePromise(childNode._scene); } | |
childNode._giveSceneRefToChildren(); | |
} | |
} | |
_resetSceneRef() { | |
this._scene = null; | |
this._scenePromise = null; | |
this._resolveScenePromise = null; | |
const children = this._children; | |
for (let i=0, l=children.length; i<l; i+=1) { | |
children[i]._resetSceneRef(); | |
} | |
} | |
} | |
Object.defineProperty(_Node, Symbol.hasInstance, { | |
value: function(obj) { | |
if (this !== _Node) { return Object.getPrototypeOf(_Node)[Symbol.hasInstance].call(this, obj) } | |
let currentProto = obj; | |
while(currentProto) { | |
const desc = Object.getOwnPropertyDescriptor(currentProto, "constructor"); | |
if (desc && desc.value && desc.value.hasOwnProperty(instanceofSymbol)) | |
{ return true } | |
currentProto = Object.getPrototypeOf(currentProto); | |
} | |
return false | |
} | |
}); | |
_Node[instanceofSymbol] = true; | |
return _Node | |
}; | |
Node$1 = NodeMixin(class{}); | |
Node$1.mixin = NodeMixin; | |
// for now, hard-mixin the HTMLNode class. We'll do this automatically later. | |
Node$1 = Node$1.mixin(HTMLNode); | |
/** | |
* Manages a DOM element. Exposes a set of recommended APIs for working with | |
* DOM efficiently. Currently doesn't do much yet... | |
*/ | |
class ElementManager { | |
constructor(element) { | |
this.element = element; | |
} | |
/** | |
* @param {Array.string} classes An array of class names to add to the | |
* managed element. | |
* | |
* Note: updating class names with `el.classList.add()` won't thrash the | |
* layout. See: http://www.html5rocks.com/en/tutorials/speed/animations | |
*/ | |
setClasses (...classes) { | |
if (classes.length) { this.element.classList.add(...classes); } | |
return this | |
} | |
/** | |
* Apply a style property to the element. | |
* | |
* @private | |
* @param {string} property The CSS property we will a apply. | |
* @param {string} value The value the CSS property wil have. | |
*/ | |
applyStyle(property, value) { | |
this.element.style[property] = value; | |
} | |
add(childElementManager) { | |
this.element.appendChild(childElementManager.element); | |
} | |
remove(childElementManager) { | |
// This conditional check is needed incase the element was already | |
// removed from the HTML-API side. | |
if (childElementManager.element.parentNode === this.element) | |
{ this.element.removeChild(childElementManager.element); } | |
} | |
connectChildElement(childImperativeNode) { | |
if ( | |
// When using the imperative API, this statement is | |
// true, so the DOM elements need to be connected. | |
!childImperativeNode._elementManager.element.parentNode | |
// This condition is irrelevant when strictly using the | |
// imperative API. However, it is possible that when | |
// using the HTML API that the HTML-API node can be placed | |
// somewhere that isn't another HTML-API node, and the | |
// imperative Node can be gotten and used to add the | |
// node to another imperative Node. In this case, the | |
// HTML-API node will be added to the proper HTMLparent. | |
|| (childImperativeNode._elementManager.element.parentElement && | |
childImperativeNode._elementManager.element.parentElement !== this.element) | |
// When an HTML-API node is already child of the | |
// relevant parent, or it is child of a shadow root of | |
// the relevant parent, there there's nothing to do, | |
// everything is already as expected, so the following | |
// conditional body is skipped. | |
) { | |
this.add(childImperativeNode._elementManager); | |
} | |
} | |
disconnectChildElement(childImperativeNode) { | |
// If DeclarativeBase#remove was called first, we don't need to | |
// call this again. | |
if (!childImperativeNode._elementManager.element.parentNode) { return } | |
this.remove(childImperativeNode._elementManager); | |
} | |
/** | |
* Apply the DOMMatrix value to the style of this Node's element. | |
*/ | |
applyTransform (domMatrix) { | |
// for now, template strings need to be on one line, otherwise Meteor | |
// users will have bugs from Meteor's injected line numbers. See: | |
// https://github.com/meteor/meteor/issues/9160 | |
var cssMatrixString = `matrix3d( ${ domMatrix.m11 }, ${ domMatrix.m12 }, ${ domMatrix.m13 }, ${ domMatrix.m14 }, ${ domMatrix.m21 }, ${ domMatrix.m22 }, ${ domMatrix.m23 }, ${ domMatrix.m24 }, ${ domMatrix.m31 }, ${ domMatrix.m32 }, ${ domMatrix.m33 }, ${ domMatrix.m34 }, ${ domMatrix.m41 }, ${ domMatrix.m42 }, ${ domMatrix.m43 }, ${ domMatrix.m44 })`; | |
this.applyStyle('transform', cssMatrixString); | |
} | |
/** | |
* [applySize description] | |
*/ | |
applySize (size) { | |
var x = size.x; | |
var y = size.y; | |
this.applyStyle('width', `${x}px`); | |
this.applyStyle('height', `${y}px`); | |
// NOTE: we ignore the Z axis on elements, since they are flat. | |
} | |
applyOpacity(opacity) { | |
this.applyStyle('opacity', opacity); | |
} | |
applyImperativeNodeProperties(node) { | |
// Only Node is Transformable | |
if (node instanceof Node$1) { | |
this.applyOpacity(node._properties.opacity); | |
this.applyTransform(node._properties.transform); | |
} | |
// But both Node and Scene are Sizeable | |
this.applySize(node._calculatedSize); | |
} | |
shouldRender() { | |
const task = Motor$1.addRenderTask(() => { | |
this.applyStyle('display', 'block'); | |
Motor$1.removeRenderTask(task); | |
}); | |
} | |
shouldNotRender() { | |
const task = Motor$1.addRenderTask(() => { | |
this.applyStyle('display', 'none'); | |
Motor$1.removeRenderTask(task); | |
}); | |
} | |
} | |
var index$9 = Object.freeze({ | |
ElementManager: ElementManager, | |
Motor: Motor$1, | |
get Node () { return Node$1; }, | |
get Scene () { return Scene; }, | |
Sizeable: Sizeable, | |
Transformable: Transformable, | |
TreeNode: TreeNode, | |
XYZValues: XYZValues, | |
Utility: Utility$2 | |
}); | |
class PushPaneLayout extends Node$1 { | |
constructor(...args) { | |
console.log(' -- PushPaneLayout created'); | |
super(...args); | |
} | |
} | |
class HTMLPushPaneLayout extends HTMLNode { | |
static define(name) { | |
customElements.define(name || 'i-push-pane-layout', HTMLPushPaneLayout); | |
} | |
// @override | |
_makeImperativeCounterpart() { | |
return new PushPaneLayout({}, this) | |
} | |
} | |
(function(){ | |
'use strict';var h=new function(){};var aa=new Set("annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" "));function m(b){var a=aa.has(b);b=/^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(b);return!a&&b}function n(b){var a=b.isConnected;if(void 0!==a){ return a; }for(;b&&!(b.__CE_isImportDocument||b instanceof Document);){ b=b.parentNode||(window.ShadowRoot&&b instanceof ShadowRoot?b.host:void 0); }return!(!b||!(b.__CE_isImportDocument||b instanceof Document))} | |
function p(b,a){for(;a&&a!==b&&!a.nextSibling;){ a=a.parentNode; }return a&&a!==b?a.nextSibling:null} | |
function t(b,a,d){d=d?d:new Set;for(var c=b;c;){if(c.nodeType===Node.ELEMENT_NODE){var e=c;a(e);var f=e.localName;if("link"===f&&"import"===e.getAttribute("rel")){c=e.import;if(c instanceof Node&&!d.has(c)){ for(d.add(c),c=c.firstChild;c;c=c.nextSibling){ t(c,a,d); } }c=p(b,e);continue}else if("template"===f){c=p(b,e);continue}if(e=e.__CE_shadowRoot){ for(e=e.firstChild;e;e=e.nextSibling){ t(e,a,d); } }}c=c.firstChild?c.firstChild:p(b,c);}}function u(b,a,d){b[a]=d;}function v(){this.a=new Map;this.o=new Map;this.f=[];this.b=!1;}function ba(b,a,d){b.a.set(a,d);b.o.set(d.constructor,d);}function w(b,a){b.b=!0;b.f.push(a);}function x(b,a){b.b&&t(a,function(a){return y(b,a)});}function y(b,a){if(b.b&&!a.__CE_patched){a.__CE_patched=!0;for(var d=0;d<b.f.length;d++){ b.f[d](a); }}}function z(b,a){var d=[];t(a,function(b){return d.push(b)});for(a=0;a<d.length;a++){var c=d[a];1===c.__CE_state?b.connectedCallback(c):A(b,c);}} | |
function B(b,a){var d=[];t(a,function(b){return d.push(b)});for(a=0;a<d.length;a++){var c=d[a];1===c.__CE_state&&b.disconnectedCallback(c);}} | |
function C(b,a,d){d=d?d:{};var c=d.w||new Set,e=d.s||function(a){return A(b,a)},f=[];t(a,function(a){if("link"===a.localName&&"import"===a.getAttribute("rel")){var d=a.import;d instanceof Node&&"complete"===d.readyState?(d.__CE_isImportDocument=!0,d.__CE_hasRegistry=!0):a.addEventListener("load",function(){var d=a.import;if(!d.__CE_documentLoadHandled){d.__CE_documentLoadHandled=!0;d.__CE_isImportDocument=!0;d.__CE_hasRegistry=!0;var f=new Set(c);f.delete(d);C(b,d,{w:f,s:e});}});}else { f.push(a); }},c); | |
if(b.b){ for(a=0;a<f.length;a++){ y(b,f[a]); } }for(a=0;a<f.length;a++){ e(f[a]); }} | |
function A(b,a){if(void 0===a.__CE_state){var d=b.a.get(a.localName);if(d){d.constructionStack.push(a);var c=d.constructor;try{try{if(new c!==a){ throw Error("The custom element constructor did not produce the element being upgraded."); }}finally{d.constructionStack.pop();}}catch(r){throw a.__CE_state=2,r;}a.__CE_state=1;a.__CE_definition=d;if(d.attributeChangedCallback){ for(d=d.observedAttributes,c=0;c<d.length;c++){var e=d[c],f=a.getAttribute(e);null!==f&&b.attributeChangedCallback(a,e,null,f,null);} }n(a)&& | |
b.connectedCallback(a);}}}v.prototype.connectedCallback=function(b){var a=b.__CE_definition;a.connectedCallback&&a.connectedCallback.call(b);};v.prototype.disconnectedCallback=function(b){var a=b.__CE_definition;a.disconnectedCallback&&a.disconnectedCallback.call(b);};v.prototype.attributeChangedCallback=function(b,a,d,c,e){var f=b.__CE_definition;f.attributeChangedCallback&&-1<f.observedAttributes.indexOf(a)&&f.attributeChangedCallback.call(b,a,d,c,e);};function D(b,a){this.c=b;this.a=a;this.b=void 0;C(this.c,this.a);"loading"===this.a.readyState&&(this.b=new MutationObserver(this.f.bind(this)),this.b.observe(this.a,{childList:!0,subtree:!0}));}function E(b){b.b&&b.b.disconnect();}D.prototype.f=function(b){var a=this.a.readyState;"interactive"!==a&&"complete"!==a||E(this);for(a=0;a<b.length;a++){ for(var d=b[a].addedNodes,c=0;c<d.length;c++){ C(this.c,d[c]); } }};function ca(){var b=this;this.b=this.a=void 0;this.f=new Promise(function(a){b.b=a;b.a&&a(b.a);});}function F(b){if(b.a){ throw Error("Already resolved."); }b.a=void 0;b.b&&b.b(void 0);}function G(b){this.i=!1;this.c=b;this.m=new Map;this.j=function(b){return b()};this.g=!1;this.l=[];this.u=new D(b,document);} | |
G.prototype.define=function(b,a){var d=this;if(!(a instanceof Function)){ throw new TypeError("Custom element constructors must be functions."); }if(!m(b)){ throw new SyntaxError("The element name '"+b+"' is not valid."); }if(this.c.a.get(b)){ throw Error("A custom element with name '"+b+"' has already been defined."); }if(this.i){ throw Error("A custom element is already being defined."); }this.i=!0;var c,e,f,r,k;try{var g=function(b){var a=l[b];if(void 0!==a&&!(a instanceof Function)){ throw Error("The '"+b+"' callback must be a function."); } | |
return a},l=a.prototype;if(!(l instanceof Object)){ throw new TypeError("The custom element constructor's prototype is not an object."); }c=g("connectedCallback");e=g("disconnectedCallback");f=g("adoptedCallback");r=g("attributeChangedCallback");k=a.observedAttributes||[];}catch(q){return}finally{this.i=!1;}a={localName:b,constructor:a,connectedCallback:c,disconnectedCallback:e,adoptedCallback:f,attributeChangedCallback:r,observedAttributes:k,constructionStack:[]};ba(this.c,b,a);this.l.push(a);this.g|| | |
(this.g=!0,this.j(function(){return da(d)}));};function da(b){if(!1!==b.g){b.g=!1;for(var a=b.l,d=[],c=new Map,e=0;e<a.length;e++){ c.set(a[e].localName,[]); }C(b.c,document,{s:function(a){if(void 0===a.__CE_state){var e=a.localName,f=c.get(e);f?f.push(a):b.c.a.get(e)&&d.push(a);}}});for(e=0;e<d.length;e++){ A(b.c,d[e]); }for(;0<a.length;){for(var f=a.shift(),e=f.localName,f=c.get(f.localName),r=0;r<f.length;r++){ A(b.c,f[r]); }(e=b.m.get(e))&&F(e);}}}G.prototype.get=function(b){if(b=this.c.a.get(b)){ return b.constructor }}; | |
G.prototype.whenDefined=function(b){if(!m(b)){ return Promise.reject(new SyntaxError("'"+b+"' is not a valid custom element name.")); }var a=this.m.get(b);if(a){ return a.f; }a=new ca;this.m.set(b,a);this.c.a.get(b)&&!this.l.some(function(a){return a.localName===b})&&F(a);return a.f};G.prototype.v=function(b){E(this.u);var a=this.j;this.j=function(d){return b(function(){return a(d)})};};window.CustomElementRegistry=G;G.prototype.define=G.prototype.define;G.prototype.get=G.prototype.get; | |
G.prototype.whenDefined=G.prototype.whenDefined;G.prototype.polyfillWrapFlushCallback=G.prototype.v;var H=window.Document.prototype.createElement,ea=window.Document.prototype.createElementNS,fa=window.Document.prototype.importNode,ga=window.Document.prototype.prepend,ha=window.Document.prototype.append,ia=window.DocumentFragment.prototype.prepend,ja=window.DocumentFragment.prototype.append,I=window.Node.prototype.cloneNode,J=window.Node.prototype.appendChild,K=window.Node.prototype.insertBefore,L=window.Node.prototype.removeChild,M=window.Node.prototype.replaceChild,N=Object.getOwnPropertyDescriptor(window.Node.prototype, | |
"textContent"),O=window.Element.prototype.attachShadow,P=Object.getOwnPropertyDescriptor(window.Element.prototype,"innerHTML"),Q=window.Element.prototype.getAttribute,R=window.Element.prototype.setAttribute,S=window.Element.prototype.removeAttribute,T=window.Element.prototype.getAttributeNS,U=window.Element.prototype.setAttributeNS,ka=window.Element.prototype.removeAttributeNS,la=window.Element.prototype.insertAdjacentElement,ma=window.Element.prototype.prepend,na=window.Element.prototype.append, | |
V=window.Element.prototype.before,oa=window.Element.prototype.after,pa=window.Element.prototype.replaceWith,qa=window.Element.prototype.remove,ra=window.HTMLElement,W=Object.getOwnPropertyDescriptor(window.HTMLElement.prototype,"innerHTML"),sa=window.HTMLElement.prototype.insertAdjacentElement;function ta(){var b=X;window.HTMLElement=function(){function a(){var a=this.constructor,c=b.o.get(a);if(!c){ throw Error("The custom element being constructed was not registered with `customElements`."); }var e=c.constructionStack;if(!e.length){ return e=H.call(document,c.localName),Object.setPrototypeOf(e,a.prototype),e.__CE_state=1,e.__CE_definition=c,y(b,e),e; }var c=e.length-1,f=e[c];if(f===h){ throw Error("The HTMLElement constructor was either called reentrantly for this constructor or called multiple times."); } | |
e[c]=h;Object.setPrototypeOf(f,a.prototype);y(b,f);return f}a.prototype=ra.prototype;return a}();}function Y(b,a,d){function c(a){return function(d){for(var e=[],c=0;c<arguments.length;++c){ e[c-0]=arguments[c]; }for(var c=[],f=[],l=0;l<e.length;l++){var q=e[l];q instanceof Element&&n(q)&&f.push(q);if(q instanceof DocumentFragment){ for(q=q.firstChild;q;q=q.nextSibling){ c.push(q); } }else { c.push(q); }}a.apply(this,e);for(e=0;e<f.length;e++){ B(b,f[e]); }if(n(this)){ for(e=0;e<c.length;e++){ f=c[e],f instanceof Element&&z(b,f); } }}}d.h&&(a.prepend=c(d.h));d.append&&(a.append=c(d.append));}function ua(){var b=X;u(Document.prototype,"createElement",function(a){if(this.__CE_hasRegistry){var d=b.a.get(a);if(d){ return new d.constructor }}a=H.call(this,a);y(b,a);return a});u(Document.prototype,"importNode",function(a,d){a=fa.call(this,a,d);this.__CE_hasRegistry?C(b,a):x(b,a);return a});u(Document.prototype,"createElementNS",function(a,d){if(this.__CE_hasRegistry&&(null===a||"http://www.w3.org/1999/xhtml"===a)){var c=b.a.get(d);if(c){ return new c.constructor }}a=ea.call(this,a,d);y(b,a);return a}); | |
Y(b,Document.prototype,{h:ga,append:ha});}function va(){var b=X;function a(a,c){Object.defineProperty(a,"textContent",{enumerable:c.enumerable,configurable:!0,get:c.get,set:function(a){if(this.nodeType===Node.TEXT_NODE){ c.set.call(this,a); }else{var e=void 0;if(this.firstChild){var d=this.childNodes,k=d.length;if(0<k&&n(this)){ for(var e=Array(k),g=0;g<k;g++){ e[g]=d[g]; } }}c.set.call(this,a);if(e){ for(a=0;a<e.length;a++){ B(b,e[a]); } }}}});}u(Node.prototype,"insertBefore",function(a,c){if(a instanceof DocumentFragment){var e=Array.prototype.slice.apply(a.childNodes); | |
a=K.call(this,a,c);if(n(this)){ for(c=0;c<e.length;c++){ z(b,e[c]); } }return a}e=n(a);c=K.call(this,a,c);e&&B(b,a);n(this)&&z(b,a);return c});u(Node.prototype,"appendChild",function(a){if(a instanceof DocumentFragment){var c=Array.prototype.slice.apply(a.childNodes);a=J.call(this,a);if(n(this)){ for(var e=0;e<c.length;e++){ z(b,c[e]); } }return a}c=n(a);e=J.call(this,a);c&&B(b,a);n(this)&&z(b,a);return e});u(Node.prototype,"cloneNode",function(a){a=I.call(this,a);this.ownerDocument.__CE_hasRegistry?C(b,a):x(b,a); | |
return a});u(Node.prototype,"removeChild",function(a){var c=n(a),e=L.call(this,a);c&&B(b,a);return e});u(Node.prototype,"replaceChild",function(a,c){if(a instanceof DocumentFragment){var e=Array.prototype.slice.apply(a.childNodes);a=M.call(this,a,c);if(n(this)){ for(B(b,c),c=0;c<e.length;c++){ z(b,e[c]); } }return a}var e=n(a),f=M.call(this,a,c),d=n(this);d&&B(b,c);e&&B(b,a);d&&z(b,a);return f});N&&N.get?a(Node.prototype,N):w(b,function(b){a(b,{enumerable:!0,configurable:!0,get:function(){for(var a=[],b= | |
0;b<this.childNodes.length;b++){ a.push(this.childNodes[b].textContent); }return a.join("")},set:function(a){for(;this.firstChild;){ L.call(this,this.firstChild); }J.call(this,document.createTextNode(a));}});});}function wa(b){var a=Element.prototype;function d(a){return function(e){for(var c=[],d=0;d<arguments.length;++d){ c[d-0]=arguments[d]; }for(var d=[],k=[],g=0;g<c.length;g++){var l=c[g];l instanceof Element&&n(l)&&k.push(l);if(l instanceof DocumentFragment){ for(l=l.firstChild;l;l=l.nextSibling){ d.push(l); } }else { d.push(l); }}a.apply(this,c);for(c=0;c<k.length;c++){ B(b,k[c]); }if(n(this)){ for(c=0;c<d.length;c++){ k=d[c],k instanceof Element&&z(b,k); } }}}V&&(a.before=d(V));V&&(a.after=d(oa));pa&&u(a,"replaceWith",function(a){for(var e= | |
[],c=0;c<arguments.length;++c){ e[c-0]=arguments[c]; }for(var c=[],d=[],k=0;k<e.length;k++){var g=e[k];g instanceof Element&&n(g)&&d.push(g);if(g instanceof DocumentFragment){ for(g=g.firstChild;g;g=g.nextSibling){ c.push(g); } }else { c.push(g); }}k=n(this);pa.apply(this,e);for(e=0;e<d.length;e++){ B(b,d[e]); }if(k){ for(B(b,this),e=0;e<c.length;e++){ d=c[e],d instanceof Element&&z(b,d); } }});qa&&u(a,"remove",function(){var a=n(this);qa.call(this);a&&B(b,this);});}function xa(){var b=X;function a(a,c){Object.defineProperty(a,"innerHTML",{enumerable:c.enumerable,configurable:!0,get:c.get,set:function(a){var e=this,d=void 0;n(this)&&(d=[],t(this,function(a){a!==e&&d.push(a);}));c.set.call(this,a);if(d){ for(var f=0;f<d.length;f++){var r=d[f];1===r.__CE_state&&b.disconnectedCallback(r);} }this.ownerDocument.__CE_hasRegistry?C(b,this):x(b,this);return a}});}function d(a,c){u(a,"insertAdjacentElement",function(a,e){var d=n(e);a=c.call(this,a,e);d&&B(b,e);n(a)&&z(b,e); | |
return a});}O&&u(Element.prototype,"attachShadow",function(a){return this.__CE_shadowRoot=a=O.call(this,a)});if(P&&P.get){ a(Element.prototype,P); }else if(W&&W.get){ a(HTMLElement.prototype,W); }else{var c=H.call(document,"div");w(b,function(b){a(b,{enumerable:!0,configurable:!0,get:function(){return I.call(this,!0).innerHTML},set:function(a){var b="template"===this.localName?this.content:this;for(c.innerHTML=a;0<b.childNodes.length;){ L.call(b,b.childNodes[0]); }for(;0<c.childNodes.length;){ J.call(b,c.childNodes[0]); }}});});}u(Element.prototype, | |
"setAttribute",function(a,c){if(1!==this.__CE_state){ return R.call(this,a,c); }var e=Q.call(this,a);R.call(this,a,c);c=Q.call(this,a);b.attributeChangedCallback(this,a,e,c,null);});u(Element.prototype,"setAttributeNS",function(a,c,d){if(1!==this.__CE_state){ return U.call(this,a,c,d); }var e=T.call(this,a,c);U.call(this,a,c,d);d=T.call(this,a,c);b.attributeChangedCallback(this,c,e,d,a);});u(Element.prototype,"removeAttribute",function(a){if(1!==this.__CE_state){ return S.call(this,a); }var c=Q.call(this,a);S.call(this, | |
a);null!==c&&b.attributeChangedCallback(this,a,c,null,null);});u(Element.prototype,"removeAttributeNS",function(a,c){if(1!==this.__CE_state){ return ka.call(this,a,c); }var d=T.call(this,a,c);ka.call(this,a,c);var e=T.call(this,a,c);d!==e&&b.attributeChangedCallback(this,c,d,e,a);});sa?d(HTMLElement.prototype,sa):la?d(Element.prototype,la):console.warn("Custom Elements: `Element#insertAdjacentElement` was not patched.");Y(b,Element.prototype,{h:ma,append:na});wa(b);}/* | |
Copyright (c) 2016 The Polymer Project Authors. All rights reserved. | |
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt | |
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | |
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt | |
Code distributed by Google as part of the polymer project is also | |
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt | |
*/ | |
var Z=window.customElements;if(!Z||Z.forcePolyfill||"function"!=typeof Z.define||"function"!=typeof Z.get){var X=new v;ta();ua();Y(X,DocumentFragment.prototype,{h:ia,append:ja});va();xa();document.__CE_hasRegistry=!0;var customElements=new G(X);Object.defineProperty(window,"customElements",{configurable:!0,enumerable:!0,value:customElements});} | |
}).call(self); | |
/** | |
* @license | |
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved. | |
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt | |
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | |
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt | |
* Code distributed by Google as part of the polymer project is also | |
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt | |
*/ | |
/** | |
* This shim allows elements written in, or compiled to, ES5 to work on native | |
* implementations of Custom Elements. | |
* | |
* ES5-style classes don't work with native Custom Elements because the | |
* HTMLElement constructor uses the value of `new.target` to look up the custom | |
* element definition for the currently called constructor. `new.target` is only | |
* set when `new` is called and is only propagated via super() calls. super() | |
* is not emulatable in ES5. The pattern of `SuperClass.call(this)`` only works | |
* when extending other ES5-style classes, and does not propagate `new.target`. | |
* | |
* This shim allows the native HTMLElement constructor to work by generating and | |
* registering a stand-in class instead of the users custom element class. This | |
* stand-in class's constructor has an actual call to super(). | |
* `customElements.define()` and `customElements.get()` are both overridden to | |
* hide this stand-in class from users. | |
* | |
* In order to create instance of the user-defined class, rather than the stand | |
* in, the stand-in's constructor swizzles its instances prototype and invokes | |
* the user-defined constructor. When the user-defined constructor is called | |
* directly it creates an instance of the stand-in class to get a real extension | |
* of HTMLElement and returns that. | |
* | |
* There are two important constructors: A patched HTMLElement constructor, and | |
* the StandInElement constructor. They both will be called to create an element | |
* but which is called first depends on whether the browser creates the element | |
* or the user-defined constructor is called directly. The variables | |
* `browserConstruction` and `userConstruction` control the flow between the | |
* two constructors. | |
* | |
* This shim should be better than forcing the polyfill because: | |
* 1. It's smaller | |
* 2. All reaction timings are the same as native (mostly synchronous) | |
* 3. All reaction triggering DOM operations are automatically supported | |
* | |
* There are some restrictions and requirements on ES5 constructors: | |
* 1. All constructors in a inheritance hierarchy must be ES5-style, so that | |
* they can be called with Function.call(). This effectively means that the | |
* whole application must be compiled to ES5. | |
* 2. Constructors must return the value of the emulated super() call. Like | |
* `return SuperClass.call(this)` | |
* 3. The `this` reference should not be used before the emulated super() call | |
* just like `this` is illegal to use before super() in ES6. | |
* 4. Constructors should not create other custom elements before the emulated | |
* super() call. This is the same restriction as with native custom | |
* elements. | |
* | |
* Compiling valid class-based custom elements to ES5 will satisfy these | |
* requirements with the latest version of popular transpilers. | |
*/ | |
(() => { | |
'use strict'; | |
// Do nothing if `customElements` does not exist. | |
if (!window.customElements) { return; } | |
const NativeHTMLElement = window.HTMLElement; | |
const nativeDefine = window.customElements.define; | |
const nativeGet = window.customElements.get; | |
/** | |
* Map of user-provided constructors to tag names. | |
* | |
* @type {Map<Function, string>} | |
*/ | |
const tagnameByConstructor = new Map(); | |
/** | |
* Map of tag names to user-provided constructors. | |
* | |
* @type {Map<string, Function>} | |
*/ | |
const constructorByTagname = new Map(); | |
/** | |
* Whether the constructors are being called by a browser process, ie parsing | |
* or createElement. | |
*/ | |
let browserConstruction = false; | |
/** | |
* Whether the constructors are being called by a user-space process, ie | |
* calling an element constructor. | |
*/ | |
let userConstruction = false; | |
window.HTMLElement = function() { | |
if (!browserConstruction) { | |
const tagname = tagnameByConstructor.get(this.constructor); | |
const fakeClass = nativeGet.call(window.customElements, tagname); | |
// Make sure that the fake constructor doesn't call back to this constructor | |
userConstruction = true; | |
const instance = new (fakeClass)(); | |
return instance; | |
} | |
// Else do nothing. This will be reached by ES5-style classes doing | |
// HTMLElement.call() during initialization | |
browserConstruction = false; | |
}; | |
// By setting the patched HTMLElement's prototype property to the native | |
// HTMLElement's prototype we make sure that: | |
// document.createElement('a') instanceof HTMLElement | |
// works because instanceof uses HTMLElement.prototype, which is on the | |
// ptototype chain of built-in elements. | |
window.HTMLElement.prototype = NativeHTMLElement.prototype; | |
const define = (tagname, elementClass) => { | |
const elementProto = elementClass.prototype; | |
const StandInElement = class extends NativeHTMLElement { | |
constructor() { | |
// Call the native HTMLElement constructor, this gives us the | |
// under-construction instance as `this`: | |
super(); | |
// The prototype will be wrong up because the browser used our fake | |
// class, so fix it: | |
Object.setPrototypeOf(this, elementProto); | |
if (!userConstruction) { | |
// Make sure that user-defined constructor bottom's out to a do-nothing | |
// HTMLElement() call | |
browserConstruction = true; | |
// Call the user-defined constructor on our instance: | |
elementClass.call(this); | |
} | |
userConstruction = false; | |
} | |
}; | |
const standInProto = StandInElement.prototype; | |
StandInElement.observedAttributes = elementClass.observedAttributes; | |
standInProto.connectedCallback = elementProto.connectedCallback; | |
standInProto.disconnectedCallback = elementProto.disconnectedCallback; | |
standInProto.attributeChangedCallback = elementProto.attributeChangedCallback; | |
standInProto.adoptedCallback = elementProto.adoptedCallback; | |
tagnameByConstructor.set(elementClass, tagname); | |
constructorByTagname.set(tagname, elementClass); | |
nativeDefine.call(window.customElements, tagname, StandInElement); | |
}; | |
const get = (tagname) => constructorByTagname.get(tagname); | |
// Workaround for Safari bug where patching customElements can be lost, likely | |
// due to native wrapper garbage collection issue | |
Object.defineProperty(window, 'customElements', | |
{value: window.customElements, configurable: true, writable: true}); | |
Object.defineProperty(window.customElements, 'define', | |
{value: define, configurable: true, writable: true}); | |
Object.defineProperty(window.customElements, 'get', | |
{value: get, configurable: true, writable: true}); | |
})(); | |
//import 'document-register-element' | |
function useDefaultNames() { | |
if (!customElements.get('i-node')) { Node$1.define(); } | |
if (!customElements.get('i-scene')) { Scene.define(); } | |
//PushPaneLayout.define() | |
} | |
var index$12 = Object.freeze({ | |
get DeclarativeBase () { return DeclarativeBase; }, | |
HTMLNode: HTMLNode, | |
HTMLPushPaneLayout: HTMLPushPaneLayout, | |
HTMLScene: HTMLScene, | |
WebComponent: WebComponentMixin, | |
useDefaultNames: useDefaultNames | |
}); | |
/* | |
* LICENSE | |
* | |
* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
*/ | |
/** | |
* A scenegraph tree that lays things out in a cube form. | |
* | |
* XXX: Rename to CubeLayout? | |
* | |
* @class Cube | |
* @extends Node | |
*/ | |
class Cube$1 extends Node$1 { | |
/** | |
* Create a new Cube. | |
* | |
* @constructor | |
* @param {Number} size The integer width of the cube. | |
*/ | |
constructor(size, options) { | |
// cubes, the same size on all sides | |
super(Object.assign({}, {absoluteSize: [size, size, size]}, options)); | |
//GenericSync.register({ | |
//mouse: MouseSync, | |
//touch: TouchSync | |
//}); | |
this.size = size; | |
this.sides = []; | |
forLength(6, n => this._createCubeSide(n)); | |
} | |
/** | |
* Creates the 6 sides of the cube (the leafnodes of the scenegraph). | |
* | |
* @private | |
* @param {Number} index The index (a integer between 0 and 5) that specifies which side to create. | |
*/ | |
_createCubeSide(index) { | |
const rotator = new Node$1({ | |
align: [0.5, 0.5], | |
mountPoint: [0.5, 0.5], | |
}); | |
const side = new Node$1({ | |
align: [0.5, 0.5], | |
mountPoint: [0.5, 0.5], | |
absoluteSize: [this.size, this.size], | |
}); | |
this.sides.push(side); | |
rotator.add(side); | |
// XXX: make a new GenericSync-like thing? | |
//const sync = new GenericSync(['mouse','touch']); | |
//side.pipe(sync); | |
//sync.pipe(this.options.handler); | |
// rotate and place each side. | |
if (index < 4) // 4 sides | |
{ rotator.rotation.y = 90 * index; } | |
else // top/bottom | |
{ rotator.rotation.x = 90 * ( index % 2 ? -1 : 1 ); } | |
side.position.z = this.size / 2; | |
this.add(rotator); | |
} | |
/** | |
* Set the content for the sides of the cube. | |
* | |
* @param {Array} content An array containing [Node](#infamous/motor/Node) | |
* instances to place in the cube sides. Only the first 6 items are used, | |
* the rest are ignored. | |
*/ | |
setContent(content) { | |
forLength(6, index => { | |
//this.cubeSideNodes[index].set(null); // TODO: how do we erase previous content? | |
this.sides[index].add(content[index]); | |
}); | |
return this; | |
} | |
} | |
var index$13 = Object.freeze({ | |
Cube: Cube$1, | |
PushPaneLayout: PushPaneLayout | |
}); | |
const version = '17.0.5'; | |
exports.Calendar = Calendar; | |
exports.DoubleSidedPlane = DoubleSidedPlane; | |
exports.Grid = Grid; | |
exports.Molecule = Molecule; | |
exports.Plane = Plane; | |
exports.PushMenuLayout = PushMenuLayout; | |
exports.utils = utils; | |
exports.core = index$9; | |
exports.html = index$12; | |
exports.components = index$13; | |
exports.version = version; | |
return exports; | |
}({})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment