(function (root, factory) {
    if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module.
        define(['react'], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('react'));
    } else {
        // Browser globals (root is window)
        root.Reactable = factory(root.React);
    }
}(this, function (React) {
    "use strict";
    var exports = {};

    // Array.prototype.map polyfill - see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Polyfill
    // Production steps of ECMA-262, Edition 5, 15.4.4.19
    // Reference: http://es5.github.io/#x15.4.4.19
    if (!Array.prototype.map) {

        Array.prototype.map = function(callback, thisArg) {
            var T, A, k;

            if (this === null) {
                throw new TypeError(" this is null or not defined");
            }

            var O = Object(this);
            var len = O.length >>> 0;

            if (typeof callback !== "function") {
                throw new TypeError(callback + " is not a function");
            }

            if (arguments.length > 1) {
                T = thisArg;
            }

            A = new Array(len);
            k = 0;

            while (k < len) {
                var kValue, mappedValue;
                if (k in O) {
                    kValue = O[k];
                    mappedValue = callback.call(T, kValue, k, O);
                    A[k] = mappedValue;
                }
                k++;
            }
            return A;
        };
    }

    // Array.prototype.indexOf polyfill for IE8
    if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function(elt /*, from*/) {
            var len = this.length >>> 0;

            var from = Number(arguments[1]) || 0;
            from = (from < 0) ? Math.ceil(from) : Math.floor(from);
            if (from < 0) {
                from += len;
            }

            for (; from < len; from++) {
                if (from in this && this[from] === elt) {
                    return from;
                }
            }
            return -1;
        };
    }

    // Array.prototype.find polyfill - see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
    if (!Array.prototype.find) {
        Object.defineProperty(Array.prototype, 'find', {
            enumerable: false,
            configurable: true,
            writable: true,
            value: function(predicate) {
                if (this === null) {
                    throw new TypeError('Array.prototype.find called on null or undefined');
                }
                if (typeof predicate !== 'function') {
                    throw new TypeError('predicate must be a function');
                }
                var list = Object(this);
                var length = list.length >>> 0;
                var thisArg = arguments[1];
                var value;

                for (var i = 0; i < length; i++) {
                    if (i in list) {
                        value = list[i];
                        if (predicate.call(thisArg, value, i, list)) {
                            return value;
                        }
                    }
                }
                return undefined;
            }
        });
    }

    if (!Array.isArray) {
        Array.isArray = function (value) {
            return Object.prototype.toString.call(value) === '[object Array]';
        };
    }

    if (!Object.assign) {
        Object.defineProperty(Object, "assign", {
            enumerable: false,
            configurable: true,
            writable: true,
            value: function(target, firstSource) {
                if (target === undefined || target === null)
                    throw new TypeError("Cannot convert first argument to object");
                var to = Object(target);
                for (var i = 1; i < arguments.length; i++) {
                    var nextSource = arguments[i];
                    if (nextSource === undefined || nextSource === null) continue;
                    var keysArray = Object.keys(Object(nextSource));
                    for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
                        var nextKey = keysArray[nextIndex];
                        var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
                        if (desc !== undefined && desc.enumerable) to[nextKey] = nextSource[nextKey];
                    }
                }
                return to;
            }
        });
    }

    function Unsafe(content) {
        this.content = content;
    }

    Unsafe.prototype.toString = function() {
        return this.content;
    };

    function stringable(thing) {
        return thing !== null && typeof(thing) !== 'undefined' && typeof(thing.toString === 'function');
    }

    // this is a bit hacky - it'd be nice if React exposed an API for this
    function isReactComponent(thing) {
        return thing !== null && typeof(thing) === 'object' && typeof(thing.props) !== 'undefined';
    }

    React.Children.children = function(children) {
        return React.Children.map(children, function(x) { return x; }) || [];
    };

    exports.unsafe = function(str) {
        return new Unsafe(str);
    };

    exports.Sort = {
        Numeric: function(a, b) {
            var valA = parseFloat(a.toString().replace(/,/g,''));
            var valB = parseFloat(b.toString().replace(/,/g,''));

            // Sort non-numeric values alphabetically at the bottom of the list
            if (isNaN(valA) && isNaN(valB)) {
                valA = a;
                valB = b;
            } else {
                if (isNaN(valA)) {
                    return 1;
                }
                if (isNaN(valB)) {
                    return -1;
                }
            }

            if (valA < valB) {
                return -1;
            }
            if (valA > valB) {
                return 1;
            }

            return 0;
        },

        NumericInteger: function(a, b) {
          if (isNaN(a) || isNaN(b)) {
            return a > b ? 1 : -1;
          }

          return a - b;
        },

        Currency: function(a, b) {
            // Parse out dollar signs, then do a regular numeric sort
            // TODO: handle non-American currency

            if (a[0] === '$') {
                a = a.substring(1);
            }
            if (b[0] === '$') {
                b = b.substring(1);
            }

            return exports.Sort.Numeric(a, b);
        },

        Date: function(a, b) {
            // Note: this function tries to do a standard javascript string -> date conversion
            // If you need more control over the date string format, consider using a different
            // date library and writing your own function
            var valA = Date.parse(a);
            var valB = Date.parse(b);

            // Handle non-date values with numeric sort
            // Sort non-numeric values alphabetically at the bottom of the list
            if (isNaN(valA) || isNaN(valB)) {
                return exports.Sort.Numeric(a, b);
            }

            if (valA > valB) {
                return 1;
            }
            if (valB > valA) {
                return -1;
            }

            return 0;
        },

        CaseInsensitive: function(a, b) {
            return a.toLowerCase().localeCompare(b.toLowerCase());
        }
    };

    var Td = exports.Td = React.createClass({
        handleClick: function(e){
            if (typeof this.props.handleClick === 'function') {
                return this.props.handleClick(e, this);
            }
        },
        render: function() {
            var tdProps = {
                className: this.props.className,
                onClick: this.handleClick
            };

            // Attach any properties on the column to this Td object to allow things like custom event handlers
            if (typeof(this.props.column) === 'object') {
                for (var key in this.props.column) {
                    if (key !== 'key' && key !== 'name') {
                        tdProps[key] = this.props.column[key];
                    }
                }
            }

            var data = this.props.data;

            if (typeof(this.props.children) !== 'undefined') {
                if (isReactComponent(this.props.children)) {
                    data = this.props.children;
                } else if (
                    typeof(this.props.data) === 'undefined' &&
                        stringable(this.props.children)
                ) {
                    data = this.props.children.toString();
                }

                if (this.props.children instanceof Unsafe) {
                    tdProps.dangerouslySetInnerHTML = { __html: this.props.children.toString() };
                } else {
                    tdProps.children = data;
                }
            }

            return React.DOM.td(tdProps);
        }
    });


    var Tr = exports.Tr = React.createClass({
        statics: {
            childNode: Td,
            dataType: 'object'
        },
        render: function() {
            var children = toArray(React.Children.children(this.props.children));
            var className = this.props.border ? "dig-has-border" : "";

            if (
                this.props.data &&
                    this.props.columns &&
                        typeof this.props.columns.map === 'function'
            ) {
                if (typeof(children.concat) === 'undefined') { console.log(children); }

                children = children.concat(this.props.columns.map(function(column, i) {
                    if (this.props.data.hasOwnProperty(column.key)) {
                        var value = this.props.data[column.key];
                        var props = {};

                        if (
                            typeof(value) !== 'undefined' &&
                                value !== null &&
                                    value.__reactableMeta === true
                        ) {
                            props = value.props;
                            value = value.value;
                        }

                        return <Td column={column} key={column.key} {...props} className={className}>{value}</Td>;
                    } else {
                        return <Td column={column} key={column.key} className={className}/>;
                    }
                }.bind(this)));
            }

            // Manually transfer props
            var props = filterPropsFrom(this.props);

            return React.DOM.tr(props, children);
        }
    });

    var Thead = exports.Thead = React.createClass({
        getColumns: function() {
            return React.Children.map(this.props.children, function(th) {
                if (typeof th.props.children === 'string') {
                    return th.props.children;
                } else {
                    throw new TypeError('<th> must have a string child');
                }
            });
        },
        handleClickTh: function (column) {
            this.props.onSort(column.key);
        },
        render: function() {

            // Declare the list of Ths
            var Ths = [];
            for (var index = 0; index < this.props.columns.length; index++) {
                var column = this.props.columns[index];
                var sortClass = '';

                if (this.props.sortableColumns[column.key]) {
                    sortClass += 'reactable-header-sortable ';
                }

                if (this.props.sort.column === column.key) {
                    sortClass += 'reactable-header-sort';
                    if (this.props.sort.direction === 1) {
                        sortClass += '-asc';
                    }
                    else {
                        sortClass += '-desc';
                    }
                }

                if (this.props.border) {
                    sortClass += " dig-has-border";
                };

                if (this.props.headerBg) {
                    sortClass += ' dig-has-header-background';
                }

                var style = {};

                if (this.props.columnWidth.length > index) {
                    style = {width: this.props.columnWidth[index]};
                };

                Ths.push(
                    <Th className={sortClass} key={index} onClick={this.handleClickTh.bind(this, column)} style={style}>
                        {column.label}
                    </Th>
                );
            }

            // Manually transfer props
            var props = filterPropsFrom(this.props);

            return (
                <thead {...props}>
                    {this.props.filtering === true ?
                        <Filterer
                            colSpan={this.props.columns.length}
                            onFilter={this.props.onFilter}
                            placeholder={this.props.filterPlaceholder}
                            value={this.props.currentFilter} /> : ''}
                    <tr className="reactable-column-header">{Ths}</tr>
                </thead>
            );
        }
    });

    var Th = exports.Th = React.createClass({
        render: function() {
                var childProps
            if (this.props.children instanceof Unsafe) {
                return <th {...filterPropsFrom(this.props)}
                    dangerouslySetInnerHTML={{__html: this.props.children.toString()}}/>
            } else {
                return <th {...filterPropsFrom(this.props)}>
                    {this.props.children}
                </th>;
            }
        }
    });

    var FiltererInput = React.createClass({
        onChange: function() {
            this.props.onFilter(this.getDOMNode().value);
        },
        render: function() {
            return (
                <input type="text"
                    className="reactable-filter-input"
                    placeholder={this.props.placeholder}
                    value={this.props.value}
                    onKeyUp={this.onChange}
                    onChange={this.onChange} />
            );
        }
    });

    var Filterer = React.createClass({
        render: function() {
            if (typeof this.props.colSpan === 'undefined') {
                throw new TypeError('Must pass a colSpan argument to Filterer');
            }

            return (
                <tr className="reactable-filterer">
                    <td colSpan={this.props.colSpan}>
                        <FiltererInput onFilter={this.props.onFilter}
                            value={this.props.value}
                            placeholder={this.props.placeholder}/>
                    </td>
                </tr>
            );
        }
    });

    var Paginator = React.createClass({
        render: function() {
            if (typeof this.props.colSpan === 'undefined') {
                throw new TypeError('Must pass a colSpan argument to Paginator');
            }

            if (typeof this.props.numPages === 'undefined') {
                throw new TypeError('Must pass a non-zero numPages argument to Paginator');
            }

            if (typeof this.props.currentPage === 'undefined') {
                throw new TypeError('Must pass a currentPage argument to Paginator');
            }

            var pageButtons = [];
            for (var i = 0; i < this.props.numPages; i++) {
                var pageNum = i;
                var className = "reactable-page-button";
                if (this.props.currentPage === i) {
                    className += " reactable-current-page";
                }

                pageButtons.push(
                    <a className={className} key={i}
                        // create function to get around for-loop closure issue
                        onClick={(function(pageNum) {
                            return function() {
                                this.props.onPageChange(pageNum);
                            }.bind(this);
                        }.bind(this))(i)}>{i + 1}</a>
                );
            }

            return (
                <tbody className="reactable-pagination">
                    <tr>
                        <td colSpan={this.props.colSpan}>
                            {pageButtons}
                        </td>
                    </tr>
                </tbody>
            );
        }
    });

    var Table = exports.Table = React.createClass({
        // Translate a user defined column array to hold column objects if strings are specified
        // (e.g. ['column1'] => [{key: 'column1', label: 'column1'}])
        translateColumnsArray: function(columns) {
            return columns.map(function(column, i) {
                if (typeof(column) === 'string') {
                    return {
                        key:   column,
                        label: column
                    };
                } else {
                    if (typeof(column.sortable) !== 'undefined') {
                        var sortFunction = column.sortable === true ? 'default' : column.sortable;
                        this._sortable[column.key] = sortFunction;
                    }

                    return column;
                }
            }.bind(this));
        },
        parseChildData: function(props) {
            var data = [];

            // Transform any children back to a data array
            if (typeof(props.children) !== 'undefined') {
                React.Children.forEach(props.children, function(child) {
                    // TODO: figure out a new way to determine the type of a component
                    /*
                       if (child.type.ConvenienceConstructor !== Tr) {
                       return; // (continue)
                       }
                       */
                    if (typeof(child.props) !== 'object') {
                        return console.warn('Child passed to <Reactable.Table> was not an object: ' + child.toString());
                    }

                    var childData = child.props.data || {};

                    React.Children.forEach(child.props.children, function(descendant) {
                        // TODO
                        /* if (descendant.type.ConvenienceConstructor === Td) { */
                        if (true) {
                            if (typeof(descendant.props.column) !== 'undefined') {
                                var value;

                                if (typeof(descendant.props.data) !== 'undefined') {
                                    value = descendant.props.data;
                                } else if (typeof(descendant.props.children) !== 'undefined') {
                                    value = descendant.props.children;
                                } else {
                                    console.warn('exports.Td specified without ' +
                                                 'a `data` property or children, ' +
                                                 'ignoring');
                                    return;
                                }

                                childData[descendant.props.column] = {
                                    value: value,
                                    props: filterPropsFrom(descendant.props),
                                    __reactableMeta: true
                                };
                            } else {
                                console.warn('exports.Td specified without a ' +
                                             '`column` property, ignoring');
                            }
                        }
                    });

                    data.push({
                        data: childData,
                        props: filterPropsFrom(child.props),
                        __reactableMeta: true
                    });
                }.bind(this));
            }

            return data;
        },

        initialize: function(props) {
            this.data = props.data || [];
            this.data = this.data.concat(this.parseChildData(props));
            this.initializeSorts(props);
        },

        initializeSorts: function() {
            this._sortable = {};
            // Transform sortable properties into a more friendly list
            for (var i in this.props.sortable) {
                var column = this.props.sortable[i];
                var columnName, sortFunction;

                if (column instanceof Object) {
                    if (typeof(column.column) !== 'undefined') {
                        columnName = column.column;
                    } else {
                        console.warn('Sortable column specified without column name');
                        return;
                    }

                    if (typeof(column.sortFunction) === 'function') {
                        sortFunction = column.sortFunction;
                    } else {
                        sortFunction = 'default';
                    }
                } else {
                    columnName      = column;
                    sortFunction    = 'default';
                }

                this._sortable[columnName] = sortFunction;
            }
        },

        getDefaultProps: function() {
            var defaultProps = {
                sortBy: false,
                defaultSort: false,
                itemsPerPage: 0,
                headerBg: true,
                border: true
            };
            return defaultProps;
        },

        getInitialState: function() {
            var initialState = {
                currentPage: 0,
                currentSort: {
                    column: null,
                    direction: 1
                },
                filter: ''
            };

            // Set the state of the current sort to the default sort
            if (this.props.sortBy !== false || this.props.defaultSort !== false) {
                var sortingColumn = this.props.sortBy || this.props.defaultSort;
                initialState.currentSort = this.getCurrentSort(sortingColumn);
            }
            return initialState;
        },

        getCurrentSort: function(column) {
            var columnName, sortDirection;

            if (column instanceof Object) {
                if (typeof(column.column) !== 'undefined') {
                    columnName = column.column;
                } else {
                    console.warn('Default column specified without column name');
                    return;
                }

                if (typeof(column.direction) !== 'undefined') {
                    if (column.direction === 1 || column.direction === 'asc') {
                        sortDirection = 1;
                    } else if (column.direction === -1 || column.direction === 'desc') {
                        sortDirection = -1;
                    } else {
                        console.warn('Invalid default sort specified.  Defaulting to ascending');
                        sortDirection = 1;
                    }
                } else {
                    sortDirection = 1;
                }
            } else {
                columnName      = column;
                sortDirection   = 1;
            }

            return {
                column: columnName,
                direction: sortDirection
            };
        },

        updateCurrentSort: function(sortBy) {
            if (sortBy !== false &&
                sortBy.column !== this.state.currentSort.column &&
                    sortBy.direction !== this.state.currentSort.direction) {

                this.setState({ currentSort: this.getCurrentSort(sortBy) });
            }
        },

        componentWillMount: function() {
            this.initialize(this.props);
            this.sortByCurrentSort();
        },
        componentWillReceiveProps: function(nextProps) {
            this.initialize(nextProps);
            this.updateCurrentSort(nextProps.sortBy);
            this.sortByCurrentSort();
        },
        onPageChange: function(page) {
            this.setState({ currentPage: page });
        },
        filterBy: function(filter) {
            this.setState({ filter: filter });
        },
        applyFilter: function(filter, children) {
            // Helper function to apply filter text to a list of table rows
            filter = filter.toLowerCase();
            var matchedChildren = [];

            for (var i = 0; i < children.length; i++) {
                var data = children[i].props.data;

                for (var j = 0; j < this.props.filterable.length; j++) {
                    var filterColumn = this.props.filterable[j];

                    if (
                        typeof(data[filterColumn]) !== 'undefined' &&
                            extractDataFrom(data, filterColumn).toString().toLowerCase().indexOf(filter) > -1
                    ) {
                        matchedChildren.push(children[i]);
                        break;
                    }
                }
            }

            return matchedChildren;
        },
        sortByCurrentSort: function(){
            // Apply a sort function according to the current sort in the state.
            // This allows us to perform a default sort even on a non sortable column.
            var currentSort = this.state.currentSort;

            if (currentSort.column === null) {
                return;
            }

            this.data.sort(function(a, b){
                var keyA = extractDataFrom(a, currentSort.column);
                keyA = (keyA instanceof Unsafe) ? keyA.toString() : keyA || '';
                var keyB = extractDataFrom(b, currentSort.column);
                keyB = (keyB instanceof Unsafe) ? keyB.toString() : keyB || '';

                // Default sort
                if (
                    typeof(this._sortable[currentSort.column]) === 'undefined' ||
                        this._sortable[currentSort.column] === 'default'
                ) {

                    // Reverse direction if we're doing a reverse sort
                    if (keyA < keyB) {
                        return -1 * currentSort.direction;
                    }

                    if (keyA > keyB) {
                        return 1 * currentSort.direction;
                    }

                    return 0;
                } else {
                    // Reverse columns if we're doing a reverse sort
                    if (currentSort.direction === 1) {
                        return this._sortable[currentSort.column](keyA, keyB);
                    } else {
                        return this._sortable[currentSort.column](keyB, keyA);
                    }
                }
            }.bind(this));
        },
        onSort: function(column) {
            // Don't perform sort on unsortable columns
            if (typeof(this._sortable[column]) === 'undefined') {
                return;
            }

            var currentSort = this.state.currentSort;

            if (currentSort.column === column) {
                currentSort.direction *= -1;
            } else {
                currentSort.column = column;
                currentSort.direction = 1;
            }

            // Set the current sort and pass it to the sort function
            this.setState({ currentSort: currentSort });
            this.sortByCurrentSort();
        },
        render: function() {
            var children = [];
            var columns;
            var userColumnsSpecified = false;

            if (
                this.props.children &&
                    this.props.children.length > 0 &&
                        this.props.children[0].type.ConvenienceConstructor === Thead
            ) {
                columns = this.props.children[0].getColumns();
            } else {
                columns = this.props.columns || [];
            }

            if (columns.length > 0) {
                userColumnsSpecified = true;
                columns = this.translateColumnsArray(columns);
            }

            // Build up table rows
            if (this.data && typeof this.data.map === 'function') {
                // Build up the columns array
                children = children.concat(this.data.map(function(rawData, i) {
                    var data = rawData;
                    var props = {};
                    if (rawData.__reactableMeta === true) {
                        data = rawData.data;
                        props = rawData.props;
                    }

                    // Loop through the keys in each data row and build a td for it
                    for (var k in data) {
                        if (data.hasOwnProperty(k)) {
                            // Update the columns array with the data's keys if columns were not
                            // already specified
                            if (userColumnsSpecified === false) {
                                var column = {
                                    key:   k,
                                    label: k
                                };

                                // Only add a new column if it doesn't already exist in the columns array
                                if (
                                    columns.find(function(element) {
                                    return element.key === column.key;
                                }) === undefined
                                ) {
                                    columns.push(column);
                                }
                            }
                        }
                    }

                    return (
                        <Tr columns={columns} key={i} data={data} {...props} border={this.props.border}/>
                    );
                }.bind(this)));
            }

            if (this.props.sortable === true) {
                for (var i = 0; i < columns.length; i++) {
                    this._sortable[columns[i].key] = 'default';
                }
            }

            // Determine if we render the filter box
            var filtering = false;
            if (
                this.props.filterable &&
                    Array.isArray(this.props.filterable) &&
                        this.props.filterable.length > 0
            ) {
                filtering = true;
            }

            // Apply filters
            var filteredChildren = children;
            if (this.state.filter !== '') {
                filteredChildren = this.applyFilter(this.state.filter, filteredChildren);
            }

            // Determine pagination properties and which columns to display
            var itemsPerPage = 0;
            var pagination = false;
            var numPages;
            var currentPage = this.state.currentPage;

            var currentChildren = filteredChildren;
            if (this.props.itemsPerPage > 0) {
                itemsPerPage = this.props.itemsPerPage;
                numPages = Math.ceil(filteredChildren.length / itemsPerPage);

                if (currentPage > numPages - 1) {
                    currentPage = numPages - 1;
                }

                pagination = true;
                currentChildren = filteredChildren.slice(
                    currentPage * itemsPerPage,
                    (currentPage + 1) * itemsPerPage
                );
            }

            var columnWidth = this.props.columnWidth || [];
            var className = this.props.border ? 'dig-has-border' : '';
            
            // Manually transfer props
            var props = filterPropsFrom(this.props);
            if (props.className) {
                className = className + ' ' + props.className;
            }else{
                className += ' dig-table';
            }
            props.className = className;

            return <table {...props} key="table">{[
                (columns && columns.length > 0 ?
                 <Thead columns={columns}
                     filtering={filtering}
                     onFilter={this.filterBy}
                     filterPlaceholder={this.props.filterPlaceholder}
                     currentFilter={this.state.filter}
                     sort={this.state.currentSort}
                     sortableColumns={this._sortable}
                     onSort={this.onSort}
                     columnWidth={columnWidth}
                     border={this.props.border}
                     headerBg={this.props.headerBg}
                     key="thead"/>
                 : null
                ),
                <tbody className="reactable-data" key="tbody">
                    {currentChildren}
                </tbody>,
                (pagination === true ?
                 <Paginator colSpan={columns.length}
                     numPages={numPages}
                     currentPage={currentPage}
                     onPageChange={this.onPageChange}
                     key="paginator"/>
                 : null
                )
            ]}</table>;
        }
    });

    function toArray(obj) {
        var ret = [];
        for (var attr in obj) {
            ret[attr] = obj;
        }

        return ret;
    }

    function filterPropsFrom(baseProps) {
        baseProps = baseProps || {};
        var props = {};
        for (var key in baseProps) {
            if (!(key in internalProps)) {
                props[key] = baseProps[key];
            }
        }
        return props;
    }

    function extractDataFrom(key, column) {
        var value;
        if (
            typeof(key) !== 'undefined' &&
                key !== null &&
                    key.__reactableMeta === true
        ) {
            value = key.data[column];
        } else {
            value = key[column];
        }

        if (
            typeof(value) !== 'undefined' &&
                value !== null &&
                    value.__reactableMeta === true
        ) {
            value = (typeof(value.props.value) !== 'undefined' && value.props.value !== null) ?
                value.props.value : value.value;
        }

        return (stringable(value) ? value : '');
    }

    var internalProps = {
        columns: true,
        sortable: true,
        filterable: true,
        sortBy: true,
        defaultSort: true,
        itemsPerPage: true,
        childNode: true,
        data: true,
        children: true
    };

    return exports;
}));
