Amp

amp-add-class

Adds class(es) to an element.

All of the following work:

addClass(el, 'foo');
addClass(el, 'foo bar');
addClass(el, 'foo', 'bar');
addClass(el, ['foo', 'bar']);

Optimized for minimal DOM manipulation. The most common usecase of adding a single class will use native Element.classList if available. Otherwise, it will do a single read of el.className and only write back to it once, and only if something was actually changed.

note: Don't mix-and-match approaches. For example, don't do addClass(el, 'foo', ['bar', 'baz']). If you need this, flatten it first.

Example

var addClass = require('amp-add-class');

var element = document.querySelector('#my-el');
element.outerHTML; //=> '<div>greetings</div>';

// basic adding
addClass(element, 'oh');
element.outerHTML; //=> '<div class="oh">greetings</div>';

// adding an existing class does nothing
addClass(element, 'oh'); 

// add multiple at once
addClass(element, 'hello', 'there'); 
element.outerHTML; //=> '<div class="oh hello there">greetings</div>';

// using array
addClass(element, ['foo', 'bar']); 
element.outerHTML; //=> '<div class="oh hello there foo bar">greetings</div>';

// add multiple at once with space-separated string
addClass(element, 'baz boo'); 
element.outerHTML; //=> '<div class="oh hello there baz boo">greetings</div>';
var hasClass = require('amp-has-class');
var isString = require('amp-is-string');
var isArray = require('amp-is-array');
var trim = require('amp-trim');
var slice = Array.prototype.slice;
var cleanup = /\s{2,}/g;
var ws = /\s+/;


module.exports = function addClass(el, cls) {
    if (arguments.length === 2 && isString(cls)) {
        cls = trim(cls).split(ws);
    } else {
        cls = isArray(cls) ? cls : slice.call(arguments, 1);    
    }
    // optimize for best, most common case
    if (cls.length === 1 && el.classList) {
        if (cls[0]) el.classList.add(cls[0]);
        return el;
    }
    var toAdd = [];
    var i = 0;
    var l = cls.length;
    var item;
    var clsName = el.className;
    // see if we have anything to add
    for (; i < l; i++) {
        item = cls[i];
        if (item && !hasClass(clsName, item)) {
            toAdd.push(item);
        }
    }
    if (toAdd.length) {
        el.className = trim((clsName + ' ' + toAdd.join(' ')).replace(cleanup, ' '));
    }
    return el;
};
var test = require('tape');
var addClass = require('./add-class');


function getEl(className) {
    var div = document.createElement('div');
    div.className = className;
    return div;
}

test('amp-add-class', function (t) {
    var el = getEl('oh');
    
    addClass(el, 'oh');
    t.equal(el.className, 'oh');   
    
    el = getEl('oh');
    addClass(el, 'oh');
    t.equal(el.className, 'oh', 'adding existing class should do nothing');   
    
    addClass(el, 'hello', 'there');
    t.equal(el.className, 'oh hello there', 'should be able to add several');   
    
    addClass(el, 'oh', 'hello', 'there');
    t.equal(el.className, 'oh hello there', 'should never add dupes');

    addClass(el, undefined, null, NaN, 0, '');
    t.equal(el.className, 'oh hello there', 'should be reasonably tolerant of nonsense');

    el = getEl('');
    addClass(el, ['oh', 'hello', 'there']);
    t.equal(el.className, 'oh hello there', 'should work with arrays too');

    el = getEl('   oh    hello  there  ');
    var clsName = el.className;
    t.equal(clsName, addClass(el, 'hello', 'there').className, 'should not touch classNames if not modifying');
    t.equal(addClass(el, 'hello', 'yo').className, 'oh hello there yo', 'should clean up className whitespacing if modifying anyway');

    el = getEl('oh');
    addClass(el, 'hello there');
    t.equal(el.className, 'oh hello there', 'supports adding space separated chars');   

    el = getEl('oh');
    addClass(el, '\t\r\nhello \n  there   ');
    t.equal(el.className, 'oh hello there', 'tolerate extra spaces when adding space separated chars');  

    t.end();
});

amp-has-class

Returns true if the element has the class and false otherwise.

If the first argument is a string, it is assumed to be what you'd get from el.className. This allows it to be used repeatedly for a single element without reading from the DOM more than once.

Example

var hasClass = require('amp-has-class');

var el = document.querySelector('#my-div');

el.outerHTML; //=> <div class="oh hi">hello</div>

hasClass(el, 'oh'); //=> true
hasClass(el, 'no'); //=> false

