[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"@babel/preset-env\"]\n}\n"
  },
  {
    "path": ".circleci/config.yml",
    "content": "version: \"2.1\"\norbs:\n  browser-tools: circleci/browser-tools@1.4.0\njobs:\n  build:\n    docker:\n      - image: cimg/node:18.7.0-browsers\n    working_directory: ~/tree-multiselect\n    steps:\n      - browser-tools/install-chrome\n      - checkout\n      - restore_cache:\n          keys:\n            - v1-dependencies-{{ checksum \"package.json\" }}\n            # fallback to using the latest cache if no exact match is found\n            - v1-dependencies-\n      - run: npm install\n      - save_cache:\n          paths:\n            - node_modules\n          key: v1-dependencies-{{ checksum \"package.json\" }}\n      - run: npm test\n"
  },
  {
    "path": ".eslintrc.yml",
    "content": "env:\n  browser: true\n  es6: true\nextends: standard\nglobals:\n  jQuery: true\n  module: true\n  exports: true\n  require: true\nparserOptions:\n  sourceType: module\nrules:\n  indent:\n    - error\n    - 2\n  linebreak-style:\n    - error\n    - unix\n  quotes:\n    - error\n    - single\n  semi:\n    - error\n    - always\n  prefer-const: off\n  no-var: off\n  object-curly-spacing: off\n"
  },
  {
    "path": ".gitignore",
    "content": "bower_components/\nnode_modules/\ncoverage/\n"
  },
  {
    "path": ".npmignore",
    "content": "*\n!dist/\n!src/\n!package.json\n!LICENSE\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "const sass = require('node-sass');\n\nmodule.exports = function(grunt) {\n  grunt.initConfig({\n\n    pkg: grunt.file.readJSON('package.json'),\n\n    eslint: {\n      target: ['src/**/*.js']\n    },\n\n    browserify: {\n      dist: {\n        options: {\n          transform: ['babelify']\n        },\n\n        files: {\n          'dist/jquery.tree-multiselect.js': ['src/tree-multiselect.js']\n        }\n      }\n    },\n\n    // Karma runner\n    karma: {\n      options: {\n        configFile: 'conf/karma.js',\n      },\n\n      local: {},\n\n      watch: {\n        autoWatch: true,\n        singleRun: false\n      }\n    },\n\n    // SASS compiler\n    sass: {\n      options: {\n        implementation: sass,\n        sourceMap: false\n      },\n\n      min: {\n        options: {\n          outputStyle: 'nested'\n        },\n\n        files: {\n          'dist/jquery.tree-multiselect.css': 'sass/style.scss'\n        }\n      },\n\n      build: {\n        options: {\n          outputStyle: 'compressed'\n        },\n\n        files: {\n          'dist/jquery.tree-multiselect.min.css': 'sass/style.scss'\n        }\n      }\n    },\n\n    // Uglify JS\n    uglify: {\n      dist: {\n        options: {\n          preserveComments: false,\n        },\n        files: {\n          'dist/jquery.tree-multiselect.min.js': ['dist/jquery.tree-multiselect.js']\n        }\n      }\n    },\n\n    // Put headers on distributed files\n    usebanner: {\n      dist: {\n        options: {\n          position: 'top',\n          banner: \"/* jQuery Tree Multiselect v<%= pkg.version %> | (c) Patrick Tsai | MIT Licensed */\",\n          linebreak: true\n        },\n        files: {\n          src: ['dist/*.js', 'dist/*.css']\n        }\n      }\n    }\n  });\n\n  grunt.loadNpmTasks('grunt-banner');\n  grunt.loadNpmTasks('grunt-browserify');\n  grunt.loadNpmTasks('grunt-eslint');\n  grunt.loadNpmTasks('grunt-karma');\n  grunt.loadNpmTasks('grunt-sass');\n  grunt.loadNpmTasks('grunt-contrib-uglify');\n\n  grunt.registerTask('lint', ['eslint']);\n  grunt.registerTask('build', ['browserify']);\n\n  grunt.registerTask('test', ['lint', 'karma:local']);\n  grunt.registerTask('test-watch', ['karma:watch']);\n\n  grunt.registerTask('release', ['test', 'build', 'uglify', 'sass:build', 'sass:min', 'usebanner']);\n  grunt.registerTask('watch', ['test-watch']);\n\n  grunt.registerTask('default', 'test');\n};\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Patrick Tsai\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "## jQuery Tree Multiselect\n[![CircleCI](https://circleci.com/gh/patosai/tree-multiselect.js.svg?style=svg)](https://circleci.com/gh/patosai/tree-multiselect.js)\n[![Coverage Status](https://codecov.io/gh/patosai/tree-multiselect.js/branch/master/graph/badge.svg)](https://codecov.io/gh/patosai/tree-multiselect.js)\n[![devDependency Status](https://david-dm.org/patosai/tree-multiselect.js/dev-status.svg)](https://david-dm.org/patosai/tree-multiselect.js#info=devDependencies)\n\n\n**This plugin allows you to add a sweet treeview frontend to a `<select>` node.**\nThe underlying `<select>` node can be used as it was before. This means you can still use `$(\"select\").val()` or `selectElement.value` to get the value, as if there was no plugin. If you want to add options dynamically, please continue reading, there are some more steps you need to take.\n\n* Make sure you've got `<meta charset=\"UTF-8\">` in your `<head>` or some of the symbols may look strange.\n* Requires jQuery v1.8+\n\n![demo image](demo.jpg \"demo image\")\n\n### Demo\n<a target=\"_blank\" href=\"https://patosai.com/tree-multiselect\">My website has a simple demo running.</a>\n\n### How To Use\n1. Set the `multiple=\"multiple\"` attribute on your `<select>`\n2. Add attributes to `<option>` nodes\n3. Execute `$.treeMultiselect(params)` with whatever params you want\n\n### Setting up your `<select>`\n* Make sure your `<select>` has the `multiple` attribute set.\n\nThe `<option>` children can have the following attributes.\n\n#### Option Attributes\nOption Attribute name         | Description\n----------------------------- | ---------------------------------\n`selected`                    | Have the option pre-selected. This is actually part of the HTML spec. For specified ordering of these, use `data-index`\n`readonly`                    | User cannot modify the value of the option. Option can be selected (ex. `<option selected readonly ...`)\n`data-section`                | The section the option will be in; can be nested\n`data-description`            | A description of the attribute; will be shown on the multiselect\n`data-index`                  | For pre-selected options, display options in this order, lowest index first. Repeated items with the same index will be shown before items with a higher index. Otherwise items will be displayed in the order of the original `<select>`\n\nAll of the above are optional.\n\nYour `data-section` can have multiple section names, separated by the `sectionDelimiter` option. If you don't have a `data-section` on an option, the option will be on the top level (no section).\n\nEx. `data-section=\"top/middle/inner\"` will show up as\n- `top`\n  - `middle`\n    - `inner`\n      - your option\n\n### API\n#### `$.treeMultiselect(params)`\nRenders a tree for the given jQuery `<select>` nodes. `params` is optional.\n\n```javascript\n$(\"select\").treeMultiselect();\n```\n```javascript\nlet params = {searchable: true};\n$(\"select\").treeMultiselect(params);\n```\n```javascript\nfunction treeOnChange(allSelectedItems, addedItems, removedItems) {\n  console.log(\"something changed!\");\n}\n\n$(\"select\").treeMultiselect({\n  allowBatchSelection: false,\n  onChange: treeOnChange,\n  startCollapsed: true\n});\n```\n\n##### Params\nName                    | Default        | Description\n----------------------- | -------------- | ---------------\n`allowBatchSelection`   | `true`         | Sections have checkboxes which when checked, check everything within them\n`collapsible`           | `true`         | Adds collapsibility to sections\n`enableSelectAll`       | `false`        | Enables selection of all or no options\n`selectAllText`         | `Select All`   | Only used if `enableSelectAll` is active\n`unselectAllText`       | `Unselect All` | Only used if `enableSelectAll` is active\n`freeze`                | `false`        | Disables selection/deselection of options; aka display-only\n`hideSidePanel`         | `false`        | Hide the right panel showing all the selected items\n`maxSelections`         | `0`            | A number that sets the maximum number of options that can be selected. Any positive integer is valid; anything else (such as `0` or `-1`) means no limit\n`onChange`              | `null`         | Callback for when select is changed. Called with (allSelectedItems, addedItems, removedItems), each of which is an array of objects with the properties `text`, `value`, `initialIndex`, and `section`\n`onlyBatchSelection`    | `false`        | Only sections can be checked, not individual items\n`sortable`              | `false`        | Selected options can be sorted by dragging (requires jQuery UI)\n`searchable`            | `false`        | Allows searching of options\n`searchParams`          | `['value', 'text', 'description', 'section']` | Set items to be searched. Array must contain `'value'`, `'text'`, or `'description'`, and/or `'section'`\n`sectionDelimiter`      | `/`            | Separator between sections in the select option `data-section` attribute\n`showSectionOnSelected` | `true`         | Show section name on the selected items\n`startCollapsed`        | `false`        | Activated only if `collapsible` is true; sections are collapsed initially\n\n#### Examples\n\n\n#### `.remove()`\nRemoves the tree from the DOM. Leaves the original `<select>` intact.\n```javascript\nlet trees = $(\"select\").treeMultiselect({searchable: true});\nlet firstTree = trees[0];\nfirstTree.remove();\n```\n\n#### `.reload()`\nReinitializes the tree. You can add `<option>` children to the original `<select>` and call `.reload()` to render the new options. User-changed selections will be saved.\n\n```javascript\nlet trees = $(\"select\").treeMultiselect();\nlet firstTree = trees[0];\n\n// add an option\n$(\"select#id\").append(\"<option value='newValue' data-section='New Section' selected='selected' data-description='New value'>New Value</option>\");\nfirstTree.reload();\n```\n\n### Installation\nLoad `jquery.tree-multiselect.min.js` on to your web page. The css file is optional (but recommended).\n\nYou can also use bower - `bower install tree-multiselect`\n\n### How to build\nYou need to have grunt-cli installed so you can run the `grunt` command.\n- Run tests: `grunt` or `grunt test`\n- Build dist JavaScript file: `grunt build`\n- Build Sass: `grunt sass`\n- Build everything: `grunt release`\n\n### FAQ\n`Help! The first element is selected when I create the tree. How do I make the first element not selected?`\nYou didn't set the `multiple` attribute on your `<select>`. This is a property of single-option select nodes - the first option is selected.\n\n### License\nMIT licensed.\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"tree-multiselect\",\n  \"description\": \"jQuery multiple select with nested options\",\n  \"main\": [\n    \"src/tree-multiselect.js\",\n    \"src/style.scss\"\n  ],\n  \"authors\": [\n    \"Patrick Tsai\"\n  ],\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"tree\",\n    \"multiselect\",\n    \"select\",\n    \"jquery\",\n    \"options\",\n    \"checkbox\"\n  ],\n  \"homepage\": \"https://github.com/patosai/tree-multiselect\",\n  \"ignore\": [\n    \"*\",\n    \"!dist/\",\n    \"!src/\",\n    \"!package.json\",\n    \"!LICENSE\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/patosai/tree-multiselect.git\"\n  }\n}\n"
  },
  {
    "path": "conf/karma.js",
    "content": "module.exports = function(config) {\n  config.set({\n\n    basePath: '../',\n\n    frameworks: ['browserify', 'mocha', 'chai'],\n\n    plugins: [\n      'karma-browserify',\n      'karma-chai',\n      'karma-chrome-launcher',\n      'karma-coverage',\n      'karma-mocha',\n      'karma-mocha-reporter',\n    ],\n\n    files: [\n      'test/vendor/jquery-1.11.3.min.js',\n      'test/vendor/jquery-ui.min.js',\n      'src/tree-multiselect.js',\n      'test/**/*.test.js'\n    ],\n\n    exclude: [],\n\n    preprocessors: {\n      // browserify handles istanbul coverage\n      'src/tree-multiselect.js': ['browserify'],\n      'test/**/*.test.js': ['browserify']\n    },\n\n    reporters: ['mocha', 'coverage'],\n\n    port: 9876,\n\n    colors: true,\n\n    // config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n    logLevel: config.LOG_INFO,\n\n    autoWatch: false,\n\n    browsers: ['ChromeHeadless'],\n\n    singleRun: true,\n\n    concurrency: Infinity,\n\n    browserify: {\n      debug: true,\n\n      transform: [\n        ['babelify'],\n        ['browserify-istanbul', {\n          instrumenterConfig: {\n            embedSource: true\n          }\n        }]\n      ],\n\n      paths: [\n        'src/tree-multiselect',\n        'node_modules'\n      ]\n    },\n\n    client: {\n      mocha: {\n        fullTrace: true\n      }\n    },\n\n    mochaReporter: {\n      showDiff: true\n    },\n\n    coverageReporter: {\n      dir: 'coverage/',\n      reporters: [\n        {type: 'text-summary'},\n        {type: 'lcovonly'},\n        {type: 'html'}\n      ]\n    }\n  });\n}\n"
  },
  {
    "path": "dist/jquery.tree-multiselect.css",
    "content": "/* jQuery Tree Multiselect v2.6.3 | (c) Patrick Tsai | MIT Licensed */\ndiv.tree-multiselect {\n  border: 2px solid #D8D8D8;\n  border-radius: 5px;\n  display: table;\n  height: inherit;\n  width: 100%; }\n  div.tree-multiselect > div.selected,\n  div.tree-multiselect > div.selections {\n    display: inline-block;\n    box-sizing: border-box;\n    overflow: auto;\n    padding: 1%;\n    vertical-align: top;\n    width: 50%; }\n  div.tree-multiselect > div.selections {\n    border-right: solid 2px #D8D8D8; }\n    div.tree-multiselect > div.selections div.item {\n      margin-left: 16px; }\n      div.tree-multiselect > div.selections div.item label {\n        cursor: pointer;\n        display: inline; }\n        div.tree-multiselect > div.selections div.item label.disabled {\n          color: #D8D8D8; }\n    div.tree-multiselect > div.selections *[searchhit=false] {\n      display: none; }\n    div.tree-multiselect > div.selections.no-border {\n      border-right: none; }\n  div.tree-multiselect > div.selected > div.item {\n    background: #EAEAEA;\n    border-radius: 2px;\n    padding: 2px 5px;\n    overflow: auto; }\n  div.tree-multiselect > div.selected.ui-sortable > div.item:hover {\n    cursor: move; }\n  div.tree-multiselect div.section > div.section,\n  div.tree-multiselect div.section > div.item {\n    padding-left: 20px; }\n  div.tree-multiselect div.section.collapsed > div.title span.collapse-section:after {\n    content: \"+\"; }\n  div.tree-multiselect div.section.collapsed:not([searchhit]) > .item,\n  div.tree-multiselect div.section.collapsed:not([searchhit]) > .section {\n    display: none; }\n  div.tree-multiselect div.title,\n  div.tree-multiselect div.item {\n    margin-bottom: 2px; }\n  div.tree-multiselect div.title {\n    background: #777;\n    color: white;\n    padding: 2px; }\n    div.tree-multiselect div.title > * {\n      display: inline-block; }\n    div.tree-multiselect div.title > span.collapse-section {\n      margin: 0 3px;\n      width: 8px; }\n      div.tree-multiselect div.title > span.collapse-section:after {\n        content: \"-\"; }\n    div.tree-multiselect div.title:hover {\n      cursor: pointer; }\n  div.tree-multiselect input[type=checkbox] {\n    display: inline;\n    margin-right: 5px; }\n    div.tree-multiselect input[type=checkbox]:not([disabled]):hover {\n      cursor: pointer; }\n  div.tree-multiselect span.remove-selected,\n  div.tree-multiselect span.description {\n    background: #777;\n    border-radius: 2px;\n    color: white;\n    margin-right: 5px;\n    padding: 0 3px; }\n  div.tree-multiselect span.remove-selected:hover {\n    cursor: pointer; }\n  div.tree-multiselect span.description:hover {\n    cursor: help; }\n  div.tree-multiselect div.temp-description-popup {\n    background: #EAEAEA;\n    border: 2px solid #676767;\n    border-radius: 3px;\n    padding: 5px; }\n  div.tree-multiselect span.section-name {\n    float: right;\n    font-style: italic; }\n  div.tree-multiselect .auxiliary {\n    display: table;\n    width: 100%; }\n    div.tree-multiselect .auxiliary input.search {\n      border: 2px solid #D8D8D8;\n      display: table-cell;\n      margin: 0;\n      padding: 5px;\n      width: 100%; }\n    div.tree-multiselect .auxiliary .select-all-container {\n      display: table-cell;\n      text-align: right; }\n      div.tree-multiselect .auxiliary .select-all-container span.select-all,\n      div.tree-multiselect .auxiliary .select-all-container span.unselect-all {\n        margin-right: 5px;\n        padding-right: 5px; }\n        div.tree-multiselect .auxiliary .select-all-container span.select-all:hover,\n        div.tree-multiselect .auxiliary .select-all-container span.unselect-all:hover {\n          cursor: pointer; }\n      div.tree-multiselect .auxiliary .select-all-container span.select-all {\n        border-right: 2px solid #D8D8D8; }\n"
  },
  {
    "path": "dist/jquery.tree-multiselect.js",
    "content": "/* jQuery Tree Multiselect v2.6.3 | (c) Patrick Tsai | MIT Licensed */\n(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c=\"function\"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error(\"Cannot find module '\"+i+\"'\");throw a.code=\"MODULE_NOT_FOUND\",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u=\"function\"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){\n\"use strict\";\n\n(function ($) {\n  'use strict';\n\n  $.fn.treeMultiselect = require('./tree-multiselect/main');\n})(jQuery);\n\n},{\"./tree-multiselect/main\":6}],2:[function(require,module,exports){\n\"use strict\";\n\nvar SEARCH_HIT_ATTR = 'searchhit';\nvar SEARCH_HIT_ATTR_VAL_TRUE = 'true';\nvar SEARCH_HIT_ATTR_VAL_FALSE = 'false';\n\nexports.addSearchHitMarker = function (node, isSearchHit) {\n  if (node) {\n    isSearchHit = isSearchHit ? SEARCH_HIT_ATTR_VAL_TRUE : SEARCH_HIT_ATTR_VAL_FALSE;\n    node.setAttribute(SEARCH_HIT_ATTR, isSearchHit);\n  }\n};\n\nexports.removeSearchHitMarker = function (node, isSearchHit) {\n  if (node) {\n    node.removeAttribute(SEARCH_HIT_ATTR);\n  }\n};\n\nexports.isNotSearchHit = function (node) {\n  return node && node.getAttribute(SEARCH_HIT_ATTR) === SEARCH_HIT_ATTR_VAL_FALSE;\n};\n\n},{}],3:[function(require,module,exports){\n\"use strict\";\n\nvar Item = require('./item');\n\nvar Section = require('./section');\n\nexports.createLookup = function (arr) {\n  return {\n    arr: arr,\n    children: {}\n  };\n};\n\nexports.createSection = function (obj) {\n  return new Section(obj);\n};\n\nexports.createItem = function (obj) {\n  return new Item(obj);\n};\n\n},{\"./item\":4,\"./section\":5}],4:[function(require,module,exports){\n\"use strict\";\n\nvar AstCommon = require('./common');\n\nvar Util = require('../utility');\n\nfunction Item(obj) {\n  obj = obj || {};\n  this.treeId = obj.treeId;\n  this.id = obj.id;\n  this.value = obj.value;\n  this.text = obj.text;\n  this.description = obj.description;\n  this.initialIndex = obj.initialIndex ? parseInt(obj.initialIndex) : null;\n  this.section = obj.section;\n  this.disabled = obj.disabled;\n  this.selected = obj.selected;\n  this.node = null;\n}\n\nItem.prototype.isSection = function () {\n  return false;\n};\n\nItem.prototype.isItem = function () {\n  return true;\n};\n\nItem.prototype.addSearchHitMarker = function (isSearchHit) {\n  AstCommon.addSearchHitMarker(this.node, isSearchHit);\n};\n\nItem.prototype.removeSearchHitMarker = function (isSearchHit) {\n  AstCommon.removeSearchHitMarker(this.node, isSearchHit);\n};\n\nItem.prototype.isNotSearchHit = function () {\n  return AstCommon.isNotSearchHit(this.node);\n};\n\nItem.prototype.render = function (createCheckboxes, disableCheckboxes) {\n  if (!this.node) {\n    this.node = Util.dom.createSelection(this, createCheckboxes, disableCheckboxes);\n  }\n\n  return this.node;\n};\n\nmodule.exports = Item;\n\n},{\"../utility\":12,\"./common\":2}],5:[function(require,module,exports){\n\"use strict\";\n\nvar AstCommon = require('./common');\n\nvar Util = require('../utility');\n\nfunction Section(obj) {\n  obj = obj || {};\n  this.treeId = obj.treeId;\n  this.id = obj.id;\n  this.name = obj.name;\n  this.items = [];\n  this.node = null;\n}\n\nSection.prototype.isSection = function () {\n  return true;\n};\n\nSection.prototype.isItem = function () {\n  return false;\n};\n\nSection.prototype.addSearchHitMarker = function (isSearchHit) {\n  AstCommon.addSearchHitMarker(this.node, isSearchHit);\n};\n\nSection.prototype.removeSearchHitMarker = function (isSearchHit) {\n  AstCommon.removeSearchHitMarker(this.node, isSearchHit);\n};\n\nSection.prototype.isNotSearchHit = function () {\n  return AstCommon.isNotSearchHit(this.node);\n};\n\nSection.prototype.render = function (createCheckboxes, disableCheckboxes) {\n  if (!this.node) {\n    this.node = Util.dom.createSection(this, createCheckboxes, disableCheckboxes);\n  }\n\n  return this.node;\n};\n\nmodule.exports = Section;\n\n},{\"../utility\":12,\"./common\":2}],6:[function(require,module,exports){\n\"use strict\";\n\nvar Tree = require('./tree');\n\nvar uniqueId = 0;\n\nfunction treeMultiselect(opts) {\n  var _this = this;\n\n  var options = mergeDefaultOptions(opts);\n  return this.map(function () {\n    var $originalSelect = _this;\n    $originalSelect.attr('multiple', '').css('display', 'none');\n    var tree = new Tree(uniqueId, $originalSelect, options);\n    tree.initialize();\n    ++uniqueId;\n    return {\n      reload: function reload() {\n        tree.reload();\n      },\n      remove: function remove() {\n        tree.remove();\n      }\n    };\n  });\n}\n\n;\n\nfunction mergeDefaultOptions(options) {\n  var defaults = {\n    allowBatchSelection: true,\n    collapsible: true,\n    enableSelectAll: false,\n    selectAllText: 'Select All',\n    unselectAllText: 'Unselect All',\n    freeze: false,\n    hideSidePanel: false,\n    maxSelections: 0,\n    onChange: null,\n    onlyBatchSelection: false,\n    searchable: false,\n    searchParams: ['value', 'text', 'description', 'section'],\n    sectionDelimiter: '/',\n    showSectionOnSelected: true,\n    sortable: false,\n    startCollapsed: false\n  };\n  return jQuery.extend({}, defaults, options);\n}\n\nmodule.exports = treeMultiselect;\n\n},{\"./tree\":8}],7:[function(require,module,exports){\n\"use strict\";\n\nvar Util = require('./utility');\n\nvar MAX_SAMPLE_SIZE = 3;\n\nfunction Search(searchHitAttr, astItems, astSections, searchParams) {\n  this.searchHitAttr = searchHitAttr;\n  this.index = {}; // key: at most three-letter combinations, value: array of data-key\n  // key: data-key, value: DOM node\n\n  this.astItems = astItems;\n  this.astItemKeys = Object.keys(astItems);\n  this.astSections = astSections;\n  this.astSectionKeys = Object.keys(astSections);\n  this.setSearchParams(searchParams);\n  this.buildIndex();\n}\n\nSearch.prototype.setSearchParams = function (searchParams) {\n  Util.assert(Array.isArray(searchParams));\n  var allowedParams = {\n    value: true,\n    text: true,\n    description: true,\n    section: true\n  };\n  this.searchParams = [];\n\n  for (var ii = 0; ii < searchParams.length; ++ii) {\n    if (allowedParams[searchParams[ii]]) {\n      this.searchParams.push(searchParams[ii]);\n    }\n  }\n};\n\nSearch.prototype.buildIndex = function () {\n  var _this = this;\n\n  var _loop = function _loop(astItemKey) {\n    var astItem = _this.astItems[astItemKey];\n    var searchItems = [];\n\n    _this.searchParams.forEach(function (searchParam) {\n      searchItems.push(astItem[searchParam]);\n    });\n\n    Util.array.removeFalseyExceptZero(searchItems);\n    var searchWords = searchItems.map(function (item) {\n      return item.toLowerCase();\n    });\n    searchWords.forEach(function (searchWord) {\n      var words = searchWord.split(' ');\n      words.forEach(function (word) {\n        _this._addToIndex(word, astItem.id);\n      });\n    });\n  };\n\n  // trigrams\n  for (var astItemKey in this.astItems) {\n    _loop(astItemKey);\n  }\n};\n\nSearch.prototype._addToIndex = function (key, id) {\n  for (var sampleSize = 1; sampleSize <= MAX_SAMPLE_SIZE; ++sampleSize) {\n    for (var startOffset = 0; startOffset < key.length - sampleSize + 1; ++startOffset) {\n      var minikey = key.substring(startOffset, startOffset + sampleSize);\n\n      if (!this.index[minikey]) {\n        this.index[minikey] = [];\n      } // don't duplicate\n      // this takes advantage of the fact that the minikeys with same id's are added sequentially\n\n\n      var length = this.index[minikey].length;\n\n      if (length === 0 || this.index[minikey][length - 1] !== id) {\n        this.index[minikey].push(id);\n      }\n    }\n  }\n};\n\nSearch.prototype.search = function (value) {\n  var _this2 = this;\n\n  value = value.trim();\n\n  if (!value) {\n    this.astItemKeys.forEach(function (id) {\n      _this2.astItems[id].removeSearchHitMarker();\n    });\n    this.astSectionKeys.forEach(function (id) {\n      _this2.astSections[id].removeSearchHitMarker();\n    });\n    return;\n  }\n\n  value = value.toLowerCase();\n  var searchWords = value.split(' ').filter(function (word) {\n    return word;\n  });\n  var searchChunks = [];\n  searchWords.forEach(function (searchWord) {\n    var chunks = splitWord(searchWord);\n    chunks.forEach(function (chunk) {\n      searchChunks.push(_this2.index[chunk] || []);\n    });\n  });\n  var matchedNodeIds = Util.array.intersectMany(searchChunks); // now we have id's that match search query\n\n  this.handleNodeVisibilities(matchedNodeIds);\n};\n\nSearch.prototype.handleNodeVisibilities = function (shownNodeIds) {\n  var _this3 = this;\n\n  var shownNodeIdsHash = {};\n  var sectionsToNotHideHash = {};\n  shownNodeIds.forEach(function (id) {\n    shownNodeIdsHash[id] = true;\n    var node = _this3.astItems[id].node; // now search for parent sections\n\n    node = node.parentNode;\n\n    while (!node.className.match(/tree-multiselect/)) {\n      if (node.className.match(/section/)) {\n        var key = Util.getKey(node);\n        Util.assert(key || key === 0);\n\n        if (sectionsToNotHideHash[key]) {\n          break;\n        } else {\n          sectionsToNotHideHash[key] = true;\n        }\n      }\n\n      node = node.parentNode;\n    }\n  }); // hide selections\n\n  this.astItemKeys.forEach(function (id) {\n    var isSearchHit = !!shownNodeIdsHash[id];\n\n    _this3.astItems[id].addSearchHitMarker(isSearchHit);\n  });\n  this.astSectionKeys.forEach(function (id) {\n    var isSearchHit = !!sectionsToNotHideHash[id];\n\n    _this3.astSections[id].addSearchHitMarker(isSearchHit);\n  });\n}; // split word into three letter (or less) pieces\n\n\nfunction splitWord(word) {\n  Util.assert(word);\n\n  if (word.length < MAX_SAMPLE_SIZE) {\n    return [word];\n  }\n\n  var chunks = [];\n\n  for (var ii = 0; ii < word.length - MAX_SAMPLE_SIZE + 1; ++ii) {\n    chunks.push(word.substring(ii, ii + MAX_SAMPLE_SIZE));\n  }\n\n  return chunks;\n}\n\nmodule.exports = Search;\n\n},{\"./utility\":12}],8:[function(require,module,exports){\n\"use strict\";\n\nfunction _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }\n\nfunction _nonIterableSpread() { throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _iterableToArray(iter) { if (typeof Symbol !== \"undefined\" && iter[Symbol.iterator] != null || iter[\"@@iterator\"] != null) return Array.from(iter); }\n\nfunction _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nvar Ast = require('./ast');\n\nvar Search = require('./search');\n\nvar UiBuilder = require('./ui-builder');\n\nvar Util = require('./utility');\n\nvar SEARCH_HIT_ATTR = 'searchhit';\n\nfunction Tree(id, $originalSelect, params) {\n  this.id = id;\n  this.$originalSelect = $originalSelect;\n  this.params = params;\n  this.resetState();\n}\n\nTree.prototype.initialize = function () {\n  this.generateSelections(this.$selectionContainer[0]);\n  this.popupDescriptionHover();\n\n  if (this.params.allowBatchSelection) {\n    this.handleSectionCheckboxMarkings();\n  }\n\n  if (this.params.collapsible) {\n    this.addCollapsibility();\n  }\n\n  if (this.params.searchable || this.params.enableSelectAll) {\n    var auxiliaryBox = Util.dom.createNode('div', {\n      \"class\": 'auxiliary'\n    });\n    this.$selectionContainer.prepend(auxiliaryBox, this.$selectionContainer.firstChild);\n\n    if (this.params.searchable) {\n      this.createSearchBar(auxiliaryBox);\n    }\n\n    if (this.params.enableSelectAll) {\n      this.createSelectAllButtons(auxiliaryBox);\n    }\n  }\n\n  this.armRemoveSelectedOnClick();\n  this.updateSelectedAndOnChange();\n  this.render(true);\n  this.uiBuilder.attach();\n};\n\nTree.prototype.remove = function () {\n  this.uiBuilder.remove();\n  this.resetState();\n};\n\nTree.prototype.reload = function () {\n  var _this = this;\n\n  var selectedOptions = {};\n  this.selectedKeys.forEach(function (key) {\n    var value = _this.astItems[key].value;\n    selectedOptions[value] = true;\n  });\n  this.remove();\n  this.$originalSelect.children('option').each(function (idx, element) {\n    var value = element.value;\n\n    if (selectedOptions[value]) {\n      element.setAttribute('selected', 'selected');\n    } else {\n      element.removeAttribute('selected');\n    }\n  });\n  this.initialize();\n  this.render(true);\n};\n\nTree.prototype.resetState = function () {\n  this.uiBuilder = new UiBuilder(this.$originalSelect, this.params.hideSidePanel);\n  this.$treeContainer = this.uiBuilder.$treeContainer;\n  this.$selectionContainer = this.uiBuilder.$selectionContainer;\n  this.$selectedContainer = this.uiBuilder.$selectedContainer; // data-key is key, provides DOM node\n\n  this.astItems = {};\n  this.astSections = {};\n  this.selectedNodes = {};\n  this.selectedKeys = [];\n  this.keysToAdd = [];\n  this.keysToRemove = [];\n};\n\nTree.prototype.generateSelections = function (parentNode) {\n  var options = this.$originalSelect.children('option');\n  var ast = this.createAst(options);\n  this.generateHtml(ast, parentNode);\n};\n\nTree.prototype.createAst = function (options) {\n  var _this$keysToAdd;\n\n  var data = [];\n  var lookup = Ast.createLookup(data);\n  var self = this;\n  var itemId = 0;\n  var sectionId = 0;\n  var initialIndexItems = [];\n  var keysToAddAtEnd = [];\n  options.each(function () {\n    var option = this;\n    option.setAttribute('data-key', itemId);\n    var item = Ast.createItem({\n      treeId: self.id,\n      id: itemId,\n      value: option.value,\n      text: option.text,\n      description: option.getAttribute('data-description'),\n      initialIndex: option.getAttribute('data-index'),\n      section: option.getAttribute('data-section'),\n      disabled: option.hasAttribute('readonly'),\n      selected: option.hasAttribute('selected')\n    });\n\n    if (item.initialIndex && item.selected) {\n      initialIndexItems[item.initialIndex] = initialIndexItems[item.initialIndex] || [];\n      initialIndexItems[item.initialIndex].push(itemId);\n    } else if (item.selected) {\n      keysToAddAtEnd.push(itemId);\n    }\n\n    self.astItems[itemId] = item;\n    ++itemId;\n    var lookupPosition = lookup;\n    var section = item.section;\n    var sectionParts = section && section.length > 0 ? section.split(self.params.sectionDelimiter) : [];\n\n    for (var ii = 0; ii < sectionParts.length; ++ii) {\n      var sectionPart = sectionParts[ii];\n\n      if (lookupPosition.children[sectionPart]) {\n        lookupPosition = lookupPosition.children[sectionPart];\n      } else {\n        var newSection = Ast.createSection({\n          treeId: self.id,\n          id: sectionId,\n          name: sectionPart\n        });\n        ++sectionId;\n        lookupPosition.arr.push(newSection);\n        var newLookupNode = Ast.createLookup(newSection.items);\n        lookupPosition.children[sectionPart] = newLookupNode;\n        lookupPosition = newLookupNode;\n      }\n    }\n\n    lookupPosition.arr.push(item);\n  });\n  this.keysToAdd = Util.array.flatten(initialIndexItems);\n  Util.array.removeFalseyExceptZero(this.keysToAdd);\n\n  (_this$keysToAdd = this.keysToAdd).push.apply(_this$keysToAdd, keysToAddAtEnd);\n\n  Util.array.uniq(this.keysToAdd);\n  return data;\n};\n\nTree.prototype.generateHtml = function (astArr, parentNode) {\n  for (var ii = 0; ii < astArr.length; ++ii) {\n    var astObj = astArr[ii];\n\n    if (astObj.isSection()) {\n      this.astSections[astObj.id] = astObj;\n      var createCheckboxes = this.params.allowBatchSelection;\n      var disableCheckboxes = this.params.freeze;\n      var node = astObj.render(createCheckboxes, disableCheckboxes);\n      parentNode.appendChild(node);\n      this.generateHtml(astObj.items, node);\n    } else if (astObj.isItem()) {\n      this.astItems[astObj.id] = astObj;\n\n      var _createCheckboxes = !this.params.onlyBatchSelection;\n\n      var _disableCheckboxes = this.params.freeze;\n\n      var _node = astObj.render(_createCheckboxes, _disableCheckboxes);\n\n      parentNode.appendChild(_node);\n    }\n  }\n};\n\nTree.prototype.popupDescriptionHover = function () {\n  this.$selectionContainer.on('mouseenter', 'div.item > span.description', function () {\n    var $item = jQuery(this).parent();\n    var description = $item.attr('data-description');\n    var descriptionDiv = document.createElement('div');\n    descriptionDiv.className = 'temp-description-popup';\n    descriptionDiv.innerHTML = description;\n    descriptionDiv.style.position = 'absolute';\n    $item.append(descriptionDiv);\n  });\n  this.$selectionContainer.on('mouseleave', 'div.item > span.description', function () {\n    var $item = jQuery(this).parent();\n    $item.find('div.temp-description-popup').remove();\n  });\n};\n\nTree.prototype.handleSectionCheckboxMarkings = function () {\n  var self = this;\n  this.$selectionContainer.on('click', 'input.section[type=checkbox]', function () {\n    var $section = jQuery(this).closest('div.section');\n    var $items = $section.find('div.item');\n    var keys = $items.map(function (idx, el) {\n      var key = Util.getKey(el);\n      var astItem = self.astItems[key];\n\n      if (!astItem.disabled && !astItem.isNotSearchHit()) {\n        return key;\n      }\n\n      return null;\n    }).get();\n\n    if (this.checked) {\n      var _self$keysToAdd;\n\n      // TODO why does this always take this branch\n      (_self$keysToAdd = self.keysToAdd).push.apply(_self$keysToAdd, _toConsumableArray(keys));\n\n      Util.array.uniq(self.keysToAdd);\n    } else {\n      var _self$keysToRemove;\n\n      (_self$keysToRemove = self.keysToRemove).push.apply(_self$keysToRemove, _toConsumableArray(keys));\n\n      Util.array.uniq(self.keysToRemove);\n    }\n\n    self.render();\n  });\n};\n\nTree.prototype.redrawSectionCheckboxes = function ($section) {\n  $section = $section || this.$selectionContainer; // returns array; bit 1 is all children are true, bit 0 is all children are false\n\n  var returnVal = 3;\n  var self = this;\n  var $childSections = $section.find('> div.section');\n  $childSections.each(function () {\n    var result = self.redrawSectionCheckboxes(jQuery(this));\n    returnVal &= result;\n  });\n\n  if (returnVal) {\n    var $childCheckboxes = $section.find('> div.item > input[type=checkbox]');\n\n    for (var ii = 0; ii < $childCheckboxes.length; ++ii) {\n      if ($childCheckboxes[ii].checked) {\n        returnVal &= ~2;\n      } else {\n        returnVal &= ~1;\n      }\n\n      if (returnVal === 0) {\n        break;\n      }\n    }\n  }\n\n  var sectionCheckbox = $section.find('> div.title > input[type=checkbox]');\n\n  if (sectionCheckbox.length) {\n    sectionCheckbox = sectionCheckbox[0];\n\n    if (returnVal & 1) {\n      sectionCheckbox.checked = true;\n      sectionCheckbox.indeterminate = false;\n    } else if (returnVal & 2) {\n      sectionCheckbox.checked = false;\n      sectionCheckbox.indeterminate = false;\n    } else {\n      sectionCheckbox.checked = false;\n      sectionCheckbox.indeterminate = true;\n    }\n  }\n\n  return returnVal;\n};\n\nTree.prototype.addCollapsibility = function () {\n  var titleSelector = 'div.title';\n  var $titleDivs = this.$selectionContainer.find(titleSelector);\n  var collapseSpan = Util.dom.createNode('span', {\n    \"class\": 'collapse-section'\n  });\n  $titleDivs.prepend(collapseSpan);\n  var sectionSelector = 'div.section';\n  var $sectionDivs = this.$selectionContainer.find(sectionSelector);\n\n  if (this.params.startCollapsed) {\n    $sectionDivs.addClass('collapsed');\n  }\n\n  this.$selectionContainer.on('click', titleSelector, function (event) {\n    if (event.target.nodeName === 'INPUT') {\n      return;\n    }\n\n    var $section = jQuery(this).parent();\n    $section.toggleClass('collapsed');\n    event.stopPropagation();\n  });\n};\n\nTree.prototype.createSearchBar = function (parentNode) {\n  var searchObj = new Search(SEARCH_HIT_ATTR, this.astItems, this.astSections, this.params.searchParams);\n  var searchNode = Util.dom.createNode('input', {\n    \"class\": 'search',\n    placeholder: 'Search...'\n  });\n  parentNode.appendChild(searchNode);\n  this.$selectionContainer.on('input', 'input.search', function () {\n    var searchText = this.value;\n    searchObj.search(searchText);\n  });\n};\n\nTree.prototype.createSelectAllButtons = function (parentNode) {\n  var selectAllNode = Util.dom.createNode('span', {\n    \"class\": 'select-all',\n    text: this.params.selectAllText\n  });\n  var unselectAllNode = Util.dom.createNode('span', {\n    \"class\": 'unselect-all',\n    text: this.params.unselectAllText\n  });\n  var selectAllContainer = Util.dom.createNode('div', {\n    \"class\": 'select-all-container'\n  });\n  selectAllContainer.appendChild(selectAllNode);\n  selectAllContainer.appendChild(unselectAllNode);\n  parentNode.appendChild(selectAllContainer);\n  var self = this;\n  this.$selectionContainer.on('click', 'span.select-all', function () {\n    var _self$keysToAdd2;\n\n    (_self$keysToAdd2 = self.keysToAdd).push.apply(_self$keysToAdd2, _toConsumableArray(self.unfilteredNodeIds()));\n\n    self.render();\n  });\n  this.$selectionContainer.on('click', 'span.unselect-all', function () {\n    var _self$keysToRemove2;\n\n    (_self$keysToRemove2 = self.keysToRemove).push.apply(_self$keysToRemove2, _toConsumableArray(self.unfilteredNodeIds()));\n\n    self.render();\n  });\n};\n\nTree.prototype.unfilteredNodeIds = function () {\n  var self = this;\n  return Object.keys(self.astItems).filter(function (key) {\n    return !self.astItems[key].node.hasAttribute(SEARCH_HIT_ATTR) || self.astItems[key].node.getAttribute(SEARCH_HIT_ATTR) === 'true';\n  });\n};\n\nTree.prototype.armRemoveSelectedOnClick = function () {\n  var self = this;\n  this.$selectedContainer.on('click', 'span.remove-selected', function () {\n    var parentNode = this.parentNode;\n    var key = Util.getKey(parentNode);\n    self.keysToRemove.push(key);\n    self.render();\n  });\n};\n\nTree.prototype.updateSelectedAndOnChange = function () {\n  var self = this;\n  this.$selectionContainer.on('click', 'input.option[type=checkbox]', function () {\n    var checkbox = this;\n    var selection = checkbox.parentNode;\n    var key = Util.getKey(selection);\n    Util.assert(key || key === 0);\n\n    if (checkbox.checked) {\n      self.keysToAdd.push(key);\n    } else {\n      self.keysToRemove.push(key);\n    }\n\n    self.render();\n  });\n\n  if (this.params.sortable && !this.params.freeze) {\n    var startIndex = null;\n    var endIndex = null;\n    this.$selectedContainer.sortable({\n      start: function start(event, ui) {\n        startIndex = ui.item.index();\n      },\n      stop: function stop(event, ui) {\n        endIndex = ui.item.index();\n\n        if (startIndex === endIndex) {\n          return;\n        }\n\n        Util.array.moveEl(self.selectedKeys, startIndex, endIndex);\n        self.render();\n      }\n    });\n  }\n};\n\nTree.prototype.render = function (noCallbacks) {\n  var _this$selectedKeys,\n      _this2 = this;\n\n  // fix arrays first\n  Util.array.uniq(this.keysToAdd);\n  Util.array.uniq(this.keysToRemove);\n  Util.array.subtract(this.keysToAdd, this.selectedKeys);\n  Util.array.intersect(this.keysToRemove, this.selectedKeys); // check for max number of selections\n\n  if (Util.isInteger(this.params.maxSelections) && this.params.maxSelections > 0) {\n    var currentLength = this.keysToAdd.length - this.keysToRemove.length + this.selectedKeys.length;\n\n    if (currentLength > this.params.maxSelections) {\n      var _this$keysToRemove;\n\n      var lengthToCut = currentLength - this.params.maxSelections;\n      var keysToCut = [];\n\n      if (lengthToCut > this.selectedKeys.length) {\n        keysToCut.push.apply(keysToCut, _toConsumableArray(this.selectedKeys));\n        lengthToCut -= this.selectedKeys.length;\n        keysToCut.push.apply(keysToCut, _toConsumableArray(this.keysToAdd.splice(0, lengthToCut)));\n      } else {\n        keysToCut.push.apply(keysToCut, _toConsumableArray(this.selectedKeys.slice(0, lengthToCut)));\n      }\n\n      (_this$keysToRemove = this.keysToRemove).push.apply(_this$keysToRemove, keysToCut);\n    }\n  } // remove items first\n\n\n  for (var ii = 0; ii < this.keysToRemove.length; ++ii) {\n    // remove the selected divs\n    var node = this.selectedNodes[this.keysToRemove[ii]];\n\n    if (node) {\n      // slightly more verbose than node.remove(), but more browser support\n      node.parentNode.removeChild(node);\n      this.selectedNodes[this.keysToRemove[ii]] = null;\n    } // uncheck these checkboxes\n\n\n    var selectionNode = this.astItems[this.keysToRemove[ii]].node;\n    selectionNode.getElementsByTagName('INPUT')[0].checked = false;\n  }\n\n  Util.array.subtract(this.selectedKeys, this.keysToRemove); // now add items\n\n  for (var jj = 0; jj < this.keysToAdd.length; ++jj) {\n    // create selected divs\n    var key = this.keysToAdd[jj];\n    var astItem = this.astItems[key];\n    this.selectedKeys.push(key);\n    var selectedNode = Util.dom.createSelected(astItem, this.params.freeze, this.params.showSectionOnSelected);\n    this.selectedNodes[astItem.id] = selectedNode;\n    this.$selectedContainer.append(selectedNode); // check the checkboxes\n\n    var inputNode = astItem.node.getElementsByTagName('INPUT')[0];\n\n    if (inputNode) {\n      inputNode.checked = true;\n    }\n  }\n\n  (_this$selectedKeys = this.selectedKeys).push.apply(_this$selectedKeys, _toConsumableArray(this.keysToAdd));\n\n  Util.array.uniq(this.selectedKeys); // redraw section checkboxes\n\n  this.redrawSectionCheckboxes(); // now fix original select\n\n  var originalValsHash = {}; // valHash hashes a value to an index\n\n  var valHash = {};\n\n  for (var kk = 0; kk < this.selectedKeys.length; ++kk) {\n    var value = this.astItems[this.selectedKeys[kk]].value;\n    originalValsHash[this.selectedKeys[kk]] = true;\n    valHash[value] = kk;\n  } // TODO is there a better way to sort the values other than by HTML?\n  // NOTE: the following does not work since jQuery duplicates option values with the same value\n  // this.$originalSelect.val(vals);\n\n\n  var options = this.$originalSelect.find('option').toArray();\n  options.sort(function (a, b) {\n    var aValue = valHash[a.value] || 0;\n    var bValue = valHash[b.value] || 0;\n    return aValue - bValue;\n  });\n  this.$originalSelect.html(options);\n  this.$originalSelect.find('option').each(function (idx, el) {\n    this.selected = !!originalValsHash[Util.getKey(el)];\n  });\n  this.$originalSelect.change();\n\n  if (!noCallbacks && this.params.onChange) {\n    var optionsSelected = this.selectedKeys.map(function (key) {\n      return _this2.astItems[key];\n    });\n    var optionsAdded = this.keysToAdd.map(function (key) {\n      return _this2.astItems[key];\n    });\n    var optionsRemoved = this.keysToRemove.map(function (key) {\n      return _this2.astItems[key];\n    });\n    this.params.onChange(optionsSelected, optionsAdded, optionsRemoved);\n  }\n\n  this.keysToRemove = [];\n  this.keysToAdd = [];\n};\n\nmodule.exports = Tree;\n\n},{\"./ast\":3,\"./search\":7,\"./ui-builder\":9,\"./utility\":12}],9:[function(require,module,exports){\n\"use strict\";\n\nfunction UiBuilder($el, hideSidePanel) {\n  var $tree = jQuery('<div class=\"tree-multiselect\"></div>');\n  var $selections = jQuery('<div class=\"selections\"></div>');\n\n  if (hideSidePanel) {\n    $selections.addClass('no-border');\n  }\n\n  $tree.append($selections);\n  var $selected = jQuery('<div class=\"selected\"></div>');\n\n  if (!hideSidePanel) {\n    $tree.append($selected);\n  }\n\n  this.$el = $el;\n  this.$treeContainer = $tree;\n  this.$selectionContainer = $selections;\n  this.$selectedContainer = $selected;\n}\n\nUiBuilder.prototype.attach = function () {\n  this.$el.after(this.$treeContainer);\n};\n\nUiBuilder.prototype.remove = function () {\n  this.$treeContainer.remove();\n};\n\nmodule.exports = UiBuilder;\n\n},{}],10:[function(require,module,exports){\n\"use strict\";\n\n// keeps if pred is true\nfunction filterInPlace(arr, pred) {\n  var idx = 0;\n\n  for (var ii = 0; ii < arr.length; ++ii) {\n    if (pred(arr[ii])) {\n      arr[idx] = arr[ii];\n      ++idx;\n    }\n  }\n\n  arr.length = idx;\n}\n\nexports.flatten = function (arr, r) {\n  if (!Array.isArray(arr)) {\n    return arr;\n  }\n\n  r = r || [];\n\n  for (var ii = 0; ii < arr.length; ++ii) {\n    if (Array.isArray(arr[ii])) {\n      r.concat(exports.flatten(arr[ii], r));\n    } else {\n      r.push(arr[ii]);\n    }\n  }\n\n  return r;\n};\n\nexports.uniq = function (arr) {\n  var hash = {};\n\n  var pred = function pred(val) {\n    var returnVal = !hash[val];\n    hash[val] = true;\n    return returnVal;\n  };\n\n  filterInPlace(arr, pred);\n};\n\nexports.removeFalseyExceptZero = function (arr) {\n  var pred = function pred(val) {\n    return val || val === 0;\n  };\n\n  filterInPlace(arr, pred);\n};\n\nexports.moveEl = function (arr, oldPos, newPos) {\n  var el = arr[oldPos];\n  arr.splice(oldPos, 1);\n  arr.splice(newPos, 0, el);\n};\n\nexports.subtract = function (arr, arrExcluded) {\n  var hash = {};\n\n  for (var ii = 0; ii < arrExcluded.length; ++ii) {\n    hash[arrExcluded[ii]] = true;\n  }\n\n  var pred = function pred(val) {\n    return !hash[val];\n  };\n\n  filterInPlace(arr, pred);\n};\n\nexports.intersect = function (arr, arrExcluded) {\n  var hash = {};\n\n  for (var ii = 0; ii < arrExcluded.length; ++ii) {\n    hash[arrExcluded[ii]] = true;\n  }\n\n  var pred = function pred(val) {\n    return hash[val];\n  };\n\n  filterInPlace(arr, pred);\n}; // takes in array of arrays\n// arrays are presorted\n\n\nexports.intersectMany = function (arrays) {\n  var indexLocations = [];\n  var maxIndexLocations = [];\n  arrays.forEach(function (array) {\n    indexLocations.push(0);\n    maxIndexLocations.push(array.length - 1);\n  });\n  var finalOutput = [];\n\n  for (; indexLocations.length > 0 && indexLocations[0] <= maxIndexLocations[0]; ++indexLocations[0]) {\n    // advance indices to be at least equal to first array element\n    var terminate = false;\n\n    for (var ii = 1; ii < arrays.length; ++ii) {\n      while (arrays[ii][indexLocations[ii]] < arrays[0][indexLocations[0]] && indexLocations[ii] <= maxIndexLocations[ii]) {\n        ++indexLocations[ii];\n      }\n\n      if (indexLocations[ii] > maxIndexLocations[ii]) {\n        terminate = true;\n        break;\n      }\n    }\n\n    if (terminate) {\n      break;\n    } // check element equality\n\n\n    var shouldAdd = true;\n\n    for (var jj = 1; jj < arrays.length; ++jj) {\n      if (arrays[0][indexLocations[0]] !== arrays[jj][indexLocations[jj]]) {\n        shouldAdd = false;\n        break;\n      }\n    }\n\n    if (shouldAdd) {\n      finalOutput.push(arrays[0][indexLocations[0]]);\n    }\n  }\n\n  return finalOutput;\n};\n\n},{}],11:[function(require,module,exports){\n\"use strict\";\n\nexports.createNode = function (tag, props) {\n  var node = document.createElement(tag);\n\n  if (props) {\n    for (var key in props) {\n      if (Object.prototype.hasOwnProperty.call(props, key) && key !== 'text') {\n        node.setAttribute(key, props[key]);\n      }\n    }\n\n    if (props.text) {\n      node.textContent = props.text;\n    }\n  }\n\n  return node;\n};\n\nexports.createSelection = function (astItem, createCheckboxes, disableCheckboxes) {\n  var props = {\n    \"class\": 'item',\n    'data-key': astItem.id,\n    'data-value': astItem.value\n  };\n  var hasDescription = !!astItem.description;\n\n  if (hasDescription) {\n    props['data-description'] = astItem.description;\n  }\n\n  if (astItem.initialIndex) {\n    props['data-index'] = astItem.initialIndex;\n  }\n\n  var selectionNode = exports.createNode('div', props);\n\n  if (hasDescription) {\n    var popup = exports.createNode('span', {\n      \"class\": 'description',\n      text: '?'\n    });\n    selectionNode.appendChild(popup);\n  }\n\n  if (!createCheckboxes) {\n    selectionNode.innerText = astItem.text || astItem.value;\n  } else {\n    var optionLabelCheckboxId = \"treemultiselect-\".concat(astItem.treeId, \"-\").concat(astItem.id);\n    var inputCheckboxProps = {\n      \"class\": 'option',\n      type: 'checkbox',\n      id: optionLabelCheckboxId\n    };\n\n    if (disableCheckboxes || astItem.disabled) {\n      inputCheckboxProps.disabled = true;\n    }\n\n    var inputCheckbox = exports.createNode('input', inputCheckboxProps); // prepend child\n\n    selectionNode.insertBefore(inputCheckbox, selectionNode.firstChild);\n    var labelProps = {\n      \"class\": astItem.disabled ? 'disabled' : '',\n      \"for\": optionLabelCheckboxId,\n      text: astItem.text || astItem.value\n    };\n    var label = exports.createNode('label', labelProps);\n    selectionNode.appendChild(label);\n  }\n\n  return selectionNode;\n};\n\nexports.createSelected = function (astItem, disableRemoval, showSectionOnSelected) {\n  var node = exports.createNode('div', {\n    \"class\": 'item',\n    'data-key': astItem.id,\n    'data-value': astItem.value,\n    text: astItem.text\n  });\n\n  if (!disableRemoval && !astItem.disabled) {\n    var removalSpan = exports.createNode('span', {\n      \"class\": 'remove-selected',\n      text: '×'\n    });\n    node.insertBefore(removalSpan, node.firstChild);\n  }\n\n  if (showSectionOnSelected) {\n    var sectionSpan = exports.createNode('span', {\n      \"class\": 'section-name',\n      text: astItem.section\n    });\n    node.appendChild(sectionSpan);\n  }\n\n  return node;\n};\n\nexports.createSection = function (astSection, createCheckboxes, disableCheckboxes) {\n  var sectionNode = exports.createNode('div', {\n    \"class\": 'section',\n    'data-key': astSection.id\n  });\n  var titleNode = exports.createNode('div', {\n    \"class\": 'title',\n    text: astSection.name\n  });\n\n  if (createCheckboxes) {\n    var checkboxProps = {\n      \"class\": 'section',\n      type: 'checkbox'\n    };\n\n    if (disableCheckboxes) {\n      checkboxProps.disabled = true;\n    }\n\n    var checkboxNode = exports.createNode('input', checkboxProps);\n    titleNode.insertBefore(checkboxNode, titleNode.firstChild);\n  }\n\n  sectionNode.appendChild(titleNode);\n  return sectionNode;\n};\n\n},{}],12:[function(require,module,exports){\n\"use strict\";\n\nexports.array = require('./array');\n\nexports.assert = function (bool, message) {\n  if (!bool) {\n    throw new Error(message || 'Assertion failed');\n  }\n};\n\nexports.dom = require('./dom');\n\nexports.getKey = function (el) {\n  exports.assert(el);\n  return parseInt(el.getAttribute('data-key'));\n};\n\nexports.isInteger = function (value) {\n  var x;\n\n  if (isNaN(value)) {\n    return false;\n  }\n\n  x = parseFloat(value);\n  return (x | 0) === x;\n};\n\n},{\"./array\":10,\"./dom\":11}]},{},[1]);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"tree-multiselect\",\n  \"version\": \"2.6.3\",\n  \"description\": \"jQuery multiple select with nested options\",\n  \"browser\": \"dist/jquery.tree-multiselect.min.js\",\n  \"keywords\": [\n    \"tree\",\n    \"multiselect\",\n    \"select\",\n    \"jquery\",\n    \"options\",\n    \"checkbox\"\n  ],\n  \"scripts\": {\n    \"release\": \"grunt release\",\n    \"test\": \"grunt test\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/patosai/tree-multiselect.git\"\n  },\n  \"author\": \"Patrick Tsai\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/patosai/tree-multiselect/issues\"\n  },\n  \"homepage\": \"https://github.com/patosai/tree-multiselect#readme\",\n  \"devDependencies\": {\n    \"@babel/preset-env\": \"^7.18.10\",\n    \"babel-core\": \"^6.26.3\",\n    \"babelify\": \"^10.0.0\",\n    \"browserify\": \"^17.0.0\",\n    \"browserify-istanbul\": \"^3.0.1\",\n    \"chai\": \"^4.3.6\",\n    \"codecov\": \"^3.8.3\",\n    \"eslint\": \"^8.22.0\",\n    \"eslint-config-standard\": \"^17.0.0\",\n    \"eslint-plugin-import\": \"^2.26.0\",\n    \"eslint-plugin-node\": \"^11.1.0\",\n    \"eslint-plugin-promise\": \"^6.0.0\",\n    \"grunt\": \"^1.5.3\",\n    \"grunt-banner\": \"^0.6.0\",\n    \"grunt-browserify\": \"^6.0.0\",\n    \"grunt-contrib-uglify\": \"^5.2.2\",\n    \"grunt-coveralls\": \"^2.0.0\",\n    \"grunt-eslint\": \"^24.0.0\",\n    \"grunt-karma\": \"^4.0.2\",\n    \"grunt-sass\": \"^3.1.0\",\n    \"karma\": \"^6.4.0\",\n    \"karma-browserify\": \"^8.1.0\",\n    \"karma-chai\": \"^0.1.0\",\n    \"karma-chrome-launcher\": \"^3.1.1\",\n    \"karma-coverage\": \"^2.2.0\",\n    \"karma-mocha\": \"^2.0.1\",\n    \"karma-mocha-reporter\": \"^2.2.5\",\n    \"mocha\": \"^10.0.0\",\n    \"node-sass\": \"^7.0.1\"\n  }\n}\n"
  },
  {
    "path": "release.sh",
    "content": "#!/usr/bin/env bash\nset -x\nVERSION=$(node -e \"console.log(require('./package.json').version);\")\ngrunt release\ngit add .\ngit commit -m \"Release version $VERSION\"\ngit tag v$VERSION\ngit push && git push --tags\n\necho \"Updated git and git tags to $VERSION\"\n\nnpm publish\n\necho \"Updated npm package\"\n\n"
  },
  {
    "path": "sass/style.scss",
    "content": "$color-border-light: #D8D8D8;\n$color-border-dark: #676767;\n\n$color-bg-dark: #777;\n$color-bg-light: #EAEAEA;\n\ndiv.tree-multiselect {\n  border: 2px solid $color-border-light;\n  border-radius: 5px;\n  display: table;\n  height: inherit;\n  width: 100%;\n\n  > div.selected,\n  > div.selections {\n    display: inline-block;\n    box-sizing: border-box;\n    overflow: auto;\n    padding: 1%;\n    vertical-align: top;\n    width: 50%;\n  }\n\n  > div.selections {\n    border-right: solid 2px $color-border-light;\n\n    div.item {\n      margin-left: 16px;\n\n      label {\n        cursor: pointer;\n        display: inline;\n        &.disabled {\n          color: $color-border-light;\n        }\n      }\n    }\n\n    *[searchhit=false] {\n      display: none;\n    }\n\n    &.no-border {\n      border-right: none;\n    }\n  }\n\n  > div.selected {\n    > div.item {\n      background: $color-bg-light;\n      border-radius: 2px;\n      padding: 2px 5px;\n      overflow: auto;\n    }\n\n    &.ui-sortable > div.item:hover {\n      cursor: move;\n    }\n  }\n\n  div.section {\n    > div.section,\n    > div.item {\n      padding-left: 20px;\n    }\n\n    &.collapsed {\n      > div.title span.collapse-section {\n        &:after {\n          content: \"+\";\n        }\n      }\n\n      &:not([searchhit]) {\n        > .item,\n        > .section {\n          display: none;\n        }\n      }\n    }\n  }\n\n  div.title,\n  div.item {\n    margin-bottom: 2px;\n  }\n\n  div.title {\n    background: $color-bg-dark;\n    color: white;\n    padding: 2px;\n\n    > * {\n      display: inline-block;\n    }\n\n    > span.collapse-section {\n      margin: 0 3px;\n      width: 8px;\n\n      &:after {\n        content: \"-\";\n      }\n    }\n\n    &:hover {\n      cursor: pointer;\n    }\n  }\n\n  input[type=checkbox] {\n    display: inline;\n    margin-right: 5px;\n\n    &:not([disabled]):hover {\n      cursor: pointer;\n    }\n  }\n\n  span.remove-selected,\n  span.description {\n    background: $color-bg-dark;\n    border-radius: 2px;\n    color: white;\n    margin-right: 5px;\n    padding: 0 3px;\n  }\n\n  span.remove-selected {\n    &:hover {\n      cursor: pointer;\n    }\n  }\n\n  span.description {\n    &:hover {\n      cursor: help;\n    }\n  }\n\n  div.temp-description-popup {\n    background: $color-bg-light;\n    border: 2px solid $color-border-dark;\n    border-radius: 3px;\n    padding: 5px;\n  }\n\n  span.section-name {\n    float: right;\n    font-style: italic;\n  }\n\n  .auxiliary {\n    display: table;\n    width: 100%;\n\n    input.search {\n      border: 2px solid $color-border-light;\n      display: table-cell;\n      margin: 0;\n      padding: 5px;\n      width: 100%;\n    }\n\n    .select-all-container {\n      display: table-cell;\n      text-align: right;\n\n      span.select-all,\n      span.unselect-all {\n        margin-right: 5px;\n        padding-right: 5px;\n\n        &:hover {\n          cursor: pointer;\n        }\n      }\n\n      span.select-all {\n        border-right: 2px solid $color-border-light;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/tree-multiselect/ast/common.js",
    "content": "const SEARCH_HIT_ATTR = 'searchhit';\nconst SEARCH_HIT_ATTR_VAL_TRUE = 'true';\nconst SEARCH_HIT_ATTR_VAL_FALSE = 'false';\n\nexports.addSearchHitMarker = function (node, isSearchHit) {\n  if (node) {\n    isSearchHit = isSearchHit ? SEARCH_HIT_ATTR_VAL_TRUE : SEARCH_HIT_ATTR_VAL_FALSE;\n    node.setAttribute(SEARCH_HIT_ATTR, isSearchHit);\n  }\n};\n\nexports.removeSearchHitMarker = function (node, isSearchHit) {\n  if (node) {\n    node.removeAttribute(SEARCH_HIT_ATTR);\n  }\n};\n\nexports.isNotSearchHit = function (node) {\n  return node && node.getAttribute(SEARCH_HIT_ATTR) === SEARCH_HIT_ATTR_VAL_FALSE;\n};\n"
  },
  {
    "path": "src/tree-multiselect/ast/index.js",
    "content": "const Item = require('./item');\nconst Section = require('./section');\n\nexports.createLookup = function (arr) {\n  return {\n    arr: arr,\n    children: {}\n  };\n};\n\nexports.createSection = function (obj) {\n  return new Section(obj);\n};\n\nexports.createItem = function (obj) {\n  return new Item(obj);\n};\n"
  },
  {
    "path": "src/tree-multiselect/ast/item.js",
    "content": "const AstCommon = require('./common');\n\nconst Util = require('../utility');\n\nfunction Item (obj) {\n  obj = obj || {};\n\n  this.treeId = obj.treeId;\n  this.id = obj.id;\n  this.value = obj.value;\n  this.text = obj.text;\n  this.description = obj.description;\n  this.initialIndex = obj.initialIndex ? parseInt(obj.initialIndex) : null;\n  this.section = obj.section;\n  this.disabled = obj.disabled;\n  this.selected = obj.selected;\n\n  this.node = null;\n}\n\nItem.prototype.isSection = function () {\n  return false;\n};\n\nItem.prototype.isItem = function () {\n  return true;\n};\n\nItem.prototype.addSearchHitMarker = function (isSearchHit) {\n  AstCommon.addSearchHitMarker(this.node, isSearchHit);\n};\n\nItem.prototype.removeSearchHitMarker = function (isSearchHit) {\n  AstCommon.removeSearchHitMarker(this.node, isSearchHit);\n};\n\nItem.prototype.isNotSearchHit = function () {\n  return AstCommon.isNotSearchHit(this.node);\n};\n\nItem.prototype.render = function (createCheckboxes, disableCheckboxes) {\n  if (!this.node) {\n    this.node = Util.dom.createSelection(this, createCheckboxes, disableCheckboxes);\n  }\n  return this.node;\n};\n\nmodule.exports = Item;\n"
  },
  {
    "path": "src/tree-multiselect/ast/section.js",
    "content": "const AstCommon = require('./common');\n\nconst Util = require('../utility');\n\nfunction Section (obj) {\n  obj = obj || {};\n\n  this.treeId = obj.treeId;\n  this.id = obj.id;\n  this.name = obj.name;\n  this.items = [];\n\n  this.node = null;\n}\n\nSection.prototype.isSection = function () {\n  return true;\n};\n\nSection.prototype.isItem = function () {\n  return false;\n};\n\nSection.prototype.addSearchHitMarker = function (isSearchHit) {\n  AstCommon.addSearchHitMarker(this.node, isSearchHit);\n};\n\nSection.prototype.removeSearchHitMarker = function (isSearchHit) {\n  AstCommon.removeSearchHitMarker(this.node, isSearchHit);\n};\n\nSection.prototype.isNotSearchHit = function () {\n  return AstCommon.isNotSearchHit(this.node);\n};\n\nSection.prototype.render = function (createCheckboxes, disableCheckboxes) {\n  if (!this.node) {\n    this.node = Util.dom.createSection(this, createCheckboxes, disableCheckboxes);\n  }\n  return this.node;\n};\n\nmodule.exports = Section;\n"
  },
  {
    "path": "src/tree-multiselect/main.js",
    "content": "let Tree = require('./tree');\n\nlet uniqueId = 0;\n\nfunction treeMultiselect (opts) {\n  let options = mergeDefaultOptions(opts);\n\n  return this.map(() => {\n    let $originalSelect = this;\n    $originalSelect.attr('multiple', '').css('display', 'none');\n\n    let tree = new Tree(uniqueId, $originalSelect, options);\n    tree.initialize();\n\n    ++uniqueId;\n\n    return {\n      reload: function () {\n        tree.reload();\n      },\n\n      remove: function () {\n        tree.remove();\n      }\n    };\n  });\n};\n\nfunction mergeDefaultOptions (options) {\n  let defaults = {\n    allowBatchSelection: true,\n    collapsible: true,\n    enableSelectAll: false,\n    selectAllText: 'Select All',\n    unselectAllText: 'Unselect All',\n    freeze: false,\n    hideSidePanel: false,\n    maxSelections: 0,\n    onChange: null,\n    onlyBatchSelection: false,\n    searchable: false,\n    searchParams: ['value', 'text', 'description', 'section'],\n    sectionDelimiter: '/',\n    showSectionOnSelected: true,\n    sortable: false,\n    startCollapsed: false\n  };\n  return jQuery.extend({}, defaults, options);\n}\n\nmodule.exports = treeMultiselect;\n"
  },
  {
    "path": "src/tree-multiselect/search.js",
    "content": "let Util = require('./utility');\n\nconst MAX_SAMPLE_SIZE = 3;\n\nfunction Search (searchHitAttr, astItems, astSections, searchParams) {\n  this.searchHitAttr = searchHitAttr;\n  this.index = {}; // key: at most three-letter combinations, value: array of data-key\n\n  // key: data-key, value: DOM node\n  this.astItems = astItems;\n  this.astItemKeys = Object.keys(astItems);\n\n  this.astSections = astSections;\n  this.astSectionKeys = Object.keys(astSections);\n\n  this.setSearchParams(searchParams);\n\n  this.buildIndex();\n}\n\nSearch.prototype.setSearchParams = function (searchParams) {\n  Util.assert(Array.isArray(searchParams));\n\n  let allowedParams = {\n    value: true,\n    text: true,\n    description: true,\n    section: true\n  };\n\n  this.searchParams = [];\n  for (let ii = 0; ii < searchParams.length; ++ii) {\n    if (allowedParams[searchParams[ii]]) {\n      this.searchParams.push(searchParams[ii]);\n    }\n  }\n};\n\nSearch.prototype.buildIndex = function () {\n  // trigrams\n  for (const astItemKey in this.astItems) {\n    const astItem = this.astItems[astItemKey];\n    let searchItems = [];\n    this.searchParams.forEach((searchParam) => {\n      searchItems.push(astItem[searchParam]);\n    });\n    Util.array.removeFalseyExceptZero(searchItems);\n    let searchWords = searchItems.map((item) => {\n      return item.toLowerCase();\n    });\n\n    searchWords.forEach((searchWord) => {\n      let words = searchWord.split(' ');\n      words.forEach((word) => {\n        this._addToIndex(word, astItem.id);\n      });\n    });\n  }\n};\n\nSearch.prototype._addToIndex = function (key, id) {\n  for (let sampleSize = 1; sampleSize <= MAX_SAMPLE_SIZE; ++sampleSize) {\n    for (let startOffset = 0; startOffset < key.length - sampleSize + 1; ++startOffset) {\n      let minikey = key.substring(startOffset, startOffset + sampleSize);\n\n      if (!this.index[minikey]) {\n        this.index[minikey] = [];\n      }\n\n      // don't duplicate\n      // this takes advantage of the fact that the minikeys with same id's are added sequentially\n      let length = this.index[minikey].length;\n      if (length === 0 || this.index[minikey][length - 1] !== id) {\n        this.index[minikey].push(id);\n      }\n    }\n  }\n};\n\nSearch.prototype.search = function (value) {\n  value = value.trim();\n\n  if (!value) {\n    this.astItemKeys.forEach((id) => {\n      this.astItems[id].removeSearchHitMarker();\n    });\n    this.astSectionKeys.forEach((id) => {\n      this.astSections[id].removeSearchHitMarker();\n    });\n    return;\n  }\n\n  value = value.toLowerCase();\n\n  let searchWords = value.split(' ').filter(word => word);\n  let searchChunks = [];\n  searchWords.forEach((searchWord) => {\n    let chunks = splitWord(searchWord);\n    chunks.forEach((chunk) => {\n      searchChunks.push(this.index[chunk] || []);\n    });\n  });\n\n  let matchedNodeIds = Util.array.intersectMany(searchChunks);\n\n  // now we have id's that match search query\n  this.handleNodeVisibilities(matchedNodeIds);\n};\n\nSearch.prototype.handleNodeVisibilities = function (shownNodeIds) {\n  let shownNodeIdsHash = {};\n  let sectionsToNotHideHash = {};\n  shownNodeIds.forEach((id) => {\n    shownNodeIdsHash[id] = true;\n    let node = this.astItems[id].node;\n\n    // now search for parent sections\n    node = node.parentNode;\n    while (!node.className.match(/tree-multiselect/)) {\n      if (node.className.match(/section/)) {\n        let key = Util.getKey(node);\n        Util.assert(key || key === 0);\n        if (sectionsToNotHideHash[key]) {\n          break;\n        } else {\n          sectionsToNotHideHash[key] = true;\n        }\n      }\n      node = node.parentNode;\n    }\n  });\n\n  // hide selections\n  this.astItemKeys.forEach((id) => {\n    let isSearchHit = !!shownNodeIdsHash[id];\n    this.astItems[id].addSearchHitMarker(isSearchHit);\n  });\n  this.astSectionKeys.forEach((id) => {\n    let isSearchHit = !!sectionsToNotHideHash[id];\n    this.astSections[id].addSearchHitMarker(isSearchHit);\n  });\n};\n\n// split word into three letter (or less) pieces\nfunction splitWord (word) {\n  Util.assert(word);\n\n  if (word.length < MAX_SAMPLE_SIZE) {\n    return [word];\n  }\n\n  let chunks = [];\n  for (let ii = 0; ii < word.length - MAX_SAMPLE_SIZE + 1; ++ii) {\n    chunks.push(word.substring(ii, ii + MAX_SAMPLE_SIZE));\n  }\n  return chunks;\n}\n\nmodule.exports = Search;\n"
  },
  {
    "path": "src/tree-multiselect/tree.js",
    "content": "let Ast = require('./ast');\nlet Search = require('./search');\nlet UiBuilder = require('./ui-builder');\nlet Util = require('./utility');\n\nconst SEARCH_HIT_ATTR = 'searchhit';\n\nfunction Tree (id, $originalSelect, params) {\n  this.id = id;\n  this.$originalSelect = $originalSelect;\n\n  this.params = params;\n\n  this.resetState();\n}\n\nTree.prototype.initialize = function () {\n  this.generateSelections(this.$selectionContainer[0]);\n\n  this.popupDescriptionHover();\n\n  if (this.params.allowBatchSelection) {\n    this.handleSectionCheckboxMarkings();\n  }\n\n  if (this.params.collapsible) {\n    this.addCollapsibility();\n  }\n\n  if (this.params.searchable || this.params.enableSelectAll) {\n    let auxiliaryBox = Util.dom.createNode('div', {class: 'auxiliary'});\n    this.$selectionContainer.prepend(auxiliaryBox, this.$selectionContainer.firstChild);\n\n    if (this.params.searchable) {\n      this.createSearchBar(auxiliaryBox);\n    }\n\n    if (this.params.enableSelectAll) {\n      this.createSelectAllButtons(auxiliaryBox);\n    }\n  }\n\n  this.armRemoveSelectedOnClick();\n  this.updateSelectedAndOnChange();\n\n  this.render(true);\n  this.uiBuilder.attach();\n};\n\nTree.prototype.remove = function () {\n  this.uiBuilder.remove();\n  this.resetState();\n};\n\nTree.prototype.reload = function () {\n  let selectedOptions = {};\n  this.selectedKeys.forEach((key) => {\n    let value = this.astItems[key].value;\n    selectedOptions[value] = true;\n  });\n\n  this.remove();\n\n  this.$originalSelect.children('option').each((idx, element) => {\n    let value = element.value;\n    if (selectedOptions[value]) {\n      element.setAttribute('selected', 'selected');\n    } else {\n      element.removeAttribute('selected');\n    }\n  });\n\n  this.initialize();\n  this.render(true);\n};\n\nTree.prototype.resetState = function () {\n  this.uiBuilder = new UiBuilder(this.$originalSelect, this.params.hideSidePanel);\n  this.$treeContainer = this.uiBuilder.$treeContainer;\n  this.$selectionContainer = this.uiBuilder.$selectionContainer;\n  this.$selectedContainer = this.uiBuilder.$selectedContainer;\n\n  // data-key is key, provides DOM node\n  this.astItems = {};\n  this.astSections = {};\n  this.selectedNodes = {};\n\n  this.selectedKeys = [];\n  this.keysToAdd = [];\n  this.keysToRemove = [];\n};\n\nTree.prototype.generateSelections = function (parentNode) {\n  let options = this.$originalSelect.children('option');\n  let ast = this.createAst(options);\n  this.generateHtml(ast, parentNode);\n};\n\nTree.prototype.createAst = function (options) {\n  let data = [];\n  let lookup = Ast.createLookup(data);\n\n  let self = this;\n  let itemId = 0;\n  let sectionId = 0;\n\n  let initialIndexItems = [];\n  let keysToAddAtEnd = [];\n  options.each(function () {\n    let option = this;\n    option.setAttribute('data-key', itemId);\n\n    let item = Ast.createItem({\n      treeId: self.id,\n      id: itemId,\n      value: option.value,\n      text: option.text,\n      description: option.getAttribute('data-description'),\n      initialIndex: option.getAttribute('data-index'),\n      section: option.getAttribute('data-section'),\n      disabled: option.hasAttribute('readonly'),\n      selected: option.hasAttribute('selected')\n    });\n\n    if (item.initialIndex && item.selected) {\n      initialIndexItems[item.initialIndex] = initialIndexItems[item.initialIndex] || [];\n      initialIndexItems[item.initialIndex].push(itemId);\n    } else if (item.selected) {\n      keysToAddAtEnd.push(itemId);\n    }\n    self.astItems[itemId] = item;\n\n    ++itemId;\n\n    let lookupPosition = lookup;\n    let section = item.section;\n    let sectionParts = (section && section.length > 0) ? section.split(self.params.sectionDelimiter) : [];\n    for (let ii = 0; ii < sectionParts.length; ++ii) {\n      let sectionPart = sectionParts[ii];\n      if (lookupPosition.children[sectionPart]) {\n        lookupPosition = lookupPosition.children[sectionPart];\n      } else {\n        let newSection = Ast.createSection({\n          treeId: self.id,\n          id: sectionId,\n          name: sectionPart\n        });\n        ++sectionId;\n\n        lookupPosition.arr.push(newSection);\n        let newLookupNode = Ast.createLookup(newSection.items);\n        lookupPosition.children[sectionPart] = newLookupNode;\n        lookupPosition = newLookupNode;\n      }\n    }\n    lookupPosition.arr.push(item);\n  });\n  this.keysToAdd = Util.array.flatten(initialIndexItems);\n  Util.array.removeFalseyExceptZero(this.keysToAdd);\n  this.keysToAdd.push(...keysToAddAtEnd);\n  Util.array.uniq(this.keysToAdd);\n  return data;\n};\n\nTree.prototype.generateHtml = function (astArr, parentNode) {\n  for (let ii = 0; ii < astArr.length; ++ii) {\n    const astObj = astArr[ii];\n    if (astObj.isSection()) {\n      this.astSections[astObj.id] = astObj;\n\n      let createCheckboxes = this.params.allowBatchSelection;\n      let disableCheckboxes = this.params.freeze;\n      let node = astObj.render(createCheckboxes, disableCheckboxes);\n      parentNode.appendChild(node);\n      this.generateHtml(astObj.items, node);\n    } else if (astObj.isItem()) {\n      this.astItems[astObj.id] = astObj;\n\n      let createCheckboxes = !this.params.onlyBatchSelection;\n      let disableCheckboxes = this.params.freeze;\n      let node = astObj.render(createCheckboxes, disableCheckboxes);\n      parentNode.appendChild(node);\n    }\n  }\n};\n\nTree.prototype.popupDescriptionHover = function () {\n  this.$selectionContainer.on('mouseenter', 'div.item > span.description', function () {\n    let $item = jQuery(this).parent();\n    let description = $item.attr('data-description');\n\n    let descriptionDiv = document.createElement('div');\n    descriptionDiv.className = 'temp-description-popup';\n    descriptionDiv.innerHTML = description;\n\n    descriptionDiv.style.position = 'absolute';\n\n    $item.append(descriptionDiv);\n  });\n\n  this.$selectionContainer.on('mouseleave', 'div.item > span.description', function () {\n    let $item = jQuery(this).parent();\n    $item.find('div.temp-description-popup').remove();\n  });\n};\n\nTree.prototype.handleSectionCheckboxMarkings = function () {\n  let self = this;\n  this.$selectionContainer.on('click', 'input.section[type=checkbox]', function () {\n    let $section = jQuery(this).closest('div.section');\n    let $items = $section.find('div.item');\n    let keys = $items.map((idx, el) => {\n      let key = Util.getKey(el);\n      let astItem = self.astItems[key];\n      if (!astItem.disabled && !astItem.isNotSearchHit()) {\n        return key;\n      }\n      return null;\n    }).get();\n\n    if (this.checked) {\n      // TODO why does this always take this branch\n      self.keysToAdd.push(...keys);\n      Util.array.uniq(self.keysToAdd);\n    } else {\n      self.keysToRemove.push(...keys);\n      Util.array.uniq(self.keysToRemove);\n    }\n    self.render();\n  });\n};\n\nTree.prototype.redrawSectionCheckboxes = function ($section) {\n  $section = $section || this.$selectionContainer;\n\n  // returns array; bit 1 is all children are true, bit 0 is all children are false\n  let returnVal = 0b11;\n\n  let self = this;\n  let $childSections = $section.find('> div.section');\n  $childSections.each(function () {\n    let result = self.redrawSectionCheckboxes(jQuery(this));\n    returnVal &= result;\n  });\n\n  if (returnVal) {\n    let $childCheckboxes = $section.find('> div.item > input[type=checkbox]');\n    for (let ii = 0; ii < $childCheckboxes.length; ++ii) {\n      if ($childCheckboxes[ii].checked) {\n        returnVal &= ~0b10;\n      } else {\n        returnVal &= ~0b01;\n      }\n\n      if (returnVal === 0) {\n        break;\n      }\n    }\n  }\n\n  let sectionCheckbox = $section.find('> div.title > input[type=checkbox]');\n  if (sectionCheckbox.length) {\n    sectionCheckbox = sectionCheckbox[0];\n    if (returnVal & 0b01) {\n      sectionCheckbox.checked = true;\n      sectionCheckbox.indeterminate = false;\n    } else if (returnVal & 0b10) {\n      sectionCheckbox.checked = false;\n      sectionCheckbox.indeterminate = false;\n    } else {\n      sectionCheckbox.checked = false;\n      sectionCheckbox.indeterminate = true;\n    }\n  }\n\n  return returnVal;\n};\n\nTree.prototype.addCollapsibility = function () {\n  let titleSelector = 'div.title';\n  let $titleDivs = this.$selectionContainer.find(titleSelector);\n\n  let collapseSpan = Util.dom.createNode('span', {class: 'collapse-section'});\n  $titleDivs.prepend(collapseSpan);\n\n  let sectionSelector = 'div.section';\n  let $sectionDivs = this.$selectionContainer.find(sectionSelector);\n\n  if (this.params.startCollapsed) {\n    $sectionDivs.addClass('collapsed');\n  }\n\n  this.$selectionContainer.on('click', titleSelector, function (event) {\n    if (event.target.nodeName === 'INPUT') {\n      return;\n    }\n\n    let $section = jQuery(this).parent();\n    $section.toggleClass('collapsed');\n    event.stopPropagation();\n  });\n};\n\nTree.prototype.createSearchBar = function (parentNode) {\n  let searchObj = new Search(SEARCH_HIT_ATTR, this.astItems, this.astSections, this.params.searchParams);\n\n  let searchNode = Util.dom.createNode('input', {class: 'search', placeholder: 'Search...'});\n  parentNode.appendChild(searchNode);\n\n  this.$selectionContainer.on('input', 'input.search', function () {\n    let searchText = this.value;\n    searchObj.search(searchText);\n  });\n};\n\nTree.prototype.createSelectAllButtons = function (parentNode) {\n  let selectAllNode = Util.dom.createNode('span', {class: 'select-all', text: this.params.selectAllText});\n  let unselectAllNode = Util.dom.createNode('span', {class: 'unselect-all', text: this.params.unselectAllText});\n\n  let selectAllContainer = Util.dom.createNode('div', {class: 'select-all-container'});\n  selectAllContainer.appendChild(selectAllNode);\n  selectAllContainer.appendChild(unselectAllNode);\n\n  parentNode.appendChild(selectAllContainer);\n\n  let self = this;\n  this.$selectionContainer.on('click', 'span.select-all', function () {\n    self.keysToAdd.push(...self.unfilteredNodeIds());\n    self.render();\n  });\n\n  this.$selectionContainer.on('click', 'span.unselect-all', function () {\n    self.keysToRemove.push(...self.unfilteredNodeIds());\n    self.render();\n  });\n};\n\nTree.prototype.unfilteredNodeIds = function () {\n  let self = this;\n  return Object.keys(self.astItems).filter((key) => {\n    return !self.astItems[key].node.hasAttribute(SEARCH_HIT_ATTR) ||\n      self.astItems[key].node.getAttribute(SEARCH_HIT_ATTR) === 'true';\n  });\n};\n\nTree.prototype.armRemoveSelectedOnClick = function () {\n  let self = this;\n  this.$selectedContainer.on('click', 'span.remove-selected', function () {\n    let parentNode = this.parentNode;\n    let key = Util.getKey(parentNode);\n    self.keysToRemove.push(key);\n    self.render();\n  });\n};\n\nTree.prototype.updateSelectedAndOnChange = function () {\n  let self = this;\n  this.$selectionContainer.on('click', 'input.option[type=checkbox]', function () {\n    let checkbox = this;\n    let selection = checkbox.parentNode;\n    let key = Util.getKey(selection);\n    Util.assert(key || key === 0);\n\n    if (checkbox.checked) {\n      self.keysToAdd.push(key);\n    } else {\n      self.keysToRemove.push(key);\n    }\n\n    self.render();\n  });\n\n  if (this.params.sortable && !this.params.freeze) {\n    let startIndex = null;\n    let endIndex = null;\n    this.$selectedContainer.sortable({\n      start: function (event, ui) {\n        startIndex = ui.item.index();\n      },\n\n      stop: function (event, ui) {\n        endIndex = ui.item.index();\n        if (startIndex === endIndex) {\n          return;\n        }\n        Util.array.moveEl(self.selectedKeys, startIndex, endIndex);\n        self.render();\n      }\n    });\n  }\n};\n\nTree.prototype.render = function (noCallbacks) {\n  // fix arrays first\n  Util.array.uniq(this.keysToAdd);\n  Util.array.uniq(this.keysToRemove);\n\n  Util.array.subtract(this.keysToAdd, this.selectedKeys);\n  Util.array.intersect(this.keysToRemove, this.selectedKeys);\n\n  // check for max number of selections\n  if (Util.isInteger(this.params.maxSelections) && this.params.maxSelections > 0) {\n    const currentLength = this.keysToAdd.length - this.keysToRemove.length + this.selectedKeys.length;\n    if (currentLength > this.params.maxSelections) {\n      let lengthToCut = currentLength - this.params.maxSelections;\n      let keysToCut = [];\n      if (lengthToCut > this.selectedKeys.length) {\n        keysToCut.push(...this.selectedKeys);\n        lengthToCut -= this.selectedKeys.length;\n        keysToCut.push(...(this.keysToAdd.splice(0, lengthToCut)));\n      } else {\n        keysToCut.push(...this.selectedKeys.slice(0, lengthToCut));\n      }\n      this.keysToRemove.push(...keysToCut);\n    }\n  }\n\n  // remove items first\n  for (let ii = 0; ii < this.keysToRemove.length; ++ii) {\n    // remove the selected divs\n    let node = this.selectedNodes[this.keysToRemove[ii]];\n    if (node) {\n      // slightly more verbose than node.remove(), but more browser support\n      node.parentNode.removeChild(node);\n      this.selectedNodes[this.keysToRemove[ii]] = null;\n    }\n\n    // uncheck these checkboxes\n    let selectionNode = this.astItems[this.keysToRemove[ii]].node;\n    selectionNode.getElementsByTagName('INPUT')[0].checked = false;\n  }\n\n  Util.array.subtract(this.selectedKeys, this.keysToRemove);\n\n  // now add items\n  for (let jj = 0; jj < this.keysToAdd.length; ++jj) {\n    // create selected divs\n    let key = this.keysToAdd[jj];\n    let astItem = this.astItems[key];\n    this.selectedKeys.push(key);\n\n    let selectedNode = Util.dom.createSelected(astItem, this.params.freeze, this.params.showSectionOnSelected);\n    this.selectedNodes[astItem.id] = selectedNode;\n    this.$selectedContainer.append(selectedNode);\n\n    // check the checkboxes\n    let inputNode = astItem.node.getElementsByTagName('INPUT')[0];\n    if (inputNode) {\n      inputNode.checked = true;\n    }\n  }\n\n  this.selectedKeys.push(...this.keysToAdd);\n  Util.array.uniq(this.selectedKeys);\n\n  // redraw section checkboxes\n  this.redrawSectionCheckboxes();\n\n  // now fix original select\n  let originalValsHash = {};\n  // valHash hashes a value to an index\n  let valHash = {};\n  for (let kk = 0; kk < this.selectedKeys.length; ++kk) {\n    let value = this.astItems[this.selectedKeys[kk]].value;\n    originalValsHash[this.selectedKeys[kk]] = true;\n    valHash[value] = kk;\n  }\n  // TODO is there a better way to sort the values other than by HTML?\n  // NOTE: the following does not work since jQuery duplicates option values with the same value\n  // this.$originalSelect.val(vals);\n  let options = this.$originalSelect.find('option').toArray();\n  options.sort(function (a, b) {\n    let aValue = valHash[a.value] || 0;\n    let bValue = valHash[b.value] || 0;\n    return aValue - bValue;\n  });\n\n  this.$originalSelect.html(options);\n  this.$originalSelect.find('option').each(function (idx, el) {\n    this.selected = !!originalValsHash[Util.getKey(el)];\n  });\n  this.$originalSelect.change();\n\n  if (!noCallbacks && this.params.onChange) {\n    let optionsSelected = this.selectedKeys.map((key) => {\n      return this.astItems[key];\n    });\n    let optionsAdded = this.keysToAdd.map((key) => {\n      return this.astItems[key];\n    });\n    let optionsRemoved = this.keysToRemove.map((key) => {\n      return this.astItems[key];\n    });\n    this.params.onChange(optionsSelected, optionsAdded, optionsRemoved);\n  }\n\n  this.keysToRemove = [];\n  this.keysToAdd = [];\n};\n\nmodule.exports = Tree;\n"
  },
  {
    "path": "src/tree-multiselect/ui-builder.js",
    "content": "function UiBuilder ($el, hideSidePanel) {\n  let $tree = jQuery('<div class=\"tree-multiselect\"></div>');\n\n  let $selections = jQuery('<div class=\"selections\"></div>');\n  if (hideSidePanel) {\n    $selections.addClass('no-border');\n  }\n  $tree.append($selections);\n\n  let $selected = jQuery('<div class=\"selected\"></div>');\n  if (!hideSidePanel) {\n    $tree.append($selected);\n  }\n\n  this.$el = $el;\n  this.$treeContainer = $tree;\n  this.$selectionContainer = $selections;\n  this.$selectedContainer = $selected;\n}\n\nUiBuilder.prototype.attach = function () {\n  this.$el.after(this.$treeContainer);\n};\n\nUiBuilder.prototype.remove = function () {\n  this.$treeContainer.remove();\n};\n\nmodule.exports = UiBuilder;\n"
  },
  {
    "path": "src/tree-multiselect/utility/array.js",
    "content": "// keeps if pred is true\nfunction filterInPlace (arr, pred) {\n  var idx = 0;\n  for (var ii = 0; ii < arr.length; ++ii) {\n    if (pred(arr[ii])) {\n      arr[idx] = arr[ii];\n      ++idx;\n    }\n  }\n  arr.length = idx;\n}\n\nexports.flatten = function (arr, r) {\n  if (!Array.isArray(arr)) {\n    return arr;\n  }\n\n  r = r || [];\n\n  for (var ii = 0; ii < arr.length; ++ii) {\n    if (Array.isArray(arr[ii])) {\n      r.concat(exports.flatten(arr[ii], r));\n    } else {\n      r.push(arr[ii]);\n    }\n  }\n\n  return r;\n};\n\nexports.uniq = function (arr) {\n  var hash = {};\n\n  var pred = function (val) {\n    var returnVal = !hash[val];\n    hash[val] = true;\n    return returnVal;\n  };\n  filterInPlace(arr, pred);\n};\n\nexports.removeFalseyExceptZero = function (arr) {\n  var pred = function (val) {\n    return val || val === 0;\n  };\n  filterInPlace(arr, pred);\n};\n\nexports.moveEl = function (arr, oldPos, newPos) {\n  var el = arr[oldPos];\n  arr.splice(oldPos, 1);\n  arr.splice(newPos, 0, el);\n};\n\nexports.subtract = function (arr, arrExcluded) {\n  var hash = {};\n\n  for (var ii = 0; ii < arrExcluded.length; ++ii) {\n    hash[arrExcluded[ii]] = true;\n  }\n\n  var pred = function (val) {\n    return !hash[val];\n  };\n  filterInPlace(arr, pred);\n};\n\nexports.intersect = function (arr, arrExcluded) {\n  var hash = {};\n\n  for (var ii = 0; ii < arrExcluded.length; ++ii) {\n    hash[arrExcluded[ii]] = true;\n  }\n\n  var pred = function (val) {\n    return hash[val];\n  };\n  filterInPlace(arr, pred);\n};\n\n// takes in array of arrays\n// arrays are presorted\nexports.intersectMany = function (arrays) {\n  var indexLocations = [];\n  var maxIndexLocations = [];\n  arrays.forEach((array) => {\n    indexLocations.push(0);\n    maxIndexLocations.push(array.length - 1);\n  });\n\n  var finalOutput = [];\n  for (; indexLocations.length > 0 && indexLocations[0] <= maxIndexLocations[0]; ++indexLocations[0]) {\n    // advance indices to be at least equal to first array element\n    var terminate = false;\n    for (var ii = 1; ii < arrays.length; ++ii) {\n      while (arrays[ii][indexLocations[ii]] < arrays[0][indexLocations[0]] &&\n             indexLocations[ii] <= maxIndexLocations[ii]) {\n        ++indexLocations[ii];\n      }\n      if (indexLocations[ii] > maxIndexLocations[ii]) {\n        terminate = true;\n        break;\n      }\n    }\n\n    if (terminate) {\n      break;\n    }\n\n    // check element equality\n    var shouldAdd = true;\n    for (var jj = 1; jj < arrays.length; ++jj) {\n      if (arrays[0][indexLocations[0]] !== arrays[jj][indexLocations[jj]]) {\n        shouldAdd = false;\n        break;\n      }\n    }\n\n    if (shouldAdd) {\n      finalOutput.push(arrays[0][indexLocations[0]]);\n    }\n  }\n\n  return finalOutput;\n};\n"
  },
  {
    "path": "src/tree-multiselect/utility/dom.js",
    "content": "exports.createNode = function (tag, props) {\n  var node = document.createElement(tag);\n\n  if (props) {\n    for (var key in props) {\n      if (Object.prototype.hasOwnProperty.call(props, key) && key !== 'text') {\n        node.setAttribute(key, props[key]);\n      }\n    }\n    if (props.text) {\n      node.textContent = props.text;\n    }\n  }\n  return node;\n};\n\nexports.createSelection = function (astItem, createCheckboxes, disableCheckboxes) {\n  var props = {\n    class: 'item',\n    'data-key': astItem.id,\n    'data-value': astItem.value\n  };\n  var hasDescription = !!astItem.description;\n  if (hasDescription) {\n    props['data-description'] = astItem.description;\n  }\n  if (astItem.initialIndex) {\n    props['data-index'] = astItem.initialIndex;\n  }\n  var selectionNode = exports.createNode('div', props);\n\n  if (hasDescription) {\n    var popup = exports.createNode('span', {class: 'description', text: '?'});\n    selectionNode.appendChild(popup);\n  }\n  if (!createCheckboxes) {\n    selectionNode.innerText = astItem.text || astItem.value;\n  } else {\n    var optionLabelCheckboxId = `treemultiselect-${astItem.treeId}-${astItem.id}`;\n    var inputCheckboxProps = {\n      class: 'option',\n      type: 'checkbox',\n      id: optionLabelCheckboxId\n    };\n    if (disableCheckboxes || astItem.disabled) {\n      inputCheckboxProps.disabled = true;\n    }\n    var inputCheckbox = exports.createNode('input', inputCheckboxProps);\n    // prepend child\n    selectionNode.insertBefore(inputCheckbox, selectionNode.firstChild);\n\n    var labelProps = {\n      class: astItem.disabled ? 'disabled' : '',\n      for: optionLabelCheckboxId,\n      text: astItem.text || astItem.value\n    };\n    var label = exports.createNode('label', labelProps);\n    selectionNode.appendChild(label);\n  }\n\n  return selectionNode;\n};\n\nexports.createSelected = function (astItem, disableRemoval, showSectionOnSelected) {\n  var node = exports.createNode('div', {\n    class: 'item',\n    'data-key': astItem.id,\n    'data-value': astItem.value,\n    text: astItem.text\n  });\n\n  if (!disableRemoval && !astItem.disabled) {\n    var removalSpan = exports.createNode('span', {class: 'remove-selected', text: '×'});\n    node.insertBefore(removalSpan, node.firstChild);\n  }\n\n  if (showSectionOnSelected) {\n    var sectionSpan = exports.createNode('span', {class: 'section-name', text: astItem.section});\n    node.appendChild(sectionSpan);\n  }\n\n  return node;\n};\n\nexports.createSection = function (astSection, createCheckboxes, disableCheckboxes) {\n  var sectionNode = exports.createNode('div', {class: 'section', 'data-key': astSection.id});\n\n  var titleNode = exports.createNode('div', {class: 'title', text: astSection.name});\n  if (createCheckboxes) {\n    var checkboxProps = {\n      class: 'section',\n      type: 'checkbox'\n    };\n    if (disableCheckboxes) {\n      checkboxProps.disabled = true;\n    }\n    var checkboxNode = exports.createNode('input', checkboxProps);\n    titleNode.insertBefore(checkboxNode, titleNode.firstChild);\n  }\n  sectionNode.appendChild(titleNode);\n  return sectionNode;\n};\n"
  },
  {
    "path": "src/tree-multiselect/utility/index.js",
    "content": "exports.array = require('./array');\n\nexports.assert = function (bool, message) {\n  if (!bool) {\n    throw new Error(message || 'Assertion failed');\n  }\n};\n\nexports.dom = require('./dom');\n\nexports.getKey = function (el) {\n  exports.assert(el);\n  return parseInt(el.getAttribute('data-key'));\n};\n\nexports.isInteger = function (value) {\n  var x;\n  if (isNaN(value)) {\n    return false;\n  }\n  x = parseFloat(value);\n  return (x | 0) === x;\n};\n"
  },
  {
    "path": "src/tree-multiselect.js",
    "content": "(($) => {\n  'use strict';\n  $.fn.treeMultiselect = require('./tree-multiselect/main');\n})(jQuery);\n"
  },
  {
    "path": "test/integration/common.js",
    "content": "chai.config.includeStack = true;\n\nvar $fixture = null;\nvar selectCount = 1;\nfunction createFixtureSelect() {\n  var select = document.createElement(\"select\");\n  select.id = 'select-' + selectCount;\n  ++selectCount;\n  select.setAttribute(\"multiple\", \"multiple\");\n  $fixture.append(select);\n  return select;\n}\n\nbeforeEach(() => {\n  $fixture = $(\"#fixture\");\n  if (!$fixture.length) {\n    $fixture = $(\"<div id='fixture'></div>\");\n    $(\"body\").append($fixture);\n  }\n\n  selectCount = 0;\n  $fixture.empty();\n  createFixtureSelect();\n});\n\nmodule.exports = {\n  assertSelection(el, params) {\n    var $el = $(el);\n    assert($el.hasClass('item'));\n    assert.equal(this.textOf($el), params.text);\n    assert.equal($el.attr('data-value'), params.value);\n  },\n\n  assertSelected(el, params) {\n    var $el = $(el);\n    assert($el.hasClass('item'));\n    assert.equal(this.textOf($el), params.text);\n    assert.equal($el.attr('data-value'), params.value);\n    var $sectionName = $el.children(\".section-name\");\n    assert.equal($sectionName.length, 1);\n    assert.equal(this.textOf($sectionName), params.section);\n  },\n\n  createFixtureSelect: createFixtureSelect,\n\n  textOf(el) {\n    var $el = $(el);\n    var $label = $el.children(\"label\");\n    if ($label.length) {\n      return $label.first().text();\n    } else {\n      return $el.clone().children().remove().end().text();\n    }\n  },\n\n  // DOM element finders\n  find(container, options) {\n    var text = null;\n    var value = null;\n\n    if (options) {\n      text = options.text;\n      value = options.value;\n    }\n\n    var selector = container + (value ? `[data-value=${value}]` : '');\n    var $els = $(selector);\n\n    if (text) {\n      $els = $els.filter((idx, el) => {\n        return this.textOf(el) === text;\n      });\n    }\n\n    return $els;\n  },\n\n  findCheckbox(container, options) {\n    var $els = this.find(container, options).children(\"input[type=checkbox]\");\n    if (options && options.checked) {\n      $els = $els.filter((idx, el) => {\n        return el.checked === options.checked;\n      });\n    }\n    return $els;\n  },\n\n  selection(options) {\n    return this.find('.selections .item', options);\n  },\n\n  selected(options) {\n    return this.find('.selected .item', options);\n  },\n\n  section(options) {\n    // need to search title text, then go back up to section\n    return this.find('.selections .section > .title', options).parent();\n  },\n\n  selectionCheckbox(options) {\n    return this.findCheckbox(\".selections .item\", options);\n  },\n\n  sectionCheckbox(options) {\n    return this.findCheckbox(\".selections .section > .title\", options);\n  }\n};\n\n"
  },
  {
    "path": "test/integration/initial-load.test.js",
    "content": "var Common = require('./common');\n\ndescribe('Initial Load', () => {\n  it('creates container', () => {\n    assert.equal($(\".tree-multiselect\").length, 0);\n    assert.equal($(\".tree-multiselect div.selections\").length, 0);\n    assert.equal($(\".tree-multiselect div.selected\").length, 0);\n\n    $(\"select\").treeMultiselect();\n    assert.equal($(\".tree-multiselect\").length, 1);\n    assert.equal($(\".tree-multiselect div.selections\").length, 1);\n    assert.equal($(\".tree-multiselect div.selected\").length, 1);\n  });\n\n  it('renders option', () => {\n    $(\"select\").append(\"<option value='one' data-section='section'>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 1);\n\n    var $selection = $selections.first();\n\n    Common.assertSelection($selection, {text: 'One', value: 'one'});\n\n    var $checkbox = $selection.children(\"input[type=checkbox]\");\n    var $label = $selection.children(\"label\");\n    assert.equal($checkbox.length, 1);\n    assert.equal($label.length, 1);\n    assert.equal($checkbox.attr('id'), $label.attr('for'));\n  });\n\n  it('renders multiple options', () => {\n    $(\"select\").append(\"<option value='one' data-section='section'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='section'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 2);\n\n    Common.assertSelection($selections[0], {text: 'One', value: 'one'});\n    Common.assertSelection($selections[1], {text: 'Two', value: 'two'});\n  });\n\n  it('renders options with the same value', () => {\n    $(\"select\").append(\"<option value='one' data-section='section'>One</option>\");\n    $(\"select\").append(\"<option value='one' data-section='section'>One duplicate</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 2);\n\n    Common.assertSelection($selections[0], {text: 'One', value: 'one'});\n    Common.assertSelection($selections[1], {text: 'One duplicate', value: 'one'});\n  });\n\n  it('renders options with the same value and text', () => {\n    $(\"select\").append(\"<option value='one' data-section='section'>One</option>\");\n    $(\"select\").append(\"<option value='one' data-section='section'>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 2);\n\n    Common.assertSelection($selections[0], {text: 'One', value: 'one'});\n    Common.assertSelection($selections[1], {text: 'One', value: 'one'});\n  });\n\n  it('renders options without data-section', () => {\n    $(\"select\").append(\"<option value='one'>One</option>\");\n    $(\"select\").append(\"<option value='two'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 2);\n\n    Common.assertSelection($selections[0], {text: 'One', value: 'one'});\n    Common.assertSelection($selections[1], {text: 'Two', value: 'two'});\n  });\n\n  it('respects selected attribute', () => {\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 1);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 1);\n\n    assert.deepEqual($(\"select\").val(), ['one']);\n  });\n\n  it('respects data-index attribute', () => {\n    $(\"select\").append(\"<option value='one' data-section='section' data-index='1' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 1);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 1);\n    Common.assertSelection($selected, {text: 'One', value: 'one'});\n\n    assert.deepEqual($(\"select\").val(), ['one']);\n  });\n\n  it(\"only respects data-index if selected\", () => {\n    $(\"select\").append(\"<option value='one' data-section='section' data-index='2'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='section' data-index='2' selected>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 2);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 1);\n    Common.assertSelection($selected, {text: 'Two', value: 'two'});\n\n    assert.deepEqual($(\"select\").val(), ['two']);\n  });\n\n  it('renders selected item correctly', () => {\n    $(\"select\").append(\"<option value='one' data-section='section/foo/bar' data-index='1' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 1);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 1);\n\n    Common.assertSelection($selected, {text: 'One', value: 'one'});\n\n    var $sectionName = $selected.first().children(\".section-name\");\n    assert.equal($sectionName.length, 1);\n    assert.equal(Common.textOf($sectionName[0]), \"section/foo/bar\");\n  });\n\n  it('ranks data-index lowest to highest', () => {\n    $(\"select\").append(\"<option value='two' data-section='section' data-index='2' selected>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='section' data-index='3' selected>Three</option>\");\n    $(\"select\").append(\"<option value='one' data-section='section' data-index='1' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 3);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 3);\n\n    Common.assertSelection($selected[0], {text: 'One', value: 'one'});\n    Common.assertSelection($selected[1], {text: 'Two', value: 'two'});\n    Common.assertSelection($selected[2], {text: 'Three', value: 'three'});\n\n    assert.deepEqual($(\"select\").val(), ['one', 'two', 'three']);\n  });\n\n  it('handles non-consecutive data-index', () => {\n    $(\"select\").append(\"<option value='two' data-section='section' data-index='593' selected>Two</option>\");\n    $(\"select\").append(\"<option value='one' data-section='section' data-index='1' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 2);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 2);\n\n    Common.assertSelection($selected[0], {text: 'One', value: 'one'});\n    Common.assertSelection($selected[1], {text: 'Two', value: 'two'});\n\n    assert.deepEqual($(\"select\").val(), ['one', 'two']);\n  });\n\n  it('ranks data-index higher than selected attribute', () => {\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='section' selected data-index='300'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 2);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 2);\n\n    Common.assertSelection($selected[0], {text: 'Two', value: 'two'});\n    Common.assertSelection($selected[1], {text: 'One', value: 'one'});\n\n    assert.deepEqual($(\"select\").val(), ['two', 'one']);\n  });\n\n  it(\"data-index doesn't do string comparison\", () => {\n    $(\"select\").append(\"<option value='one' data-section='section' data-index='2' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='section' data-index='10' selected>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 2);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 2);\n\n    Common.assertSelection($selected[0], {text: 'One', value: 'one'});\n    Common.assertSelection($selected[1], {text: 'Two', value: 'two'});\n\n    assert.deepEqual($(\"select\").val(), ['one', 'two']);\n  });\n\n  it(\"repeated data-index results in original order\", () => {\n    $(\"select\").append(\"<option value='two' data-section='section' data-index='2' selected>Two</option>\");\n    $(\"select\").append(\"<option value='one' data-section='section' data-index='2' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 2);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 2);\n\n    Common.assertSelection($selected[0], {text: 'Two', value: 'two'});\n    Common.assertSelection($selected[1], {text: 'One', value: 'one'});\n\n    assert.deepEqual($(\"select\").val(), ['two', 'one']);\n  });\n\n  it(\"repeated data-index comes before sequential data-index\", () => {\n    $(\"select\").append(\"<option value='one' data-section='section' data-index='2' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='section' data-index='3' selected>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='section' data-index='2' selected>Three</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 3);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 3);\n\n    Common.assertSelection($selected[0], {text: 'One', value: 'one'});\n    Common.assertSelection($selected[1], {text: 'Three', value: 'three'});\n    Common.assertSelection($selected[2], {text: 'Two', value: 'two'});\n\n    assert.deepEqual($(\"select\").val(), ['one', 'three', 'two']);\n  });\n});\n"
  },
  {
    "path": "test/integration/interactivity.test.js",
    "content": "var Common = require('./common');\n\ndescribe('Interactivity', () => {\n  it('data-description pops up when moused over', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' selected='selected' data-description='One'>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $descriptions = $(\"div.item > span.description\");\n    assert.equal($descriptions.length, 1);\n\n    $descriptions.first().mouseenter();\n    var $tempPopup = $(\".temp-description-popup\");\n    assert.equal($tempPopup.length, 1);\n    assert.equal(Common.textOf($tempPopup), 'One');\n\n    $descriptions.first().mouseleave();\n    $tempPopup = $(\".temp-description-popup\");\n    assert.equal($tempPopup.length, 0);\n  });\n\n  it('data-description is removed when mouse leaves', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' selected='selected' data-description='One'>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $descriptions = $(\"div.item > span.description\");\n    assert.equal($descriptions.length, 1);\n\n    $descriptions.first().mouseenter();\n    var $tempPopup = $(\".temp-description-popup\");\n    assert.equal($tempPopup.length, 1);\n\n    $descriptions.first().mouseleave();\n    $tempPopup = $(\".temp-description-popup\");\n    assert.equal($tempPopup.length, 0);\n  });\n\n  //it('collapses when clicking on titlebar', () => {\n    //$(\"select\").append(\"<option value='one' data-section='foo' data-description='One'>One</option>\");\n    //$(\"select\").append(\"<option value='two' data-section='foo' selected='selected' data-description='Two'>Two</option>\");\n    //$(\"select\").append(\"<option value='three' data-section='foo' data-description='Three'>Three</option>\");\n    //$(\"select\").treeMultiselect();\n\n    //var $title = Common.section({text: 'foo'}).children(\".title\");\n    //assert.equal($(\"div.selections div.item:visible\").length, 3);\n\n    //$title.click();\n    //assert.equal($(\"div.selections div.item:visible\").length, 0);\n  //});\n\n  //it('collapse indicator changes', () => {\n    //$(\"select\").append(\"<option value='one' data-section='foo' data-description='One'>One</option>\");\n    //$(\"select\").append(\"<option value='two' data-section='foo' selected='selected' data-description='Two'>Two</option>\");\n    //$(\"select\").append(\"<option value='three' data-section='foo' data-description='Three'>Three</option>\");\n    //$(\"select\").treeMultiselect();\n\n    //var $title = Common.section({text: 'foo'}).children(\".title\");\n    //var $collapse = $title.children(\".collapse-section\");\n    //assert.equal($collapse.text(), '-');\n\n    //$title.click();\n\n    //assert.equal($collapse.text(), '+');\n  //});\n\n  it('has correct label id', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' data-description='One'>One</option>\");\n    $(\"select\").treeMultiselect();\n    var $option = $(\"input.option\");\n    assert.equal($option.length, 1);\n    assert.equal($(\"#\" + $option.attr('id')).length, 1);\n\n    $(\"body\").append(\"select#two\");\n    $(\"#two\").append(\"<option value='two' data-section='foo' data-description='Two'>Two</option>\");\n    $(\"#two\").treeMultiselect();\n    assert.equal($(\"#\" + $option.attr('id')).length, 1);\n  })\n});\n"
  },
  {
    "path": "test/integration/options.test.js",
    "content": "var Common = require('./common');\n\ndescribe('Options', () => {\n  it('is collapsible', () => {\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test'>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='test'>Three</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $section = Common.section();\n    assert.equal($section.length, 1);\n\n    var $title = $section.children(\"div.title\");\n    assert.equal($title.length, 1);\n\n    Common.section().each(function() {\n      assert(!$(this).hasClass(\"collapsed\"));\n    });\n\n    $title.click();\n\n    Common.section().each(function() {\n      assert($(this).hasClass(\"collapsed\"));\n    });\n  });\n\n  it('startCollapsed', () => {\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test/inner'>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='test/inner2'>Three</option>\");\n    $(\"select\").append(\"<option value='1' data-section='another'>Beep</option>\");\n    var options = {\n      startCollapsed: true\n    };\n    $(\"select\").treeMultiselect(options);\n\n    var $section = Common.section();\n    assert.equal($section.length, 4);\n    var $hiddenSections = $section.filter((idx, el) => {\n      return $(el).hasClass(\"collapsed\");\n    });\n    assert.equal($hiddenSections.length, 4);\n  });\n\n  it(\"startCollapsed doesn't do anything if collapsible is false\", () => {\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test'>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='test'>Three</option>\");\n\n    $(\"select\").append(\"<option value='four' data-section='test/inner'>Four</option>\");\n    $(\"select\").append(\"<option value='five' data-section='test/inner2'>Five</option>\");\n    $(\"select\").append(\"<option value='Six' data-section='test/inner2'>Six</option>\");\n\n    var options = {\n      collapsible: false,\n      startCollapsed: true\n    };\n\n    $(\"select\").treeMultiselect(options);\n\n    var $section = Common.section();\n    assert.equal($section.length, 3);\n\n    var $title = $section.children(\"div.title\");\n    assert.equal($title.length, 3);\n\n    Common.selection().each(function() {\n      assert.notOk($(this).hasClass(\"collapsed\"));\n    });\n\n    $title.each(() => {\n      $(this).click();\n      Common.selection().each(function() {\n        assert.notOk($(this).hasClass(\"collapsed\"));\n      });\n    });\n  });\n\n  it('can set a different section delimiter', () => {\n    var options = {\n      sectionDelimiter: '-'\n    };\n\n    $(\"select\").append(\"<option value='one' data-section='top-inner'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='top-inner'>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='top-inner2'>Three</option>\");\n\n    $(\"select\").treeMultiselect(options);\n\n    var $selections = Common.selection();\n    assert.equal($selections.length, 3);\n\n    var $sections = Common.section();\n    assert.equal($selections.length, 3);\n    var $innerSections = $sections.first().children(\".section\");\n    assert.equal($innerSections.length, 2);\n\n    assert.equal(Common.textOf($innerSections.first().children('div.title')), 'inner');\n    assert.equal(Common.textOf($innerSections.last().children('div.title')), 'inner2');\n  });\n\n  it('can disable batch selection', () => {\n    var options = {\n      allowBatchSelection: false\n    };\n\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test'>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='test'>Three</option>\");\n    $(\"select\").append(\"<option value='four' data-section='test/inner'>Four</option>\");\n    $(\"select\").append(\"<option value='five' data-section='test/inner2'>Five</option>\");\n    $(\"select\").append(\"<option value='Six' data-section='test/inner2'>Six</option>\");\n    $(\"select\").treeMultiselect(options);\n\n    assert.equal($(\"input.section[type=checkbox]\").length, 0);\n  });\n\n  it('can disable section display for selected items', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' data-description='foobar' selected='selected'>One</option>\");\n    var options = {\n      showSectionOnSelected: false\n    };\n    $(\"select\").treeMultiselect(options);\n\n    var $selectedItem = Common.selection();\n    assert.equal($selectedItem.length, 1);\n    assert.equal($selectedItem.find(\"span.section-name\").length, 0);\n  });\n\n  it('can freeze selections', () => {\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected='selected'>Two</option>\");\n    var options = {\n      freeze: true\n    };\n    $(\"select\").treeMultiselect(options);\n\n    var $checkboxes = Common.selection().children(\"input[type=checkbox]\");\n    assert.equal($checkboxes.length, 2);\n    $checkboxes.each(function() {\n      var $checkbox = $(this);\n      assert($checkbox.attr('disabled'));\n    });\n\n    var removeSpans = $(\"div.selected span.remove-selected\");\n    assert.equal(removeSpans.length, 0);\n  });\n\n  it('section checkboxes are indetermine if some children are selected', () => {\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected='selected'>Two</option>\");\n    var options = {\n      freeze: true\n    };\n    $(\"select\").treeMultiselect(options);\n\n    var $sectionCheckbox = Common.sectionCheckbox({text: 'test'});\n    assert.equal($sectionCheckbox.length, 1);\n    var checkbox = $sectionCheckbox[0];\n    assert(checkbox.indeterminate);\n    assert(!checkbox.checked);\n  });\n\n  it('applies only to one tree and not another', () => {\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    $(\"#fixture\").append(\"<select id='frozen'></select>\");\n    $(\"select#frozen\").append(\"<option value='two' data-section='anothertest' selected='selected'>Two</option>\");\n    var options = {\n      freeze: true\n    };\n    $(\"select#frozen\").treeMultiselect(options);\n\n    var $frozenOption = Common.selection({text: 'Two'});\n    assert.equal($frozenOption.length, 1);\n    assert($frozenOption.find(\"input[type=checkbox]\").attr('disabled'));\n\n    var $unfrozenOption = Common.selection({text: 'One'});\n    assert.equal($unfrozenOption.length, 1);\n    var $checkbox = $unfrozenOption.find(\"input[type=checkbox]\");\n    assert.notOk($checkbox.attr('disabled'));\n    $checkbox.click();\n\n    var $unfrozenSelection = Common.selected({text: 'One'});\n    assert.equal($unfrozenSelection.length, 1);\n    assert.deepEqual($(\"select\").val(), ['one']);\n    assert.deepEqual($(\"select#frozen\").val(), ['two']);\n  });\n\n  it('hides side panel', () => {\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    var options = {\n      hideSidePanel: true\n    };\n    $(\"select\").treeMultiselect(options);\n\n    assert.equal($(\"div.selected\").length, 0);\n  });\n\n  it('onlyBatchSelection gives checkboxes to only sections', () => {\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    var options = {\n      onlyBatchSelection: true\n    };\n    $(\"select\").treeMultiselect(options);\n\n    assert.equal($(\"input.section[type=checkbox]\").length, 1);\n    assert.equal($(\"input.option[type=checkbox]\").length, 0);\n  });\n\n  it('calls onChange with correct arguments when item is added', (done) => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test'>Two</option>\");\n    var options = {\n      onChange: function(all, added, removed) {\n                  assert.equal(all.length, 2);\n                  assert.equal(added.length, 1);\n                  assert.equal(removed.length, 0);\n                  var expectedSecondSelections = [all[1], added[0]];\n                  for (var i = 0; i < expectedSecondSelections.length; ++i) {\n                    var selection = expectedSecondSelections[i];\n                    assert.equal(selection.text, 'Two');\n                    assert.equal(selection.value, 'two');\n                    assert.isNull(selection.initialIndex);\n                    assert.equal(selection.section, 'test');\n                  }\n                  assert.equal(all[0].text, 'One');\n                  assert.equal(all[0].value, 'one');\n                  assert.isNull(all[0].initialIndex);\n                  assert.equal(all[0].section, 'test');\n                  done();\n                }\n    };\n    $(\"select\").treeMultiselect(options);\n\n    var $item = Common.selection({text: 'Two'});\n    assert.equal($item.length, 1);\n    $item.find(\"input[type=checkbox]\").click();\n  });\n\n  it('calls onChange with correct arguments when item is removed', (done) => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test'>Two</option>\");\n    var options = {\n      onChange: function(all, added, removed) {\n                  assert.equal(all.length, 0);\n                  assert.equal(added.length, 0);\n                  assert.equal(removed.length, 1);\n                  assert.equal(removed[0].text, 'One');\n                  assert.equal(removed[0].value, 'one');\n                  assert.isNull(removed[0].initialIndex);\n                  assert.equal(removed[0].section, 'test');\n                  done();\n                }\n    };\n    $(\"select\").treeMultiselect(options);\n\n    var $item = Common.selection({text: 'One'});\n    assert.equal($item.length, 1);\n    $item.find(\"input[type=checkbox]\").click();\n  });\n\n  it('fixes original select value when sorted', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected>Two</option>\");\n    $(\"select\").treeMultiselect({ sortable: true });\n\n    assert.deepEqual($(\"select\").val(), ['one', 'two']);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 2);\n    var $one = $selected.first();\n    var $two = $selected.last();\n\n    assert($(\"div.selected\").sortable('option', 'start'));\n    $(\"div.selected\").sortable('option', 'start')(null, {\n      item: $one\n    });\n    $one.insertAfter($two);\n    assert($(\"div.selected\").sortable('option', 'stop'));\n    $(\"div.selected\").sortable('option', 'stop')(null, {\n      item: $one\n    });\n\n    assert.deepEqual($(\"select\").val(), ['two', 'one']);\n  });\n\n  it('puts selected items in right order when sorted', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected>Two</option>\");\n    $(\"select\").treeMultiselect({ sortable: true });\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 2);\n    var $one = $selected.first();\n    var $two = $selected.last();\n\n    Common.assertSelected($one, {text: 'One', value: 'one', section: 'test'})\n    Common.assertSelected($two, {text: 'Two', value: 'two', section: 'test'})\n\n    assert($(\"div.selected\").sortable('option', 'start'));\n    $(\"div.selected\").sortable('option', 'start')(null, {\n      item: $one\n    });\n    $one.insertAfter($two);\n    assert($(\"div.selected\").sortable('option', 'stop'));\n    $(\"div.selected\").sortable('option', 'stop')(null, {\n      item: $one\n    });\n\n    $selected = Common.selected();\n    assert.equal($selected.length, 2);\n    Common.assertSelected($selected.first(), {text: 'Two', value: 'two', section: 'test'})\n    Common.assertSelected($selected.last(), {text: 'One', value: 'one', section: 'test'})\n  });\n\n  it(\"doesn't do anything when sorted in same order\", () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected>Two</option>\");\n    $(\"select\").treeMultiselect({ sortable: true });\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 2);\n    var $one = $selected.first();\n    var $two = $selected.last();\n\n    Common.assertSelected($one, {text: 'One', value: 'one', section: 'test'})\n    Common.assertSelected($two, {text: 'Two', value: 'two', section: 'test'})\n\n    assert($(\"div.selected\").sortable('option', 'start'));\n    $(\"div.selected\").sortable('option', 'start')(null, {\n      item: $one\n    });\n    assert($(\"div.selected\").sortable('option', 'stop'));\n    $(\"div.selected\").sortable('option', 'stop')(null, {\n      item: $one\n    });\n\n    $selected = Common.selected();\n    assert.equal($selected.length, 2);\n    Common.assertSelected($selected.first(), {text: 'One', value: 'one', section: 'test'})\n    Common.assertSelected($selected.last(), {text: 'Two', value: 'two', section: 'test'})\n  });\n\n  it('select all button works', () => {\n    $(\"select\").append(\"<option value='one' data-section='test'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test'>Two</option>\");\n    $(\"select\").treeMultiselect({ enableSelectAll: true });\n\n    var $selectAll = $(\".select-all\");\n    assert.equal($selectAll.length, 1);\n\n    var $selectedItems = Common.selected();\n    assert.equal($selectedItems.length, 0);\n    assert.deepEqual($(\"select\").val(), null);\n\n    $selectAll.click();\n\n    $selectedItems = Common.selected();\n    assert.equal($selectedItems.length, 2);\n    assert.deepEqual($(\"select\").val(), ['one', 'two']);\n  });\n\n  it('unselect button works', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected='selected'>Two</option>\");\n    $(\"select\").treeMultiselect({ enableSelectAll: true });\n\n    var $unselectAll = $(\".unselect-all\");\n    assert.equal($unselectAll.length, 1);\n\n    var $selectedItems = Common.selected();\n    assert.equal($selectedItems.length, 2);\n    assert.deepEqual($(\"select\").val(), ['one', 'two']);\n\n    $unselectAll.click();\n\n    $selectedItems = Common.selected();\n    assert.equal($selectedItems.length, 0);\n    assert.deepEqual($(\"select\").val(), null);\n  });\n\n  it('select all text option', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected'>One</option>\");\n    var selectAllText = \"foobar\";\n    $(\"select\").treeMultiselect({ enableSelectAll: true, selectAllText: selectAllText });\n\n    var $selectAll = $(\".select-all\");\n    assert.equal($selectAll.text(), selectAllText);\n  });\n\n  it('unselect all text option', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected'>One</option>\");\n    var unselectAllText = \"foobar\";\n    $(\"select\").treeMultiselect({ enableSelectAll: true, unselectAllText: unselectAllText });\n\n    var $unselectAll = $(\".unselect-all\");\n    assert.equal($unselectAll.text(), unselectAllText);\n  });\n\n  it('can have individual readonly attributes', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected='selected' readonly>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $firstSelectionCheckbox = Common.selectionCheckbox({value: 'one'});\n    assert.equal($firstSelectionCheckbox.length, 1);\n    assert.isFalse($firstSelectionCheckbox.prop('disabled'));\n\n    $firstSelectionCheckbox = Common.selectionCheckbox({value: 'two'});\n    assert.equal($firstSelectionCheckbox.length, 1);\n    assert.isTrue($firstSelectionCheckbox.prop('disabled'));\n  });\n\n  it('has readonly attributes that still appear in select val', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected readonly>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['one'])\n  });\n\n  it('cannot remove readonly elements by selected elements', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected' readonly>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected='selected'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 2);\n\n    var $selectedCheckboxes = $selected.children(\"span.remove-selected\");\n    assert.equal($selectedCheckboxes.length, 1);\n    $selectedCheckboxes.click();\n\n    $selected = Common.selected();\n    assert.equal($selected.length, 1);\n\n    $selected = Common.selected({value: 'one'});\n    assert.equal($selected.length, 1);\n  });\n\n  it('can set a maximum number of selections', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected='selected'>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='test' selected='selected'>Three</option>\");\n    $(\"select\").append(\"<option value='four' data-section='test' selected='selected'>Four</option>\");\n    $(\"select\").treeMultiselect({maxSelections: 2});\n\n    assert.equal(Common.selected().length, 2);\n    assert.deepEqual($(\"select\").val(), ['three', 'four'])\n\n    var $checkbox = Common.selectionCheckbox();\n    $checkbox.first().click();\n\n    assert.equal(Common.selected().length, 2);\n    assert.deepEqual($(\"select\").val(), ['four', 'one'])\n  })\n\n  it('maximum number of selections doesn\\'t work with negative numbers', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected='selected'>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='test' selected='selected'>Three</option>\");\n    $(\"select\").append(\"<option value='four' data-section='test' selected='selected'>Four</option>\");\n    $(\"select\").treeMultiselect({maxSelections: -1});\n\n    assert.equal(Common.selected().length, 4);\n  })\n\n  it('maximum number of selections doesn\\'t work with non-numerical truthy values', () => {\n    $(\"select\").append(\"<option value='one' data-section='test' selected='selected'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='test' selected='selected'>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='test' selected='selected'>Three</option>\");\n    $(\"select\").append(\"<option value='four' data-section='test' selected='selected'>Four</option>\");\n    $(\"select\").treeMultiselect({maxSelections: true});\n\n    assert.equal(Common.selected().length, 4);\n  })\n});\n"
  },
  {
    "path": "test/integration/reloading.test.js",
    "content": "var Common = require('./common');\n\ndescribe('Reloading', () => {\n  it('can reload tree', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' selected='selected' data-description='One'>One</option>\");\n    var trees = $(\"select\").treeMultiselect();\n    var tree = trees[0];\n\n    assert.equal(Common.selection().length, 1);\n    assert.equal(Common.selection({text: 'One'}).length, 1);\n\n    $(\"select\").append(\"<option value='two' data-section='foo' selected='selected' data-description='Two'>Two</option>\");\n\n    tree.reload();\n    assert.equal(Common.selection().length, 2);\n    assert.equal(Common.selection({text: 'One'}).length, 1);\n    assert.equal(Common.selection({text: 'Two'}).length, 1);\n  });\n\n  it('reload saves user-changed options', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' selected='selected' data-description='One'>One</option>\");\n    var trees = $(\"select\").treeMultiselect();\n    var tree = trees[0];\n\n    assert.equal(Common.selection().length, 1);\n    assert.equal(Common.selected().length, 1);\n    assert.equal($(\"select\").val().length, 1);\n\n    $(\"select\").val([]);\n    $(\"select\").change();\n\n    $(\"select\").append(\"<option value='two' data-section='foo' selected='selected' data-description='Two'>Two</option>\");\n    tree.reload();\n\n    assert.equal(Common.selection().length, 2);\n    assert.equal(Common.selected().length, 1);\n    assert.deepEqual($(\"select\").val(), ['one']);\n  });\n});\n"
  },
  {
    "path": "test/integration/removing.test.js",
    "content": "var Common = require('./common');\n\ndescribe('Removing', () => {\n  it('can remove tree', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' selected='selected' data-description='One'>One</option>\");\n    var trees = $(\"select\").treeMultiselect();\n    var tree = trees[0];\n\n    assert.equal($(\".tree-multiselect\").length, 1);\n\n    tree.remove();\n    assert.equal($(\".tree-multiselect\").length, 0);\n  });\n});\n"
  },
  {
    "path": "test/integration/search.test.js",
    "content": "var Common = require('./common');\n\nfunction getVisibleSelections(props) {\n  return Common.selection(props).filter((_, el) => {\n    return el.getAttribute('searchhit') === 'true';\n  })\n}\n\nfunction getHiddenSelections(props) {\n  return Common.selection(props).filter((_, el) => {\n    return el.getAttribute('searchhit') === 'false';\n  })\n}\n\ndescribe('Search', () => {\n  var $input;\n\n  describe('default behavior', () => {\n    beforeEach(() => {\n      // value, section\n      $(\"select\").append(\"<option value='abcde' data-section='s1'></option>\");\n\n      // section\n      $(\"select\").append(\"<option value='fghij' data-section='s1'>yyyyy</option>\");\n\n      // text\n      $(\"select\").append(\"<option value='KLMNOP' data-section='s2'>klmnop</option>\");\n\n      // description\n      $(\"select\").append(\"<option value='QRS' data-section='ttt/uuu/vvv' data-description='fox'></option>\");\n\n      // description with spaces\n      $(\"select\").append(\"<option value='passion' data-section='s3' data-description='Passion'></option>\");\n      $(\"select\").append(\"<option value='passionfruit' data-section='fruit' data-description='Passion Fruit'></option>\");\n\n      $(\"select\").treeMultiselect({searchable: true, enableSelectAll: true});\n\n      $input = $(\"input.search\");\n      assert.equal($input.length, 1);\n    });\n\n    it('matches on value', () => {\n      ['a', 'c', 'abc', 'cde', 'bcd', 'abcde', 'abcd'].forEach((searchTerm) => {\n        $input.val(searchTerm).trigger('input');\n        assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'true');\n        assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'false');\n        assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false');\n        assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'false');\n      });\n\n      ['fghij', 'ghi', 'fgh'].forEach((searchTerm) => {\n        $input.val(searchTerm).trigger('input');\n        assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'false');\n        assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'true');\n        assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false');\n        assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'false');\n      });\n\n      ['q', 'qr', 'qrs', 'rs'].forEach((searchTerm) => {\n        $input.val(searchTerm).trigger('input');\n        assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'false');\n        assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'false');\n        assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false');\n        assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'true');\n      });\n    });\n\n    it('matches on section', () => {\n      $input.val('s1').trigger('input');\n      assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'true');\n      assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'true');\n      assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false');\n      assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'false');\n    });\n\n    it('matches on text', () => {\n      $input.val('yyy').trigger('input');\n      assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'false');\n      assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'true');\n      assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false');\n      assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'false');\n    });\n\n    it('matches on description', () => {\n      $input.val('fox').trigger('input');\n      assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'false');\n      assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'false');\n      assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false');\n      assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'true');\n    });\n\n    it('hides sections with no nodes visible', () => {\n      $input.val('s1').trigger('input');\n      assert.equal(Common.section({text: 's1'}).attr('searchhit'), 'true');\n      assert.equal(Common.section({text: 's2'}).attr('searchhit'), 'false');\n      assert.equal(Common.section({text: 'ttt'}).attr('searchhit'), 'false');\n      assert.equal(Common.section({text: 'uuu'}).attr('searchhit'), 'false');\n      assert.equal(Common.section({text: 'vvv'}).attr('searchhit'), 'false');\n\n      $input.val('uuu').trigger('input');\n      assert.equal(Common.section({text: 's1'}).attr('searchhit'), 'false');\n      assert.equal(Common.section({text: 's2'}).attr('searchhit'), 'false');\n      assert.equal(Common.section({text: 'ttt'}).attr('searchhit'), 'true');\n      assert.equal(Common.section({text: 'uuu'}).attr('searchhit'), 'true');\n      assert.equal(Common.section({text: 'vvv'}).attr('searchhit'), 'true');\n    });\n\n    it('shows all sections when no search term is entered', () => {\n      $input.val('43t#Q%').trigger('input'); // no nodes should be shown\n      assert.equal(getVisibleSelections().length, 0);\n\n      $input.val('').trigger('input'); // no nodes should be shown\n      assert.equal(getHiddenSelections().length, 0);\n    });\n\n    it('only adds filtered selections when using section checkbox', () => {\n      $input.val('abcde').trigger('input');\n\n      assert.equal(Common.selected().length, 0);\n      Common.sectionCheckbox({text: 's1'}).click();\n      assert.equal(Common.selected().length, 1);\n\n      $input.val('fox').trigger('input');\n      assert.equal(Common.selected().length, 1);\n      var $tttCheckbox = Common.sectionCheckbox({text: 'ttt'})\n      $tttCheckbox.click();\n      assert.equal(Common.selected().length, 2);\n\n      $tttCheckbox.click();\n      assert.equal(Common.selected().length, 1);\n    });\n\n    it('only adds filtered selections when selecting and unselecting all', () => {\n      $input.val('s1').trigger('input');\n\n      var $selectAll = $(\".select-all\");\n      var $unselectAll = $(\".unselect-all\");\n      assert.equal($selectAll.length, 1);\n      assert.equal($unselectAll.length, 1);\n\n      assert.equal(Common.selected().length, 0);\n      $selectAll.click();\n      assert.equal(Common.selected().length, 2);\n\n      $input.val('abcde').trigger('input');\n      $unselectAll.click();\n      assert.equal(Common.selected().length, 1);\n    })\n\n    it('handles empty search queries', () => {\n      $input.val(' ').trigger('input');\n\n      assert.equal(getVisibleSelections().length, 0);\n      assert.equal(getHiddenSelections().length, 0);\n    })\n\n    it('handles search queries with spaces in them', () => {\n      $input.val('passion ').trigger('input');\n      assert.equal(getVisibleSelections().length, 2);\n      assert.equal(getHiddenSelections().length, 4);\n\n      $input.val('passion f ').trigger('input');\n      assert.equal(getVisibleSelections().length, 1);\n      assert.equal(getHiddenSelections().length, 5);\n    })\n  });\n\n  describe('custom search params', () => {\n    beforeEach(() => {\n      $(\"select\").append(\"<option value='abcde' data-section='s1' data-description='xyz'>ayy</option>\");\n\n    });\n\n    it('section only', () => {\n      $(\"select\").treeMultiselect({searchable: true, searchParams: ['section']});\n      $input = $(\"input.search\");\n\n      $input.val('s1').trigger('input');\n      assert.equal(getVisibleSelections().length, 1);\n\n      $input.val('abc').trigger('input');\n      assert.equal(getVisibleSelections().length, 0);\n\n      $input.val('ayy').trigger('input');\n      assert.equal(getVisibleSelections().length, 0);\n\n      $input.val('xyz').trigger('input');\n      assert.equal(getVisibleSelections().length, 0);\n    });\n\n    it('value only', () => {\n      $(\"select\").treeMultiselect({searchable: true, searchParams: ['value']});\n      $input = $(\"input.search\");\n\n      $input.val('s1').trigger('input');\n      assert.equal(getVisibleSelections().length, 0);\n\n      $input.val('abc').trigger('input');\n      assert.equal(getVisibleSelections().length, 1);\n\n      $input.val('ayy').trigger('input');\n      assert.equal(getVisibleSelections().length, 0);\n\n      $input.val('xyz').trigger('input');\n      assert.equal(getVisibleSelections().length, 0);\n    });\n\n    it('text and description', () => {\n      $(\"select\").treeMultiselect({searchable: true, searchParams: ['text', 'description']});\n      $input = $(\"input.search\");\n\n      $input.val('s1').trigger('input');\n      assert.equal(getVisibleSelections().length, 0);\n\n      $input.val('abc').trigger('input');\n      assert.equal(getVisibleSelections().length, 0);\n\n      $input.val('ayy').trigger('input');\n      assert.equal(getVisibleSelections().length, 1);\n\n      $input.val('xyz').trigger('input');\n      assert.equal(getVisibleSelections().length, 1);\n    });\n  });\n});\n"
  },
  {
    "path": "test/integration/section-checkboxes.test.js",
    "content": "var Common = require('./common');\n\ndescribe('Section Checkboxes', () => {\n  it('is all checked when all children are selected initially', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='foo' selected>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.equal($(\"input[type=checkbox]\").length, 3);\n    assert.equal($(\"input.option[type=checkbox]\").length, 2);\n    assert.equal($(\"input.section[type=checkbox]\").length, 1);\n    assert.equal($(\"input[type=checkbox]:checked\").length, 3);\n  });\n\n  it('should all be checked when all children are selected', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='foo'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.equal($(\"input[type=checkbox]\").length, 3);\n    assert.equal($(\"input.option[type=checkbox]\").length, 2);\n    assert.equal($(\"input.section[type=checkbox]\").length, 1);\n\n    assert.equal($(\"input.section[type=checkbox]:checked\").length, 0);\n    assert.equal($(\"input.option[type=checkbox]:checked\").length, 1);\n\n    Common.selection().last().children(\"input[type=checkbox]\").click();\n\n    assert.equal($(\"input.section[type=checkbox]:checked\").length, 1);\n    assert.equal($(\"input.option[type=checkbox]:checked\").length, 2);\n  });\n\n  it('should not check top level parent if only one child section is completely checked', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo/bar' selected='selected'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='foo/baz'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $topLevel = Common.section({text: 'foo'});\n    assert.notOk($topLevel.find(\"> div.title > input.section[type=checkbox]\").is(\":checked\"));\n  });\n\n  it('should uncheck parent sections when a child is unselected', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.equal($(\"input.section[type=checkbox]:checked\").length, 1);\n\n    Common.selection().first().children(\"input.option[type=checkbox]\").click();\n\n    assert.equal($(\"input.section[type=checkbox]:checked\").length, 0);\n  });\n\n  it('checks nested titles', () => {\n    $(\"select\").append(\"<option value='one' data-section='top/middle/inner'>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.equal(Common.sectionCheckbox().length, 3);\n    assert.equal(Common.selectionCheckbox().length, 1);\n    assert.equal($(\"input[type=checkbox]\").length, 4);\n    assert.equal($(\"input[type=checkbox]:checked\").length, 0);\n\n    var $middleSectionCheckbox = Common.sectionCheckbox({text: 'middle'});\n    $middleSectionCheckbox.click();\n\n    assert.equal($(\"div.title > input[type=checkbox]:checked\").length, 3);\n    assert.equal($(\"div.item > input[type=checkbox]:checked\").length, 1);\n    assert.equal($(\"input[type=checkbox]:checked\").length, 4);\n  });\n\n  it('only checks relevant titles', () => {\n    $(\"select\").append(\"<option value='one' data-section='top/middle/inner'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='top'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.notOk(Common.sectionCheckbox({text: 'top'}).prop('checked'));\n    assert.notOk(Common.sectionCheckbox({text: 'middle'}).prop('checked'));\n    assert.notOk(Common.sectionCheckbox({text: 'inner'}).prop('checked'));\n\n    assert.notOk(Common.selectionCheckbox({text: 'One'}).prop('checked'));\n\n    Common.sectionCheckbox({text: 'middle'}).click();\n\n    assert.notOk(Common.sectionCheckbox({text: 'top'}).prop('checked'));\n    assert(Common.sectionCheckbox({text: 'middle'}).prop('checked'));\n    assert(Common.sectionCheckbox({text: 'inner'}).prop('checked'));\n\n    assert(Common.selectionCheckbox({text: 'One'}).prop('checked'));\n  });\n\n  it('checkbox is indeterminate when some children are selected', () => {\n    $(\"select\").append(\"<option value='one' data-section='top/middle/inner' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='top'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $topCheckbox = Common.sectionCheckbox({text: 'top'});\n    assert.notOk($topCheckbox.prop('checked'));\n    assert($topCheckbox.prop('indeterminate'));\n  });\n\n  it('checkbox is not indeterminate when all children are selected', () => {\n    $(\"select\").append(\"<option value='one' data-section='top' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='top' selected>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $titleCheckbox = Common.sectionCheckbox({text: 'top'});\n    assert.notOk($titleCheckbox.prop('indeterminate'));\n  });\n\n  it('checkbox is not indeterminate when no children are selected', () => {\n    $(\"select\").append(\"<option value='one' data-section='top'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='top'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    var $titleCheckbox = Common.sectionCheckbox({text: 'top'});\n    assert.notOk($titleCheckbox.prop('indeterminate'));\n  });\n\n  it('checkbox doesn\\'t select unselected readonly children', () => {\n    $(\"select\").append(\"<option value='one' data-section='top' readonly>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='top'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.equal(Common.selected().length, 0);\n\n    var $titleCheckbox = Common.sectionCheckbox({text: 'top'});\n    $titleCheckbox.click();\n\n    assert.equal(Common.selected().length, 1);\n    assert.equal(Common.selected({value: 'two'}).length, 1);\n  });\n\n  it('checkbox doesn\\'t unselect selected readonly children', () => {\n    $(\"select\").append(\"<option value='one' data-section='top' selected readonly>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='top' selected>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.equal(Common.selected().length, 2);\n\n    var $titleCheckbox = Common.sectionCheckbox({text: 'top'});\n    $titleCheckbox.click();\n\n    assert.equal(Common.selected().length, 1);\n    assert.equal(Common.selected({value: 'one'}).length, 1);\n  });\n});\n"
  },
  {
    "path": "test/integration/section-selections.test.js",
    "content": "var Common = require('./common');\n\ndescribe('Section Selections', () => {\n  it('adds all child elements when section checkbox clicked', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='foo'>Two</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), null);\n    var $selected = Common.selected();\n    assert.equal($selected.length, 0);\n\n    var $checkbox = Common.sectionCheckbox();\n    assert.equal($checkbox.length, 1);\n    $checkbox.click();\n\n    assert.deepEqual($(\"select\").val(), ['one', 'two']);\n\n    $selected = Common.selected();\n    assert.equal($selected.length, 2);\n    Common.assertSelected($selected[0], {text: 'One', value: 'one', section: 'foo'});\n    Common.assertSelected($selected[1], {text: 'Two', value: 'two', section: 'foo'});\n  });\n\n  it(\"doesn't add child elements twice when other child elements are selected\", () => {\n    $(\"select\").append(\"<option value='one' data-section='foo'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='foo'>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='foo/bar/baz' selected>Three</option>\");\n    $(\"select\").append(\"<option value='four' data-section='foo/bar/baz' selected>Four</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['three', 'four']);\n    var $selected = Common.selected();\n    assert.equal($selected.length, 2);\n    Common.assertSelected($selected[0], {text: 'Three', value: 'three', section: 'foo/bar/baz'});\n    Common.assertSelected($selected[1], {text: 'Four', value: 'four', section: 'foo/bar/baz'});\n\n    var $checkbox = Common.sectionCheckbox();\n    assert.equal($checkbox.length, 3);\n    $checkbox.first().click();\n\n    assert.deepEqual($(\"select\").val(), ['three', 'four', 'one', 'two']);\n\n    $selected = Common.selected();\n    assert.equal($selected.length, 4);\n    Common.assertSelected($selected[0], {text: 'Three', value: 'three', section: 'foo/bar/baz'});\n    Common.assertSelected($selected[1], {text: 'Four', value: 'four', section: 'foo/bar/baz'});\n    Common.assertSelected($selected[2], {text: 'One', value: 'one', section: 'foo'});\n    Common.assertSelected($selected[3], {text: 'Two', value: 'two', section: 'foo'});\n  });\n\n  it('removes child elements when section unselected', () => {\n    $(\"select\").append(\"<option value='one' data-section='foo' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='foo' selected>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='foo/bar/baz' selected>Three</option>\");\n    $(\"select\").append(\"<option value='four' data-section='foo/bar/baz' selected>Four</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['one', 'two', 'three', 'four']);\n    var $selected = Common.selected();\n    Common.assertSelected($selected[0], {text: 'One', value: 'one', section: 'foo'});\n    Common.assertSelected($selected[1], {text: 'Two', value: 'two', section: 'foo'});\n    Common.assertSelected($selected[2], {text: 'Three', value: 'three', section: 'foo/bar/baz'});\n    Common.assertSelected($selected[3], {text: 'Four', value: 'four', section: 'foo/bar/baz'});\n\n    var $checkbox = Common.sectionCheckbox({text: 'baz'});\n    assert.equal($checkbox.length, 1);\n    $checkbox.click();\n\n    assert.deepEqual($(\"select\").val(), ['one', 'two']);\n\n    $selected = Common.selected();\n    assert.equal($selected.length, 2);\n    Common.assertSelected($selected[0], {text: 'One', value: 'one', section: 'foo'});\n    Common.assertSelected($selected[1], {text: 'Two', value: 'two', section: 'foo'});\n  });\n});\n"
  },
  {
    "path": "test/integration/single-selections.test.js",
    "content": "var Common = require('./common');\n\ndescribe('Single Selection', () => {\n  it('can add an item', () => {\n    $(\"select\").append(\"<option value='one' data-section='section'>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), null);\n\n    var $checkbox = Common.selectionCheckbox({checked: false});\n    assert.equal($checkbox.length, 1);\n    $checkbox.click();\n\n    var $checkboxChecked = Common.selectionCheckbox({checked: true});\n    assert.equal($checkboxChecked.length, 1);\n\n    assert.deepEqual($(\"select\").val(), ['one']);\n  });\n\n  it('can remove an item', () => {\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['one']);\n\n    var $checkbox = Common.selectionCheckbox({checked: true});\n    assert.equal($checkbox.length, 1);\n    $checkbox.click();\n\n    var $checkboxChecked = Common.selectionCheckbox({checked: true});\n    assert.equal($checkboxChecked.length, 0);\n\n    assert.deepEqual($(\"select\").val(), null);\n  });\n\n  it('can add an item with the same text', () => {\n    $(\"select\").append(\"<option value='one' data-section='section'>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='section' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['two']);\n\n    var $checkboxChecked = Common.selectionCheckbox({checked: true});\n    assert.equal($checkboxChecked.length, 1);\n\n    var $checkbox = Common.selectionCheckbox();\n    assert.equal($checkbox.length, 2);\n\n    $checkbox.first().click();\n\n    $checkboxChecked = Common.selectionCheckbox({checked: true});\n    assert.equal($checkboxChecked.length, 2);\n\n    assert.deepEqual($(\"select\").val(), ['two', 'one']);\n  });\n\n  it('can add an item with the same value as another', () => {\n    $(\"select\").append(\"<option value='one' data-section='section'>One</option>\");\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One2</option>\");\n    $(\"select\").append(\"<option value='one' data-section='section'>One3</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['one']);\n\n    var $checkboxChecked = Common.selectionCheckbox({checked: true});\n    assert.equal($checkboxChecked.length, 1);\n\n    var $checkbox = Common.selectionCheckbox();\n    assert.equal($checkbox.length, 3);\n\n    $checkbox.last().click();\n\n    $checkboxChecked = Common.selectionCheckbox({checked: true});\n    assert.equal($checkboxChecked.length, 2);\n\n    assert.deepEqual($(\"select\").val(), ['one', 'one']);\n  });\n\n  it('can remove an item with the same value as another', () => {\n    $(\"select\").append(\"<option value='one' data-section='section'>One</option>\");\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One2</option>\");\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One3</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['one', 'one']);\n\n    var $checkboxChecked = Common.selectionCheckbox({checked: true});\n    assert.equal($checkboxChecked.length, 2);\n\n    var $checkbox = Common.selectionCheckbox();\n    assert.equal($checkbox.length, 3);\n\n    $checkbox.last().click();\n\n    $checkboxChecked = Common.selectionCheckbox({checked: true});\n    assert.equal($checkboxChecked.length, 1);\n\n    assert.deepEqual($(\"select\").val(), ['one']);\n  });\n\n  it('can remove an item by selected item remove button', () => {\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['one']);\n\n    var $selected = Common.selected();\n    assert.equal($selected.length, 1);\n\n    var $removeSpan = $selected.children(\".remove-selected\");\n    $removeSpan.click();\n\n    assert.equal(Common.selected().length, 0);\n    assert.equal($(\"select\").val(), null);\n  });\n\n  it('can remove an item by unchecking selection checkbox', () => {\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['one']);\n\n    var $selections = Common.selection();\n    var $checkbox = $selections.children(\"input[type=checkbox]\");\n    $checkbox.click();\n\n    assert.deepEqual($(\"select\").val(), null);\n  });\n\n  it('removing an item does not remove any others', () => {\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One</option>\");\n    $(\"select\").append(\"<option value='two' data-section='section' selected>Two</option>\");\n    $(\"select\").append(\"<option value='three' data-section='section' selected>Three</option>\");\n    $(\"select\").treeMultiselect();\n\n    assert.deepEqual($(\"select\").val(), ['one', 'two', 'three']);\n\n    var $selections = Common.selected();\n    assert.equal($selections.length, 3);\n\n    var $removeSpan = $selections.first().children(\".remove-selected\");\n    $removeSpan.click();\n\n    assert.equal(Common.selected().length, 2);\n    assert.deepEqual($(\"select\").val(), ['two', 'three']);\n  });\n\n  it('fires change event on original select', (done) => {\n    $(\"select\").append(\"<option value='one' data-section='section' selected>One</option>\");\n    $(\"select\").treeMultiselect();\n\n    $(\"select\").change(function() {\n      done();\n    });\n\n    Common.selection().children(\"input[type=checkbox]\").click();\n  });\n});\n"
  },
  {
    "path": "test/test-performance.html",
    "content": "<!DOCTYPE html5>\n<html>\n  <head>\n    <title>Tree Multiselect test</title>\n\n    <meta charset=\"UTF-8\">\n\n    <script src=\"./vendor/jquery-1.11.3.min.js\"></script>\n    <script src=\"./vendor/jquery-ui.min.js\"></script>\n    <script src=\"../dist/jquery.tree-multiselect.js\"></script>\n\n    <style>\n      * {\n        font-family: sans-serif;\n      }\n    </style>\n    <link rel=\"stylesheet\" href=\"../dist/jquery.tree-multiselect.min.css\">\n  </head>\n\n  <body>\n    <select id=\"test-select\" multiple=\"multiple\">\n      <option value=\"blueberry\" data-section=\"Smoothies\">Blueberry</option>\n      <option value=\"strawberry\" data-section=\"Smoothies\">Strawberries</option>\n      <option value=\"peach\" data-section=\"Smoothies\">Peach</option>\n      <option value=\"milk tea\" data-section=\"Smoothies/Bubble Tea\">Milk Tea</option>\n      <option value=\"green apple\" data-section=\"Smoothies/Bubble Tea\">Green Apple</option>\n      <option value=\"passion fruit\" data-section=\"Smoothies/Bubble Tea\" data-description=\"The greatest flavor\" selected=\"selected\">Passion Fruit</option>\n    </select>\n\n    <script type=\"text/javascript\">\n      var $select = $('#test-select');\n      for (var ii = 0; ii < 2000; ++ii) {\n        var $option = $('<option value=\"fruit' + ii +'\" data-section=\"Smoothies/' + ii + '\" data-description=\"The greatest flavor\" selected=\"selected\">Passion Fruit</option>');\n        $select.append($option);\n      }\n      var time = new Date();\n      console.profile('tree-multiselect');\n      $select.treeMultiselect({ enableSelectAll: true, sortable: true, searchable: true });\n      console.profileEnd();\n      console.log(\"time elapsed - \" + (new Date() - time) + \"ms\");\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/test.html",
    "content": "<!DOCTYPE html5>\n<html>\n  <head>\n    <title>Tree Multiselect test</title>\n\n    <meta charset=\"UTF-8\">\n\n    <style>\n      * {\n        font-family: sans-serif;\n      }\n    </style>\n    <link rel=\"stylesheet\" href=\"../dist/jquery.tree-multiselect.min.css\">\n\n    <script src=\"./vendor/jquery-1.11.3.min.js\"></script>\n    <script src=\"./vendor/jquery-ui.min.js\"></script>\n    <script src=\"../dist/jquery.tree-multiselect.js\"></script>\n  </head>\n\n  <body>\n    <select id=\"test-select\" multiple=\"multiple\">\n      <option value=\"blueberry\" data-section=\"Smoothies\">Blueberry</option>\n      <option value=\"longan\" data-description=\"Really good :o\" selected=\"selected\">Longan</option>\n      <option value=\"milk tea\" data-section=\"Smoothies/Bubble Tea\">Milk Tea</option>\n      <option value=\"green apple\" data-section=\"Smoothies/Bubble Tea\">Green Apple</option>\n      <option value=\"passion fruit\" data-section=\"Smoothies/Bubble Tea\" data-description=\"The greatest flavor\" selected=\"selected\">Passion Fruit</option>\n      <option value=\"strawberry\" data-section=\"Smoothies\">Strawberries</option>\n      <option value=\"peach\" data-section=\"Smoothies\">Peach</option>\n    </select>\n\n    <select id=\"test-select-2\" multiple=\"multiple\">\n      <option value=\"foo\" data-section=\"section/ayy/lmao\">foo</option>\n      <option value=\"bar\" data-section=\"section/ayy/lmao\" readonly>bar</option>\n      <option value=\"disabled1\" selected readonly>readonly and selected item</option>\n      <option value=\"disabled1\" readonly>readonly item</option>\n    </select>\n\n    <select id=\"test-select-3\" multiple=\"multiple\">\n      <option value=\"baz1\" data-section=\"hidden stuff\">baz1</option>\n      <option value=\"baz2\" data-section=\"hidden stuff\">baz2</option>\n      <option value=\"baz3\" data-section=\"hidden stuff\">baz3</option>\n      <option value=\"baz4\" data-section=\"hidden stuff\">baz4</option>\n      <option value=\"quux1\" data-section=\"section\">quux1</option>\n      <option value=\"quux2\" data-section=\"section\">quux2</option>\n      <option value=\"quux3\" data-section=\"section\">quux3</option>\n      <option value=\"quux4\" data-section=\"section\">quux4</option>\n      <option value=\"abc\" data-section=\"section/ayy\">abc</option>\n      <option value=\"taeyeon\" data-section=\"section/ayy\">태연</option>\n      <option value=\"ayyy\" >ayyy</option>\n      <option value=\"disabled1\" readonly>wow it's readonly</option>\n      <option value=\"disabled2\" selected readonly>also readonly!</option>\n    </select>\n\n    <select id=\"test-select-4\" multiple=\"multiple\">\n      <option value=\"baz1\" data-section=\"hidden stuff\" selected data-index=\"1\">baz1</option>\n      <option value=\"baz2\" data-section=\"hidden stuff\" selected data-index=\"1\">baz2</option>\n      <option value=\"baz3\" data-section=\"hidden stuff\" selected data-index=\"1\">baz3</option>\n      <option value=\"baz4\" data-section=\"hidden stuff\" selected data-index=\"1\">baz4</option>\n      <option value=\"quux1\" data-section=\"section\" selected data-index=\"1\">quux1</option>\n      <option value=\"quux2\" data-section=\"section\" selected data-index=\"1\">quux2</option>\n      <option value=\"quux3\" data-section=\"section\" selected data-index=\"1\">quux3</option>\n      <option value=\"quux4\" data-section=\"section\" selected data-index=\"1\">quux4</option>\n      <option value=\"abc\" data-section=\"section/ayy\" selected data-index=\"1\">abc</option>\n      <option value=\"ayyy\" >ayyy</option>\n      <option value=\"disabled1\" readonly>wow it's readonly</option>\n      <option value=\"disabled2\" selected readonly>also readonly!</option>\n    </select>\n\n\n\n    <script type=\"text/javascript\">\n      var tree1 = $(\"#test-select\").treeMultiselect({ enableSelectAll: true, sortable: true });\n      var tree2 = $(\"#test-select-2\").treeMultiselect({\n        searchable: true\n      });\n      var tree3 = $(\"#test-select-3\").treeMultiselect({\n        allowBatchSelection: false,\n        enableSelectAll: true,\n        maxSelections: 4,\n        searchable: true,\n        sortable: true,\n        startCollapsed: true\n      });\n      var tree4 = $(\"#test-select-4\").treeMultiselect({\n        allowBatchSelection: true,\n        enableSelectAll: true,\n        searchable: true,\n        sortable: true,\n        startCollapsed: true\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/unit/utility.test.js",
    "content": "var Ast = require('ast');\nvar Util = require('utility');\n\ndescribe('Utility', () => {\n  it('asserts', () => {\n    var func = () => {\n      Util.assert(true);\n    }\n    assert.doesNotThrow(func);\n\n    func = () => {\n      Util.assert(false);\n    }\n    assert.throws(func);\n\n    func = () => {\n      Util.assert(NaN);\n    }\n    assert.throws(func);\n  });\n\n  it('getKey', () => {\n     var el = $(\"<div data-key='4'></div>\")[0];\n     assert.equal(Util.getKey(el), 4);\n\n     el = $(\"<div></div>\")[0];\n     assert.isNaN(Util.getKey(el));\n\n     el = $(\"<div data-key='foobar'></div>\")[0];\n     assert.isNaN(Util.getKey(el));\n  });\n\n  it('isInteger', () => {\n     assert.isTrue(Util.isInteger(1));\n     assert.isTrue(Util.isInteger(-1));\n     assert.isTrue(Util.isInteger(0));\n     assert.isTrue(Util.isInteger(\" 3 \"));\n\n     assert.isFalse(Util.isInteger(true));\n     assert.isFalse(Util.isInteger(null));\n     assert.isFalse(Util.isInteger([]));\n     assert.isFalse(Util.isInteger(function () {}));\n  })\n\n  describe('array', () => {\n    it('subtract', () => {\n      var arr1 = [1, 2, 5, 7, 0];\n      var arr2 = [2, 7, 8];\n      Util.array.subtract(arr1, arr2);\n      assert.deepEqual(arr1, [1, 5, 0]);\n\n      arr1 = [];\n      arr2 = [];\n      Util.array.subtract(arr1, arr2);\n      assert.deepEqual(arr1, []);\n\n      arr1 = [];\n      arr2 = [0];\n      Util.array.subtract(arr1, arr2);\n      assert.deepEqual(arr1, []);\n\n      arr1 = [6, 8, 1256];\n      arr2 = [];\n      Util.array.subtract(arr1, arr2);\n      assert.deepEqual(arr1, [6, 8, 1256]);\n\n      arr1 = [\"foo\", \"bar\"];\n      arr2 = [\"baz\"];\n      Util.array.subtract(arr1, arr2);\n      assert.deepEqual(arr1, [\"foo\", \"bar\"]);\n    });\n\n    it('uniq', () => {\n      var arr = [1, 2, 5, 7, 0];\n      Util.array.uniq(arr);\n      assert.deepEqual(arr, [1, 2, 5, 7, 0]);\n\n      arr = [];\n      Util.array.uniq(arr);\n      assert.deepEqual(arr, []);\n\n      arr = [\"abc\", \"abc\", \"ghi\"];\n      Util.array.uniq(arr);\n      assert.deepEqual(arr, [\"abc\", \"ghi\"]);\n\n      arr = [123, 678, 900, 123];\n      Util.array.uniq(arr);\n      assert.deepEqual(arr, [123, 678, 900]);\n    });\n\n    it('removeFalseyExceptZero', () => {\n      var arr = [1, 4, 0, null, NaN, undefined, 3];\n      Util.array.removeFalseyExceptZero(arr);\n      assert.deepEqual(arr, [1, 4, 0, 3]);\n    });\n\n    it('moveEl in-place', () => {\n      var arr = [0, 1, 2, 3, 4, 5];\n      Util.array.moveEl(arr, 5, 1);\n      assert.deepEqual(arr, [0, 5, 1, 2, 3, 4]);\n\n      arr = [0, 1, 2, 3, 4];\n      Util.array.moveEl(arr, 4, 0);\n      assert.deepEqual(arr, [4, 0, 1, 2, 3]);\n\n      arr = [0, 1, 2, 3, 4];\n      Util.array.moveEl(arr, 0, 4);\n      assert.deepEqual(arr, [1, 2, 3, 4, 0]);\n    });\n\n    it('intersect', () => {\n      var arr = [0, 1, 2, 3, 4, 5];\n      var arr2 = [0, 2, 4, 6];\n      Util.array.intersect(arr, arr2);\n      assert.deepEqual(arr, [0, 2, 4]);\n\n      arr = [1, 17, 536, 24];\n      arr2 = [536, 0, 0, 0];\n      Util.array.intersect(arr, arr2);\n      assert.deepEqual(arr, [536]);\n\n      arr = [0, 0, 0, 0, 0, 0];\n      arr2 = [0, 1];\n      Util.array.intersect(arr, arr2);\n      assert.deepEqual(arr, [0, 0, 0, 0, 0, 0]);\n    });\n\n    it('intersectMany', () => {\n      var arrays = [[1, 3, 5, 7], [2, 3, 6, 7], [3, 9]];\n      assert.deepEqual(Util.array.intersectMany(arrays), [3]);\n\n      arrays = [[1, 3, 5, 7], [2, 3, 6, 7], [9]];\n      assert.deepEqual(Util.array.intersectMany(arrays), []);\n\n      arrays = [[1, 2, 3, 4], [4, 5, 6, 7], [8, 9]];\n      assert.deepEqual(Util.array.intersectMany(arrays), []);\n\n      arrays = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]];\n      assert.deepEqual(Util.array.intersectMany(arrays), [1, 2, 3, 4]);\n\n      arrays = [[9, 10, 11, 12], [1, 2, 3, 4], [1, 2, 3, 4]];\n      assert.deepEqual(Util.array.intersectMany(arrays), []);\n    });\n\n    it('flatten', () => {\n      assert.deepEqual(Util.array.flatten([[1], [2], [3, [[4], 'foo', 'bar']]]), [1, 2, 3, 4, 'foo', 'bar']);\n      assert.deepEqual(Util.array.flatten([]), []);\n      assert.deepEqual(Util.array.flatten(null), null);\n      assert.deepEqual(Util.array.flatten('foo'), 'foo');\n    });\n  });\n\n  describe('dom', () => {\n    it('creates nodes with correct tag', () => {\n      var node = Util.dom.createNode('div');\n      assert.equal(node.tagName, 'DIV');\n\n      node = Util.dom.createNode('span');\n      assert.equal(node.tagName, 'SPAN');\n    });\n\n    it('creates nodes with correct properties', () => {\n      var props = {\n        foo: 'bar',\n        baz: 'over 9000',\n        text: 'foo'\n      }\n      var node = Util.dom.createNode('div', props);\n\n      assert.equal(node.attributes.length, 2);\n      assert.equal(node.getAttribute('foo'), props.foo);\n      assert.equal(node.getAttribute('baz'), props.baz);\n\n      assert.equal(node.textContent, props.text);\n    });\n\n    it('can tell that AST item is not a section', () => {\n      var option = Ast.createItem({\n        id: 0,\n        value: 'val',\n        text: 'text',\n        description: 'description'\n      });\n\n      assert(option.isItem());\n      assert.isFalse(option.isSection());\n    });\n\n    it('creates selection node with all properties', () => {\n      var section = Ast.createSection('name');\n\n      assert(section.isSection());\n      assert.isFalse(section.isItem());\n    });\n\n    it('creates selection node with value as text', () => {\n      var option = Ast.createItem({\n        id: 0,\n        value: 'val',\n        text: null\n      });\n      var node = Util.dom.createSelection(option, 0, true, true);\n      assert.equal(node.getAttribute('data-value'), 'val');\n    });\n  });\n});\n"
  }
]