Skip to content
This repository was archived by the owner on Oct 19, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
coverage
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
sudo: false
language: node_js

node_js:
- "6"

script: npm test && cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
diffHTML Inline Transitions
---------------------------

[![Build Status](https://travis-ci.org/tbranyen/diffhtml-inline-transitions.svg?branch=master)](https://travis-ci.org/tbranyen/diffhtml-inline-transitions)
[![Coverage Status](https://coveralls.io/repos/github/tbranyen/diffhtml-inline-transitions/badge.svg?branch=master)](https://coveralls.io/github/tbranyen/diffhtml-inline-transitions?branch=master)

Tiny module to support binding/unbinding transition hooks declaratively.

#### Install
Expand Down
143 changes: 104 additions & 39 deletions dist/inline-transitions.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,127 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.inlineTransitions = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,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; };

exports.default = function (_ref) {
var _this = this;
// Store maps of elements to handlers that are associated to transitions.
var transitionsMap = {
attached: new Map(),
detached: new Map(),
replaced: new Map(),
attributeChanged: new Map(),
textChanged: new Map()
};

var
// Internal global transition state handlers, allows us to bind once and match.
var boundHandlers = [];

/**
* Binds inline transitions to the parent element and triggers for any matching
* nested children.
*/
addTransitionState = _ref.addTransitionState;
/**
* Binds inline transitions to the parent element and triggers for any matching
* nested children.
*/
module.exports = function (_ref) {
var addTransitionState = _ref.addTransitionState;
var removeTransitionState = _ref.removeTransitionState;

addTransitionState('attached', function (element) {
if (element.attributes.attached) {
return element.attached.call(_this, element, element);
// Monitors whenever an element changes an attribute, if the attribute
// is a valid state name, add this element into the related Set.
var attributeChanged = function attributeChanged(element, name, oldVal, newVal) {
var map = transitionsMap[name];

// Abort early if not a valid transition or if the new value exists, but
// isn't a function.
if (!map || newVal && typeof newVal !== 'function') {
return;
}
});

// Add or remove based on the value existence and type.
map[typeof newVal === 'function' ? 'set' : 'delete'](element, newVal);
};

// This will unbind any internally bound transition states.
var unsubscribe = function unsubscribe() {
// Unbind all the transition states.
removeTransitionState('attributeChanged', attributeChanged);

// Remove all elements from the internal cache.
Object.keys(transitionsMap).forEach(function (name) {
var map = transitionsMap[name];

// Unbind the associated global handler.
removeTransitionState(name, boundHandlers.shift());

// Empty the associated element set.
map.clear();
});

// Empty the bound handlers.
boundHandlers.length = 0;
};

// If this function gets repeatedly called, unbind the previous to avoid doubling up.
unsubscribe();

// Set a "global" `attributeChanged` to monitor all elements for transition
// states being attached.
addTransitionState('attributeChanged', function (element, name, oldVal, newVal) {
var internalMap = transitionsMap.get(element) || {};
addTransitionState('attributeChanged', attributeChanged);

if (states.indexOf(name) === -1) {
return;
}
// Add a transition for every type.
Object.keys(transitionsMap).forEach(function (name) {
var map = transitionsMap[name];

if (newVal) {
transitionsMap.set(element, Object.assign(internalMap, _defineProperty({}, name, function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var handler = function handler(child) {
for (var _len = arguments.length, rest = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
rest[_key - 1] = arguments[_key];
}

if (element.contains(args[0])) {
return newVal.apply(_this, [element].concat(args));
// If there are no elements to match here, abort.
if (!map.size) {
return;
}
// If the child element triggered in the transition is the root element,
// this is an easy lookup for the handler.
else if (map.has(child)) {
return map.get(child).apply(child, [child].concat(rest));
}
})));
// The last resort is looping through all the registered elements to see
// if the child is contained within. If so, it aggregates all the valid
// handlers and if they return Promises return them into a `Promise.all`.
else {
var _ret = function () {
var retVal = [];

addTransitionState(name, internalMap[name]);
} else if (internalMap[name]) {
removeTransitionState(name, internalMap[name]);
delete internalMap[name];
transitionsMap.set(element, internalMap);
}
});
};
// Last resort check for child.
map.forEach(function (fn, element) {
if (element.contains(child)) {
retVal.push(fn.apply(child, [element].concat(child, rest)));
}
});

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var hasPromise = retVal.some(function (ret) {
return Boolean(ret.then);
});

var states = ['attached', 'detached', 'replaced', 'attributeChanged', 'textChanged'];
// This is the only time the return value matters.
if (hasPromise) {
return {
v: Promise.all(retVal)
};
}
}();

var transitionsMap = new WeakMap();module.exports = exports['default'];
if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v;
}
};

// Save the handler for later unbinding.
boundHandlers.push(handler);

// Add the state handler.
addTransitionState(name, handler);
});

return unsubscribe;
};

},{}]},{},[1])(1)
});
120 changes: 88 additions & 32 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,104 @@
const states = [
'attached',
'detached',
'replaced',
'attributeChanged',
'textChanged',
];
// Store maps of elements to handlers that are associated to transitions.
const transitionsMap = {
attached: new Map(),
detached: new Map(),
replaced: new Map(),
attributeChanged: new Map(),
textChanged: new Map(),
};