// also works with the string result of `el.className`
var className = el.className;

hasClass(className, 'oh'); //=> true
hasClass(className, 'no'); //=> false
var isString = require('amp-is-string');
var whitespaceRE = /[\t\r\n\f]/g;


// note: this is jQuery's approach
module.exports = function hasClass(el, cls) {
    var cName = (isString(el) ? el : el.className).replace(whitespaceRE, ' ');
    return (' ' + cName + ' ').indexOf(' ' + cls + ' ') !== -1;
};
var test = require('tape');
var hasClass = require('./has-class');


function getEl(className) {
    var div = document.createElement('div');
    div.className = className;
    return div;
}

test('amp-has-class', function (t) {
    t.ok(hasClass(getEl('hi'), 'hi'));
    t.ok(hasClass(getEl('hi there'), 'there'));
    t.notOk(hasClass(getEl('hi'), 'something'));
    t.notOk(hasClass(getEl('hi there'), 'something'));
    t.notOk(hasClass(getEl('hi-there'), 'hi'));
    t.notOk(hasClass(getEl('hi-there'), 'there'));
    t.notOk(hasClass(getEl('hi-there'), 'i-t'));
    t.notOk(hasClass(getEl('hi-there'), '-'));

    var className = getEl('hi there').className;
    t.ok(hasClass(className, 'hi'));
    t.ok(hasClass(className, 'there'));
    t.notOk(hasClass(className, 'something'));

    t.ok(hasClass(getEl('\thi'), 'hi'), 'can be \t issue #27');
    t.ok(hasClass(getEl('\nhi'), 'hi'), 'can be \n issue #27');
    t.ok(hasClass(getEl('\rhi'), 'hi'), 'can be \r issue #27');
    t.ok(hasClass(getEl('\fhello\fhi\f'), 'hi'), 'can be \f issue #27');

    t.end();
});

amp-remove-class

Removes class(es) from an element.

All of the following work:

removeClass(el, 'foo');
removeClass(el, 'foo bar');
removeClass(el, 'foo', 'bar');
removeClass(el, ['foo', 'bar']);

Optimized for minimal DOM manipulation. The most common usecase of removing a single class will use native Element.classList if available. Otherwise, it will do a single read of el.className and only write back to it once, and only if something was actually changed.

note: Don't mix-and-match approaches. For example, don't do removeClass(el, 'foo', ['bar', 'baz']). If you need this, flatten it first.

Example

var removeClass = require('amp-remove-class');

var element = document.querySelector('#my-el');
element.outerHTML; //=> '<div class="oh hello there">greetings</div>';

removeClass(element, 'oh');
element.outerHTML; //=> '<div class="hello there">greetings</div>';

// remove multiple at once
removeClass(element, 'hello', 'there'); 
// can be done with a space-separated string
removeClass(element, 'hello there');
// can also be done by passing array
removeClass(element, ['hello', 'there']); 

element.outerHTML; //=> '<div class="">greetings</div>';
var isString = require('amp-is-string');
var isArray = require('amp-is-array');
var trim = require('amp-trim');
var slice = Array.prototype.slice;
var cleanup = /\s{2,}/g;
var ws = /\s+/;


module.exports = function removeClass(el, cls) {
    if (arguments.length === 2 && isString(cls)) {
        cls = trim(cls).split(ws);
    } else {
        cls = isArray(cls) ? cls : slice.call(arguments, 1);    
    }
    // optimize for best, most common case
    if (cls.length === 1 && el.classList) {
        if (cls[0]) el.classList.remove(cls[0]);
        return el;
    }
    // store two copies
    var clsName = ' ' + el.className + ' ';
    var result = clsName;
    var current;
    var start;
    for (var i = 0, l = cls.length; i < l; i++) {
        current = cls[i];
        start = current ? result.indexOf(' ' + current + ' ') : -1;
        if (start !== -1) {
            start += 1;
            result = result.slice(0, start) + result.slice(start + current.length);
        }
    }
    // only write if modified
    if (clsName !== result) {
        el.className = trim(result.replace(cleanup, ' '));
    }
    return el;
};
var test = require('tape');
var removeClass = require('./remove-class');


function getEl(className) {
    var div = document.createElement('div');
    div.className = className;
    return div;
}


