Skip to content

Instantly share code, notes, and snippets.

@trusktr
Created October 5, 2017 02:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trusktr/44cb3e33ede49a79619e2d98b5c2d620 to your computer and use it in GitHub Desktop.
Save trusktr/44cb3e33ede49a79619e2d98b5c2d620 to your computer and use it in GitHub Desktop.
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}
*/
var Molecule = (function (RenderNode) {
function Molecule(initialOptions) {
RenderNode.call(this);
// "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;
}
if ( RenderNode ) Molecule.__proto__ = RenderNode;
Molecule.prototype = Object.create( RenderNode && RenderNode.prototype );
Molecule.prototype.constructor = Molecule;
var prototypeAccessors = { options: {},transform: {} };
/**
* @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).
*/
prototypeAccessors.options.set = function (newOptions) {
this.resetOptions();
this.setOptions(newOptions);
};
prototypeAccessors.options.get = function () {
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).
*/
prototypeAccessors.transform.set = function (newTransform) {
this.setOptions({transform: newTransform});
};
prototypeAccessors.transform.get = function () {
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`.
*/
Molecule.prototype.setOptions = function 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.
*/
Molecule.prototype.resetOptions = function 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
*/
Molecule.prototype.pipe = function 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
*/
Molecule.prototype.unpipe = function 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).
*/
Molecule.prototype.on = function 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).
*/
Molecule.prototype.off = function off () {
const args = Array.prototype.splice.call(arguments, 0);
return this.options.handler.on.apply(this.options.handler, args);
};
Object.defineProperties( Molecule.prototype, prototypeAccessors );
return Molecule;
}(RenderNode_1));
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
*/
var Grid = (function (Molecule$$1) {
function Grid(columns, rows, size) {
Molecule$$1.call(this, {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));
}
if ( Molecule$$1 ) Grid.__proto__ = Molecule$$1;
Grid.prototype = Object.create( Molecule$$1 && Molecule$$1.prototype );
Grid.prototype.constructor = Grid;
/**
* Creates a grid cell at the given index.
*
* @private
* @param {Number} index The integer index of the grid cell.
*/
Grid.prototype._createGridCell = function _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.
*/
Grid.prototype.setChildren = function 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;
};
return Grid;
}(Molecule));
/* 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
*/
var Plane = (function (Molecule$$1) {
function Plane(initialOptions) {
Molecule$$1.call(this, initialOptions);
this.surface = new Surface_1(this.options);
this.add(this.surface);
this.surface.pipe(this.options.handler);
}
if ( Molecule$$1 ) Plane.__proto__ = Molecule$$1;
Plane.prototype = Object.create( Molecule$$1 && Molecule$$1.prototype );
Plane.prototype.constructor = Plane;
/**
* 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).
*/
Plane.prototype.getContent = function 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).
*/
Plane.prototype.setContent = function setContent () {
const args = Array.prototype.splice.call(arguments, 0);
return this.surface.setContent.apply(this.surface, args);
};
return Plane;
}(Molecule));
/*
* 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
*/
var DoubleSidedPlane = (function (Molecule$$1) {
function DoubleSidedPlane(initialOptions) {
Molecule$$1.call(this, 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);
}
if ( Molecule$$1 ) DoubleSidedPlane.__proto__ = Molecule$$1;
DoubleSidedPlane.prototype = Object.create( Molecule$$1 && Molecule$$1.prototype );
DoubleSidedPlane.prototype.constructor = DoubleSidedPlane;
/**
* 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".
*/
DoubleSidedPlane.prototype.getContent = function 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".
*/
DoubleSidedPlane.prototype.setContent = function setContent (content) {
this.plane1.setContent(content[0]);
this.plane2.setContent(content[1]);
};
return DoubleSidedPlane;
}(Molecule));
/*
* 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
*/
var Calendar = (function (Molecule$$1) {
function Calendar(calendarSize, transition) {
Molecule$$1.call(this, {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);
}
if ( Molecule$$1 ) Calendar.__proto__ = Molecule$$1;
Calendar.prototype = Object.create( Molecule$$1 && Molecule$$1.prototype );
Calendar.prototype.constructor = Calendar;
/**
* Creates the grid used for the layout of the day cells.
*
* @private
*/
Calendar.prototype._createGrid = function _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
*/
Calendar.prototype._initializeTransitions = function _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)
};
};
return Calendar;
}(Molecule));
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
*/
var PushMenuLayout = (function (Molecule$$1) {
function PushMenuLayout(initialOptions) {
Molecule$$1.call(this, 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();
}
if ( Molecule$$1 ) PushMenuLayout.__proto__ = Molecule$$1;
PushMenuLayout.prototype = Object.create( Molecule$$1 && Molecule$$1.prototype );
PushMenuLayout.prototype.constructor = PushMenuLayout;
/**
* See Molecule.setOptions
*
* @override
*/
PushMenuLayout.prototype.setOptions = function setOptions (newOptions) {
Molecule$$1.prototype.setOptions.call(this, newOptions);
};
/**
* See Molecule.resetOptions
*
* @override
*/
PushMenuLayout.prototype.resetOptions = function resetOptions () {
Molecule$$1.prototype.resetOptions.call(this);
};
/**
* Creates the menu area, content area, `Plane` for the fade effect, etc.
*
* @private
*/
PushMenuLayout.prototype._createComponents = function _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$$1();
this.menuMol = new Molecule$$1({
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$$1();
this.contentMol = new Molecule$$1({
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
*/
PushMenuLayout.prototype._initializeEvents = function _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.
*/
PushMenuLayout.prototype.setContent = function 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.
*/
PushMenuLayout.prototype.setMenu = function setMenu (node) {
this.menuContentMol.add(node);
if (node instanceof Molecule$$1) {
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.
*/
PushMenuLayout.prototype.openMenu = function 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.
*/
PushMenuLayout.prototype.closeMenu = function 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.
*/
PushMenuLayout.prototype.toggleMenu = function 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.
*/
PushMenuLayout.prototype._animate = function _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.
*/
PushMenuLayout.prototype._haltAnimation = function _haltAnimation (cancelCallback) {
if (this.isAnimating) {
if (!cancelCallback && typeof this.transitionCallback == 'function') {
this.transitionCallback();
}
this.transitionCallback = undefined;
this.animationTransition.halt();
}
};
/**
* @override
*/
PushMenuLayout.prototype.render = function 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 Molecule$$1.prototype.render.call(this)
};
return PushMenuLayout;
}(Molecule));
// 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 => {
var Observable = (function (base) {
function Observable(options) {
if ( options === void 0 ) options = {};
base.call(this, options);
}
if ( base ) Observable.__proto__ = base;
Observable.prototype = Object.create( base && base.prototype );
Observable.prototype.constructor = Observable;
Observable.prototype.on = function 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.') }
};
Observable.prototype.off = function 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; }
};
Observable.prototype.triggerEvent = function 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);
}
};
return Observable;
}(base));
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((function () {
function anonymous () {}
return anonymous;
}()));
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'}
*/
var XYZValues = (function (Observable$$1) {
function XYZValues(x, y, z) {
if ( x === void 0 ) x = 0;
if ( y === void 0 ) y = 0;
if ( z === void 0 ) z = 0;
Observable$$1.call(this);
this._x = x;
this._y = y;
this._z = z;
}
if ( Observable$$1 ) XYZValues.__proto__ = Observable$$1;
XYZValues.prototype = Object.create( Observable$$1 && Observable$$1.prototype );
XYZValues.prototype.constructor = XYZValues;
var prototypeAccessors = { x: {},y: {},z: {} };
prototypeAccessors.x.set = function (value) {
this._x = value;
this.triggerEvent('valuechanged', {x: value});
};
prototypeAccessors.x.get = function () { return this._x };
prototypeAccessors.y.set = function (value) {
this._y = value;
this.triggerEvent('valuechanged', {y: value});
};
prototypeAccessors.y.get = function () { return this._y };
prototypeAccessors.z.set = function (value) {
this._z = value;
this.triggerEvent('valuechanged', {z: value});
};
prototypeAccessors.z.get = function () { return this._z };
Object.defineProperties( XYZValues.prototype, prototypeAccessors );
return XYZValues;
}(Observable));
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 => {
var TreeNode = (function (base) {
function TreeNode(options) {
if ( options === void 0 ) options = {};
base.call(this, options);
this._parent = null; // default to no parent.
this._children = [];
}
if ( base ) TreeNode.__proto__ = base;
TreeNode.prototype = Object.create( base && base.prototype );
TreeNode.prototype.constructor = TreeNode;
var prototypeAccessors = { parent: {},children: {},childCount: {} };
/**
* 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
*/
prototypeAccessors.parent.get = function () {
return this._parent
};
/**
* @readonly
*/
prototypeAccessors.children.get = function () {
// 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.
*/
TreeNode.prototype.add = function 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.
*/
TreeNode.prototype.addChildren = function addChildren (nodes) {
nodes.forEach(node => this.add()(node));
return this
};
/**
* Remove a child node from this node.
*
* @param {TreeNode} childNode The node to remove.
*/
TreeNode.prototype.remove = function 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.
*/
TreeNode.prototype.removeChildren = function removeChildren (nodes) {
nodes.forEach(node => this.remove(node));
return this
};
/**
* Shortcut to remove all children.
*/
TreeNode.prototype.removeAllChildren = function removeAllChildren () {
this.removeChildren(this._children);
return this
};
/**
* @readonly
* @return {number} How many children this TreeNode has.
*/
prototypeAccessors.childCount.get = function () {
return this._children.length
};
Object.defineProperties( TreeNode.prototype, prototypeAccessors );
return TreeNode;
}(base));
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((function () {
function anonymous () {}
return anonymous;
}()));
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]
}
},
};
var Geometry = function Geometry(...args) {
this._init(...args);
};
var prototypeAccessors$1 = { color: {} };
Geometry.prototype._init = function _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.
prototypeAccessors$1.color.set = function (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
}
};
prototypeAccessors$1.color.get = function () {
return this._color
};
Object.defineProperties( Geometry.prototype, prototypeAccessors$1 );
var IsoscelesTriangle = (function (Geometry) {
function IsoscelesTriangle () {
Geometry.apply(this, arguments);
}
if ( Geometry ) IsoscelesTriangle.__proto__ = Geometry;
IsoscelesTriangle.prototype = Object.create( Geometry && Geometry.prototype );
IsoscelesTriangle.prototype.constructor = IsoscelesTriangle;
IsoscelesTriangle.prototype._init = function _init (width, height) {
this.width = width; // number
this.height = height; // number
Geometry.prototype._init.call(this);
};
IsoscelesTriangle.prototype._calcVerts = function _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];
}
};
return IsoscelesTriangle;
}(Geometry));
var SymmetricTrapezoid = (function (Geometry) {
function SymmetricTrapezoid () {
Geometry.apply(this, arguments);
}
if ( Geometry ) SymmetricTrapezoid.__proto__ = Geometry;
SymmetricTrapezoid.prototype = Object.create( Geometry && Geometry.prototype );
SymmetricTrapezoid.prototype.constructor = SymmetricTrapezoid;
SymmetricTrapezoid.prototype._init = function _init (baseWidth, topWidth, height) {
this.baseWidth = baseWidth; // number
this.topWidth = topWidth; // number
this.height = height; // number
Geometry.prototype._init.call(this);
};
SymmetricTrapezoid.prototype._calcVerts = function _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];
}
};
return SymmetricTrapezoid;
}(Geometry));
var Quad = (function (Geometry) {
function Quad () {
Geometry.apply(this, arguments);
}
if ( Geometry ) Quad.__proto__ = Geometry;
Quad.prototype = Object.create( Geometry && Geometry.prototype );
Quad.prototype.constructor = Quad;
Quad.prototype._init = function _init (width, height) {
this.width = width; // number
this.height = height; // number
Geometry.prototype._init.call(this);
};
Quad.prototype._calcVerts = function _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];
}
};
return Quad;
}(Geometry));
var Cube = (function (Geometry) {
function Cube () {
Geometry.apply(this, arguments);
}
if ( Geometry ) Cube.__proto__ = Geometry;
Cube.prototype = Object.create( Geometry && Geometry.prototype );
Cube.prototype.constructor = Cube;
Cube.prototype._init = function _init (x, y, width) {
// the top front left corner
this.x = x; // number
this.y = y; // number
this.width = width; // number
Geometry.prototype._init.call(this);
};
Cube.prototype._calcVerts = function _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];
}
};
return Cube;
}(Geometry));
var FourSidedPyramid = (function (Geometry) {
function FourSidedPyramid () {
Geometry.apply(this, arguments);
}
if ( Geometry ) FourSidedPyramid.__proto__ = Geometry;
FourSidedPyramid.prototype = Object.create( Geometry && Geometry.prototype );
FourSidedPyramid.prototype.constructor = FourSidedPyramid;
FourSidedPyramid.prototype._init = function _init () {
Geometry.prototype._init.call(this);
};
FourSidedPyramid.prototype._calcVerts = function _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
]);
};
return FourSidedPyramid;
}(Geometry));
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);
};
var WebGlRenderer = function WebGlRenderer () {};
WebGlRenderer.prototype.initGl = function 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;
};
WebGlRenderer.prototype.drawScene = function 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]);
}
};
WebGlRenderer.prototype.drawNodeAndRecurse = function 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;
var Motor = function Motor() {
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.
*/
Motor.prototype._startAnimationLoop = function _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().
*/
Motor.prototype.addRenderTask = function 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
};
Motor.prototype.removeRenderTask = function removeRenderTask (fn) {
const taskIndex = this._allRenderTasks.indexOf(fn);
if (taskIndex == -1) { return }
this._allRenderTasks.splice(taskIndex, 1);
this._numberOfTasks -= 1;
this._taskIterationIndex -= 1;
};
Motor.prototype._runRenderTasks = function _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); }
}
};
Motor.prototype._setNodeToBeRendered = function _setNodeToBeRendered (node) {
if (this._nodesToBeRendered.includes(node)) { return }
this._nodesToBeRendered.push(node);
if (!this._inFrame) { this._startAnimationLoop(); }
};
Motor.prototype._renderNodes = function _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,
});
}
var XYZNonNegativeValues = (function (XYZValues$$1) {
function XYZNonNegativeValues(x, y, z) {
if ( x === void 0 ) x = 0;
if ( y === void 0 ) y = 0;
if ( z === void 0 ) z = 0;
XYZValues$$1.call(this, x, y, z);
}
if ( XYZValues$$1 ) XYZNonNegativeValues.__proto__ = XYZValues$$1;
XYZNonNegativeValues.prototype = Object.create( XYZValues$$1 && XYZValues$$1.prototype );
XYZNonNegativeValues.prototype.constructor = XYZNonNegativeValues;
var prototypeAccessors = { x: {},y: {},z: {} };
XYZNonNegativeValues.prototype._checkForNegative = function _checkForNegative (axisName, value) {
if(value < 0) {
throw new Error(axisName + " value was " + value + ". Size values cannot be negative.")
}
};
prototypeAccessors.x.set = function (value) {
this._checkForNegative("X", value);
XYZValues$$1.prototype.x = value;
};
prototypeAccessors.y.set = function (value) {
this._checkForNegative("Y", value);
XYZValues$$1.prototype.y = value;
};
prototypeAccessors.z.set = function (value) {
this._checkForNegative("Z", value);
XYZValues$$1.prototype.z = value;
};
Object.defineProperties( XYZNonNegativeValues.prototype, prototypeAccessors );
return XYZNonNegativeValues;
}(XYZValues));
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.
var Sizeable = (function (superclass) {
function Sizeable(options) {
if ( options === void 0 ) options = {};
superclass.call(this, options);
this._propertyFunctions = null;
this._calculatedSize = { x:0, y:0, z:0 };
this._properties = {};
this._setDefaultProperties();
this._setPropertyObservers();
this.properties = options;
}
if ( superclass ) Sizeable.__proto__ = superclass;
Sizeable.prototype = Object.create( superclass && superclass.prototype );
Sizeable.prototype.constructor = Sizeable;
var prototypeAccessors = { sizeMode: {},absoluteSize: {},actualSize: {},proportionalSize: {},properties: {} };
Sizeable.prototype._setDefaultProperties = function _setDefaultProperties () {
Object.assign(this._properties, {
sizeMode: new XYZValues('absolute', 'absolute', 'absolute'),
absoluteSize: new XYZNonNegativeValues(0, 0, 0),
proportionalSize: new XYZNonNegativeValues(1, 1, 1),
});
};
Sizeable.prototype._setPropertyObservers = function _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'));
};
Sizeable.prototype._calcSize = function _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));
}
};
Sizeable.prototype._getParentSize = function _getParentSize () {
return this._parent ? this._parent._calculatedSize : {x:0,y:0,z:0}
};
Sizeable.prototype._setPropertyXYZ = function _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}.`)
}
};
Sizeable.prototype._setPropertySingle = function _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);
}
};
Sizeable.prototype._render = function _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.
*/
prototypeAccessors.sizeMode.set = function (newValue) {
this._setPropertyXYZ(Sizeable, 'sizeMode', newValue);
};
prototypeAccessors.sizeMode.get = function () {
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.
*/
prototypeAccessors.absoluteSize.set = function (newValue) {
this._setPropertyXYZ(Sizeable, 'absoluteSize', newValue);
};
prototypeAccessors.absoluteSize.get = function () {
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.
*/
prototypeAccessors.actualSize.get = function () {
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.
*/
prototypeAccessors.proportionalSize.set = function (newValue) {
this._setPropertyXYZ(Sizeable, 'proportionalSize', newValue);
};
prototypeAccessors.proportionalSize.get = function () {
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}
* }
*/
prototypeAccessors.properties.set = function (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; }
};
Object.defineProperties( Sizeable.prototype, prototypeAccessors );
return Sizeable;
}(TreeNode.mixin(Observable.mixin(base))));
// 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((function () {
function anonymous () {}
return anonymous;
}()));
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);
var Transformable = (function (ParentClass) {
function Transformable(options) {
if ( options === void 0 ) options = {};
ParentClass.call(this, options);
this._worldMatrix = null;
}
if ( ParentClass ) Transformable.__proto__ = ParentClass;
Transformable.prototype = Object.create( ParentClass && ParentClass.prototype );
Transformable.prototype.constructor = Transformable;
var prototypeAccessors = { position: {},rotation: {},scale: {},opacity: {},align: {},mountPoint: {},properties: {} };
Transformable.prototype._setDefaultProperties = function _setDefaultProperties () {
ParentClass.prototype._setDefaultProperties.call(this);
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,
});
};
Transformable.prototype._setPropertyObservers = function _setPropertyObservers () {
ParentClass.prototype._setPropertyObservers.call(this);
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
*/
Transformable.prototype._calculateMatrix = function _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.
Transformable.prototype._calculateWorldMatricesInSubtree = function _calculateWorldMatricesInSubtree () {
this._calculateWorldMatrixFromParent();
const children = this._children;
for (let i=0, l=children.length; i<l; i+=1) {
children[i]._calculateWorldMatricesInSubtree();
}
};
Transformable.prototype._calculateWorldMatrixFromParent = function _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; }
};
Transformable.prototype._render = function _render () {
ParentClass.prototype._render.call(this);
// 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.
*/
prototypeAccessors.position.set = function (newValue) {
this._setPropertyXYZ(Transformable, 'position', newValue);
};
prototypeAccessors.position.get = function () {
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.
*/
prototypeAccessors.rotation.set = function (newValue) {
this._setPropertyXYZ(Transformable, 'rotation', newValue);
};
prototypeAccessors.rotation.get = function () {
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.
*/
prototypeAccessors.scale.set = function (newValue) {
this._setPropertyXYZ(Transformable, 'scale', newValue);
};
prototypeAccessors.scale.get = function () {
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.
*/
prototypeAccessors.opacity.set = function (newValue) {
if (!isRealNumber(newValue)) { newValue = undefined; }
this._setPropertySingle(Transformable, 'opacity', newValue, 'number');
};
prototypeAccessors.opacity.get = function () {
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.
*/
prototypeAccessors.align.set = function (newValue) {
this._setPropertyXYZ(Transformable, 'align', newValue);
};
prototypeAccessors.align.get = function () {
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.
*/
prototypeAccessors.mountPoint.set = function (newValue) {
this._setPropertyXYZ(Transformable, 'mountPoint', newValue);
};
prototypeAccessors.mountPoint.get = function () {
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,
* }
*/
prototypeAccessors.properties.set = function (properties) {
if ( properties === void 0 ) properties = {};
ParentClass.prototype.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; }
};
Object.defineProperties( Transformable.prototype, prototypeAccessors );
return Transformable;
}(ParentClass));
// 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((function () {
function anonymous () {}
return anonymous;
}()));
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.
var WebComponent = (function (elementClass) {
function WebComponent() {
console.log(' ------ ', elementClass)
console.trace(' tttttt ', elementClass)
elementClass.call(this);
// 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;
}
if ( elementClass ) WebComponent.__proto__ = elementClass;
WebComponent.prototype = Object.create( elementClass && elementClass.prototype );
WebComponent.prototype.constructor = WebComponent;
var staticAccessors = { observedAttributes: {} };
// Subclasses can implement these.
WebComponent.prototype.childConnectedCallback = function childConnectedCallback (child) { };
WebComponent.prototype.childDisconnectedCallback = function childDisconnectedCallback (child) { };
WebComponent.prototype.connectedCallback = function connectedCallback () {
if (elementClass.prototype.connectedCallback) { elementClass.prototype.connectedCallback.call(this); }
this._connected = true;
if (!this._initialized) {
this.init();
this._initialized = true;
}
};
WebComponent.prototype._createStyles = function _createStyles () {
const rule = jss.createRule(this.getStyles());
rule.applyTo(this);
return rule
};
WebComponent.prototype.disconnectedCallback = function disconnectedCallback () {
if (elementClass.prototype.disconnectedCallback) { elementClass.prototype.disconnectedCallback.call(this); }
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
*/
WebComponent.prototype.getStyles = function 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.
*/
WebComponent.prototype.init = function 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); }
}
};
staticAccessors.observedAttributes.get = function () {
console.warn(`WebComponent: Your custom element (${ this.name }) should specify observed attributes or attributeChangedCallback won't be called`);
};
WebComponent.prototype.attributeChangedCallback = function attributeChangedCallback (...args) {
//console.log(' --- attributeChangedCallback', typeof args[2])
if (elementClass.prototype.attributeChangedCallback) { elementClass.prototype.attributeChangedCallback.call(this, ...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.
*/
WebComponent.prototype.deinit = function deinit () {
// Nothing much at the moment, but extending classes can extend
// this to add deintialization logic.
this._initialized = false;
};
Object.defineProperties( WebComponent, staticAccessors );
return WebComponent;
}(elementClass));
classCache.set(elementClass, WebComponent);
return WebComponent
}
initDeclarativeBase();
var HTMLNode = (function (DeclarativeBase$$1) {
function HTMLNode () {
DeclarativeBase$$1.apply(this, arguments);
}
if ( DeclarativeBase$$1 ) HTMLNode.__proto__ = DeclarativeBase$$1;
HTMLNode.prototype = Object.create( DeclarativeBase$$1 && DeclarativeBase$$1.prototype );
HTMLNode.prototype.constructor = HTMLNode;
var staticAccessors = { observedAttributes: {} };
HTMLNode.prototype.getStyles = function getStyles () {
return styles$1
};
// TODO: get these from somewhere dynamically, and do same for
// proxyGettersSetters and _updateNodeProperty
staticAccessors.observedAttributes.get = function () { return [
'sizeMode',
'absoluteSize',
'proportionalSize',
'align',
'mountPoint',
'rotation',
'position',
'scale',
'origin',
'skew',
'opacity',
].map(a => a.toLowerCase())};
HTMLNode.prototype.attributeChangedCallback = function attributeChangedCallback (...args) {
DeclarativeBase$$1.prototype.attributeChangedCallback.call(this, ...args);
this._updateNodeProperty(...args);
};
//async attributeChangedCallback(...args) {
//super.attributeChangedCallback(...args)
//this._updateNodeProperty(...args)
//}
HTMLNode.prototype._updateNodeProperty = function _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 */
}
}
};
Object.defineProperties( HTMLNode, staticAccessors );
return HTMLNode;
}(DeclarativeBase));
// 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 = (function (superclass) {
function DeclarativeBase() {
superclass.call(this);
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();
}
if ( superclass ) DeclarativeBase.__proto__ = superclass;
DeclarativeBase.prototype = Object.create( superclass && superclass.prototype );
DeclarativeBase.prototype.constructor = DeclarativeBase;
/**
* 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.
*/
DeclarativeBase.prototype._associateImperativeNode = function _associateImperativeNode (imperativeCounterpart) {
// if the association is made already, noop
if (this.imperativeCounterpart) { return }
this.imperativeCounterpart = this;
};
DeclarativeBase.prototype.childConnectedCallback = function 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.
DeclarativeBase.prototype.handleEvent = function handleEvent (event) {
if (event.type == 'slotchange') {
const slot = event.target;
this._handleDistributedChildren(slot);
}
};
DeclarativeBase.prototype._handleDistributedChildren = function _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; }
}
};
DeclarativeBase.prototype._getDistributedChildDifference = function _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
};
DeclarativeBase.prototype.childDisconnectedCallback = function 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);
}
};
DeclarativeBase.prototype.setAttribute = function setAttribute (attr, value) {
//if (this.tagName.toLowerCase() == 'motor-scene')
//console.log('setting attribute', arguments[1])
superclass.prototype.setAttribute.call(this, attr, value);
};
return DeclarativeBase;
}(WebComponentMixin(window.HTMLElement)));
}
// 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();
var HTMLScene = (function (superclass) {
function HTMLScene() {
superclass.call(this);
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); }
}
if ( superclass ) HTMLScene.__proto__ = superclass;
HTMLScene.prototype = Object.create( superclass && superclass.prototype );
HTMLScene.prototype.constructor = HTMLScene;
HTMLScene.define = function define (name) {
customElements.define(name || 'i-scene', HTMLScene);
};
HTMLScene.prototype.__startSizePolling = function __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.
HTMLScene.prototype._checkSize = function _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 */
HTMLScene.prototype.getStyles = function getStyles () {
return styles
};
HTMLScene.prototype.deinit = function deinit () {
superclass.prototype.deinit.call(this);
this.imperativeCounterpart.unmount();
};
HTMLScene.prototype.__stopSizePolling = function __stopSizePolling () {
Motor$1.removeRenderTask(this._sizePollTask);
this._sizePollTask = null;
};
HTMLScene.prototype.connectedCallback = function connectedCallback () {
superclass.prototype.connectedCallback.call(this);
// When the HTMLScene gets addded to the DOM, make it be "mounted".
if (!this.imperativeCounterpart._mounted)
{ this.imperativeCounterpart.mount(this.parentNode); }
};
return HTMLScene;
}(Observable.mixin(DeclarativeBase)));
// 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.
var _Scene = (function (superclass) {
function _Scene(options) {
if ( options === void 0 ) options = {};
superclass.call(this, 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();
}
if ( superclass ) _Scene.__proto__ = superclass;
_Scene.prototype = Object.create( superclass && superclass.prototype );
_Scene.prototype.constructor = _Scene;
var prototypeAccessors = { sizeMode: {} };
// TODO: we need to deinit webgl too.
_Scene.define = function define (name) {
customElements.define(name || 'i-scene', Scene);
};
_Scene.prototype.initWebGl = function 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)
//}
_Scene.prototype._setDefaultProperties = function _setDefaultProperties () {
superclass.prototype._setDefaultProperties.call(this);
Object.assign(this._properties, {
sizeMode: new XYZValues('proportional', 'proportional', 'absolute'),
});
};
_Scene.prototype._startOrStopSizePolling = function _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.
_Scene.prototype._startSizePolling = function _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.
_Scene.prototype._stopSizePolling = function _stopSizePolling () {
if (!this._elementManager) { return }
this._elementManager.element.off('parentsizechange', this._onElementParentSizeChange);
this._elementManager.element.__stopSizePolling();
};
/** @override */
_Scene.prototype._getParentSize = function _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.
*/
_Scene.prototype.mount = function 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.
*/
_Scene.prototype.unmount = function 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();
};
prototypeAccessors.sizeMode.set = function (value) {
superclass.prototype.sizeMode = value;
this._startOrStopSizePolling();
};
Object.defineProperties( _Scene.prototype, prototypeAccessors );
return _Scene;
}(ImperativeBase.mixin(Sizeable.mixin(base))));
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((function () {
function anonymous () {}
return anonymous;
}()));
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;
var ImperativeBase = (function (ParentClass) {
function ImperativeBase(options) {
if ( options === void 0 ) options = {};
ParentClass.call(this, 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();
});
}
if ( ParentClass ) ImperativeBase.__proto__ = ParentClass;
ImperativeBase.prototype = Object.create( ParentClass && ParentClass.prototype );
ImperativeBase.prototype.constructor = ImperativeBase;
var prototypeAccessors = { mountPromise: {},element: {},properties: {} };
/**
* 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
*/
ImperativeBase.prototype._makeElement = function _makeElement () {
throw new Error('Subclasses need to override ImperativeBase#_makeElement.')
};
/**
* @readonly
*/
prototypeAccessors.mountPromise.get = function () {
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
};
ImperativeBase.prototype._waitForMountThenResolveMountPromise = function _waitForMountThenResolveMountPromise () {
// extended in Node or Scene to await for anything that mount
// depends on.
};
/**
* @readonly
*/
prototypeAccessors.element.get = function () {
return this._elementManager.element
};
/**
* @override
*/
ImperativeBase.prototype.add = function 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().
`)
}
ParentClass.prototype.add.call(this, 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
};
ImperativeBase.prototype.remove = function remove (childNode, /*private use*/leaveInDom) {
if (!(childNode instanceof Node$1)) { return }
ParentClass.prototype.remove.call(this, 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); }
};
ImperativeBase.prototype._resetMountPromise = function _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();
}
};
ImperativeBase.prototype._needsToBeRendered = function _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().
ImperativeBase.prototype._getAncestorToBeRendered = function _getAncestorToBeRendered () {
let parent = this._parent;
while (parent) {
if (parent._willBeRendered) { return parent }
parent = parent._parent;
}
return false
};
ImperativeBase.prototype._render = function _render (timestamp) {
ParentClass.prototype._render.call(this);
// 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'],
* }
*/
prototypeAccessors.properties.set = function (properties) {
if ( properties === void 0 ) properties = {};
ParentClass.prototype.properties = properties;
if (properties.classes)
{ this._elementManager.setClasses(...properties.classes); }
};
Object.defineProperties( ImperativeBase.prototype, prototypeAccessors );
return ImperativeBase;
}(ParentClass));
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 => {
var _Node = (function (superclass) {
function _Node (options) {
if ( options === void 0 ) options = {};
superclass.call(this, 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();
}
if ( superclass ) _Node.__proto__ = superclass;
_Node.prototype = Object.create( superclass && superclass.prototype );
_Node.prototype.constructor = _Node;
var prototypeAccessors = { scene: {} };
/**
* @private
*/
_Node.define = function define (name) {
customElements.define(name || 'i-node', Node$1);
};
_Node.prototype._waitForMountThenResolveMountPromise = function _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.
*/
_Node.prototype._getScenePromise = function _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
*/
prototypeAccessors.scene.get = function () {
// 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.
*/
_Node.prototype._giveSceneRefToChildren = function _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();
}
};
_Node.prototype._resetSceneRef = function _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.defineProperties( _Node.prototype, prototypeAccessors );
return _Node;
}(ImperativeBase.mixin(Transformable.mixin(base))));
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((function () {
function anonymous () {}
return anonymous;
}()));
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...
*/
var ElementManager = function ElementManager(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
*/
ElementManager.prototype.setClasses = function 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} valueThe value the CSS property wil have.
*/
ElementManager.prototype.applyStyle = function applyStyle (property, value) {
this.element.style[property] = value;
};
ElementManager.prototype.add = function add (childElementManager) {
this.element.appendChild(childElementManager.element);
};
ElementManager.prototype.remove = function 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); }
};
ElementManager.prototype.connectChildElement = function 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);
}
};
ElementManager.prototype.disconnectChildElement = function 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.
*/
ElementManager.prototype.applyTransform = function 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]
*/
ElementManager.prototype.applySize = function 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.
};
ElementManager.prototype.applyOpacity = function applyOpacity (opacity) {
this.applyStyle('opacity', opacity);
};
ElementManager.prototype.applyImperativeNodeProperties = function 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);
};
ElementManager.prototype.shouldRender = function shouldRender () {
const task = Motor$1.addRenderTask(() => {
this.applyStyle('display', 'block');
Motor$1.removeRenderTask(task);
});
};
ElementManager.prototype.shouldNotRender = function 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
});
var PushPaneLayout = (function (Node) {
function PushPaneLayout(...args) {
console.log(' -- PushPaneLayout created');
Node.call(this, ...args);
}
if ( Node ) PushPaneLayout.__proto__ = Node;
PushPaneLayout.prototype = Object.create( Node && Node.prototype );
PushPaneLayout.prototype.constructor = PushPaneLayout;
return PushPaneLayout;
}(Node$1));
var HTMLPushPaneLayout = (function (HTMLNode$$1) {
function HTMLPushPaneLayout () {
HTMLNode$$1.apply(this, arguments);
}
if ( HTMLNode$$1 ) HTMLPushPaneLayout.__proto__ = HTMLNode$$1;
HTMLPushPaneLayout.prototype = Object.create( HTMLNode$$1 && HTMLNode$$1.prototype );
HTMLPushPaneLayout.prototype.constructor = HTMLPushPaneLayout;
HTMLPushPaneLayout.define = function define (name) {
customElements.define(name || 'i-push-pane-layout', HTMLPushPaneLayout);
};
// @override
HTMLPushPaneLayout.prototype._makeImperativeCounterpart = function _makeImperativeCounterpart () {
return new PushPaneLayout({}, this)
};
return HTMLPushPaneLayout;
}(HTMLNode));
/*!
Copyright (C) 2014-2016 by Andrea Giammarchi - @WebReflection
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// global window Object
// optional polyfill info
// 'auto' used by default, everything is feature detected
// 'force' use the polyfill even if not fully needed
function installCustomElements(window, polyfill) {'use strict';
// DO NOT USE THIS FILE DIRECTLY, IT WON'T WORK
// THIS IS A PROJECT BASED ON A BUILD SYSTEM
// THIS FILE IS JUST WRAPPED UP RESULTING IN
// build/document-register-element.node.js
var
document = window.document,
Object = window.Object;
var htmlClass = (function (info) {
// (C) Andrea Giammarchi - @WebReflection - MIT Style
var
catchClass = /^[A-Z]+[a-z]/,
filterBy = function (re) {
var arr = [], tag;
for (tag in register) {
if (re.test(tag)) { arr.push(tag); }
}
return arr;
},
add = function (Class, tag) {
tag = tag.toLowerCase();
if (!(tag in register)) {
register[Class] = (register[Class] || []).concat(tag);
register[tag] = (register[tag.toUpperCase()] = Class);
}
},
register = (Object.create || Object)(null),
htmlClass = {},
i, section, tags, Class;
for (section in info) {
for (Class in info[section]) {
tags = info[section][Class];
register[Class] = tags;
for (i = 0; i < tags.length; i++) {
register[tags[i].toLowerCase()] =
register[tags[i].toUpperCase()] = Class;
}
}
}
htmlClass.get = function get(tagOrClass) {
return typeof tagOrClass === 'string' ?
(register[tagOrClass] || (catchClass.test(tagOrClass) ? [] : '')) :
filterBy(tagOrClass);
};
htmlClass.set = function set(tag, Class) {
return (catchClass.test(tag) ?
add(tag, Class) :
add(Class, tag)
), htmlClass;
};
return htmlClass;
}({
"collections": {
"HTMLAllCollection": [
"all"
],
"HTMLCollection": [
"forms"
],
"HTMLFormControlsCollection": [
"elements"
],
"HTMLOptionsCollection": [
"options"
]
},
"elements": {
"Element": [
"element"
],
"HTMLAnchorElement": [
"a"
],
"HTMLAppletElement": [
"applet"
],
"HTMLAreaElement": [
"area"
],
"HTMLAttachmentElement": [
"attachment"
],
"HTMLAudioElement": [
"audio"
],
"HTMLBRElement": [
"br"
],
"HTMLBaseElement": [
"base"
],
"HTMLBodyElement": [
"body"
],
"HTMLButtonElement": [
"button"
],
"HTMLCanvasElement": [
"canvas"
],
"HTMLContentElement": [
"content"
],
"HTMLDListElement": [
"dl"
],
"HTMLDataElement": [
"data"
],
"HTMLDataListElement": [
"datalist"
],
"HTMLDetailsElement": [
"details"
],
"HTMLDialogElement": [
"dialog"
],
"HTMLDirectoryElement": [
"dir"
],
"HTMLDivElement": [
"div"
],
"HTMLDocument": [
"document"
],
"HTMLElement": [
"element",
"abbr",
"address",
"article",
"aside",
"b",
"bdi",
"bdo",
"cite",
"code",
"command",
"dd",
"dfn",
"dt",
"em",
"figcaption",
"figure",
"footer",
"header",
"i",
"kbd",
"mark",
"nav",
"noscript",
"rp",
"rt",
"ruby",
"s",
"samp",
"section",
"small",
"strong",
"sub",
"summary",
"sup",
"u",
"var",
"wbr"
],
"HTMLEmbedElement": [
"embed"
],
"HTMLFieldSetElement": [
"fieldset"
],
"HTMLFontElement": [
"font"
],
"HTMLFormElement": [
"form"
],
"HTMLFrameElement": [
"frame"
],
"HTMLFrameSetElement": [
"frameset"
],
"HTMLHRElement": [
"hr"
],
"HTMLHeadElement": [
"head"
],
"HTMLHeadingElement": [
"h1",
"h2",
"h3",
"h4",
"h5",
"h6"
],
"HTMLHtmlElement": [
"html"
],
"HTMLIFrameElement": [
"iframe"
],
"HTMLImageElement": [
"img"
],
"HTMLInputElement": [
"input"
],
"HTMLKeygenElement": [
"keygen"
],
"HTMLLIElement": [
"li"
],
"HTMLLabelElement": [
"label"
],
"HTMLLegendElement": [
"legend"
],
"HTMLLinkElement": [
"link"
],
"HTMLMapElement": [
"map"
],
"HTMLMarqueeElement": [
"marquee"
],
"HTMLMediaElement": [
"media"
],
"HTMLMenuElement": [
"menu"
],
"HTMLMenuItemElement": [
"menuitem"
],
"HTMLMetaElement": [
"meta"
],
"HTMLMeterElement": [
"meter"
],
"HTMLModElement": [
"del",
"ins"
],
"HTMLOListElement": [
"ol"
],
"HTMLObjectElement": [
"object"
],
"HTMLOptGroupElement": [
"optgroup"
],
"HTMLOptionElement": [
"option"
],
"HTMLOutputElement": [
"output"
],
"HTMLParagraphElement": [
"p"
],
"HTMLParamElement": [
"param"
],
"HTMLPictureElement": [
"picture"
],
"HTMLPreElement": [
"pre"
],
"HTMLProgressElement": [
"progress"
],
"HTMLQuoteElement": [
"blockquote",
"q",
"quote"
],
"HTMLScriptElement": [
"script"
],
"HTMLSelectElement": [
"select"
],
"HTMLShadowElement": [
"shadow"
],
"HTMLSlotElement": [
"slot"
],
"HTMLSourceElement": [
"source"
],
"HTMLSpanElement": [
"span"
],
"HTMLStyleElement": [
"style"
],
"HTMLTableCaptionElement": [
"caption"
],
"HTMLTableCellElement": [
"td",
"th"
],
"HTMLTableColElement": [
"col",
"colgroup"
],
"HTMLTableElement": [
"table"
],
"HTMLTableRowElement": [
"tr"
],
"HTMLTableSectionElement": [
"thead",
"tbody",
"tfoot"
],
"HTMLTemplateElement": [
"template"
],
"HTMLTextAreaElement": [
"textarea"
],
"HTMLTimeElement": [
"time"
],
"HTMLTitleElement": [
"title"
],
"HTMLTrackElement": [
"track"
],
"HTMLUListElement": [
"ul"
],
"HTMLUnknownElement": [
"unknown",
"vhgroupv",
"vkeygen"
],
"HTMLVideoElement": [
"video"
]
},
"nodes": {
"Attr": [
"node"
],
"Audio": [
"audio"
],
"CDATASection": [
"node"
],
"CharacterData": [
"node"
],
"Comment": [
"#comment"
],
"Document": [
"#document"
],
"DocumentFragment": [
"#document-fragment"
],
"DocumentType": [
"node"
],
"HTMLDocument": [
"#document"
],
"Image": [
"img"
],
"Option": [
"option"
],
"ProcessingInstruction": [
"node"
],
"ShadowRoot": [
"#shadow-root"
],
"Text": [
"#text"
],
"XMLDocument": [
"xml"
]
}
}));
// passed at runtime, configurable via nodejs module
if (typeof polyfill !== 'object') { polyfill = {type: polyfill || 'auto'}; }
var
// V0 polyfill entry
REGISTER_ELEMENT = 'registerElement',
// IE < 11 only + old WebKit for attributes + feature detection
EXPANDO_UID = '__' + REGISTER_ELEMENT + (window.Math.random() * 10e4 >> 0),
// shortcuts and costants
ADD_EVENT_LISTENER = 'addEventListener',
ATTACHED = 'attached',
CALLBACK = 'Callback',
DETACHED = 'detached',
EXTENDS = 'extends',
ATTRIBUTE_CHANGED_CALLBACK = 'attributeChanged' + CALLBACK,
ATTACHED_CALLBACK = ATTACHED + CALLBACK,
CONNECTED_CALLBACK = 'connected' + CALLBACK,
DISCONNECTED_CALLBACK = 'disconnected' + CALLBACK,
CREATED_CALLBACK = 'created' + CALLBACK,
DETACHED_CALLBACK = DETACHED + CALLBACK,
ADDITION = 'ADDITION',
MODIFICATION = 'MODIFICATION',
REMOVAL = 'REMOVAL',
DOM_ATTR_MODIFIED = 'DOMAttrModified',
DOM_CONTENT_LOADED = 'DOMContentLoaded',
DOM_SUBTREE_MODIFIED = 'DOMSubtreeModified',
PREFIX_TAG = '<',
PREFIX_IS = '=',
// valid and invalid node names
validName = /^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/,
invalidNames = [
'ANNOTATION-XML',
'COLOR-PROFILE',
'FONT-FACE',
'FONT-FACE-SRC',
'FONT-FACE-URI',
'FONT-FACE-FORMAT',
'FONT-FACE-NAME',
'MISSING-GLYPH'
],
// registered types and their prototypes
types = [],
protos = [],
// to query subnodes
query = '',
// html shortcut used to feature detect
documentElement = document.documentElement,
// ES5 inline helpers || basic patches
indexOf = types.indexOf || function (v) {
for(var i = this.length; i-- && this[i] !== v;){}
return i;
},
// other helpers / shortcuts
OP = Object.prototype,
hOP = OP.hasOwnProperty,
iPO = OP.isPrototypeOf,
defineProperty = Object.defineProperty,
empty = [],
gOPD = Object.getOwnPropertyDescriptor,
gOPN = Object.getOwnPropertyNames,
gPO = Object.getPrototypeOf,
sPO = Object.setPrototypeOf,
// jshint proto: true
hasProto = !!Object.__proto__,
// V1 helpers
fixGetClass = false,
DRECEV1 = '__dreCEv1',
customElements = window.customElements,
usableCustomElements = !/^force/.test(polyfill.type) && !!(
customElements &&
customElements.define &&
customElements.get &&
customElements.whenDefined
),
Dict = Object.create || Object,
Map = window.Map || function Map() {
var K = [], V = [], i;
return {
get: function (k) {
return V[indexOf.call(K, k)];
},
set: function (k, v) {
i = indexOf.call(K, k);
if (i < 0) { V[K.push(k) - 1] = v; }
else { V[i] = v; }
}
};
},
Promise = window.Promise || function (fn) {
var
notify = [],
done = false,
p = {
'catch': function () {
return p;
},
'then': function (cb) {
notify.push(cb);
if (done) { setTimeout(resolve, 1); }
return p;
}
};
function resolve(value) {
done = true;
while (notify.length) { notify.shift()(value); }
}
fn(resolve);
return p;
},
justCreated = false,
constructors = Dict(null),
waitingList = Dict(null),
nodeNames = new Map(),
secondArgument = function (is) {
return is.toLowerCase();
},
// used to create unique instances
create = Object.create || function Bridge(proto) {
// silly broken polyfill probably ever used but short enough to work
return proto ? ((Bridge.prototype = proto), new Bridge()) : this;
},
// will set the prototype if possible
// or copy over all properties
setPrototype = sPO || (
hasProto ?
function (o, p) {
o.__proto__ = p;
return o;
} : (
(gOPN && gOPD) ?
(function(){
function setProperties(o, p) {
for (var
key,
names = gOPN(p),
i = 0, length = names.length;
i < length; i++
) {
key = names[i];
if (!hOP.call(o, key)) {
defineProperty(o, key, gOPD(p, key));
}
}
}
return function (o, p) {
do {
setProperties(o, p);
} while ((p = gPO(p)) && !iPO.call(p, o));
return o;
};
}()) :
function (o, p) {
for (var key in p) {
o[key] = p[key];
}
return o;
}
)),
// DOM shortcuts and helpers, if any
MutationObserver = window.MutationObserver ||
window.WebKitMutationObserver,
HTMLElementPrototype = (
window.HTMLElement ||
window.Element ||
window.Node
).prototype,
IE8 = !iPO.call(HTMLElementPrototype, documentElement),
safeProperty = IE8 ? function (o, k, d) {
o[k] = d.value;
return o;
} : defineProperty,
isValidNode = IE8 ?
function (node) {
return node.nodeType === 1;
} :
function (node) {
return iPO.call(HTMLElementPrototype, node);
},
targets = IE8 && [],
attachShadow = HTMLElementPrototype.attachShadow,
cloneNode = HTMLElementPrototype.cloneNode,
dispatchEvent = HTMLElementPrototype.dispatchEvent,
getAttribute = HTMLElementPrototype.getAttribute,
hasAttribute = HTMLElementPrototype.hasAttribute,
removeAttribute = HTMLElementPrototype.removeAttribute,
setAttribute = HTMLElementPrototype.setAttribute,
// replaced later on
createElement = document.createElement,
patchedCreateElement = createElement,
// shared observer for all attributes
attributesObserver = MutationObserver && {
attributes: true,
characterData: true,
attributeOldValue: true
},
// useful to detect only if there's no MutationObserver
DOMAttrModified = MutationObserver || function(e) {
doesNotSupportDOMAttrModified = false;
documentElement.removeEventListener(
DOM_ATTR_MODIFIED,
DOMAttrModified
);
},
// will both be used to make DOMNodeInserted asynchronous
asapQueue,
asapTimer = 0,
// internal flags
V0 = REGISTER_ELEMENT in document &&
!/^force-all/.test(polyfill.type),
setListener = true,
justSetup = false,
doesNotSupportDOMAttrModified = true,
dropDomContentLoaded = true,
// needed for the innerHTML helper
notFromInnerHTMLHelper = true,
// optionally defined later on
onSubtreeModified,
callDOMAttrModified,
getAttributesMirror,
observer,
observe,
// based on setting prototype capability
// will check proto or the expando attribute
// in order to setup the node once
patchIfNotAlready,
patch;
// only if needed
if (!V0) {
if (sPO || hasProto) {
patchIfNotAlready = function (node, proto) {
if (!iPO.call(proto, node)) {
setupNode(node, proto);
}
};
patch = setupNode;
} else {
patchIfNotAlready = function (node, proto) {
if (!node[EXPANDO_UID]) {
node[EXPANDO_UID] = Object(true);
setupNode(node, proto);
}
};
patch = patchIfNotAlready;
}
if (IE8) {
doesNotSupportDOMAttrModified = false;
(function (){
var
descriptor = gOPD(HTMLElementPrototype, ADD_EVENT_LISTENER),
addEventListener = descriptor.value,
patchedRemoveAttribute = function (name) {
var e = new CustomEvent(DOM_ATTR_MODIFIED, {bubbles: true});
e.attrName = name;
e.prevValue = getAttribute.call(this, name);
e.newValue = null;
e[REMOVAL] = e.attrChange = 2;
removeAttribute.call(this, name);
dispatchEvent.call(this, e);
},
patchedSetAttribute = function (name, value) {
var
had = hasAttribute.call(this, name),
old = had && getAttribute.call(this, name),
e = new CustomEvent(DOM_ATTR_MODIFIED, {bubbles: true});
setAttribute.call(this, name, value);
e.attrName = name;
e.prevValue = had ? old : null;
e.newValue = value;
if (had) {
e[MODIFICATION] = e.attrChange = 1;
} else {
e[ADDITION] = e.attrChange = 0;
}
dispatchEvent.call(this, e);
},
onPropertyChange = function (e) {
// jshint eqnull:true
var
node = e.currentTarget,
superSecret = node[EXPANDO_UID],
propertyName = e.propertyName,
event;
if (superSecret.hasOwnProperty(propertyName)) {
superSecret = superSecret[propertyName];
event = new CustomEvent(DOM_ATTR_MODIFIED, {bubbles: true});
event.attrName = superSecret.name;
event.prevValue = superSecret.value || null;
event.newValue = (superSecret.value = node[propertyName] || null);
if (event.prevValue == null) {
event[ADDITION] = event.attrChange = 0;
} else {
event[MODIFICATION] = event.attrChange = 1;
}
dispatchEvent.call(node, event);
}
};
descriptor.value = function (type, handler, capture) {
if (
type === DOM_ATTR_MODIFIED &&
this[ATTRIBUTE_CHANGED_CALLBACK] &&
this.setAttribute !== patchedSetAttribute
) {
this[EXPANDO_UID] = {
className: {
name: 'class',
value: this.className
}
};
this.setAttribute = patchedSetAttribute;
this.removeAttribute = patchedRemoveAttribute;
addEventListener.call(this, 'propertychange', onPropertyChange);
}
addEventListener.call(this, type, handler, capture);
};
defineProperty(HTMLElementPrototype, ADD_EVENT_LISTENER, descriptor);
}());
} else if (!MutationObserver) {
documentElement[ADD_EVENT_LISTENER](DOM_ATTR_MODIFIED, DOMAttrModified);
documentElement.setAttribute(EXPANDO_UID, 1);
documentElement.removeAttribute(EXPANDO_UID);
if (doesNotSupportDOMAttrModified) {
onSubtreeModified = function (e) {
var
node = this,
oldAttributes,
newAttributes,
key;
if (node === e.target) {
oldAttributes = node[EXPANDO_UID];
node[EXPANDO_UID] = (newAttributes = getAttributesMirror(node));
for (key in newAttributes) {
if (!(key in oldAttributes)) {
// attribute was added
return callDOMAttrModified(
0,
node,
key,
oldAttributes[key],
newAttributes[key],
ADDITION
);
} else if (newAttributes[key] !== oldAttributes[key]) {
// attribute was changed
return callDOMAttrModified(
1,
node,
key,
oldAttributes[key],
newAttributes[key],
MODIFICATION
);
}
}
// checking if it has been removed
for (key in oldAttributes) {
if (!(key in newAttributes)) {
// attribute removed
return callDOMAttrModified(
2,
node,
key,
oldAttributes[key],
newAttributes[key],
REMOVAL
);
}
}
}
};
callDOMAttrModified = function (
attrChange,
currentTarget,
attrName,
prevValue,
newValue,
action
) {
var e = {
attrChange: attrChange,
currentTarget: currentTarget,
attrName: attrName,
prevValue: prevValue,
newValue: newValue
};
e[action] = attrChange;
onDOMAttrModified(e);
};
getAttributesMirror = function (node) {
for (var
attr, name,
result = {},
attributes = node.attributes,
i = 0, length = attributes.length;
i < length; i++
) {
attr = attributes[i];
name = attr.name;
if (name !== 'setAttribute') {
result[name] = attr.value;
}
}
return result;
};
}
}
// set as enumerable, writable and configurable
document[REGISTER_ELEMENT] = function registerElement(type, options) {
upperType = type.toUpperCase();
if (setListener) {
// only first time document.registerElement is used
// we need to set this listener
// setting it by default might slow down for no reason
setListener = false;
if (MutationObserver) {
observer = (function(attached, detached){
function checkEmAll(list, callback) {
for (var i = 0, length = list.length; i < length; callback(list[i++])){}
}
return new MutationObserver(function (records) {
for (var
current, node, newValue,
i = 0, length = records.length; i < length; i++
) {
current = records[i];
if (current.type === 'childList') {
checkEmAll(current.addedNodes, attached);
checkEmAll(current.removedNodes, detached);
} else {
node = current.target;
if (notFromInnerHTMLHelper &&
node[ATTRIBUTE_CHANGED_CALLBACK] &&
current.attributeName !== 'style') {
newValue = getAttribute.call(node, current.attributeName);
if (newValue !== current.oldValue) {
node[ATTRIBUTE_CHANGED_CALLBACK](
current.attributeName,
current.oldValue,
newValue
);
}
}
}
}
});
}(executeAction(ATTACHED), executeAction(DETACHED)));
observe = function (node) {
observer.observe(
node,
{
childList: true,
subtree: true
}
);
return node;
};
observe(document);
if (attachShadow) {
HTMLElementPrototype.attachShadow = function () {
return observe(attachShadow.apply(this, arguments));
};
}
} else {
asapQueue = [];
document[ADD_EVENT_LISTENER]('DOMNodeInserted', onDOMNode(ATTACHED));
document[ADD_EVENT_LISTENER]('DOMNodeRemoved', onDOMNode(DETACHED));
}
document[ADD_EVENT_LISTENER](DOM_CONTENT_LOADED, onReadyStateChange);
document[ADD_EVENT_LISTENER]('readystatechange', onReadyStateChange);
HTMLElementPrototype.cloneNode = function (deep) {
var
node = cloneNode.call(this, !!deep),
i = getTypeIndex(node);
if (-1 < i) { patch(node, protos[i]); }
if (deep && query.length) { loopAndSetup(node.querySelectorAll(query)); }
return node;
};
}
if (justSetup) { return (justSetup = false); }
if (-2 < (
indexOf.call(types, PREFIX_IS + upperType) +
indexOf.call(types, PREFIX_TAG + upperType)
)) {
throwTypeError(type);
}
if (!validName.test(upperType) || -1 < indexOf.call(invalidNames, upperType)) {
throw new Error('The type ' + type + ' is invalid');
}
var
constructor = function () {
return extending ?
document.createElement(nodeName, upperType) :
document.createElement(nodeName);
},
opt = options || OP,
extending = hOP.call(opt, EXTENDS),
nodeName = extending ? options[EXTENDS].toUpperCase() : upperType,
upperType,
i;
if (extending && -1 < (
indexOf.call(types, PREFIX_TAG + nodeName)
)) {
throwTypeError(nodeName);
}
i = types.push((extending ? PREFIX_IS : PREFIX_TAG) + upperType) - 1;
query = query.concat(
query.length ? ',' : '',
extending ? nodeName + '[is="' + type.toLowerCase() + '"]' : nodeName
);
constructor.prototype = (
protos[i] = hOP.call(opt, 'prototype') ?
opt.prototype :
create(HTMLElementPrototype)
);
if (query.length) { loopAndVerify(
document.querySelectorAll(query),
ATTACHED
); }
return constructor;
};
document.createElement = (patchedCreateElement = function (localName, typeExtension) {
var
is = getIs(typeExtension),
node = is ?
createElement.call(document, localName, secondArgument(is)) :
createElement.call(document, localName),
name = '' + localName,
i = indexOf.call(
types,
(is ? PREFIX_IS : PREFIX_TAG) +
(is || name).toUpperCase()
),
setup = -1 < i;
if (is) {
node.setAttribute('is', is = is.toLowerCase());
if (setup) {
setup = isInQSA(name.toUpperCase(), is);
}
}
notFromInnerHTMLHelper = !document.createElement.innerHTMLHelper;
if (setup) { patch(node, protos[i]); }
return node;
});
}
function ASAP() {
var queue = asapQueue.splice(0, asapQueue.length);
asapTimer = 0;
while (queue.length) {
queue.shift().call(
null, queue.shift()
);
}
}
function loopAndVerify(list, action) {
for (var i = 0, length = list.length; i < length; i++) {
verifyAndSetupAndAction(list[i], action);
}
}
function loopAndSetup(list) {
for (var i = 0, length = list.length, node; i < length; i++) {
node = list[i];
patch(node, protos[getTypeIndex(node)]);
}
}
function executeAction(action) {
return function (node) {
if (isValidNode(node)) {
verifyAndSetupAndAction(node, action);
if (query.length) { loopAndVerify(
node.querySelectorAll(query),
action
); }
}
};
}
function getTypeIndex(target) {
var
is = getAttribute.call(target, 'is'),
nodeName = target.nodeName.toUpperCase(),
i = indexOf.call(
types,
is ?
PREFIX_IS + is.toUpperCase() :
PREFIX_TAG + nodeName
);
return is && -1 < i && !isInQSA(nodeName, is) ? -1 : i;
}
function isInQSA(name, type) {
return -1 < query.indexOf(name + '[is="' + type + '"]');
}
function onDOMAttrModified(e) {
var
node = e.currentTarget,
attrChange = e.attrChange,
attrName = e.attrName,
target = e.target,
addition = e[ADDITION] || 2,
removal = e[REMOVAL] || 3;
if (notFromInnerHTMLHelper &&
(!target || target === node) &&
node[ATTRIBUTE_CHANGED_CALLBACK] &&
attrName !== 'style' && (
e.prevValue !== e.newValue ||
// IE9, IE10, and Opera 12 gotcha
e.newValue === '' && (
attrChange === addition ||
attrChange === removal
)
)) {
node[ATTRIBUTE_CHANGED_CALLBACK](
attrName,
attrChange === addition ? null : e.prevValue,
attrChange === removal ? null : e.newValue
);
}
}
function onDOMNode(action) {
var executor = executeAction(action);
return function (e) {
asapQueue.push(executor, e.target);
if (asapTimer) { clearTimeout(asapTimer); }
asapTimer = setTimeout(ASAP, 1);
};
}
function onReadyStateChange(e) {
if (dropDomContentLoaded) {
dropDomContentLoaded = false;
e.currentTarget.removeEventListener(DOM_CONTENT_LOADED, onReadyStateChange);
}
if (query.length) { loopAndVerify(
(e.target || document).querySelectorAll(query),
e.detail === DETACHED ? DETACHED : ATTACHED
); }
if (IE8) { purge(); }
}
function patchedSetAttribute(name, value) {
// jshint validthis:true
var self = this;
setAttribute.call(self, name, value);
onSubtreeModified.call(self, {target: self});
}
function setupNode(node, proto) {
setPrototype(node, proto);
if (observer) {
observer.observe(node, attributesObserver);
} else {
if (doesNotSupportDOMAttrModified) {
node.setAttribute = patchedSetAttribute;
node[EXPANDO_UID] = getAttributesMirror(node);
node[ADD_EVENT_LISTENER](DOM_SUBTREE_MODIFIED, onSubtreeModified);
}
node[ADD_EVENT_LISTENER](DOM_ATTR_MODIFIED, onDOMAttrModified);
}
if (node[CREATED_CALLBACK] && notFromInnerHTMLHelper) {
node.created = true;
node[CREATED_CALLBACK]();
node.created = false;
}
}
function purge() {
for (var
node,
i = 0,
length = targets.length;
i < length; i++
) {
node = targets[i];
if (!documentElement.contains(node)) {
length--;
targets.splice(i--, 1);
verifyAndSetupAndAction(node, DETACHED);
}
}
}
function throwTypeError(type) {
throw new Error('A ' + type + ' type is already registered');
}
function verifyAndSetupAndAction(node, action) {
var
fn,
i = getTypeIndex(node),
counterAction;
if (-1 < i) {
patchIfNotAlready(node, protos[i]);
i = 0;
if (action === ATTACHED && !node[ATTACHED]) {
node[DETACHED] = false;
node[ATTACHED] = true;
counterAction = 'connected';
i = 1;
if (IE8 && indexOf.call(targets, node) < 0) {
targets.push(node);
}
} else if (action === DETACHED && !node[DETACHED]) {
node[ATTACHED] = false;
node[DETACHED] = true;
counterAction = 'disconnected';
i = 1;
}
if (i && (fn = (
node[action + CALLBACK] ||
node[counterAction + CALLBACK]
))) { fn.call(node); }
}
}
// V1 in da House!
function CustomElementRegistry() {}
CustomElementRegistry.prototype = {
constructor: CustomElementRegistry,
// a workaround for the stubborn WebKit
define: usableCustomElements ?
function (name, Class, options) {
if (options) {
CERDefine(name, Class, options);
} else {
var NAME = name.toUpperCase();
constructors[NAME] = {
constructor: Class,
create: [NAME]
};
nodeNames.set(Class, NAME);
customElements.define(name, Class);
}
} :
CERDefine,
get: usableCustomElements ?
function (name) {
return customElements.get(name) || get(name);
} :
get,
whenDefined: usableCustomElements ?
function (name) {
return Promise.race([
customElements.whenDefined(name),
whenDefined(name)
]);
} :
whenDefined
};
function CERDefine(name, Class, options) {
var
is = options && options[EXTENDS] || '',
CProto = Class.prototype,
proto = create(CProto),
attributes = Class.observedAttributes || empty,
definition = {prototype: proto};
// TODO: is this needed at all since it's inherited?
// defineProperty(proto, 'constructor', {value: Class});
safeProperty(proto, CREATED_CALLBACK, {
value: function () {
if (justCreated) { justCreated = false; }
else if (!this[DRECEV1]) {
this[DRECEV1] = true;
new Class(this);
if (CProto[CREATED_CALLBACK])
{ CProto[CREATED_CALLBACK].call(this); }
var info = constructors[nodeNames.get(Class)];
if (!usableCustomElements || info.create.length > 1) {
notifyAttributes(this);
}
}
}
});
safeProperty(proto, ATTRIBUTE_CHANGED_CALLBACK, {
value: function (name) {
if (-1 < indexOf.call(attributes, name))
{ CProto[ATTRIBUTE_CHANGED_CALLBACK].apply(this, arguments); }
}
});
if (CProto[CONNECTED_CALLBACK]) {
safeProperty(proto, ATTACHED_CALLBACK, {
value: CProto[CONNECTED_CALLBACK]
});
}
if (CProto[DISCONNECTED_CALLBACK]) {
safeProperty(proto, DETACHED_CALLBACK, {
value: CProto[DISCONNECTED_CALLBACK]
});
}
if (is) { definition[EXTENDS] = is; }
name = name.toUpperCase();
constructors[name] = {
constructor: Class,
create: is ? [is, secondArgument(name)] : [name]
};
nodeNames.set(Class, name);
document[REGISTER_ELEMENT](name.toLowerCase(), definition);
whenDefined(name);
waitingList[name].r();
}
function get(name) {
var info = constructors[name.toUpperCase()];
return info && info.constructor;
}
function getIs(options) {
return typeof options === 'string' ?
options : (options && options.is || '');
}
function notifyAttributes(self) {
var
callback = self[ATTRIBUTE_CHANGED_CALLBACK],
attributes = callback ? self.attributes : empty,
i = attributes.length,
attribute;
while (i--) {
attribute = attributes[i]; // || attributes.item(i);
callback.call(
self,
attribute.name || attribute.nodeName,
null,
attribute.value || attribute.nodeValue
);
}
}
function whenDefined(name) {
name = name.toUpperCase();
if (!(name in waitingList)) {
waitingList[name] = {};
waitingList[name].p = new Promise(function (resolve) {
waitingList[name].r = resolve;
});
}
return waitingList[name].p;
}
function polyfillV1() {
if (customElements) { delete window.customElements; }
defineProperty(window, 'customElements', {
configurable: true,
value: new CustomElementRegistry()
});
defineProperty(window, 'CustomElementRegistry', {
configurable: true,
value: CustomElementRegistry
});
for (var
patchClass = function (name) {
var Class = window[name];
if (Class) {
window[name] = function CustomElementsV1(self) {
var info, isNative;
if (!self) { self = this; }
if (!self[DRECEV1]) {
justCreated = true;
info = constructors[nodeNames.get(self.constructor)];
isNative = usableCustomElements && info.create.length === 1;
self = isNative ?
Reflect.construct(Class, empty, info.constructor) :
document.createElement.apply(document, info.create);
self[DRECEV1] = true;
justCreated = false;
if (!isNative) { notifyAttributes(self); }
}
return self;
};
window[name].prototype = Class.prototype;
try {
Class.prototype.constructor = window[name];
} catch(WebKit) {
fixGetClass = true;
defineProperty(Class, DRECEV1, {value: window[name]});
}
}
},
Classes = htmlClass.get(/^HTML[A-Z]*[a-z]/),
i = Classes.length;
i--;
patchClass(Classes[i])
) {}
(document.createElement = function (name, options) {
var is = getIs(options);
return is ?
patchedCreateElement.call(this, name, secondArgument(is)) :
patchedCreateElement.call(this, name);
});
if (!V0) {
justSetup = true;
document[REGISTER_ELEMENT]('');
}
}
// if customElements is not there at all
if (!customElements || /^force/.test(polyfill.type)) { polyfillV1(); }
else if(!polyfill.noBuiltIn) {
// if available test extends work as expected
try {
(function (DRE, options, name) {
options[EXTENDS] = 'a';
DRE.prototype = create(HTMLAnchorElement.prototype);
DRE.prototype.constructor = DRE;
window.customElements.define(name, DRE, options);
if (
getAttribute.call(document.createElement('a', {is: name}), 'is') !== name ||
(usableCustomElements && getAttribute.call(new DRE(), 'is') !== name)
) {
throw options;
}
}(
function DRE() {
return Reflect.construct(HTMLAnchorElement, [], DRE);
},
{},
'document-register-element-a'
));
} catch(o_O) {
// or force the polyfill if not
// and keep internal original reference
polyfillV1();
}
}
// FireFox only issue
if(!polyfill.noBuiltIn) {
try {
createElement.call(document, 'a', 'a');
} catch(FireFox) {
secondArgument = function (is) {
return {is: is.toLowerCase()};
};
}
}
}
installCustomElements(commonjsGlobal);
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
*/
var Cube$1 = (function (Node) {
function Cube(size, options) {
// cubes, the same size on all sides
Node.call(this, Object.assign({}, {absoluteSize: [size, size, size]}, options));
//GenericSync.register({
//mouse: MouseSync,
//touch: TouchSync
//});
this.size = size;
this.sides = [];
forLength(6, n => this._createCubeSide(n));
}
if ( Node ) Cube.__proto__ = Node;
Cube.prototype = Object.create( Node && Node.prototype );
Cube.prototype.constructor = Cube;
/**
* 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.
*/
Cube.prototype._createCubeSide = function _createCubeSide (index) {
const rotator = new Node({
align: [0.5, 0.5],
mountPoint: [0.5, 0.5],
});
const side = new Node({
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.
*/
Cube.prototype.setContent = function 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;
};
return Cube;
}(Node$1));
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