first commit
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
!(function() {
|
||||
|
||||
/**
|
||||
* Wraps fastdom in a Promise API
|
||||
* for improved control-flow.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // returning a result
|
||||
* fastdom.measure(() => el.clientWidth)
|
||||
* .then(result => ...);
|
||||
*
|
||||
* // returning promises from tasks
|
||||
* fastdom.measure(() => {
|
||||
* var w = el1.clientWidth;
|
||||
* return fastdom.mutate(() => el2.style.width = w + 'px');
|
||||
* }).then(() => console.log('all done'));
|
||||
*
|
||||
* // clearing pending tasks
|
||||
* var promise = fastdom.measure(...)
|
||||
* fastdom.clear(promise);
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var exports = {
|
||||
initialize: function() {
|
||||
this._tasks = new Map();
|
||||
},
|
||||
|
||||
mutate: function(fn, ctx) {
|
||||
return create(this, 'mutate', fn, ctx);
|
||||
},
|
||||
|
||||
measure: function(fn, ctx) {
|
||||
return create(this, 'measure', fn, ctx);
|
||||
},
|
||||
|
||||
clear: function(promise) {
|
||||
var tasks = this._tasks;
|
||||
var task = tasks.get(promise);
|
||||
this.fastdom.clear(task);
|
||||
tasks.delete(promise);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a fastdom task wrapped in
|
||||
* a 'cancellable' Promise.
|
||||
*
|
||||
* @param {FastDom} fastdom
|
||||
* @param {String} type - 'measure'|'muatate'
|
||||
* @param {Function} fn
|
||||
* @return {Promise}
|
||||
*/
|
||||
function create(promised, type, fn, ctx) {
|
||||
var tasks = promised._tasks;
|
||||
var fastdom = promised.fastdom;
|
||||
var task;
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
task = fastdom[type](function() {
|
||||
tasks.delete(promise);
|
||||
try { resolve(ctx ? fn.call(ctx) : fn()); }
|
||||
catch (e) { reject(e); }
|
||||
}, ctx);
|
||||
});
|
||||
|
||||
tasks.set(promise, task);
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Expose to CJS, AMD or global
|
||||
if ((typeof define)[0] == 'f') define(function() { return exports; });
|
||||
else if ((typeof module)[0] == 'o') module.exports = exports;
|
||||
else window.fastdomPromised = exports;
|
||||
|
||||
})();
|
||||
+962
@@ -0,0 +1,962 @@
|
||||
(function webpackUniversalModuleDefinition(root, factory) {
|
||||
if(typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = factory(require("fastdom"));
|
||||
else if(typeof define === 'function' && define.amd)
|
||||
define(["fastdom"], factory);
|
||||
else if(typeof exports === 'object')
|
||||
exports["fastdom"] = factory(require("fastdom"));
|
||||
else
|
||||
root["fastdom"] = factory(root["fastdom"]);
|
||||
})(this, function(__WEBPACK_EXTERNAL_MODULE_2__) {
|
||||
return /******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId])
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ exports: {},
|
||||
/******/ id: moduleId,
|
||||
/******/ loaded: false
|
||||
/******/ };
|
||||
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.loaded = true;
|
||||
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
|
||||
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "";
|
||||
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(0);
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([
|
||||
/* 0 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var strictdom = __webpack_require__(1);
|
||||
var fastdom = __webpack_require__(2);
|
||||
|
||||
/**
|
||||
* Mini logger
|
||||
*
|
||||
* @return {Function}
|
||||
*/
|
||||
var debug = 0 ? console.log.bind(console, '[fastdom-strict]') : function() {};
|
||||
|
||||
/**
|
||||
* Enabled state
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
var enabled = false;
|
||||
|
||||
window.fastdom = module.exports = fastdom.extend({
|
||||
measure: function(fn, ctx) {
|
||||
debug('measure');
|
||||
var task = !ctx ? fn : fn.bind(ctx);
|
||||
return this.fastdom.measure(function() {
|
||||
if (!enabled) return task();
|
||||
return strictdom.phase('measure', task);
|
||||
}, ctx);
|
||||
},
|
||||
|
||||
mutate: function(fn, ctx) {
|
||||
debug('mutate');
|
||||
var task = !ctx ? fn : fn.bind(ctx);
|
||||
return this.fastdom.mutate(function() {
|
||||
if (!enabled) return task();
|
||||
return strictdom.phase('mutate', task);
|
||||
}, ctx);
|
||||
},
|
||||
|
||||
strict: function(value) {
|
||||
if (value) {
|
||||
enabled = true;
|
||||
strictdom.enable();
|
||||
} else {
|
||||
enabled = false;
|
||||
strictdom.disable();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// turn on strict-mode
|
||||
window.fastdom.strict(true);
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 1 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
var __WEBPACK_AMD_DEFINE_RESULT__;!(function() {
|
||||
'use strict';
|
||||
|
||||
var debug = 0 ? console.log.bind(console, '[strictdom]') : function() {};
|
||||
|
||||
/**
|
||||
* Crude webkit test.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
// var isWebkit = !!window.webkitURL;
|
||||
|
||||
/**
|
||||
* List of properties observed.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var properties = {
|
||||
prototype: {
|
||||
Document: {
|
||||
execCommand: Mutate,
|
||||
elementFromPoint: Measure,
|
||||
elementsFromPoint: Measure,
|
||||
scrollingElement: Measure
|
||||
},
|
||||
|
||||
Node: {
|
||||
appendChild: {
|
||||
type: Mutate,
|
||||
test: function(dom, parent, args) {
|
||||
var attached = isAttached(parent) || isAttached(args[0]);
|
||||
if (attached && dom.not('mutate')) throw error(3, this.name);
|
||||
}
|
||||
},
|
||||
|
||||
insertBefore: {
|
||||
type: Mutate,
|
||||
test: function(dom, parent, args) {
|
||||
var attached = isAttached(parent) || isAttached(args[0]);
|
||||
if (attached && dom.not('mutate')) throw error(3, this.name);
|
||||
}
|
||||
},
|
||||
|
||||
removeChild: {
|
||||
type: Mutate,
|
||||
test: function(dom, parent, args) {
|
||||
var attached = isAttached(parent) || isAttached(args[0]);
|
||||
if (attached && dom.not('mutate')) throw error(3, this.name);
|
||||
}
|
||||
},
|
||||
|
||||
textContent: Mutate
|
||||
},
|
||||
|
||||
Element: {
|
||||
scrollIntoView: Mutate,
|
||||
scrollBy: Mutate,
|
||||
scrollTo: Mutate,
|
||||
getClientRects: Measure,
|
||||
getBoundingClientRect: Measure,
|
||||
clientLeft: Measure,
|
||||
clientWidth: Measure,
|
||||
clientHeight: Measure,
|
||||
scrollLeft: Accessor,
|
||||
scrollTop: Accessor,
|
||||
scrollWidth: Measure,
|
||||
scrollHeight: Measure,
|
||||
innerHTML: Mutate,
|
||||
outerHTML: Mutate,
|
||||
insertAdjacentHTML: Mutate,
|
||||
remove: Mutate,
|
||||
setAttribute: Mutate,
|
||||
removeAttribute: Mutate,
|
||||
className: Mutate,
|
||||
classList: ClassList
|
||||
},
|
||||
|
||||
HTMLElement: {
|
||||
offsetLeft: Measure,
|
||||
offsetTop: Measure,
|
||||
offsetWidth: Measure,
|
||||
offsetHeight: Measure,
|
||||
offsetParent: Measure,
|
||||
innerText: Accessor,
|
||||
outerText: Accessor,
|
||||
focus: Measure,
|
||||
blur: Measure,
|
||||
style: Style,
|
||||
|
||||
// `element.dataset` is hard to wrap.
|
||||
// We could use `Proxy` but it's not
|
||||
// supported in Chrome yet. Not too
|
||||
// concerned as `data-` attributes are
|
||||
// not often associated with render.
|
||||
// dataset: DATASET
|
||||
},
|
||||
|
||||
CharacterData: {
|
||||
remove: Mutate,
|
||||
data: Mutate
|
||||
},
|
||||
|
||||
Range: {
|
||||
getClientRects: Measure,
|
||||
getBoundingClientRect: Measure
|
||||
},
|
||||
|
||||
MouseEvent: {
|
||||
layerX: Measure,
|
||||
layerY: Measure,
|
||||
offsetX: Measure,
|
||||
offsetY: Measure
|
||||
},
|
||||
|
||||
HTMLButtonElement: {
|
||||
reportValidity: Measure
|
||||
},
|
||||
|
||||
HTMLDialogElement: {
|
||||
showModal: Mutate
|
||||
},
|
||||
|
||||
HTMLFieldSetElement: {
|
||||
reportValidity: Measure
|
||||
},
|
||||
|
||||
HTMLImageElement: {
|
||||
width: Accessor,
|
||||
height: Accessor,
|
||||
x: Measure,
|
||||
y: Measure
|
||||
},
|
||||
|
||||
HTMLInputElement: {
|
||||
reportValidity: Measure
|
||||
},
|
||||
|
||||
HTMLKeygenElement: {
|
||||
reportValidity: Measure
|
||||
},
|
||||
|
||||
SVGSVGElement: {
|
||||
currentScale: Accessor
|
||||
}
|
||||
},
|
||||
|
||||
instance: {
|
||||
window: {
|
||||
getComputedStyle: {
|
||||
type: Measure,
|
||||
|
||||
/**
|
||||
* Throws when the Element is in attached
|
||||
* and strictdom is not in the 'measure' phase.
|
||||
*
|
||||
* @param {StrictDom} strictdom
|
||||
* @param {Window} win
|
||||
* @param {Object} args
|
||||
*/
|
||||
test: function(strictdom, win, args) {
|
||||
if (isAttached(args[0]) && strictdom.not('measure')) {
|
||||
throw error(2, 'getComputedStyle');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// innerWidth: {
|
||||
// type: isWebkit ? Value : Measure,
|
||||
//
|
||||
// /**
|
||||
// * Throws when the window is nested (in <iframe>)
|
||||
// * and StrictDom is not in the 'measure' phase.
|
||||
// *
|
||||
// * @param {StrictDom} strictdom
|
||||
// */
|
||||
// test: function(strictdom) {
|
||||
// var inIframe = window !== window.top;
|
||||
// if (inIframe && strictdom.not('measure')) {
|
||||
// throw error(2, '`.innerWidth` (in iframe)');
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
//
|
||||
// innerHeight: {
|
||||
// type: isWebkit ? Value : Measure,
|
||||
//
|
||||
// /**
|
||||
// * Throws when the window is nested (in <iframe>)
|
||||
// * and StrictDom is not in the 'measure' phase.
|
||||
// *
|
||||
// * @param {StrictDom} strictdom
|
||||
// */
|
||||
// test: function(strictdom) {
|
||||
// var inIframe = window !== window.top;
|
||||
// if (inIframe && strictdom.not('measure')) {
|
||||
// throw error(2, '`.innerHeight` (in iframe)');
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
//
|
||||
// scrollX: isWebkit ? Value : Measure,
|
||||
// scrollY: isWebkit ? Value : Measure,
|
||||
scrollBy: Mutate,
|
||||
scrollTo: Mutate,
|
||||
scroll: Mutate,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The master controller for all properties.
|
||||
*
|
||||
* @param {Window} win
|
||||
*/
|
||||
function StrictDom(win) {
|
||||
this.properties = [];
|
||||
this._phase = null;
|
||||
this.win = win;
|
||||
|
||||
this.createPrototypeProperties();
|
||||
this.createInstanceProperties();
|
||||
}
|
||||
|
||||
StrictDom.prototype = {
|
||||
|
||||
/**
|
||||
* Set the current phase.
|
||||
* @param {[type]} value [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
phase: function(type, task) {
|
||||
if (!arguments.length) return this._phase;
|
||||
if (!this.knownPhase(type)) throw error(4, type);
|
||||
|
||||
var previous = this._phase;
|
||||
this._phase = type;
|
||||
|
||||
if (typeof task != 'function') return;
|
||||
|
||||
var result = task();
|
||||
this._phase = previous;
|
||||
return result;
|
||||
},
|
||||
|
||||
knownPhase: function(value) {
|
||||
return !!~['measure', 'mutate', null].indexOf(value);
|
||||
},
|
||||
|
||||
is: function(value) {
|
||||
return this._phase === value;
|
||||
},
|
||||
|
||||
not: function(value) {
|
||||
return !this.is(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable strict mode.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
enable: function() {
|
||||
if (this.enabled) return;
|
||||
debug('enable');
|
||||
var i = this.properties.length;
|
||||
while (i--) this.properties[i].enable();
|
||||
this.enabled = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable strict mode.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
disable: function() {
|
||||
if (!this.enabled) return;
|
||||
debug('disable');
|
||||
var i = this.properties.length;
|
||||
while (i--) this.properties[i].disable();
|
||||
this.enabled = false;
|
||||
this.phase(null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create wrappers for each of
|
||||
* of the prototype properties.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createPrototypeProperties: function() {
|
||||
debug('create prototype properties');
|
||||
var props = properties.prototype;
|
||||
for (var key in props) {
|
||||
for (var name in props[key]) {
|
||||
var object = this.win[key] && this.win[key].prototype;
|
||||
if (!object || !object.hasOwnProperty(name)) continue;
|
||||
this.properties.push(this.create(object, name, props[key][name]));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create wrappers for each of
|
||||
* of the instance properties.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createInstanceProperties: function() {
|
||||
debug('create instance properties');
|
||||
var props = properties.instance;
|
||||
for (var key in props) {
|
||||
for (var name in props[key]) {
|
||||
var object = this.win[key];
|
||||
if (!object || !object.hasOwnProperty(name)) continue;
|
||||
this.properties.push(this.create(object, name, props[key][name]));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a wrapped `Property` that
|
||||
* can be individually enabled/disabled.
|
||||
*
|
||||
* @param {Object} object - the parent object (eg. Node.prototype)
|
||||
* @param {String} name - the property name (eg. 'appendChild')
|
||||
* @param {(constructor|Object)} config - from the above property definition
|
||||
* @return {Property}
|
||||
*/
|
||||
create: function(object, name, config) {
|
||||
debug('create', name);
|
||||
var Constructor = config.type || config;
|
||||
return new Constructor(object, name, config, this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new `Property`.
|
||||
*
|
||||
* A wrapper around a property that observes
|
||||
* usage, throwing errors when used in the
|
||||
* incorrect phase.
|
||||
*
|
||||
* @param {Object} object - the parent object (eg. Node.prototype)
|
||||
* @param {[type]} name - the property name (eg. 'appendChild')
|
||||
* @param {(constructor|Object)} config - from the above definition
|
||||
* @param {StrictDom} strictdom - injected as a dependency
|
||||
*/
|
||||
function Property(object, name, config, strictdom) {
|
||||
debug('Property', name, config);
|
||||
|
||||
this.strictdom = strictdom;
|
||||
this.object = object;
|
||||
this.name = name;
|
||||
|
||||
var descriptor = this.getDescriptor();
|
||||
|
||||
// defaults can be overriden from config
|
||||
if (typeof config == 'object') Object.assign(this, config);
|
||||
|
||||
this.descriptors = {
|
||||
unwrapped: descriptor,
|
||||
wrapped: this.wrap(descriptor)
|
||||
};
|
||||
}
|
||||
|
||||
Property.prototype = {
|
||||
|
||||
/**
|
||||
* Get the property's descriptor.
|
||||
*
|
||||
* @return {Object}
|
||||
* @private
|
||||
*/
|
||||
getDescriptor: function() {
|
||||
debug('get descriptor', this.name);
|
||||
return Object.getOwnPropertyDescriptor(this.object, this.name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable observation by replacing the
|
||||
* current descriptor with the wrapped one.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
enable: function() {
|
||||
debug('enable', this.name);
|
||||
Object.defineProperty(this.object, this.name, this.descriptors.wrapped);
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable observation by replacing the
|
||||
* current descriptor with the original one.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
disable: function() {
|
||||
debug('disable', this.name);
|
||||
Object.defineProperty(this.object, this.name, this.descriptors.unwrapped);
|
||||
},
|
||||
|
||||
// to be overwritten by subclass
|
||||
wrap: function() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* A wrapper for properties that measure
|
||||
* geometry data from the DOM.
|
||||
*
|
||||
* Once a `Measure` property is enabled
|
||||
* it can only be used when StrictDom
|
||||
* is in the 'measure' phase, else it
|
||||
* will throw.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Property
|
||||
*/
|
||||
function Measure() {
|
||||
Property.apply(this, arguments);
|
||||
}
|
||||
|
||||
Measure.prototype = extend(Property, {
|
||||
|
||||
/**
|
||||
* Return a wrapped descriptor.
|
||||
*
|
||||
* @param {Object} descriptor
|
||||
* @return {Object}
|
||||
*/
|
||||
wrap: function(descriptor) {
|
||||
debug('wrap measure', this.name);
|
||||
|
||||
var clone = Object.assign({}, descriptor);
|
||||
var value = descriptor.value;
|
||||
var get = descriptor.get;
|
||||
var self = this;
|
||||
|
||||
if (typeof value == 'function') {
|
||||
clone.value = function() {
|
||||
debug('measure', self.name);
|
||||
self.test(self.strictdom, this, arguments);
|
||||
return value.apply(this, arguments);
|
||||
};
|
||||
} else if (get) {
|
||||
clone.get = function() {
|
||||
debug('measure', self.name);
|
||||
self.test(self.strictdom, this, arguments);
|
||||
return get.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
return clone;
|
||||
},
|
||||
|
||||
/**
|
||||
* Throws an Error if the element is attached
|
||||
* and StrictDOM is not in the 'measure' phase.
|
||||
*
|
||||
* If methods/properties are used without
|
||||
* a context (eg. `getComputedStyle()` instead
|
||||
* of `window.getComputedStyle()`) we infer
|
||||
* a `window` context.
|
||||
*
|
||||
* @param {StrictDom} strictdom
|
||||
* @param {Node} ctx
|
||||
*/
|
||||
test: function(strictdom, ctx) {
|
||||
if (isAttached(ctx || window) && strictdom.not('measure')) {
|
||||
throw error(2, this.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A wrapper for properties that mutate
|
||||
* to the DOM, triggering style/reflow
|
||||
* operations.
|
||||
*
|
||||
* Once a `Mutate` property is enabled
|
||||
* it can only be used when StrictDom
|
||||
* is in the 'measure' phase, else it
|
||||
* will throw.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Property
|
||||
*/
|
||||
function Mutate() {
|
||||
Property.apply(this, arguments);
|
||||
}
|
||||
|
||||
Mutate.prototype = extend(Property, {
|
||||
|
||||
/**
|
||||
* Return a wrapped descriptor.
|
||||
*
|
||||
* @param {Object} descriptor
|
||||
* @return {Object}
|
||||
*/
|
||||
wrap: function(descriptor) {
|
||||
debug('wrap mutate', this.name);
|
||||
|
||||
var clone = Object.assign({}, descriptor);
|
||||
var value = descriptor.value;
|
||||
var self = this;
|
||||
|
||||
if (typeof value == 'function') {
|
||||
clone.value = function() {
|
||||
self.test(self.strictdom, this, arguments);
|
||||
return value.apply(this, arguments);
|
||||
};
|
||||
} else if (descriptor.set) {
|
||||
clone.set = function() {
|
||||
self.test(self.strictdom, this, arguments);
|
||||
return descriptor.set.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
return clone;
|
||||
},
|
||||
|
||||
/**
|
||||
* Throws an Error if the element is attached
|
||||
* and StrictDOM is not in the 'mutate' phase.
|
||||
*
|
||||
* If methods/properties are used without
|
||||
* a context (eg. `getComputedStyle()` instead
|
||||
* of `window.getComputedStyle()`) we infer
|
||||
* a `window` context.
|
||||
*
|
||||
* @param {StrictDom} strictdom
|
||||
* @param {Node} ctx
|
||||
*/
|
||||
test: function(strictdom, ctx) {
|
||||
if (isAttached(ctx || window) && strictdom.not('mutate')) {
|
||||
throw error(3, this.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A wrapper for 'accessor' (get/set) properties.
|
||||
*
|
||||
* An `Accessor` should be used to wrap
|
||||
* properties that can both measure and mutate
|
||||
* the DOM (eg. `element.scrollTop`).
|
||||
*
|
||||
* @constructor
|
||||
* @extends Property
|
||||
*/
|
||||
function Accessor() {
|
||||
Property.apply(this, arguments);
|
||||
}
|
||||
|
||||
Accessor.prototype = extend(Property, {
|
||||
|
||||
/**
|
||||
* Return a wrapped descriptor.
|
||||
*
|
||||
* @param {Object} descriptor
|
||||
* @return {Object}
|
||||
*/
|
||||
wrap: function(descriptor) {
|
||||
debug('wrap accessor', this.name);
|
||||
|
||||
var clone = Object.assign({}, descriptor);
|
||||
var get = descriptor.get;
|
||||
var set = descriptor.set;
|
||||
var self = this;
|
||||
|
||||
if (get) {
|
||||
clone.get = function() {
|
||||
self.testRead(self.strictdom, this, arguments);
|
||||
return get.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
if (descriptor.set) {
|
||||
clone.set = function() {
|
||||
self.testWrite(self.strictdom, this, arguments);
|
||||
return set.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
return clone;
|
||||
},
|
||||
|
||||
testRead: Measure.prototype.test,
|
||||
testWrite: Mutate.prototype.test
|
||||
});
|
||||
|
||||
/**
|
||||
* A wrapper for 'value' properties.
|
||||
*
|
||||
* A `Value` should be used to wrap special
|
||||
* values that like `window.innerWidth`, which
|
||||
* in Chrome (not Gecko) are not normal 'getter'
|
||||
* functions, but magical flat getters.
|
||||
*
|
||||
* Value wrappers are a for very special cases.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Property
|
||||
*/
|
||||
function Value() {
|
||||
Property.apply(this, arguments);
|
||||
}
|
||||
|
||||
Value.prototype = extend(Property, {
|
||||
|
||||
/**
|
||||
* Calling `Object.getOwnDescriptor()` can
|
||||
* trigger a reflow as it returns the `value`
|
||||
* of the property. So here we just
|
||||
* return an empty object instead.
|
||||
*
|
||||
* @return {Object}
|
||||
* @private
|
||||
*/
|
||||
getDescriptor: function() {
|
||||
return {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Value wrappers are disabled by simply
|
||||
* deleting them from the instance,
|
||||
* revealing the original descriptor.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
disable: function() {
|
||||
delete this.object[this.name];
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a wrapped descriptor.
|
||||
*
|
||||
* `Value` properties are actually on the
|
||||
* instance of objects. To wrap them we need
|
||||
* to replace them with a getter which
|
||||
* deletes itself on access, call into the v8
|
||||
* interceptor, and then add themselves back.
|
||||
*
|
||||
* This won't be fast, but these are rarely
|
||||
* accessed so it should be fine.
|
||||
*
|
||||
* @param {Object} descriptor
|
||||
* @return {Object}
|
||||
*/
|
||||
wrap: function(descriptor) {
|
||||
debug('wrap value');
|
||||
var name = this.name;
|
||||
var self = this;
|
||||
|
||||
descriptor.get = function() {
|
||||
debug('get value', name);
|
||||
self.test(self.strictdom, this, arguments);
|
||||
self.disable();
|
||||
var result = this[name];
|
||||
self.enable();
|
||||
return result;
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
},
|
||||
|
||||
test: Measure.prototype.test
|
||||
});
|
||||
|
||||
function Style() {
|
||||
Property.apply(this, arguments);
|
||||
}
|
||||
|
||||
Style.prototype = extend(Property, {
|
||||
wrap: function(descriptor) {
|
||||
debug('wrap style');
|
||||
var strictdom = this.strictdom;
|
||||
var clone = Object.assign({}, descriptor);
|
||||
clone.get = function() { return new StrictStyle(this, strictdom); };
|
||||
return clone;
|
||||
}
|
||||
});
|
||||
|
||||
function ClassList() {
|
||||
Property.apply(this, arguments);
|
||||
}
|
||||
|
||||
ClassList.prototype = extend(Property, {
|
||||
wrap: function(descriptor) {
|
||||
debug('wrap style');
|
||||
var strictdom = this.strictdom;
|
||||
var clone = Object.assign({}, descriptor);
|
||||
clone.get = function() { return new StrictClassList(this, strictdom); };
|
||||
return clone;
|
||||
}
|
||||
});
|
||||
|
||||
function StrictStyle(el, strictdom) {
|
||||
this.strictdom = strictdom;
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
StrictStyle.prototype = {
|
||||
_getter: getDescriptor(HTMLElement.prototype, 'style').get,
|
||||
_get: function() {
|
||||
return this._getter.call(this.el);
|
||||
},
|
||||
|
||||
setProperty: function(key, value) {
|
||||
var illegal = isAttached(this.el) && this.strictdom.not('mutate');
|
||||
if (illegal) throw error(1, 'style.' + key);
|
||||
return this._get()[key] = value;
|
||||
},
|
||||
|
||||
removeProperty: function(key) {
|
||||
var illegal = isAttached(this.el) && this.strictdom.not('mutate');
|
||||
if (illegal) throw error(1, 'style.' + key);
|
||||
return this._get().removeProperty(key);
|
||||
}
|
||||
};
|
||||
|
||||
// dynamically construct prototype
|
||||
// from real element.style
|
||||
(function() {
|
||||
var styles = document.createElement('div').style;
|
||||
var proto = {};
|
||||
|
||||
for (var key in styles) {
|
||||
if (styles[key] === '') {
|
||||
Object.defineProperty(StrictStyle.prototype, key, {
|
||||
get: getter(key),
|
||||
set: setter(key)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[
|
||||
'item',
|
||||
'getPropertyValue',
|
||||
'getPropertyCSSValue',
|
||||
'getPropertyPriority'
|
||||
].forEach(function(method) {
|
||||
StrictStyle.prototype[method] = caller(method);
|
||||
});
|
||||
|
||||
function getter(key) {
|
||||
return function() {
|
||||
return this._get()[key];
|
||||
};
|
||||
}
|
||||
|
||||
function setter(key) {
|
||||
return function(value) {
|
||||
var illegal = isAttached(this.el) && this.strictdom.not('mutate');
|
||||
if (illegal) throw error(1, 'style.' + key);
|
||||
return this.setProperty(key, value);
|
||||
};
|
||||
}
|
||||
|
||||
function caller(key) {
|
||||
return function() {
|
||||
var style = this._get();
|
||||
return style[key].apply(style, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
return proto;
|
||||
})();
|
||||
|
||||
function StrictClassList(el, strictdom) {
|
||||
this.strictdom = strictdom;
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
StrictClassList.prototype = {
|
||||
_getter: getDescriptor(Element.prototype, 'classList').get,
|
||||
_get: function() { return this._getter.call(this.el); },
|
||||
|
||||
add: function(className) {
|
||||
var illegal = isAttached(this.el) && this.strictdom.not('mutate');
|
||||
if (illegal) throw error(1, 'class names');
|
||||
this._get().add(className);
|
||||
},
|
||||
|
||||
contains: function(className) {
|
||||
return this._get().contains(className);
|
||||
},
|
||||
|
||||
remove: function(className) {
|
||||
var illegal = isAttached(this.el) && this.strictdom.not('mutate');
|
||||
if (illegal) throw error(1, 'class names');
|
||||
this._get().remove(className);
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
var illegal = isAttached(this.el) && this.strictdom.not('mutate');
|
||||
if (illegal) throw error(1, 'class names');
|
||||
var classList = this._get();
|
||||
return classList.toggle.apply(classList, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Utils
|
||||
*/
|
||||
|
||||
function error(type) {
|
||||
return new Error({
|
||||
1: 'Can only set ' + arguments[1] + ' during \'mutate\' phase',
|
||||
2: 'Can only get ' + arguments[1] + ' during \'measure\' phase',
|
||||
3: 'Can only call `.' + arguments[1] + '()` during \'mutate\' phase',
|
||||
4: 'Invalid phase: ' + arguments[1]
|
||||
}[type]);
|
||||
}
|
||||
|
||||
function getDescriptor(object, prop) {
|
||||
return Object.getOwnPropertyDescriptor(object, prop);
|
||||
}
|
||||
|
||||
function extend(parent, props) {
|
||||
return Object.assign(Object.create(parent.prototype), props);
|
||||
}
|
||||
|
||||
function isAttached(el) {
|
||||
return el === window || document.contains(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
// Only ever allow one `StrictDom` per document
|
||||
var exports = window['strictdom'] = (window['strictdom'] || new StrictDom(window)); // jshint ignore:line
|
||||
|
||||
// CJS & AMD support
|
||||
if (("function")[0] == 'f') !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { return exports; }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
||||
else if ((typeof module)[0] == 'o') module.exports = exports;
|
||||
|
||||
})();
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 2 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = __WEBPACK_EXTERNAL_MODULE_2__;
|
||||
|
||||
/***/ })
|
||||
/******/ ])
|
||||
});
|
||||
;
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
!(function(win) {
|
||||
|
||||
/**
|
||||
* FastDom
|
||||
*
|
||||
* Eliminates layout thrashing
|
||||
* by batching DOM read/write
|
||||
* interactions.
|
||||
*
|
||||
* @author Wilson Page <wilsonpage@me.com>
|
||||
* @author Kornel Lesinski <kornel.lesinski@ft.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Mini logger
|
||||
*
|
||||
* @return {Function}
|
||||
*/
|
||||
var debug = 0 ? console.log.bind(console, '[fastdom]') : function() {};
|
||||
|
||||
/**
|
||||
* Normalized rAF
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
var raf = win.requestAnimationFrame
|
||||
|| win.webkitRequestAnimationFrame
|
||||
|| win.mozRequestAnimationFrame
|
||||
|| win.msRequestAnimationFrame
|
||||
|| function(cb) { return setTimeout(cb, 16); };
|
||||
|
||||
/**
|
||||
* Initialize a `FastDom`.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function FastDom() {
|
||||
var self = this;
|
||||
self.reads = [];
|
||||
self.writes = [];
|
||||
self.raf = raf.bind(win); // test hook
|
||||
debug('initialized', self);
|
||||
}
|
||||
|
||||
FastDom.prototype = {
|
||||
constructor: FastDom,
|
||||
|
||||
/**
|
||||
* We run this inside a try catch
|
||||
* so that if any jobs error, we
|
||||
* are able to recover and continue
|
||||
* to flush the batch until it's empty.
|
||||
*
|
||||
* @param {Array} tasks
|
||||
*/
|
||||
runTasks: function(tasks) {
|
||||
debug('run tasks');
|
||||
var task; while (task = tasks.shift()) task();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a job to the read batch and
|
||||
* schedules a new frame if need be.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Object} ctx the context to be bound to `fn` (optional).
|
||||
* @public
|
||||
*/
|
||||
measure: function(fn, ctx) {
|
||||
debug('measure');
|
||||
var task = !ctx ? fn : fn.bind(ctx);
|
||||
this.reads.push(task);
|
||||
scheduleFlush(this);
|
||||
return task;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a job to the
|
||||
* write batch and schedules
|
||||
* a new frame if need be.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Object} ctx the context to be bound to `fn` (optional).
|
||||
* @public
|
||||
*/
|
||||
mutate: function(fn, ctx) {
|
||||
debug('mutate');
|
||||
var task = !ctx ? fn : fn.bind(ctx);
|
||||
this.writes.push(task);
|
||||
scheduleFlush(this);
|
||||
return task;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears a scheduled 'read' or 'write' task.
|
||||
*
|
||||
* @param {Object} task
|
||||
* @return {Boolean} success
|
||||
* @public
|
||||
*/
|
||||
clear: function(task) {
|
||||
debug('clear', task);
|
||||
return remove(this.reads, task) || remove(this.writes, task);
|
||||
},
|
||||
|
||||
/**
|
||||
* Extend this FastDom with some
|
||||
* custom functionality.
|
||||
*
|
||||
* Because fastdom must *always* be a
|
||||
* singleton, we're actually extending
|
||||
* the fastdom instance. This means tasks
|
||||
* scheduled by an extension still enter
|
||||
* fastdom's global task queue.
|
||||
*
|
||||
* The 'super' instance can be accessed
|
||||
* from `this.fastdom`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var myFastdom = fastdom.extend({
|
||||
* initialize: function() {
|
||||
* // runs on creation
|
||||
* },
|
||||
*
|
||||
* // override a method
|
||||
* measure: function(fn) {
|
||||
* // do extra stuff ...
|
||||
*
|
||||
* // then call the original
|
||||
* return this.fastdom.measure(fn);
|
||||
* },
|
||||
*
|
||||
* ...
|
||||
* });
|
||||
*
|
||||
* @param {Object} props properties to mixin
|
||||
* @return {FastDom}
|
||||
*/
|
||||
extend: function(props) {
|
||||
debug('extend', props);
|
||||
if (typeof props != 'object') throw new Error('expected object');
|
||||
|
||||
var child = Object.create(this);
|
||||
mixin(child, props);
|
||||
child.fastdom = this;
|
||||
|
||||
// run optional creation hook
|
||||
if (child.initialize) child.initialize();
|
||||
|
||||
return child;
|
||||
},
|
||||
|
||||
// override this with a function
|
||||
// to prevent Errors in console
|
||||
// when tasks throw
|
||||
catch: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Schedules a new read/write
|
||||
* batch if one isn't pending.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function scheduleFlush(fastdom) {
|
||||
if (!fastdom.scheduled) {
|
||||
fastdom.scheduled = true;
|
||||
fastdom.raf(flush.bind(null, fastdom));
|
||||
debug('flush scheduled');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs queued `read` and `write` tasks.
|
||||
*
|
||||
* Errors are caught and thrown by default.
|
||||
* If a `.catch` function has been defined
|
||||
* it is called instead.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function flush(fastdom) {
|
||||
debug('flush');
|
||||
|
||||
var writes = fastdom.writes;
|
||||
var reads = fastdom.reads;
|
||||
var error;
|
||||
|
||||
try {
|
||||
debug('flushing reads', reads.length);
|
||||
fastdom.runTasks(reads);
|
||||
debug('flushing writes', writes.length);
|
||||
fastdom.runTasks(writes);
|
||||
} catch (e) { error = e; }
|
||||
|
||||
fastdom.scheduled = false;
|
||||
|
||||
// If the batch errored we may still have tasks queued
|
||||
if (reads.length || writes.length) scheduleFlush(fastdom);
|
||||
|
||||
if (error) {
|
||||
debug('task errored', error.message);
|
||||
if (fastdom.catch) fastdom.catch(error);
|
||||
else throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from an Array.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @param {*} item
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function remove(array, item) {
|
||||
var index = array.indexOf(item);
|
||||
return !!~index && !!array.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin own properties of source
|
||||
* object into the target.
|
||||
*
|
||||
* @param {Object} target
|
||||
* @param {Object} source
|
||||
*/
|
||||
function mixin(target, source) {
|
||||
for (var key in source) {
|
||||
if (source.hasOwnProperty(key)) target[key] = source[key];
|
||||
}
|
||||
}
|
||||
|
||||
// There should never be more than
|
||||
// one instance of `FastDom` in an app
|
||||
var exports = win.fastdom = (win.fastdom || new FastDom()); // jshint ignore:line
|
||||
|
||||
// Expose to CJS & AMD
|
||||
if ((typeof define) == 'function') define(function() { return exports; });
|
||||
else if ((typeof module) == 'object') module.exports = exports;
|
||||
|
||||
})( typeof window !== 'undefined' ? window : this);
|
||||
|
||||
!(function() {
|
||||
|
||||
/**
|
||||
* Wraps fastdom in a Promise API
|
||||
* for improved control-flow.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // returning a result
|
||||
* fastdom.measure(() => el.clientWidth)
|
||||
* .then(result => ...);
|
||||
*
|
||||
* // returning promises from tasks
|
||||
* fastdom.measure(() => {
|
||||
* var w = el1.clientWidth;
|
||||
* return fastdom.mutate(() => el2.style.width = w + 'px');
|
||||
* }).then(() => console.log('all done'));
|
||||
*
|
||||
* // clearing pending tasks
|
||||
* var promise = fastdom.measure(...)
|
||||
* fastdom.clear(promise);
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var exports = {
|
||||
initialize: function() {
|
||||
this._tasks = new Map();
|
||||
},
|
||||
|
||||
mutate: function(fn, ctx) {
|
||||
return create(this, 'mutate', fn, ctx);
|
||||
},
|
||||
|
||||
measure: function(fn, ctx) {
|
||||
return create(this, 'measure', fn, ctx);
|
||||
},
|
||||
|
||||
clear: function(promise) {
|
||||
var tasks = this._tasks;
|
||||
var task = tasks.get(promise);
|
||||
this.fastdom.clear(task);
|
||||
tasks.delete(promise);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a fastdom task wrapped in
|
||||
* a 'cancellable' Promise.
|
||||
*
|
||||
* @param {FastDom} fastdom
|
||||
* @param {String} type - 'measure'|'muatate'
|
||||
* @param {Function} fn
|
||||
* @return {Promise}
|
||||
*/
|
||||
function create(promised, type, fn, ctx) {
|
||||
var tasks = promised._tasks;
|
||||
var fastdom = promised.fastdom;
|
||||
var task;
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
task = fastdom[type](function() {
|
||||
tasks.delete(promise);
|
||||
try { resolve(ctx ? fn.call(ctx) : fn()); }
|
||||
catch (e) { reject(e); }
|
||||
}, ctx);
|
||||
});
|
||||
|
||||
tasks.set(promise, task);
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Expose to CJS, AMD or global
|
||||
if ((typeof define)[0] == 'f') define(function() { return exports; });
|
||||
else if ((typeof module)[0] == 'o') module.exports = exports;
|
||||
else window.fastdomPromised = exports;
|
||||
|
||||
})();
|
||||
|
||||
window.fastdomPromised = fastdom.extend(fastdomPromised);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";!function(t){function e(){var e=this;e.reads=[],e.writes=[],e.raf=a.bind(t),o("initialized",e)}function n(t){t.scheduled||(t.scheduled=!0,t.raf(i.bind(null,t)),o("flush scheduled"))}function i(t){o("flush");var e=t.writes,i=t.reads,r;try{o("flushing reads",i.length),t.runTasks(i),o("flushing writes",e.length),t.runTasks(e)}catch(t){r=t}if(t.scheduled=!1,(i.length||e.length)&&n(t),r){if(o("task errored",r.message),!t.catch)throw r;t.catch(r)}}function r(t,e){var n=t.indexOf(e);return!!~n&&!!t.splice(n,1)}function s(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])}var o=function(){},a=t.requestAnimationFrame||t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.msRequestAnimationFrame||function(t){return setTimeout(t,16)};e.prototype={constructor:e,runTasks:function t(e){var n;for(o("run tasks");n=e.shift();)n()},measure:function t(e,i){o("measure");var r=i?e.bind(i):e;return this.reads.push(r),n(this),r},mutate:function t(e,i){o("mutate");var r=i?e.bind(i):e;return this.writes.push(r),n(this),r},clear:function t(e){return o("clear",e),r(this.reads,e)||r(this.writes,e)},extend:function t(e){if(o("extend",e),"object"!=typeof e)throw new Error("expected object");var n=Object.create(this);return s(n,e),n.fastdom=this,n.initialize&&n.initialize(),n},catch:null};var u=t.fastdom=t.fastdom||new e;"function"==typeof define?define((function(){return u})):"object"==typeof module&&(module.exports=u)}("undefined"!=typeof window?window:void 0),function(){function t(t,e,n,i){var r=t._tasks,s=t.fastdom,o,a=new Promise((function(t,u){o=s[e]((function(){r.delete(a);try{t(i?n.call(i):n())}catch(t){u(t)}}),i)}));return r.set(a,o),a}var e={initialize:function t(){this._tasks=new Map},mutate:function e(n,i){return t(this,"mutate",n,i)},measure:function e(n,i){return t(this,"measure",n,i)},clear:function t(e){var n=this._tasks,i=n.get(e);this.fastdom.clear(i),n.delete(e)}};"f"==(typeof define)[0]?define((function(){return e})):"o"==(typeof module)[0]?module.exports=e:window.fastdomPromised=e}(),window.fastdomPromised=fastdom.extend(fastdomPromised);
|
||||
Reference in New Issue
Block a user