test('amp-remove-class', function (t) {
    var el = getEl('oh hello there');
    
    removeClass(el, 'oh');
    t.equal(el.className, 'hello there');   
    
    removeClass(el, 'oh');
    t.equal(el.className, 'hello there', 'removing non-existant class should do nothing');   
    
    removeClass(el, 'hello', 'there');
    t.equal(el.className, '', 'should be able to remove several');   
    
    removeClass(el, 'oh', 'hello', 'there');
    t.equal(el.className, '', 'should be ok if doing again');

    // reset it
    el = getEl('oh hello there');

    removeClass(el, undefined, null, NaN, 0, '');
    t.equal(el.className, 'oh hello there', 'should be reasonably tolerant of nonsense');

    el = getEl('undefined null NaN');
    removeClass(el, undefined, null, NaN, 0, '');
    t.equal(el.className, 'undefined null NaN', 'should not remove classes named `undefined` etc.');

    el = getEl('oh hello there');
    t.equal(el.className, 'oh hello there');
    removeClass(el, ['oh', 'hello', 'there']);
    t.equal(el.className, '');

    el = getEl('  oh   hello there');
    var clsName = el.className;

    t.equal(clsName, removeClass(el, 'not', 'changing').className, 'should not touch classNames if not modifying');
    t.equal(removeClass(el, 'hello', 'yo').className, 'oh there', 'should clean up classNames whitespacing, if modifying anyway');

    el = removeClass(getEl('foobar'), 'foo', 'bar');
    t.equal(el.className, 'foobar');

    el = getEl('oh hello there');
    t.equal(removeClass(el, 'hello there').className, 'oh', 'should support space separated string of classes');

    el = getEl('oh hello there');
    t.equal(removeClass(el, '\r  hello \t \nthere  ').className, 'oh', 'should support other weird whitespace when using a string of classes');

    t.end();
});

amp-toggle-class

Toggles the existence of a class on an element. If the class exists it will be removed, if it doesn't it will be added.

If a condition argument is supplied, the truthiness of that condition is what determines whether the class should exist on the element or not. This simplifies common use case of an element needing a class if condition is true. condition can be passed as either a primitive which will eventually coerce to boolean or as a function. If you pass a function, you get current element passed as a first argument.

Example

var toggleClass = require('amp-toggle-class');

var element = document.querySelector('#my-el');
element.outerHTML; //=> '<div>greetings</div>';

// if no condition, toggles based on presence
toggleClass(element, 'oh');
element.outerHTML; //=> '<div class="oh">greetings</div>';

// running again, will remove it
toggleClass(element, 'oh'); 
element.outerHTML; //=> '<div class="">greetings</div>';

// toggling based on condition
// the `condition` always wins.
// Here, the class is missing, 
// but condition is falsy
// so `toggleClass` does nothing.
toggleClass(element, 'oh', false); 
element.outerHTML; //=> '<div class="">greetings</div>';
var isUndefined = require('amp-is-undefined');
var isFunction = require('amp-is-function');
var hasClass = require('amp-has-class');
var addClass = require('amp-add-class');
var removeClass = require('amp-remove-class');


var actions = {
    add: addClass,
    remove: removeClass
};

module.exports = function toggleClass(el, cls, condition) {
    var action;
    if (!isUndefined(condition)) {
        condition = isFunction(condition) ? condition.call(null, el) : condition;
        action = condition ? 'add' : 'remove';
    } else {
        action = hasClass(el, cls) ? 'remove' : 'add';
    }
    return actions[action](el, cls);
};
var test = require('tape');
var toggleClass = require('./toggle-class');


function getEl(className) {
    var div = document.createElement('div');
    div.className = className;
    return div;
}