const transitionsMap = new WeakMap();
// Internal global transition state handlers, allows us to bind once and match.
const boundHandlers = [];

/**
* Binds inline transitions to the parent element and triggers for any matching
* nested children.
*/
export default function({ addTransitionState, removeTransitionState }) {
addTransitionState('attached', (element) => {
if (element.attributes.attached) {
return element.attached.call(this, element, element);
module.exports = function({ addTransitionState, removeTransitionState }) {
// Monitors whenever an element changes an attribute, if the attribute
// is a valid state name, add this element into the related Set.
const attributeChanged = function(element, name, oldVal, newVal) {
const map = transitionsMap[name];

// Abort early if not a valid transition or if the new value exists, but
// isn't a function.
if (!map || (newVal && typeof newVal !== 'function')) {
return;
}
});

// Add or remove based on the value existence and type.
map[typeof newVal === 'function' ? 'set' : 'delete'](element, newVal);
};

// This will unbind any internally bound transition states.
const unsubscribe = () => {
// Unbind all the transition states.
removeTransitionState('attributeChanged', attributeChanged);

// Remove all elements from the internal cache.
Object.keys(transitionsMap).forEach(name => {
const map = transitionsMap[name];

// Unbind the associated global handler.
removeTransitionState(name, boundHandlers.shift());

// Empty the associated element set.
map.clear();
});

// Empty the bound handlers.
boundHandlers.length = 0;
};

// If this function gets repeatedly called, unbind the previous to avoid doubling up.
unsubscribe();

// Set a "global" `attributeChanged` to monitor all elements for transition
// states being attached.
addTransitionState('attributeChanged', (element, name, oldVal, newVal) => {
const internalMap = transitionsMap.get(element) || {};
addTransitionState('attributeChanged', attributeChanged);

if (states.indexOf(name) === -1) {
return;
}
// Add a transition for every type.
Object.keys(transitionsMap).forEach(name => {
const map = transitionsMap[name];

const handler = (child, ...rest) => {
// If there are no elements to match here, abort.
if (!map.size) {
return;
}
// If the child element triggered in the transition is the root element,
// this is an easy lookup for the handler.
else if (map.has(child)) {
return map.get(child).apply(child, [child].concat(rest));
}
// The last resort is looping through all the registered elements to see
// if the child is contained within. If so, it aggregates all the valid
// handlers and if they return Promises return them into a `Promise.all`.
else {
const retVal = [];

if (newVal) {
transitionsMap.set(element, Object.assign(internalMap, {
[name]: (...args) => {
if (element.contains(args[0])) {
return newVal.apply(this, [element].concat(args));
// Last resort check for child.
map.forEach((fn, element) => {
if (element.contains(child)) {
retVal.push(fn.apply(child, [element].concat(child, rest)));
}
});

const hasPromise = retVal.some(ret => Boolean(ret.then));

// This is the only time the return value matters.
if (hasPromise) {
return Promise.all(retVal);
}
}));
}
};

addTransitionState(name, internalMap[name])
}
else if (internalMap[name]) {
removeTransitionState(name, internalMap[name])
delete internalMap[name];
transitionsMap.set(element, internalMap);
}
})
// Save the handler for later unbinding.
boundHandlers.push(handler);

// Add the state handler.
addTransitionState(name, handler);
});

return unsubscribe;
}
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"description": "Monitors inline attributes and assigns transition hooks",
"main": "dist/inline-transitions.js",
"scripts": {
"build": "browserify -t babelify -s inlineTransitions index.js -o dist/inline-transitions.js"
"build": "browserify -t babelify -s inlineTransitions index.js -o dist/inline-transitions.js",
"mocha": "mocha test/_setup test/*.js",
"test": "istanbul cover _mocha -- -- test/_setup test/*.js"
},
"keywords": [
"diffhtml",
Expand All @@ -18,6 +20,11 @@
"babel-preset-es2015": "^6.9.0",
"babelify": "^7.3.0",
"browserify": "^13.0.1",
"derequire": "^2.0.3"
"coveralls": "^2.11.9",
"derequire": "^2.0.3",
"diffhtml": "^0.8.4",
"istanbul": "^1.0.0-alpha.2",
"mocha": "^2.5.3",
"stringdom": "jugglinmike/stringdom#f42ad65227fc4e5a1d120ae432c7ec4eaf6aa11b"
}
}
30 changes: 30 additions & 0 deletions test/_setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const Document = require('stringdom');

// Set up a pretend DOM for the tests.
global.document = new Document();
global.document.body = document.createElement('body');
global.document.createComment = function() {
return document.createElement('noscript');
};

// Get access to the Node prototype.
const Node = Object.getPrototypeOf(global.document.body);

// No-op the event functions.
global.CustomEvent = function() {};
Node.dispatchEvent = () => {};

// Copied from a project by Jonathan Neal.
Node.contains = function(node) {
if (!(0 in arguments)) {
throw new TypeError('1 argument is required');
}

do {
if (this === node) {
return true;
}
} while (node = node && node.parentNode);

return false;
};
Loading