test('amp-toggle-class', function (t) {
    var el = getEl('oh');

    toggleClass(el, 'oh');
    t.equal(el.className, '', 'removes if present without condition passed');   
    toggleClass(el, 'oh');
    t.equal(el.className, 'oh', 'puts it back when called again');   
    
    el = getEl('oh');
    toggleClass(el, 'oh', true);
    t.equal(el.className, 'oh', 'should leave it alone if condition is boolean true and class is already present');

    el = getEl('oh');
    toggleClass(el, 'hi', true);
    t.equal(el.className, 'oh hi', 'should add class if condition is boolean true');
    
    el = getEl('');
    toggleClass(el, 'oh', false);
    t.equal(el.className, '', 'should not remove class if not present and condition is false');

    el = getEl('oh');
    toggleClass(el, 'oh', false);
    t.equal(el.className, '', 'should remove class if condition is boolean false');

    // toggling with condition as a booleans
    el = getEl('oh');
    toggleClass(el, 'hi', function (element) {
        t.equal(el, element, 'should pass element to a condition function');
        return 1 + 1 === 2;
    });
    t.equal(el.className, 'oh hi', 'should add class if condition is a function returning true');

    el = getEl('oh');
    toggleClass(el, 'oh', function (element) {
        t.equal(el, element, 'should pass element to a condition function');
        return 1 + 1 === 1;
    });
    t.equal(el.className, '', 'should remove class if condition is a function returning false');

    var nonsense = [undefined, null, NaN, 0, ''];

    for (var i = 0, l = nonsense.length; i < l; i++) {
        el = getEl('ok')
        toggleClass(el, nonsense[i]);
        t.equal(el.className, 'ok', 'should be reasonably tolerant of nonsense');
    }
    
    el = getEl('   oh    hello  there  ');
    var clsName = el.className;
    t.equal(clsName, toggleClass(el, 'hello', true).className, 'should not touch classNames if not modifying');
    
    // toggling with conditions
    el = getEl('oh');
    t.equal(el, toggleClass(el, 'hi', true), 'should always return element');
    el = getEl('oh');
    t.equal(el, toggleClass(el, 'hi', false), 'should always return element');

    // toggling without conditions
    el = getEl('oh');
    t.equal(el, toggleClass(el, 'oh'), 'should always return element');
    el = getEl('oh');
    t.equal(el, toggleClass(el, 'hi'), 'should always return element');

    t.end();
});

amp-jump

If you pass an array and an item in that array, returns the item immediately following that item.

Optionally, pass a number to specify how much to jump by (defaults to 1). This number can be positive or negative.

If item is not in array, returns undefined.

If you jump out of range, returns undefined unless you pass true as the loop argument. In which case it loops around to other end.

Example

var jump = require('amp-jump');


var arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];

// basic case
jump(arr, 'a'); //=> 'b'
jump(arr, 'a', 3); //=> 'd'

// returns undefined if out of range
jump(arr, 'g'); //=> undefined

// negative jump values work fine
jump(arr, 'b', -1); //=> 'a'

// if you want to loop around, pass `true` as `loop` argument
jump(arr, 'a', -1, true); //=> 'g'
jump(arr, 'a', 7, true); //=> 'a'

// returns `undefined` if the item passed
// isn't found in the list at all
jump(arr, 'z'); //=> undefined
var isNumber = require('amp-is-number');
var isArray = require('amp-is-array');
var indexOf = require('amp-index-of');


module.exports = function jump(array, currentItem, jumpSize, loop) {
    if (!isArray(array)) return array;
    var index = indexOf(array, currentItem);
    if (index === -1) {
        return;
    }
    if (!isNumber(jumpSize)) {
        jumpSize = 1;
    }

    var len = array.length;    
    var newIndex = index + jumpSize;

    // we jumped too far
    if (newIndex > (len - 1)) {
        return loop ? array[newIndex % len] : undefined;
    }

    // we're negative
    if (newIndex < 0) {
        if (!loop) return;
        newIndex = len + (newIndex % len);
        if (newIndex === len) {
            newIndex = 0;
        }
    }

    // return our new item
    return array[newIndex];
};
var test = require('tape');
var jump = require('./jump');


test('amp-jump', function (t) {
    var arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];

    var obj = {};
    t.equal(jump(obj), obj, 'should return passed value for non arrays');
    t.equal(jump(1), 1, 'should return passed value for simple values');
    
    t.equal(jump(arr, 'x'), undefined, 'returns `undefined` if passed item not present');
    t.equal(jump(arr, 'a', -1), undefined, 'returns `undefined` if out of range low end');
    t.equal(jump(arr, 'a', 7), undefined, 'returns `undefined` if out of range on high end');

    t.equal(jump(arr, 'a'), 'b', '`jump` defaults to `1`');
    t.equal(jump(arr, 'a', 1), 'b', 'jumps work');
    t.equal(jump(arr, 'a', 3), 'd', 'jumps work');
    t.equal(jump(arr, 'c', -2), 'a', 'works for negative `jump`');

    // with loop argument
    t.equal(jump(arr, 'g', null, true), 'a', 'loops around to front if not given a jump');
    t.equal(jump(arr, 'g', 1, true), 'a', 'loops around to front');
    t.equal(jump(arr, 'g', 5, true), 'e', 'loops around to front with jumps');
    t.equal(jump(arr, 'g', 706, true), 'f', 'loops around to front with even larger jumps');
    
    t.equal(jump(arr, 'a', -1, true), 'g', 'loops around to back');
    t.equal(jump(arr, 'a', -5, true), 'c', 'loops around to back with slightly larger jumps');
    t.equal(jump(arr, 'a', -705, true), 'c', 'loops around to back with much larger jumps');
    
    t.equal(jump(arr, 'a', 0), 'a', 'should work for zero');
    t.equal(jump(arr, 'd', 0), 'd', 'should work for zero at any point');
    t.equal(jump(arr, 'g', 0), 'g', 'should work for zero at end');
    
    t.end();
});

amp-sorted-insert

Inserts an item onto an already sorted array (or array-like object) in a way that leaves the array sorted and returns the index at which the new item was inserted.

Uses a binary search using the new item and the items in the array to determine where to put the new item.

Sorting is done either by raw comparison (<, >, =) or via a comparator which can be a string representing a named attribute of the itme, or a function that will be given one item and will return the value to be used in comparisons.

Example

var sortedInsert = require('amp-sorted-insert');

//Using native compare
var items = [1,3,5,7]; //Already sorted
sortedInsert(items, 2); //[1,2,3,5,7]


//Using string comparator
var people = [
    {name: 'Robert', rank: 2},
    {name: 'Sally', rank: 4}
]; //Already sorted by rank

var pat = {name: 'Pat', rank: 1};

sortedInsert(people, pat, 'rank'); //[{name: 'Pat', rank:1}, {name: 'Robert', rank: 2}, {name: 'Sally', rank: 4}]


//Using function comparator
function nameLength(person) {
    return person.name.length;
}

var people = [
    {name: 'Pat', rank: 1},
    {name: 'Robert', rank: 2}
]; //Already sorted by name length

var sally = {name: 'Sally', rank: 4};

sortedInsert(people, sally, nameLength); //[{name: 'Pat', rank: 1}, {name: 'Sally', rank: 4}, {name: 'Robert', rank: 2}]
var sortedIndex = require('amp-sorted-index');
var splice = Array.prototype.splice;


module.exports = function sortedInsert(array, item) {
    var index = sortedIndex.apply(this, arguments);
    splice.call(array, index, 0, item);
    return index;
};
var test = require('tape');
var sortedInsert = require('./sorted-insert');


test('amp-sorted-insert', function (t) {
    var numbers = [1,3,5,7];
    sortedInsert(numbers, 2);
    t.equal(numbers.length, 5);
    t.equal(numbers[1], 2);

    var robert = {name: 'Robert', rank: 2};
    var sally = {name: 'Sally', rank: 4};
    var pat = {name: 'Pat', rank: 1};
    var people = [robert, sally];
    
    sortedInsert(people, pat, 'rank');
    t.deepEqual(people, [pat, robert, sally]);

    people = [pat, robert];
    sortedInsert(people, sally, function (person) { return person.name.length; });
    t.deepEqual(people, [pat, sally, robert]);

    var arrayLike = {
        0: 'a',
        1: 'b',
        length: 2
    };
    var index = sortedInsert(arrayLike, 'c');
    t.equal(index, 2, 'should be inserted at 2');
    t.deepEqual(arrayLike, {
        0: 'a',
        1: 'b',
        2: 'c',
        length: 3
    }, 'should modify array-like object');

    t.end();
});

amp-array-to-readable

Takes an array of values and returns a readable string separated with comma and conjunction if length warrants it.

Example

var arrayToReadable = require('amp-array-to-readable');

arrayToReadable([1, 2, 3]); //=> '1, 2, and 3'
arrayToReadable([1, 2]); //=> '1 and 2'
arrayToReadable([1]); //=> '1'

var words = ['one', 'two', 'three'];

arrayToReadable(words); //=> 'one, two, and three'
arrayToReadable(words, {conjunction: 'or'}); //=> 'one, two, or three'
arrayToReadable(words, {oxford: false}); //=> 'one, two and three'
module.exports = function arrayToReadable(arr, opts) {
    if (!opts) {
        opts = {};
    }
    var length = arr.length;
    var conj = opts.conjunction || 'and';
    var oxford = opts.oxford !== false;
    switch(length) {
        case 1:
            return arr[0].toString();
        case 2:
            return arr.join(' ' + conj + ' ');
        default:
            return arr.slice(0, -1).join(', ') + (oxford ? ',' : '') + ' ' + conj + ' ' + arr.slice(-1);
    }
};
var test = require('tape');
var arrayToReadable = require('./array-to-readable');


test('amp-array-to-readable', function (t) {
    t.equal(arrayToReadable([1, 2, 3]), '1, 2, and 3');
    t.equal(arrayToReadable([1, 2]), '1 and 2');
    t.equal(arrayToReadable([1]), '1');
    t.equal(arrayToReadable(['one', 'two', 'three', 'four']), 'one, two, three, and four');
    t.equal(arrayToReadable(['one', 'two', 'three', 'four'], {conjunction: 'or'}), 'one, two, three, or four');
    t.equal(arrayToReadable(['one', 'two', 'three', 'four'], {oxford: false}), 'one, two, three and four');
    t.end();
});

amp-create-callback

Internal amp utility for creating callbacks used in looping functions such as map and filter (mainly used internally). Having a known argument count makes it more optimizable by browsers.

Example

var createCallback = require('amp-create-callback');

var callback = createCallback(function () {
    // something
}, context, 3);

[1, 2, 3].map(callback);
module.exports = function createCallback(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount) {
    case 1: 
        return function(value) {
            return func.call(context, value);
        };
    case 2: 
        return function(value, other) {
            return func.call(context, value, other);
        };
    case 3: 
        return function(value, index, collection) {
            return func.call(context, value, index, collection);
        };
    case 4: 
        return function(accumulator, value, index, collection) {
            return func.call(context, accumulator, value, index, collection);
        };
    }
    return function() {
        return func.apply(context, arguments);
    };
};
var test = require('tape');
var createCallback = require('./create-callback');


test('amp-create-callback', function (t) {
    var argLength = 1;
    var context = {hi: 'there'};

    var callback = createCallback(function () {
        t.equal(this, context, 'should maintain context');
        t.equal(arguments.length, 1, 'should never have more than arg length passed');
    }, context, 1);
    callback(1, 2, 3, 4, 5);

    callback = createCallback(function () {
        t.equal(this, context, 'should maintain context');
        t.equal(arguments.length, 2, 'should never have more than arg length passed');
    }, context, 2);
    callback(1, 2, 3, 4, 5);

    callback = createCallback(function () {
        t.equal(this, context, 'should maintain context');
        t.equal(arguments.length, 3, 'should never have more than arg length passed');
    }, context, 3);
    callback(1, 2, 3, 4, 5);

    callback = createCallback(function () {
        t.equal(this, context, 'should maintain context');
        t.equal(arguments.length, 4, 'should never have more than arg length passed');
    }, context, 4);
    callback(1, 2, 3, 4, 5);
    
    callback = createCallback(function () {
        t.equal(arguments.length, 5, 'should have as many as passed');
    });
    callback(1, 2, 3, 4, 5);
    
    t.end();
});

amp-internal-flatten

An internal flatten implementation for re-use.

Takes following args:

Example

var internalFlatten = require('amp-internal-flatten');

var arr = [1, [1], [[1]], 'something'];

// shallow
internalFlatten(arr, true, false, []); 
//=> [1, 1, [1], 'something'];

// recursive
internalFlatten(arr, false, false, []); 
//=> [1, 1, 1, 'something'];

// recursive + strict 
// these options *always* will
// result an empty array as output
internalFlatten(arr, false, true, []); 
//=> [];

// shallow + strict
// any non-arrays are removed
// but only run through once
// so only 2ns and 3rd args make it
// but are flattened one level
internalFlatten(arr, true, true, []); 
//=> [1, [1]];
var isArray = require('amp-is-array');
var isArguments = require('amp-is-arguments');


var flatten = function flatten(input, shallow, strict, startIndex) {
    var output = [], idx = 0, value;
    for (var i = startIndex || 0, length = input && input.length; i < length; i++) {
        value = input[i];
        if (value && value.length >= 0 && (isArray(value) || isArguments(value))) {
            //flatten current level of array or arguments object
            if (!shallow) value = flatten(value, shallow, strict);
            var j = 0, len = value.length;
            output.length += len;
            while (j < len) {
                output[idx++] = value[j++];
            }
        } else if (!strict) {
            output[idx++] = value;
        }
    }
    return output;
};

module.exports = flatten;
var test = require('tape');
var flatten = require('./internal-flatten');


test('amp-internal-flatten', function (t) {
    var arr = [1, [1], [[1]], 'something'];

    t.deepEqual(flatten(arr, true, false), [1, 1, [1], 'something'], 'shallow');
    t.deepEqual(flatten(arr, false, false), [1, 1, 1, 'something'], 'recursive');
    t.deepEqual(flatten(arr, false, true), [], 'recursive + strict');
    t.deepEqual(flatten(arr, true, true), [1, [1]], 'shallow + strict');

    t.end();
});