[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    [\"@babel/preset-env\"]\n  ]\n}"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: josdejong\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "name: Node.js CI\n\non: [push, pull_request]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [20.x, 22.x, 24.x]\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n      - run: npm install\n      - run: npm run test\n        env:\n          CI: true\n\n  lint:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 24.x\n      - run: npm ci\n      - run: npm run lint\n        env:\n          CI: true\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n*.iml\n.vscode\nbuild\ndist\ndownloads\nnode_modules\n*.zip\nnpm-debug.log\n/.vs\n"
  },
  {
    "path": ".npmignore",
    "content": "bower.json\nCONTRIBUTING.md\ndownloads\nmisc\nnode_modules\ntest\ntools\n.idea\ncomponent.json\n.npmignore\n.gitignore\n*.zip\nnpm-debug.log\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Contributing\n\nContributions to the `jsoneditor` library are very welcome! We can't do this\nalone. You can contribute in different ways: spread the word, report bugs, come\nup with ideas and suggestions, and contribute to the code.\n\nThere are a few preferences regarding code contributions:\n\n-   Send pull requests to the `develop` branch, not the `master` branch.\n-   You can use modern JavaScript features, the code is transpiled using Babel.\n-   `jsoneditor` follows the https://standardjs.com/ code style. To test:\n\n    ```\n    npm run lint\n    ```\n\n-   If possible, create a unit test for any new functionality. To run tests:\n\n    ```\n    npm test\n    ```\n\nThanks!\n"
  },
  {
    "path": "HISTORY.md",
    "content": "# JSON Editor - History\n\n<https://github.com/josdejong/jsoneditor>\n\n## 2025-10-15, version 10.4.2\n\n- Fix: #1680 ensure that method `.validate()` always returns a `Promise`.\n\n## 2025-08-28, version 10.4.1\n\n- Fix: show autocompletion dropdown for single element when `option.text` is \n  available (#1676). Thanks @ahmed-saber.\n\n## 2025-08-22, version 10.4.0\n\n- Feat: implemented a new option `withPath` for the `expand` method, \n  see #1671.\n- Feat: update dependencies `jsonrepair` and `ace-builds`.\n- Fix: #1673 refinements in the autocomplete text highlighting and dropdown \n  logic (#1674). Thanks @ahmed-saber.\n- Fix: some class names for highlighting search results in the `darktheme.css` \n  example file (#1672). Thanks @epeleh.\n\n## 2025-08-01, version 10.3.0\n\n- Feat: add support for autocompletion options with separate `text` and `value` \n  (#1669). Thanks @ahmed-saber.\n- Fix: update dependencies (like `jsonrepair` and `ace-builds`).\n\n## 2025-03-28, version 10.2.0\n\n- Feat: stringify integer numbers bigger than max safe integer (#1646).\n  Thanks @pawfrolow.\n\n## 2025-02-17, version 10.1.3\n\n- Fix: #1643 handle `true` property value in a JSON Schema (#1644). \n  Thanks @joshkel.\n\n## 2024-12-18, version 10.1.2\n\n- Fix: #1637 remove `mobius1-selectr` from the list with dependencies to get \n  rid of a security vulnerability in its docs.\n\n## 2024-11-13, version 10.1.1\n\n- Fix: when editing a key giving it a ` (copy)` suffix, the suffix was removed. \n- Fix: #1627 `onValidationError` did not always trigger.\n- Fix: upgrade to the latest version of `ace`.\n- Fix: upgrade to the latest version of `sass`.\n\n## 2024-06-19, version 10.1.0\n\n- Feat: upgrade to the latest version of `ace` and `jsonrepair`\n- Fix: #1601 do not convert hexadecimal values into regular numbers.\n\n## 2024-05-09, version 10.0.4\n\n- Fix: #1591 select contents in tree mode only when using the left mouse button,\n  not the middle button or right mouse button.\n\n## 2024-04-25, version 10.0.3\n\n- Update dependencies `ace-builds`, `jsonrepair`, and `vanilla-picker`.\n- Fix: fix HTML open tag wrongly being `label` instead of `div`.\n  Thanks @ppetkow.\n\n## 2024-03-18, version 10.0.2\n\n- Fix: autocompletion sometimes throwing an error when there are no suggestions.\n  Thanks @jpage-godaddy.\n- Update dependencies `ace` and `jsonrepair`.\n\n## 2024-02-09, version 10.0.1\n\n- Fix #1570: Spanish grammar corrections (#1571). Thanks @gonchesan.\n- Update dependencies `ace` and `jsonrepair`.\n\n## 2023-12-21, version 10.0.0\n\n- BREAKING CHANGE: dropped support for Internet Explorer 11.\n- Update dependencies `ace` and `jsonrepair`.\n\n## 2023-12-21, version 9.10.5\n\n- Revert dependency `jsonrepair` to `v3.1.0` to stay compatible with\n  Internet Explorer 11. Regression introduced in `v9.10.3`. See #1563.\n\n## 2023-11-08, version 9.10.4\n\n- Fix #1558: url not wrapping on Chrome in mode `view`, font-size increasing\n  on mobile Webview.\n- Upgraded to the latest version of `ace` and `jsonrepair`.\n\n## 2023-09-21, version 9.10.3\n\n- Fix #1550: trimming zero in front of a number (#1549). Thanks @DarkFisk.\n- Updated dependencies, most notable `ace`, `jsonrepair`, and `vanilla-picker`.\n\n## 2023-05-12, version 9.10.2\n\n- Fix option `showErrorTable` (see #1515):\n  - a console warning was logged when using the option `showErrorTable`\n  - `showErrorTable: false` was not working\n  - `showErrorTable` was not working for `preview` mode\n- Upgraded to the latest version of `ace` and `jsonrepair`.\n\n## 2023-05-12, version 9.10.1\n\n- Fix #1515: a console warning is logged when using the option `showErrorTable`.\n\n## 2023-02-20, version 9.10.0\n\n- Implement option `showErrorTable` to customize in which modes the error table\n  shows up initially (#1497). Thanks @magedhennawy.\n- Upgrade to the latest version of Ace editor, `v1.15.2`.\n- Upgrade to the latest version of jsonrepair, `v3.0.2`.\n- Fix #1208: source map issue \"Could not load source file \"0\" in source map\"\n  (#1499). Thanks @joshkel.\n\n## 2022-09-20, version 9.9.2\n\n- Fix #1470: update the code of Selectr from `2.4.0` to `2.4.13`.\n\n## 2022-09-19, version 9.9.1\n\n- Upgrade to the latest version of Ace editor, `v1.10.1`.\n\n## 2022-06-13, version 9.9.0\n\n- Implemented #968: support for auto-completion based on JSON Schema (#1435).\n  Thanks @meirotstein.\n- Upgrade to the latest version of Ace editor, `v1.6.0`.\n\n## 2022-05-31, version 9.8.0\n\n- Implemented method `editor.expand({ path, isExpand, recursive })` and callback\n  `onExpand({ path, isExpand, recursive })`. Thanks @himanshu520.\n- Upgrade to the latest version of Ace editor, `v1.5.3`.\n\n## 2022-03-15, version 9.7.4\n\n- Fix #1421: fix `onBlur` event not firing when focus goes to an `iframe`.\n\n## 2022-03-04, version 9.7.3\n\n- Fix #1422: fix `setSchema` not working.\n\n## 2022-02-09, version 9.7.2\n\n- Fix #1419: blurry text preview query on Chrome.\n\n## 2022-02-09, version 9.7.1\n\n- Fix #1419: blurry text preview on Chrome.\n\n## 2022-01-30, version 9.7.0\n\n- Implement #1413: show color indicator on readonly fields and in mode `view`.\n\n## 2022-01-13, version 9.6.0\n\n- Pass a new property `value` along with the `onNodeName` callback, see #1409.\n  Thanks @brianpos.\n- Implement the `value` property of the callbacks `onNodeName`, `onEditable`,\n  and `onClassName` for objects and arrays too (was `undefined` before).\n  Since this can be a heavy recursive operation, the property is changed into\n  a lazy getter.\n\n## 2021-12-29, version 9.5.11\n\n- Fix the font on Ubuntu for real by add the \"ubuntu mono\" font. See #1405.\n\n## 2021-12-27, version 9.5.10\n\n- Fix the font on Ubuntu: add \"dejavu sans mono\". See #1405.\n\n## 2021-12-22, version 9.5.9\n\n- More tweaking of the font because the 13px Cascadia Mono font on Windows gave\n  issues in Ace editor. Changed to 14px Consolas on Windows, 14px Menlo on Mac,\n  see #1392, #1403.\n\n## 2021-12-19, version 9.5.8\n\n- Fix vertical centering of text and buttons, see #1392.\n- Improve font: 13px Cascadia Mono on Windows, 13px Monaco on Mac, see #1392.\n\n## 2021-11-06, version 9.5.7\n\n- More robust polyfill for `Element.remove`, `window.CharacterData.remove`,\n  and `window.DocumentType.remove`. Thanks @caok2709.\n- Update dependencies: `ace-builds@1.4.13`, `vanilla-picker@2.12.1`.\n\n## 2021-09-22, version 9.5.6\n\n- Fix inefficient regex to replace return characters.\n\n## 2021-09-01, version 9.5.5\n\n- Fix `setMode` not throwing an exception anymore in case of a parse error\n  (regression since `9.5.4`).\n\n## 2021-08-25, version 9.5.4\n\n- Use `noreferrer` for window.open, see #1365. Thanks @rajitbanerjee.\n- Fix #1363: parsing error contains html characters.\n- Fix opening the Transform or Sort modal in code mode with invalid JSON\n  contents not triggering the `onError` callback (see #1364).\n- Change the default behavior of error handling to open a basic alert instead\n  of logging the error in the console (see #1364).\n\n## 2021-07-28, version 9.5.3\n\n- Fix #1356: background of tree mode is transparent instead of white.\n- Fix #473: enum dropdown not working on referenced schemas and templates,\n  see #1355. Thanks @mpccolorado.\n\n## 2021-07-22, version 9.5.2\n\n- Fix #675: Relative image urls in CSS replaced with absolute urls by build\n  script, see #1354. Thanks @esulu.\n\n## 2021-06-30, version 9.5.1\n\n- Upgrade to `jsonrepair@2.2.1`.\n\n## 2021-06-05, version 9.5.0\n\n- Implemented new method `JSONEditor.validate(): Promise<ValidationError[]>`.\n  Thanks @ChrisAcrobat.\n\n## 2021-06-02, version 9.4.2\n\n- Fix #1311: exception being thrown under certain conditions when switching\n  from `code` mode to `preview` mode.\n- Rename spin animation of `selectr` to prevent conflicts with tailwind,\n  see #1333. Thanks @mdix.\n\n## 2021-04-25, version 9.4.1\n\n- Improvements in the Korean translation. Thanks @luasenvy.\n\n## 2021-04-17, version 9.4.0\n\n- Added Korean translation. Thanks @luasenvy.\n- Added Spanish translation. Thanks @joabac.\n- Fix #1282: JSON schema enum dropdown not working for conditionals like\n  `oneOf`, `anyOf`, `allOf`. Thanks @maufl.\n- Fix #1307: losing caret position when calling `refresh()` during `onChange`\n  callback.\n\n## 2021-04-10, version 9.3.1\n\n- Introduced a new sass variable `$jse-icons-url`, see #1268. Thanks @ppetkow.\n\n## 2021-04-01, version 9.3.0\n\n- Improved Russian translation. Thanks @PunKHS.\n- Upgraded dependencies to `jsonrepair@2.1.0`.\n\n## 2021-02-24, version 9.2.0\n\n- Added Russian translation. Thanks @PunKHS.\n- Changed shortcut keys for Format and Compact in code mode from `Ctrl+\\` and\n  `Ctrl+Shift+\\` to `Ctrl+I` and `Ctrl+Shift+I` respectively, because not all\n  browsers and operating systems support this key combination.\n\n## 2021-02-14, version 9.1.10\n\n- Fixed resolving a JSON schema reference linking to an other schema, see #1239.\n  Thanks @Hagartinger.\n- Upgraded to latest dependencies (`vanilla-picker@2.11.2`).\n\n## 2021-01-23, version 9.1.9\n\n- Fix `jsoneditor-minimalist` bundle being too large. Regression since `v9.1.5`\n  (caused by a recent upgrade to Webpack 5). Thanks @cbmgit.\n\n## 2021-01-16, version 9.1.8\n\n- Replaced `simple-json-repair` with `jsonrepair` (library was renamed).\n\n## 2020-12-30, version 9.1.7\n\n- Fix #1206: library bundle broken on IE 11, regression introduced in v9.1.6.\n\n## 2020-12-23, version 9.1.6\n\n- Fix #1192: enum dropdown from a JSON schema not rendered when using\n  `additionalProperties`. Thanks @maufl.\n- Fix #1191: clarify docs about configuration option `ajv`.\n- Fix #1193: simplify and fix example 20_custom_css_style_for_nodes.html.\n\n## 2020-12-14, version 9.1.5\n\n- Fix #1185: enum dropdown not selecting actual value when this is not a string.\n- Fix selected value of enum dropdown not updated when changed programmatically.\n\n## 2020-11-23, version 9.1.4\n\n- Fix #1119: list of keys in navigation bar missing a scroll bar.\n  Thanks @tanmayrajani.\n\n## 2020-11-19, version 9.1.3\n\n- Fix #1158: JSON schema_findSchema not found if using internal references. Thanks @maufl.\n- Update dependencies: `vanilla-picker@2.11.0`.\n\n## 2020-11-07, version 9.1.2\n\n- Fix #1126: fire `onEvent` for boolean checkbox and enum selectbox too.\n- Log a clear error in the console when the returned value of `onEditable` is\n  invalid. See #1112.\n- Updated dependency to `ajv@6.12.6`.\n- Extract the JSON repair functionality into a separate,\n  library `simple-json-repair` with many improvements.\n\n## 2020-09-23, version 9.1.1\n\n- Fix #1111: Enum dropdown not showing when using patternProperties for schema.\n  Thanks @ziga-miklic.\n- Fixed JSONEditor not working when opened in a new window, see #1098.\n  Thanks @joshkel.\n- Fix quick-key `Ctrl+D` (duplicate) not working.\n- Define \"charset: utf-8\" in all HTML examples.\n\n## 2020-09-15, version 9.1.0\n\n- Implemented German translation (`de`). Thanks @s-a.\n- Fix quick-keys `Ctrl-\\` (format) and `Ctrl-Shift-\\` (compact) not working\n  in `code` mode.\n- Updated dependencies to `ajv@6.12.5`.\n\n## 2020-09-09, version 9.0.5\n\n- Fix #1090: autocomplete firing on dragging or clicking a node.\n- Fix #1096: editor crashing when passing an empty string as `name`.\n- Updated dependencies to `ajv@6.12.4`.\n\n## 2020-08-15, version 9.0.4\n\n- Updated dependencies to `ace-builds@1.4.12`, `ajv@6.12.3`.\n- Fix #1077: change the `main` field in `package.json` to point to the actual\n  bundled and minified file instead of a node.js index file.\n\n## 2020-07-02, version 9.0.3\n\n- Fix regression introduced in `v9.0.2` in the select boxes in the\n  Transform model not lighlighting the matches correctly.\n\n## 2020-07-01, version 9.0.2\n\n- Fix #1029: XSS vulnerabilities. Thanks @onemoreflag for reporting.\n- Fix #1017: unable to style the color of a value containing a color.\n  Thanks @p3x-robot.\n\n## 2020-06-24, version 9.0.1\n\n- Fixed broken link to the Ace editor website (<https://ace.c9.io/>).\n  Thanks @p3x-robot.\n- Fix #1027: create IE11 Array polyfills `find` and `findIndex` in such a way\n  that they are not iterable.\n\n## 2020-05-24, version 9.0.0\n\n- Implemented option `limitDragging`, see #962. This is a breaking change when\n  using a JSON schema: dragging is more restrictive by default in that case.\n  Set `limitDragging: false` to keep the old, non-restricted behavior.\n\n## 2020-05-13, version 8.6.8\n\n- Fix #936: too many return characters inserted when pasting formatted text\n  from OpenOffice.\n\n## 2020-05-10, version 8.6.7\n\n- Fix #858: the `dist/jsoneditor.js` bundle containing a link to a\n  non-existing source map.\n- Fix #978: in some special cases the caret was jumping to the beginning of the\n  line whilst typing.\n- Update dependencies to `ajv@6.12.2`.\n\n## 2020-04-21, version 8.6.6\n\n- Fix #969: adding a new property to an empty object or array is broken.\n  Regression introduced in `v8.6.5`.\n\n## 2020-04-19, version 8.6.5\n\n- Fix #964: translation of titles of some context menu items not working.\n- Update dependencies to `ace-builds@1.4.11`, `ajv@6.12.1`.\n\n## 2020-03-29, version 8.6.4\n\n- Fix #921: `sortObjectKeys` emits `onChange` events.\n- Fix #946: `language` not working in modes `text`, `code`, and `preview`.\n- Revert reckoning with the order of object properties when updating an\n  object (introduced in `v8.6.2`). See #917.\n- Implement support for repairing line separate JSON.\n\n## 2020-03-18, version 8.6.3\n\n- Fix #932: `JSONEditor.update` broken, did not always recognize when the\n  input changed. Regression introduced in `v8.6.2`.\n\n## 2020-03-18, version 8.6.2\n\n- Fixed #917, #926: Keep order of properties when updating an object.  \n- Fixed #928: Custom root name not reflected in path of navigation bar.\n- Upgraded to `ajv@6.12.0`\n\n## 2020-02-17, version 8.6.1\n\n- Fixed #908: editor throwing an exception when switching from `'preview'`\n  to `'code'` mode.\n\n## 2020-02-16, version 8.6.0\n\n- Fixed #906: Implemented turning Python objects containing `True`, `False`\n  and `None` into valid JSON using repair.\n\n## 2020-02-06, version 8.5.3\n\n- Fix #892: the undo/redo buttons in mode `code` being broken when custom\n  loading an old version of Ace Editor.\n\n## 2020-02-05, version 8.5.2\n\n- Fix undo/redo buttons in mode `code` not always updating.\n\n## 2020-02-05, version 8.5.1\n\n- Fix broken build.\n\n## 2020-02-05, version 8.5.0\n\n- Implemented support for customizing the query language used in the\n  Transform modal. New options `createQuery`, `executeQuery`, and\n  `queryDescription` are available for this now. An example is available\n  in `examples/23_custom_query_language.html`. See #857, #871.\n- Implement undo/redo buttons in `code` mode.\n- Fix history (undo/redo) being cleared in mode `code` and `text` after\n  transforming or sorting.\n\n## 2020-01-25, version 8.4.1\n\n- Fix `console.log` in production code. Oopsie.\n\n## 2020-01-25, version 8.4.0\n\n- Added CSS classes `jsoneditor-expanded` and `jsoneditor-collapsed` on array\n  and object nodes reflecting there state.\n\n## 2020-01-18, version 8.3.0\n\n- Update dependency `ajv` to `v6.11.0`.\n- Fix #790: editor breaking when missing a translation containing a\n  placeholder.\n\n## 2020-01-16, version 8.2.0\n\n- Make it easy to create custom styling by overriding default SASS variable\n  values, see #881. Thanks @petermanders89.\n- Update `ace` to `v1.4.8`.\n\n## 2020-01-06, version 8.1.2\n\n- Fix #873: buttons Format, Compact, and Repair not supporting\n  internationalization.\n- Fix #877: Some CSS styling issues when used in combination with Materialize.\n- Updated dependency `vanilla-picker` to `v2.10.1`.\n\n## 2019-12-28, version 8.1.1\n\n- Fixed the file size reported in `preview` mode show `KB` and `MB` instead\n  of `KiB` and `MiB` in order to match the size reported by filesystems.\n\n## 2019-12-18, version 8.1.0\n\n- Implemented `popupAnchor` allowing to select a custom anchor element.\n  See #869 and #870.  \n- Fixed #502: CSS rule `* { font-family: ... }` resulting in Ace editor (`code`\n  mode) not having a mono-space font anymore.\n\n## 2019-12-11, version 8.0.0\n\n- Implemented option `timestampFormat` which allows customizing the formatting\n  of timestamp tags. See also option `timestampTag`. Thanks @smallp.\n- Changed the behavior of `timestampTag` to fallback on the built-in rules when\n  the function does not return a boolean. See #856.  \n- Reverted the heuristics introduced in `v7.3.0` to check whether some field\n  contains a timestamp based on the field name, because they can give wrong\n  timestamps in case of values in seconds instead of the assumed milliseconds\n  (see #847, #856).\n\n## 2019-12-08, version 7.5.0\n\n- Extended the callback `onValidationError` to also report parse errors,\n  and distinguish between JSON schema validation errors and custom errors.\n  See #861 and #612. Thanks @meirotstein.\n\n## 2019-12-01, version 7.4.0\n\n- Implemented callback function `onValidationError`, see #612, #854.\n  Thanks @meirotstein.\n- Fixed #850: make autocomplete options robust against non-string inputs\n  like `null`, `123`, `true`, `false`.\n\n## 2019-12-01, version 7.3.1\n\n- Fixed #855: `onFocus` and `onBlur` not working in modes `text` and `code`\n  when editor was created without main menu bar, and `editor.destroy()`\n  throwing an exception.\n\n## 2019-11-27, version 7.3.0\n\n- Implemented callbacks `onFocus` and `onBlur` (PR #809, issue #727).\n  Thanks @123survesh.\n- Fixed #847: allow customizing the in rules determining whether a value\n  is a timestamp or not by passing a callback function to `timestampTag`.\n\n## 2019-10-27, version 7.2.1\n\n- Fixed #826: editor not allowing indentation `0`.\n- Fixed #828: do not expand/collapse when clicking the text of a node\n  in modes `view` or `form`.\n- Fixed #829: z-index issue of context-menu button and conflicting css names.\n\n## 2019-10-23, version 7.2.0\n\n- Implemented Japanese translation (`ja`). Thanks @yutakiyama.\n- Implemented French translation (`fr-FR`), and some improvements in the\n  translation. Thanks @yannickyvin.\n- Upgraded to the latest version of Ace editor, 1.4.7.\n- Fixed #824: Parse errors not displayed with bottom right error icon in modes\n  `code` and `text`.\n\n## 2019-10-13, version 7.1.0\n\n- Upgraded to the latest version of Ace editor 1.4.6. Changed implementation\n  to use `ace-builds` directly instead of `brace` (still using Ace 1.2.9).\n- Improved Portuguese translation. Thanks @victorananias.\n\n## 2019-10-06, version 7.0.5\n\n- Upgraded dependencies: `vanilla-picker@2.10.0`.\n- Minor documentation improvements. Thanks @slash-arun.\n- Minor styling fixes.\n\n## 2019-09-11, version 7.0.4\n\n- Fixed #723: schema error popup and color picker not always fully visible.\n- Fixed wrong text color in search box when using JSONEditor in combination\n  with bootstrap. See #791. Thanks @dmitry-kulikov.\n- Fixed react examples not working out of the box when cloning or downloading\n  the git repository of JSONEditor. See #787, #788. Thanks @vishwasnavadak.\n\n## 2019-09-04, version 7.0.3\n\n- Fixed `index.js` pointing to non-transpiled code. See #783.\n- Fixed absolute url of images in SASS. Thanks @moonbreezee.\n\n## 2019-09-02, version 7.0.2\n\n- Fix #781: race condition when destroying the editor right after setting data.\n\n## 2019-09-01, version 7.0.1\n\n- Fix npm package missing `dist` folder.\n\n## 2019-09-01, version 7.0.0\n\n- Converted the code largely to ES6, put Babel transpiler in place.\n- Dropped support for bower, removed the `dist` folder from the git repository.\n- Fixed #586: caret position lost when switching browser tabs.\n\n## 2019-08-28, version 6.4.1\n\n- Fix styling of autocompletion dropdown broken. Regression since `v6.4.0`.\n\n## 2019-08-28, version 6.4.0\n\n- Replaces CSS with SASS internally, improvements in styling. Thanks @ppetkow.\n- Fixed #761: JSON schema errors not rendered in the gutter for mode `code`\n  when the path contained a property with a forward slash, and errors not\n  clickable in the error table.\n- Fixed #777: option `sortObjectKeys` broken.\n\n## 2019-08-15, version 6.3.0\n\n- Fixed #755: JSONEditor throwing an exception in mode `code`, `text`, and\n  `preview` when `statusBar: false`.\n- When duplicating an object property, move focus to the field and do not\n  immediately add the `(copy)` suffix. See #766.\n- Fixed #769: option `name` not working anymore. Regression since `v6.1.0`.\n- Fixed #763: `autocomplete.trigger: 'focus'` throws an error when opening the\n  context menu. Thanks @Thaina.\n- Updated dependencies `json-source-map@0.6.1`\n\n## 2019-08-01, version 6.2.1\n\n- Updated Chinese translation. Thanks @SargerasWang.\n\n## 2019-07-28, version 6.2.0\n\n- Implemented new mode `preview`, capable of working with large JSON documents\n  up to 500 MiB.\n- Repair button is now capable of turning MongoDB documents into valid JSON.\n- Fixed #730: in `code` mode, there was an initial undo action which clears\n  the content.\n- Upgraded dependencies `vanilla-picker@2.9.2`, `mobius1-selectr@2.4.13`,\n  `ajv@6.10.2`.\n\n## 2019-06-22, version 6.1.0\n\n- Implemented menu options `sort` and `transform` for modes `code` and `text`.\n- Implemented new context menu item `extract`.\n- Minor tweaks in the way paths are displayed in the sort and transform modals.\n\n## 2019-06-12, version 6.0.0\n\n- Breaking change: upgraded dependency `ajv@6.10.0`, supporting JSON schema\n  draft-07 alongside draft-06 and draft-04.\n- Upgraded dependency `vanilla-picker@2.8.1`.\n- Use JSON schema title as name for the root object if defined (see #635).\n\n## 2019-06-08, version 5.34.0\n\n- Extended the autocomplete feature with new options `filter` and `trigger`.\n  Thanks @Gcaufy.\n- Removed :hover style on disabled buttons. Thanks @Gcaufy.\n- Upgraded dependency `mobius1-selectr@2.4.12`.\n\n## 2019-05-29, version 5.33.0\n\n- Fixed #697: JSON Schema enum dropdown not working inside an array.\n- Fixed #698: When using `onCreateMenu`, `node.path` is null when clicking\n  on an append node or when multiple nodes are selected.\n- Upgraded dependencies to `mobius1-selectr@2.4.10`, `vanilla-picker@2.8.0`.\n- Remove :hover style on disabled buttons. Thanks @Gcaufy.\n\n## 2019-04-27, version 5.32.5\n\n- Fixed a bug in the JMESPath query wizard which didn't correctly handle\n  selecting multiple fields.\n- Fixed context menu not working when multiple nodes are selected.\n\n## 2019-04-10, version 5.32.4\n\n- Fixed #682 and #687: JSONEditor not being able to handle JSON schema\n  validation errors when the root of the document is an Array. Thanks @DusuWen.\n\n## 2019-04-04, version 5.32.3\n\n- Fixed #684: `const` used in bundled library.\n\n## 2019-04-03, version 5.32.2\n\n- Fixed #416: Clipped action menu for append nodes.\n- Improve detection of value type in transform modal.\n- Styling improvements in the transform modal.\n- Fix CSS class for default/non-default schema values not applied to enums,\n  see (#666).\n- Fixed #671: Improved handling of duplicate property names, which could cause\n  values to be cleared when used as a controlled component in for example React.\n\n## 2019-03-28, version 5.32.1\n\n- Fixed a regression in parsing JSON paths: numbers where parsed as strings\n  instead of a numeric value. See #679. Thanks @AdamVig.\n- Fixed using hyphens in the path of custom validation errors (see #665).\n  Thanks @tobiasfriden.\n\n## 2019-03-20, version 5.32.0\n\n- Implemented support for reckoning with JSON schema default values: custom\n  styling can be applied for default and non-default values. Thanks @AdamVig.\n- Fixed #667: resolving JSON Schema examples and descriptions did not always\n  work for referenced schemas. Thanks @AdamVig.\n- Fixed #676: JSON Paths containing array properties with a `]` not parsed\n  correctly.\n\n## 2019-03-14, version 5.31.1\n\n- Fix IE11 issue.\n- Some fixes in the Simplified Chinese translation.\n  Thanks @@adf0001 and @yuxizhe.\n\n## 2019-03-10, version 5.31.0\n\n- Display JSON schema examples in tooltip (#664). Thanks @AdamVig.\n\n## 2019-03-02, version 5.30.0\n\n- Implemented a new option `onCreateMenu` to customize the action menu.\n  Thanks @RobAley.\n\n## 2019-02-20, version 5.29.1\n\n- Fixed #661: JSONEditor broken on IE11 caused by duplicate JSON entries\n  in a translation.\n\n## 2019-02-16, version 5.29.0\n\n- Added Simplified Chinese localization. Thanks @long2ice.\n- Added Turkish localization. Thanks @beratpostalci.\n- Improved JSON schema titles on fields. Fixes #321. Thanks @AdamVig.\n- Fixes in resolving JSON schemas, see #651. Thanks @AdamVig.\n- Fix #657: `onClassName` throwing an error when a node is removed.\n\n## 2019-01-23, version 5.28.2\n\n- Fix #639: Occurrence of non-ES5 `const` declaration in published code.\n  Regression introduced in `v5.28.0`.\n\n## 2019-01-22, version 5.28.1\n\n- Fix #637: Vertical white border left/right from the main menu in some\n  specific circumstances.\n- Fix #638: Cannot expand after collapse. Regression introduced in v5.28.0.\n\n## 2019-01-21, version 5.28.0\n\n- Implemented new option `maxVisibleChilds` to customize the maximum number\n  childs that is rendered by default. Thanks @20goto10.\n- Implemented new option `onClassName`, allowing customized and dynamic\n  styling of nodes. See 20_custom_css_style_for_nodes.html for a demo.\n  Thanks @maestr0.\n- Make the method `refresh()` public.\n\n## 2019-01-16, version 5.27.1\n\n- Improved navigating deeply nested paths via the navigation bar, see #619.\n  Thanks @meirotstein.\n- Sdd title from schema description to show the tips for user input.\n  Thanks @tylerchen.\n- Fix JSON Schema not resolving refs `$ref`, and not creating enum dropdowns.\n  Thanks @tylerchen.\n\n## 2019-01-05, version 5.27.0\n\n- Implemented customizing object and array names via a new option\n  `onNodeName`. Thanks @bnanchen.\n- Visibility of schema validation errors at the bottom of mode code and text\n  are now toggleable. Thanks @meirotstein.\n- Fixed text of the mode switcher not being translated. Thanks @antfu.\n\n## 2018-12-06, version 5.26.3\n\n- Fixed #610: JSON Repair now removes trailing commas.\n- Upgraded devDependency `gulp` to v4. Thanks @maestr0.\n\n## 2018-11-13, version 5.26.2\n\n- Fixed dragging and selecting multiple nodes not working\n  (regression introduced in `v5.26.1`).\n\n## 2018-11-13, version 5.26.1\n\n- Fixed `.update()` throwing an exception when replacing a JSON object\n  with `null`. Thanks @DullReferenceException.\n- Fixed #598: Search field can't be focused in object view.\n\n## 2018-11-12, version 5.26.0\n\n- Implemented option `mainMenuBar` to enable/disable the main menu bar.\n  Thanks @tanmayrajani.\n\n## 2018-10-29, version 5.25.0\n\n- Implemented options `enableSort` and `enableTransform` so you can turn off\n  these features. Thanks @tanmayrajani.\n- Fixed #590: validation failing in code and text mode when status\n  bar is disabled.\n- Fixed #589: the path in the navigation bar is not updated\n  when duplicating or removing a node, and neither after an undo/redo action.\n- Fixed duplicate and remove of the action menu of multiple selected\n  nodes not working.\n- Fixed not preventing default selection of text when selecting nodes.\n- Fixed #595: navigation bar path link not working.\n\n## 2018-10-08, version 5.24.7\n\n- Fix #582: parse error annotations not always up to date in\n  code editor. Thanks @meirotstein.\n\n## 2018-09-12, version 5.24.6\n\n- Fix #548: `import JSONEditor from 'jsoneditor'` not working in\n  TypeScript projects (gave a constructor is undefined error).\n\n## 2018-09-06, version 5.24.5\n\n- Fixed a bug in textmode on IE 11, not loading the editor when\n  `Promise` is undefined.\n\n## 2018-09-06, version 5.24.4\n\n- Fixed #576: Visualization in mode `view` when an array\n  with more than 100 items is rendered.\n- Fixed JSONEditor not working on IE11: continue and throw console\n  errors when `Promise` is undefined. Regression since `v5.23.0`.\n- Fixed `onClose` of color picker not being fired when clicking outside\n  the picker to close it.\n- Upgraded dependencies `brace`, `mobius1-selectr`, `vanilla-picker`.\n- Upgraded devDependency `mocha`.\n\n## 2018-08-29, version 5.24.3\n\n- Fixed color picker not working in ES6 projects.\n- Fixed color picker closing immediately after the first `onChange`\n  event, and `onChange` events are now debounced like all text inputs.\n\n## 2018-08-27, version 5.24.2\n\n- Improved error and validation messaging in `text` mode.\n  Thanks @meirotstein.\n  - Clicking a message now selects the line where the error occurs.\n  - Icon bottom right showing when there are warnings or errors.\n- Fixed field still editable after moving a node from an object\n  to an array, changing the field from a property into an index.\n\n## 2018-08-26, version 5.24.1\n\n- Context menu and color picker are now absolutely positioned, and\n  can overflow the borders of the editor.\n- Fixed #568: mode switcher disappearing when selecting the current\n  mode again.\n- Fixed `transform` not creating/removing expand button when the type\n  of a node changed.\n\n## 2018-08-22, version 5.24.0\n\n- Implemented a color picker, and allow hooking in a custom color\n  picker. new options are `colorPicker` and `onColorPicker`.\n- Implemented a timestamp tag displayed right from timestamps,\n  with corresponding option `timestampTag`.\n\n## 2018-08-17, version 5.23.1\n\n- Fixed #566: transform function broken, regression since `v5.20.0`.\n\n## 2018-08-15, version 5.23.0\n\n- Implemented support for custom validation using a new `onValidate` callback.\n- In tree mode, nodes containing a validation error now have a className\n  `jsoneditor-validation-error` which can be used for custom styling.\n\n## 2018-08-13, version 5.22.0\n\n- Implemented `onEvent` callback triggered when an event occurs in a JSON\n  field or value. Thanks @cristinabarrantes.\n\n## 2018-08-12, version 5.21.0\n\n- Show validation errors inline instead of at the bottom when in code\n  mode. Thanks @meirotstein.\n- Fix #562: allow `$` character in property names of of a JSON schema.\n\n## 2018-08-10, version 5.20.0\n\n_Good news: JSONEditor is finally framework friendly and can now be easily\nintegrated in React, Vue, and Angular!_\n\n- Implemented new methods `update` and `updateText`, which maintain the state\n  of the editor (expanded nodes, search, selection). This makes it easy to\n  integrate in frameworks like React.\n- Implemented options `onChangeJSON(json)` and `onChangeText(jsonString)`.\n- Added two React examples to the `examples` folder.\n- Fixed menu buttons \"Sort\" and \"Transform\" being available in modes `view`\n  and `form`.\n\n## 2018-08-02, version 5.19.2\n\n- Fixed #558: scrolling to search results and automatically scrolling up/down\n  when dragging an item broken (regression since v5.19.1).\n\n## 2018-07-28, version 5.19.1\n\n- Fixed #557: inner contents of the scrollable area being displayed outside of\n  the editor (on Chrome only).\n\n## 2018-07-11, version 5.19.0\n\n- No more grayed out icons of the context menu, see #532.\n- Added Sort and Transform buttons to the main menu.\n- Fixes and improvements in the Transform dialog.\n\n## 2018-06-27, version 5.18.0\n\n- Implemented JMESPath support for advanced filtering, sorting, and\n  transforming of JSON documents.\n- Implemented a new option `modalAnchor` to control at which part of the\n  screen the modals are displayed.\n- Fixed #544: JSON Schema errors sometimes not being displayed in the\n  editor.\n\n## 2018-06-03, version 5.17.1\n\n- Fixed a bug in a translation text.\n\n## 2018-06-03, version 5.17.0\n\n- Implemented advanced sorting for arrays.\n\n## 2018-05-23, version 5.16.0\n\n- Better handling of JSON documents containing large arrays:\n  - Only displays the first 100 items of large arrays,\n    with buttons \"show more\" and \"show all\" to render more items.\n  - Search results are now limited to max 1000 matches,\n    and search does no longer expand the paths to all matches\n    but only expands the path of the current search result.\n- Fixed index numbers of Array items not being updated after sorting.\n\n## 2018-05-02, version 5.15.0\n\n- Implemented selection API: `onSelectionChanged`, `onTextSelectionChanged`,\n  `getSelection`, `getTextSelection`, `setSelection`, `setTextSelection`,\n  and `getNodesByRange`. Thanks @meirotstein.\n\n## 2018-03-21, version 5.14.1\n\n- Fixed absolute path of css image `jsoneditor-icons.svg`, which could.\n  give issues with webpack plugin \"file-loader\". Thanks @landru29.\n\n## 2018-02-25, version 5.14.0\n\n- Implemented support for translations. Thanks @mariohmol.\n- Fixed a bug sometimes occurring when dragging items from array to\n  object, see #509. Thanks @43081j.\n- Fixed autocomplete not accepting returned `null` values, see #512.\n  Thanks @43081j.\n- Fixed memory inefficiency when working with large JSON Schema's\n  generating many errors. Thanks @43081j.\n\n## 2018-02-07, version 5.13.3\n\n- Fixed a positioning issue with JSON Schema errors in text/code mode.\n\n## 2018-01-18, version 5.13.2\n\n- Fixed view mode opening links in a new tab instead of current tab\n  when Ctrl key is not down. Thanks @LEW21.\n- Fixed #502: code editor not showing a monospaced font some cases.\n\n## 2017-12-28, version 5.13.1\n\n- Fixed another occurrence of #494: properties not escaped in the\n  navigation bar.\n\n## 2017-12-28, version 5.13.0\n\n- Implemented cursor position in text mode. Thanks @meirotstein.\n- Fixed #494: properties not escaped in the navigation bar.\n  Thanks @meirotstein.\n\n## 2017-12-18, version 5.12.0\n\n- Implemented #482: Include `caseSensitive` option for autocomplete.\n  Thanks @israelito3000.\n- Upgraded dependencies\n  - `ajv@5.5.2`\n\n## 2017-11-22, version 5.11.0\n\n- Upgraded dependencies\n  - `ajv@5.4.0`\n  - `brace@0.11.0`\n- Fixed dropdown for JSON Schema enums when defined inside pattern\n  properties. Thanks @alquist.\n- Fixed code containing a non UTF-8 character. Thanks @alshakero.\n\n## 2017-11-15, version 5.10.1\n\n- Some styling tweaks in the navigation bar and status bar.\n- Don't display status bar in `text` mode (which doesn't yet support\n  row and col counts).\n\n## 2017-11-15, version 5.10.0\n\n- Implemented a navigation bar showing the path. Thanks @meirotstein.\n- Implemented a status bar showing cursor location.\n  Thanks @meirotstein.\n- Implemented repairing JSON objects containing left and right single\n  and double quotes (which you get when typing a JSON object in Word)\n  in `text` and `code` mode.\n- Implemented repairing JSON objects containing special white space\n  characters like non-breaking space.\n- Upgraded dependency `ajv` to version `5.3.0`.\n- Fixed #481: A polyfill required `DocumentType` which is not defined\n  in all environments.\n\n## 2017-09-16, version 5.9.6\n\n- Fixed displaying a dropdown for enums inside composite schemas.\n  Thanks @hachichaud.\n- Fixed #461: Urls opening twice on Firefox and Safari.\n\n## 2017-08-26, version 5.9.5\n\n- Fixed a regression introduced in `v5.9.4`: after using the context\n  menu once, it was not possible to set focus to an other input field\n  anymore.\n\n## 2017-08-20, version 5.9.4\n\n- Fixed #447: context menus not working in Shadow DOM. Thanks @tomalec.\n\n## 2017-07-24, version 5.9.3\n\n- Fixed broken multi-selection (regression).\n\n## 2017-07-13, version 5.9.2\n\n- Fixed a bug in the JSON sanitizer.\n\n## 2017-07-13, version 5.9.1\n\n- `setText` method of tree mode now automatically sanitizes JSON input\n  when needed.\n- Fixed #430: automatically fix unescaped control characters in\n  JSON input.\n\n## 2017-07-10, version 5.9.0\n\n- Implemented support for JSON schema references `$ref`, see #302.\n  Thanks @meirotstein.\n- Fixed #429: JSONEditor no longer accepting an empty array for option\n  `modes`. Thanks @trystan2k.\n- Fixed JSONEditor picking the first entry of `modes` as initial mode\n  instead of option `mode`.\n\n## 2017-07-08, version 5.8.2\n\n- Select first option from `modes` instead of `tree` when `mode` is not\n  configured. Thanks @bag-man.\n- Some fixes and improvements in the API of autocompletion.\n  Thanks @israelito3000.\n\n## 2017-07-03, version 5.8.1\n\n- Fixed broken minified bundles in folder `dist` (again...).\n\n## 2017-07-02, version 5.8.0\n\n- Implemented support for autocompletion. Thanks @israelito3000.\n\n## 2017-06-27, version 5.7.2\n\n- Fixed broken minified bundles in folder `dist`\n  (reverted to `uglify-js@2.8.22` for now).\n\n## 2017-06-25, version 5.7.1\n\n- Upgraded dependency `ajv` to version `5.2.0`. Resolves warnings in\n  Webpack build processes.\n\n## 2017-05-26, version 5.7.0\n\n- Implemented support for template items. Thanks @israelito3000.\n- Upgraded dependencies to the latest versions. Thanks @andreykaipov.\n\n## 2017-04-15, version 5.6.0\n\n- Implemented readonly option for modes `text` and `code.`\n  Thanks @walkerrandolphsmith.\n- Upgraded dependencies (`brance` and `ajv`) to the latest versions.\n- Fixed not being able to move focus to enum select box when clicking\n  a JSON Schema warning.\n- Fixed #309: already loaded version of Ace being overwritten by the\n  embedded version of JSONEditor.\n- Fixed #368: Mode selection drop down not fully visible on small screen.\n- Fixed #253: Optimize the input experience of Chinese IME.\n  Thanks @chinesedfan.\n\n## 2017-01-06, version 5.5.11\n\n- Fixed embedded version of jsoneditor ace theme not being loaded in\n  minimalist version (see #55).\n- Fixed a styling issue in the SearchBox of Ace editor (mode `code`).\n- Fixed #347: CSS more robust against global settings of div position.\n- Added docs and example on how to use a custom version of Ace editor.\n\n## 2016-11-02, version 5.5.10\n\n- Fixed #85: pressing enter in an input in a form containing a JSONEditor too\n  breaks submitting the form.\n\n## 2016-10-17, version 5.5.9\n\n- Fixed #329: Editor showing duplicate key warnings for keys defined on the\n  Object prototype, like `toString` and `watch`.\n\n## 2016-09-27, version 5.5.8\n\n- Fixed #314: JSON schema validation throwing an error \"Unexpected token ' in\n  JSON at position 0\" in specific cases. Thanks @apostrophest\n\n## 2016-08-17, version 5.5.7\n\n- Fixed #308: wrong positioning of label \"empty array\" when `onEditable`\n  returns false.\n\n## 2016-06-15, version 5.5.6\n\n- Fixed #303: editor contents collapsed when the parent div of the JSONEditor\n  has no height set.\n- Improved example 04_load_and_save.html. Thanks @RDCH106.\n\n## 2016-05-24, version 5.5.5\n\n- Fixed #298: Switch mode button disappears when switching from text/code to\n  tree/form/view mode when the JSON contained errors.\n- Fixed enum drop downs not working when the JSONEditor is configured with\n  a name.\n\n## 2016-05-22, version 5.5.4\n\n- Fixed #285: an issue with the enum drop down when having defined multiple\n  enums in a JSON schema.\n- Fixed a (harmless) error in the console when clicking right from an enum\n  drop down.\n\n## 2016-05-22, version 5.5.3\n\n- Fixed #299: reverted the fix of #268 by trimming text in fields and values.\n\n## 2016-04-18, version 5.5.2\n\n- Fixed #294: Fields reset their caret location on every key press in Firefox.\n\n## 2016-04-16, version 5.5.1\n\n- Fixed enum select boxes not being rendered/removed when setting or removing\n  a JSON schema via `editor.setSchema(schema)`.\n\n## 2016-04-16, version 5.5.0\n\n- Implemented a dropdown for values having an JSON Schema enum.\n  Thanks @tdakanalis.\n- Fixed #291, #292: Some CSS broken when using the editor in combination with\n  bootstrap. Thanks @nucleartide.\n\n## 2016-04-09, version 5.4.0\n\n- Upgraded all dependencies (`ajv`, `brace`, etc).\n- Fixed #289: Some CSS breaking when using the editor in combination with\n  materialize.css or bootstrap.\n- Fixed #290: `setText()` not working in mode text or code.\n\n## 2016-04-06, version 5.3.0\n\n- Implemented support for sorting object keys naturally. Thanks @edufelipe.\n- Sorting object keys or array items via the context menu is now also naturally\n  sorted.\n- Fixed #283: improved JSON schema error message in case of no\n  additionalProperties.\n- Fixed #286: Calling `get()` or `getText()` caused the editor to lose focus.\n  A regression introduced in v5.2.0.\n\n## 2016-03-20, version 5.2.0\n\n- Implemented method `editor.destroy()` to properly cleanup the editor (#278).\n- Fixed #268: JSONEditor now trims text in fields and values.\n- Fixed #280: Some CSS issues when used in combination with bootstrap.\n\n## 2016-02-15, version 5.1.5\n\n- Fixed #272: Checkbox for boolean values visible in view mode.\n\n## 2016-02-13, version 5.1.4\n\n- Fixed broken example 04_load_and_save.html. See #265.\n\n## 2016-02-03, version 5.1.3\n\n- Fixed #264: Clicking items in the context menu not working on Firefox.\n\n## 2016-01-21, version 5.1.2\n\n- Improvements in sanitizing invalid JSON.\n- Updated dependencies to the latest version.\n- Fixed clicking format/compact not triggering an onChange event.\n- Fixed #259: when having a JSONEditor inside an HTML form, clicking an entry\n  in the context menu did submit the form.\n- Fixed browserify build, see #260. Thanks @onip.\n\n## 2016-01-16, version 5.1.1\n\n- Fixed #257: Improving error messages for enum errors failed when the\n  schema contains references.\n- Fixed #255: Removed wrong console warning about the option `search`.\n- Fixed error thrown when option `search` is false (see #256). Thanks @MiroHibler.\n\n## 2016-01-14, version 5.1.0\n\n- Implemented support for JSON schema validation, powered by `ajv`.\n- Implemented #197: display an error in case of duplicate keys in an object.\n- Implemented #183: display a checkbox left from boolean values, so you can\n  easily switch between true/false.\n- Implemented debouncing of keyboard input, resulting in much less history\n  actions whilst typing.\n- Added a minimalist bundle to the `dist` folder, excluding `ace` and `ajv`.\n- Fixed #222: editor throwing `onChange` events when switching mode.\n- Fixed an error throw when switching to mode \"code\" via the menu.\n- Fixed interfering shortcut keys: changed quick keys to select multiple fields\n  from `Shift+Arrow Up/Down` to `Ctrl+Shift+Arrow Up/Down`.\n\n## 2015-12-31, version 5.0.1\n\n- Fixed a bug in positioning of the context menu for multiple selected nodes.\n- Fixed #130: option `onEditable` not available in mode `form`.\n- Fixed #202: removed `version` field from bower.json.\n\n## 2015-12-31, version 5.0.0\n\n- New design.\n- Implemented selection of multiple nodes, allowing to move/duplicate/remove\n  multiple nodes at once (See #106).\n- Implemented a new option `escapeUnicode`, which will show the hexadecimal\n  unicode instead of the character itself. (See #93 and #230).\n- Implemented method `getMode`.\n- Implemented option `onModeChange(oldMode, newMode)`.\n- Implemented #203: Objects and arrays in mode `form` and `view` are now\n  expandable by clicking the field names too.\n- Replaced the PNG icon images with SVG. Thanks @1j01.\n- Renamed all CSS classes They now have prefixes `.jsoneditor-` to prevent\n  name collisions with css frameworks like bootstrap.\n- Renamed options `change`, `editable`, `error` to respectively `onChange`,\n  `onEditable`, and `onError`. Old options are still working and give a\n  deprecation warning.\n- Colors of values are now customizable using CSS.\n- JSONEditor new throws a warning in the console in case of unknown options.\n- Fixed #93 and #227: html codes like `&amp;` not escaped.\n- Fixed #149: Memory leak when switching mode from/to `code` mode, web worker\n  of Ace editor wasn't cleaned up.\n- Fixed #234: Remove dependency on a fork of the `jsonlint` project on github.\n- Fixed: disabled `Ctrl+L` quick key to go to a line, instead use the default\n  browser behavior of selecting the address bar.\n- Fixed #38: clear search results after a new JSON object is set.\n- Fixed #242: row stays highlighted when dragging outside editor.\n- Fixed quick-keys Shift+Alt+Arrows not registering actions in history.\n- Fixed #104: context menus are now positioned relative to the elements of the\n  editor instead of an absolute position in the window.\n\n## 2015-06-13, version 4.2.1\n\n- Fixed #161: Cannot select text in Ace editor on systems using Chinese fonts.\n\n## 2015-05-14, version 4.2.0\n\n- Implemented option `theme`, allowing to set a custom theme for the Ace\n  editor. Thanks @nfvs.\n- Implemented option `ace`, which allows to pass a custom instance of the Ace\n  instead of the embedded version.\n- Fixed #186: binding issue to `jsonlint.parse`.\n- Fixed `editor.get()` manipulating the code when containing an error.\n\n## 2015-03-15, version 4.1.1\n\n- Added missing file `index.js` to the bower package.\n  \n## 2015-03-15, version 4.1.0\n\n- Implemented a function `focus()` for modes tree, view, and form.\n- Added `./src` folder to the distributed package, needed for usage via\n  node.js/browserify.\n\n## 2015-02-28, version 4.0.0\n\n- Ace editor and jsonlint are now packed with jsoneditor.js by default.\n  This makes the library about 4 times larger. If Ace is not needed, a custom\n  build of the library can be done.\n- The distribution files are now moved from the root to the `/dist` folder.\n- Reworked the source code to CommonJS modules, using `brace` to load Ace.\n- JSONP is now automatically stripped from JSON. Thanks @yanivefraim.\n- Fixed bugs in the JSON sanitizer, no longer manipulating JSON-like structures\n  inside strings.\n\n## 2015-01-25, version 3.2.0\n\n- Implemented shortcut keys `Ctrl+\\` to format and `Ctrl+Shift+\\` to compact\n  JSON when in mode `text` or `code`.\n- Before an error is thrown because of invalid text, the editor first tries to\n  sanitize the text (replace JavaScript notation with JSON notation), and only\n  after that throws the error.\n- Fixed Node.path() not working for a JSON Object `\"\"`. Thanks @tomalec.\n- Minor styling improvements.\n- Fixed configured indentation not being applied to Ace editor.\n\n## 2014-09-03, version 3.1.2\n\n- Some fixes/improvements in `parseJS` (to parse a JSON object from a JavaScript\n  object).\n- Fixed the lack of a semi colon at end of the bundled files.\n\n## 2014-08-01, version 3.1.1\n\n- Replaced parsing of JavaScript objects into JSON from `eval` to a dedicated\n  `parseJS` function.\n\n## 2014-07-28, version 3.1.0\n\n- JSONEditor now accepts JavaScript objects as input, and can turn them into\n  valid JSON. For example `{a:2,b:'str'}` can be turned into `{\"a\":2,\"b\":\"str\"}`.\n- Implemented an option `editable`, a callback function, which allows to set\n  individual nodes (their field and/or value) editable or read-only.\n- Fixed: shortcut keys to manipulate the nodes are now disabled when mode\n  is `form` or `view`.\n\n## 2014-05-31, version 3.0.0\n\n- Large code reorganization.\n- Editor must be loaded as `new JSONEditor(...)` instead of\n  `new jsoneditor.JSONEditor(...)`.\n- Css is not automatically loaded anymore when using AMD.\n- Web application has been moved to another project.\n\n## 2014-01-03, version 2.3.6\n\n- Fixed positioning issue of the action menu.\n\n## 2013-12-09, version 2.3.5\n\n- Fixed a positioning issue of the action menu again.\n- Fixed an issue with non-breaking space characters.\n\n## 2013-11-19, version 2.3.4\n\n- Dropped support for IE8, cleaned up legacy code for old browsers.\n- Disabled saving files using HTML5 on Firefox to prevent a Firefox bug\n  blocking cut/paste functionality in editable divs after using a.download.\n\n## 2013-10-17, version 2.3.3\n\n- Added support for search (Ctrl+F) in the code editor Ace.\n- Fixed a positioning issue of the action menu when in bootstrap modal.\n  (thanks tsash).\n\n## 2013-09-26, version 2.3.2\n\n- The web application is now available offline. Thanks ayanamist.\n\n## 2013-09-24, version 2.3.1\n\n- Fixed non-working action menu when in bootstrap modal (z-index issue).\n- Fixed missing main field in package.json.\n\n## 2013-09-13, version 2.3.0\n\n- Implemented an option `modes`, which creates a menu in the editor\n  where the user can switch between the selected editor modes.\n- Fixed wrong title on fields with value `null`.\n- Fixed buggy loading of files in the web application.\n\n## 2013-08-01, version 2.2.2\n\n- Fixed non working option `indentation`.\n- Fixed css not being loaded with AMD in case of multiple scripts.\n- Fixed a security error in the server side file retriever script of\n  the web application.\n\n## 2013-05-27, version 2.2.1\n\n- Fixed undefined options in TextEditor. Thanks Wiseon3.\n- Fixed non-working save function on Firefox 21. Thanks youxiachai.\n\n## 2013-05-04, version 2.2.0\n\n- Unified JSONFormatter and JSONEditor in one editor with a switchable mode.\n- Urls are navigable now.\n- Improved error and log handling.\n- Added jsoneditor to package managers npm and bower.\n\n## 2013-03-11, version 2.1.1\n\n- Fixed an issue with console outputs on IE8, causing the editor not to work\n  at all on IE8.\n\n## 2013-03-08, version 2.1.0\n\n- Replaced the plain text editor with code editor Ace, which brings in syntax\n  highlighting and code inspection.\n- Improved the splitter between the two panels. Panels can be hided.\n\n## 2013-02-26, version 2.0.2\n\n- Fixed: dragarea of the root node was wrongly visible is removed now.\n\n## 2013-02-21, version 2.0.1\n\n- Fixed undefined variable in the redo method.\n- Removed the \"hide ads\" button. Not allowed by Google AdSense, sorry.\n\n## 2013-02-09, version 2.0.0\n\n- Implemented a context menu, replacing the action buttons on the right side of\n  the editor and the inline action buttons. This gives a cleaner interface,\n  more space for the actual contents, and more room for new controls (like\n  insert and sort).\n- Implemented shortcut keys. The JSON Editor can be used with just a keyboard.\n- Implemented sort action, which sorts the childs of an array or object.\n- Implemented auto scrolling up and down when dragging a node and reaching\n  the top or bottom of the editor.\n- Added support for CommonJS and RequireJS.\n- Added more examples.\n- Improved performance and memory usage.\n- Implemented a new mode 'form', in which only values are editable and the\n  fields are fixed.\n- Minor improvements and bug fixes.\n\n## 2012-12-08, version 1.7.0\n\n- Implemented two modes: 'editor' (default), and 'viewer'. In viewer mode,\n  the data and datastructure is read-only.\n- Implemented methods set(json, name), setName(name), and getName(), which\n  allows for setting and getting the field name of the root node.\n- Fixed an issue where the search bar does not work when there is no global\n  window.editor object.\n\n## 2012-11-26, version 1.6.2\n\n- Fixed a bug in the change callback handler, resulting in an infinite loop\n  when requesting the contents of the editor inside the callback (issue #19).\n\n## 2012-11-21, version 1.6.1\n\n- Added a request header \"Accept: application/json\" when loading files and urls.\n\n## 2012-11-03, version 1.6.0\n\n- Added feature to the web application to load and save files from disk and url.\n- Improved error messages in the web application using JSONLint.\n- Made the web application pass the W3C markup validation service.\n- Added option 'change' to both editor and formatter, which allows to set a\n  callback which is triggered when the contents of the editor or formatter\n  changes.\n- Changed the default indentation of the JSONFormatter to 4 spaces.\n- Renamed options 'enableSearch' and 'enableHistory' to 'search' and 'history'\n  respectively.\n- Added parameter 'json' to the JSONFormatter constructor.\n- Added option 'indentation' to the JSONFormatter.\n\n## 2012-10-08, version 1.5.1\n\n- Replaced the paid Chrome App with a free, hosted Chrome App (with ads).\n\n## 2012-10-02, version 1.5.0\n\n- Implemented history: undo/redo all actions.\n- Created menu icons (instead of text buttons).\n- Cleaned up the code (removed unused params, improved comments, etc).\n- Minor performance improvements.\n\n## 2012-08-31, version 1.4.4\n\n- Changed: description of advertisement now gives information about the Chrome\n  App (without ads).\n- Changed: Chrome App is now configured to be available offline.\n- Fixed: When zooming your browser window, the fields/values did get wrapped\n  on Chrome (thanks Henri Gourvest), and on Firefox sometimes the jsoneditor\n  disappeared due to wrapping of the interface contents.\n\n## 2012-08-25, version 1.4.3\n\n- Changed: changed code for the buttons to copy from formatter to editor and\n  vice versa, no inline javascript (gives security policy errors in chrome app).\n\n## 2012-08-25, version 1.4.2\n\n- Changed: other bootstrapping mechanism for the chrome app, in a separate\n  javascript file, as inline javascript is not allowed (security policy).\n- Fixed: drop down menu for changing the field type did throw javascript errors\n  (did not break any functionality though).\n\n## 2012-08-23, version 1.4.1\n\n- New: Chrome app created.\n\n## 2012-08-23, version 1.4.0\n\n- New: Improved icon, logo, and interface header.\n\n## 2012-08-19, version 1.3.0\n\n- New: Added buttons next and previous to the search box in the upper right.\n- New: Escape characters are automatically inserted before \" and \\ missing\n  and escape character, making the string contents valid JSON. New lines are\n  automatically replaced with \\n. (Thanks Steve Clay)\n- Changed: all icons have been put in a single sprite. This will improve page\n  load times as there are much less server requests needed to load the editor.\n\n## 2012-08-12, version 1.2.0\n\n- New: Added search functionality. Search results are expanded and highlighted.\n  Quickkeys in the search box: Enter (next), Shift+Enter (previous), Ctrl+Enter\n  (search again).\n- New: The position of the vertical separator between left and right panel is\n  stored.\n- New: Link to the sourcecode on github added at the bottom of the page.\n- Changed: Refinements in the layout: fonts, colors, icons.\n- Fixed: leading an trailing spaces not being displayed in the editor.\n- Fixed: wrapping of long words and urls in Chrome.\n- Fixed: ignoring functions and undefined values in the loaded JSON object\n  (they where interpreted as empty object and string instead of being ignored).\n\n## 2012-07-01, version 1.1.1\n\n- Fixed global event listener for the focus/blur events, causing changes in\n  fields and values not always being registered.\n- Fixed a css issue with Firefox (box-sizing of the editor).\n\n## 2012-04-24, version 1.1\n\n- Fixed a bug. Dragging an object down which has been expanded and collapsed\n  again did not work.\n- Using a minified version of jsoneditor.js, to improve page load time and\n  save bandwidth.\n\n## 2012-04-21, version 1.0\n\n- Values are no longer aligned in one global column, but placed directly right\n  from the field. Having field and value close together improves readability,\n  especially in case of deeply nested data.\n- Values are colorized by their type: strings are green, values read, booleans\n  blue, and null is purple.\n- Font is changed to a monotype font for better readability.\n- Special characters like \\t are now handled nicely.\n- Overall performance and memory usage improved.\n- When clicking on whitespace, focus is set to the closest field or value.\n- some other small interface tweaks.\n- Fixed a bug with casting a value from type auto to string and vice versa\n  (the value was not casted at all).\n\n## 2012-03-01, version 0.9.10\n\n- Nicer looking select box for the field types, with icons.\n- Improved drag and drop: better visualized, and now working in all browsers.\n- Previous values will be restored after changing the type of a field. When\n  changing the type back, the previous value or childs will be restored.\n- When hovering buttons (fieldtype, duplicate, delete, add) or when dragging\n  a field, corresponding field including its childs is highlighted. This makes\n  it easier to see what part of the data will be edited.\n- Errors are now displayed in a message window on top of the page instead of\n  an alert which pops up.\n- Fixed a bug with displaying enters in fields.\n- Fixed a bug where the last trailing enter was removed when setting json\n  in the editor.\n- Added a fix to get around Internet Explorer 8 issues with vertical scrollbars.\n\n## 2012-01-29, version 0.9.9\n\n- Fields can be duplicated\n- Support for drag and drop:\n  - fields in the editor itself can be moved via drag and drop\n  - fields can be exported from the editor as JSON\n  - external JSON can be dropped inside the editor\n- When changing type from array to object and vice versa, childs will be\n  maintained instead of removed.\n- Updated interface. Works now in IE8 too.\n\n## 2012-01-16, version 0.9.8\n\n- Improved the performance of expanding a node with all its childs.\n\n## 2012-01-09, version 0.9.7\n\n- Added functionality to expand/collapse a node and all its childs. Click\n  the expand button of a node while holding Ctrl down.\n- Small interface improvements\n\n## 2011-11-28, version 0.9.6\n\n- First fully usable version of the JSON editor\n"
  },
  {
    "path": "LICENSE",
    "content": "                               Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "NOTICE",
    "content": "JSON Editor\nhttps://github.com/josdejong/jsoneditor\n\nCopyright (C) 2011-2026 Jos de Jong\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# JSON Editor\n\n[![Version](https://img.shields.io/npm/v/jsoneditor.svg)](https://www.npmjs.com/package/jsoneditor)\n[![Downloads](https://img.shields.io/npm/dm/jsoneditor.svg)](https://www.npmjs.com/package/jsoneditor)\n[![Maintenance](https://img.shields.io/maintenance/yes/2026.svg)](https://github.com/josdejong/jsoneditor/pulse)\n[![License](https://img.shields.io/github/license/josdejong/jsoneditor.svg)](https://github.com/josdejong/jsoneditor/blob/master/LICENSE)\n[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjosdejong%2Fjsoneditor.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjosdejong%2Fjsoneditor?ref=badge_shield)\n\nJSON Editor is a web-based tool to view, edit, format, and validate JSON. It has various modes such as a tree editor, a code editor, and a plain text editor. The editor can be used as a component in your own web application. It can be loaded as CommonJS module, AMD module, or as a regular javascript file.\n\nThe library was originally developed as core component of the popular web application https://jsoneditoronline.org and has been open sourced since then.\n\nSupported browsers: Chrome, Firefox, Safari, Edge.\n\n<img alt=\"json editor\" src=\"https://raw.github.com/josdejong/jsoneditor/master/misc/jsoneditor.png\"> &nbsp; <img alt=\"code editor\" src=\"https://raw.github.com/josdejong/jsoneditor/master/misc/codeeditor.png\">\n\nContinuous integration tests are run on [GitHub Actions](https://github.com/josdejong/mathjs/actions), and [LambdaTest](https://www.lambdatest.com) is used to test on all major browsers.\n\n[![LambdaTest](https://raw.github.com/josdejong/mathjs/master/misc/lambdatest.svg)](https://www.lambdatest.com)\n\nThanks, GitHub Actions and LambdaTest for the generous support for this open source project!\n\n## Successor: svelte-jsoneditor\n\nThis library [`jsoneditor`](https://github.com/josdejong/jsoneditor) has a successor: [`svelte-jsoneditor`](https://github.com/josdejong/svelte-jsoneditor). The new editor is not a one-to-one replacement, so there may be reasons to stick with `jsoneditor`. \nThe main differences between the two [are described here](https://github.com/josdejong/svelte-jsoneditor#differences-between-josdejongsvelte-jsoneditor-and-josdejongjsoneditor).\n\n## Features\n\nJSONEditor has various modes, with the following features.\n\n### Tree mode\n\n- Change, add, move, remove, and duplicate fields and values.\n- Sort arrays and objects.\n- Transform JSON using [JMESPath](http://jmespath.org/) queries.\n- Colorized code.\n- Color picker.\n- Search & highlight text in the tree view.\n- Undo and redo all actions.\n- JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)).\n\n### Code mode\n\n- Colorized code (powered by [Ace](https://ace.c9.io)).\n- Inspect JSON (powered by [Ace](https://ace.c9.io)).\n- Format and compact JSON.\n- Repair JSON.\n- JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)).\n\n### Text mode\n\n- Format and compact JSON.\n- Repair JSON.\n- JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)).\n\n### Preview mode\n\n- Handle large JSON documents up to 500 MiB.\n- Transform JSON using [JMESPath](http://jmespath.org/) queries.\n- Format and compact JSON.\n- Repair JSON.\n- JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)).\n\n## Documentation\n\n- Documentation:\n  - [API](https://github.com/josdejong/jsoneditor/tree/master/docs/api.md)\n  - [Usage](https://github.com/josdejong/jsoneditor/tree/master/docs/usage.md)\n  - [Shortcut keys](https://github.com/josdejong/jsoneditor/tree/master/docs/shortcut_keys.md)\n- [Examples](https://github.com/josdejong/jsoneditor/tree/master/examples)\n- [Source](https://github.com/josdejong/jsoneditor)\n- [History](https://github.com/josdejong/jsoneditor/blob/master/HISTORY.md)\n\n\n## Install\n\nwith npm (recommended):\n\n    npm install jsoneditor\n\nAlternatively, you can use another JavaScript package manager like https://yarnpkg.com/, or a CDN such as https://cdnjs.com/ or https://www.jsdelivr.com/.\n\n## Use\n\n> Note that in the following example, you'll have to change the urls `jsoneditor/dist/jsoneditor.min.js` and `jsoneditor/dist/jsoneditor.min.css` to match the place where you've downloaded the library, or fill in the URL of the CDN you're using.\n\n```html\n<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n    <!-- when using the mode \"code\", it's important to specify charset utf-8 -->\n    <meta charset=\"utf-8\">\n\n    <link href=\"jsoneditor/dist/jsoneditor.min.css\" rel=\"stylesheet\" type=\"text/css\">\n    <script src=\"jsoneditor/dist/jsoneditor.min.js\"></script>\n</head>\n<body>\n    <div id=\"jsoneditor\" style=\"width: 400px; height: 400px;\"></div>\n\n    <script>\n        // create the editor\n        const container = document.getElementById(\"jsoneditor\")\n        const options = {}\n        const editor = new JSONEditor(container, options)\n\n        // set json\n        const initialJson = {\n            \"Array\": [1, 2, 3],\n            \"Boolean\": true,\n            \"Null\": null,\n            \"Number\": 123,\n            \"Object\": {\"a\": \"b\", \"c\": \"d\"},\n            \"String\": \"Hello World\"\n        }\n        editor.set(initialJson)\n\n        // get json\n        const updatedJson = editor.get()\n    </script>\n</body>\n</html>\n```\n\n\n## Build\n\nThe code of the JSON Editor is located in the folder `./src`. To build \njsoneditor:\n\n- Install dependencies:\n\n  ```\n  npm install\n  ```\n\n- Build JSON Editor:\n\n  ```\n  npm run build\n  ```\n\n  This will generate the files `./jsoneditor.js`, `./jsoneditor.css`, and  \n  minified versions in the dist of the project.\n\n- To automatically build when a source file has changed:\n\n  ```\n  npm start\n  ```\n\n  This will update `./jsoneditor.js` and `./jsoneditor.css` in the dist folder\n  on every change, but it will **NOT** update the minified versions as that's\n  an expensive operation.\n\n\n## Test\n\nRun unit tests:\n\n```\nnpm test\n```\n\nRun code linting ([JavaScript Standard Style](https://standardjs.com/)):\n\n```\nnpm run lint\n```\n\n\n## License\n\n`jsoneditor` is released as open source under the permissive the [Apache 2.0 license](LICENSE.md).\n\n**If you are using jsoneditor commercially, there is a _social_ (but no legal) expectation that you help fund its maintenance. [Start here](https://github.com/sponsors/josdejong).**\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease report (suspected) security vulnerabilities privately to one of the maintainers of the library, for example to Jos de Jong: https://github.com/josdejong.\n"
  },
  {
    "path": "docs/api.md",
    "content": "# API Reference\n\n## JSONEditor\n\n### Constructor\n\n#### `JSONEditor(container [, options [, json]])`\n\nConstructs a new JSONEditor.\n\n*Parameters:*\n\n- `{Element} container`\n\n  An HTML DIV element. The JSONEditor will be created inside this container element.\n\n- `{Object} options`\n\n  Optional object with options. The available options are described under\n  [Configuration options](#configuration-options).\n\n- `{JSON} json`\n\n  Initial JSON data to be loaded into the JSONEditor. Alternatively, the method `JSONEditor.set(json)` can be used to load JSON data into the editor.\n\n*Returns:*\n\n- `{JSONEditor} editor`\n\n  New instance of a JSONEditor.\n\n### Configuration options\n\n- `{Object} ace`\n\n  Provide a custom version of the [Ace editor](http://ace.c9.io/) and use this instead of the version that comes embedded with JSONEditor. Only applicable when `mode` is `code`.\n  \n  When providing your own instance of Ace editor, be aware that JSONEditor assumes the following Ace plugins to be loaded: `mode-json`, `worker-json`, `ext-searchbox`, `ext-language_tools`.\n\n  Note that when using the minimalist version of JSONEditor (which has Ace excluded), JSONEditor will try to load the Ace plugins `ace/mode/json` and `ace/ext/searchbox`. These plugins must be loaded beforehand or be available in the folder of the Ace editor.\n\n- `{Object} ajv`\n\n  Provide a custom instance of [ajv](https://github.com/epoberezkin/ajv), the\n  library used for JSON schema validation. Example:\n\n  ```js\n  var options = {\n    ajv: Ajv({ \n      allErrors: true, \n      verbose: true,\n      jsonPointers: false,\n      $data: true\n    })\n  }\n  ```\n  \n  > IMPORTANT: JSONEditor relies on some specific configuration of Ajv. \n  > Providing different configuration (like `jsonPointers: true` instead of `false`) \n  > results in JSONEditor breaking because the format of the Ajv errors differs\n  > from what is expected.\n\n- `{function} onChange()`\n\n  Set a callback function triggered when the contents of the JSONEditor change.\n  This callback does not pass the changed contents, use `get()` or `getText()` for that.\n  Note that `get()` can throw an exception in mode `text`, `code`, or `preview`, when the editor contains invalid JSON.\n  Will only be triggered on changes made by the user, not in case of programmatic changes via the functions `set`, `setText`, `update`, or `updateText`.\n  See also callback functions `onChangeJSON(json)` and `onChangeText(jsonString)`.\n\n- `{function} onChangeJSON(json)`\n\n  Set a callback function triggered when the contents of the JSONEditor change.\n  Passes the changed JSON document.\n  Only applicable when option `mode` is `tree`, `form`, or `view`.\n  The callback will only be triggered on changes made by the user, not in case of programmatic changes via the functions `set`, `setText`, `update`, or `updateText`.\n  Also see the callback function `onChangeText(jsonString)`.\n\n- `{function} onChangeText(jsonString)`\n\n  Set a callback function triggered when the contents of the JSONEditor change.\n  Passes the changed JSON document inside a string (stringified).\n  The callback will only be triggered on changes made by the user, not in case of programmatic changes via the functions `set`, `setText`, `update`, or `updateText`.\n  Also see the callback function `onChangeJSON(json)`.\n\n- `{function} onClassName({ path, field, value })`\n\n  Set a callback function to add custom CSS classes to the rendered nodes. Only applicable when option `mode` is `tree`, `form`, or `view`.\n\n  The callback is invoked with an object containing `path`, `field` and `value`:\n\n  ```\n  {\n    path: string[],\n    field: string,\n    value: string\n  }\n  ```\n  The function must either return a string containing CSS class names, or return `undefined` in order to do nothing for a specific node.\n\n  In order to update css classes when they depend on external state, you can call `editor.refresh()`.\n\n- `{function} onExpand({ path, isExpand, recursive })`\n\n  Set a callback function to be invoked when a node is expanded/collapsed (not programtically via APIs). Only applicable when option `mode` is `tree`, `form`, or `view`.\n\n  The callback is invoked with an object containing `path`, `isExpand` and `recursive`:\n\n  ```\n  {\n    path: string[],\n    isExpand: boolean,\n    recursive: boolean\n  }\n  ```\n\n- `{function} onEditable({ path, field, value })`\n\n  Set a callback function to determine whether individual nodes are editable or read-only. Only applicable when option `mode` is `tree`, `text`, or `code`.\n\n  In case of mode `tree`, the callback is invoked as `editable(node)`, where the first parameter is an object:\n\n  ```\n  {\n    field: string,\n    value: string,\n    path: string[]\n  }\n  ```\n\n  The function must either return a boolean value to set both the nodes field and value editable or read-only, or return an object `{field: boolean, value: boolean}` to set set the read-only attribute for field and value individually.\n\n  In modes `text` and `code`, the callback is invoked as `editable(node)` where `node` is an empty object (no field, value, or path). In that case the function can return false to make the text or code editor completely read-only.\n\n- `{function} onError(error)`\n\n  Set a callback function triggered when an error occurs. Invoked with the error as first argument. The callback is only invoked\n  for errors triggered by a users action, like switching from code mode to tree mode or clicking the Format button whilst the editor doesn't contain valid JSON. When not defined, a basic alert with the error message will be opened.\n\n- `{function} onModeChange(newMode, oldMode)`\n\n  Set a callback function triggered right after the mode is changed by the user. Only applicable when\n  the mode can be changed by the user (i.e. when option `modes` is set).\n\n- `{function} onNodeName({ path, type, size, value })`\n\n  Customize the name of object and array nodes. By default the names are brackets with the number of childs inside,\n  like `{5}` and `[32]`. The number inside can be customized. using `onNodeName`.\n\n  The first parameter is an object containing the following properties:\n\n  ```\n  {\n    path: string[],\n    type: 'object' | 'array',\n    size: number,\n    value: object\n  }\n  ```\n\n  The `onNodeName` function should return a string containing the name for the node. If nothing is returned,\n  the size (number of childs) will be displayed.\n\n- `{function} onValidate(json)`\n\n  Set a callback function for custom validation. Available in all modes.\n\n  On a change of the JSON, the callback function is invoked with the changed data. The function should return\n  an array with errors or null if there are no errors. The function can also return a `Promise` resolving with\n  the errors retrieved via an asynchronous validation (like sending a request to a server for validation).\n  The returned errors must have the following structure: `{path: Array.<string | number>, message: string}`.\n  Example:\n\n  ```js\n  var options = {\n    onValidate: function (json) {\n      var errors = [];\n\n      if (json && json.customer && !json.customer.address) {\n        errors.push({\n          path: ['customer'],\n          message: 'Required property \"address\" missing.'\n        });\n      }\n\n      return errors;\n    }\n  }\n  ```\n  \n  Also see the option `schema` for JSON schema validation.\n\n- `{function} onValidationError(errors: ValidationError[])`\n\n  Set a callback function for validation and parse errors. Available in all modes.\n  The `ValidationError` contains a `type`, and an `error` object. \n\n  On validation of the json, if errors of any kind were found this callback is invoked with the errors data.\n\n  On change, the callback will be invoked only if errors were changed.\n\n  See also method `JSONEditor.validate()`.\n\n  Example:\n\n  ```js\n  var options = {\n    /**\n    * @param {Array} errors validation errors\n    */\n    onValidationError: function (errors) {\n      errors.forEach((error) => {\n        switch (error.type) {\n          case 'validation': // schema validation error\n            ...\n            break;\n          case 'customValidation': // custom validation error\n            ...\n            break;\n          case 'error':  // json parse error\n            ...\n            break;\n          ...\n        }\n      });\n      ...\n    }\n  }\n  ```\n  \n  \n- `{function} onCreateMenu(items, node)`\n  \n  Customize context menus in tree mode.\n\n  Sets a callback function to customize the context menu in tree mode. Each time the user clicks on the context menu button, an array of menu items is created. If this callback is configured, the array with menu items is passed to this function. The menu items can be customized in this function in any aspect of these menu items, including deleting them and/or adding new items. The function should return the final array of menu items to be displayed to the user.\n\n  Each menu item is represented by an object, which may also contain a submenu array of items. See the source code of example 21 in the examples folder for more info on the format of the items and submenu objects.\n\n  The second argument `node` is an object containing the following properties:\n\n  ```\n  {\n    type: 'single' | 'multiple' | 'append'\n    path: Array,\n    paths: Array with paths\n  }\n  ```\n\n  The property `path` containing the path of the node, and `paths` contains the same path or in case there are multiple selected nodes it contains the paths of all selected nodes.\n  When the user opens the context menu of an append node (in an empty object or array), the `type` will be `'append'` and the `path` will contain the path of the parent node.\n\n- `{boolean} escapeUnicode`\n\n  If `true`, unicode characters are escaped and displayed as their hexadecimal code (like `\\u260E`) instead of the character itself (like `☎`). `false` by default.\n\n- `{boolean} sortObjectKeys`\n\n  If `true`, object keys in 'tree', 'view' or 'form' mode list be listed alphabetically instead by their insertion order. Sorting is performed using a natural sort algorithm, which makes it easier to see objects that have string numbers as keys. `false` by default.\n\n- `{boolean} limitDragging`\n\n  If `false`, nodes can be dragged from any parent node to any other parent node. If `true`, nodes can only be dragged inside the same parent node, which effectively only allows reordering of nodes. By default, `limitDragging` is `true` when no JSON `schema` is defined, and `false` otherwise.\n\n- `{boolean} history`\n\n  Enables history, adds a button Undo and Redo to the menu of the JSONEditor. `true` by default. Only applicable when `mode` is `'tree'`, `'form'`, or `'preview'`.\n\n- `{String} mode`\n\n  Set the editor mode. Available values: 'tree' (default), 'view', 'form', 'code', 'text', 'preview'. In 'view' mode, the data and datastructure is read-only. In 'form' mode, only the value can be changed, the data structure is read-only. Mode 'code' requires the Ace editor to be loaded on the page. Mode 'text' shows the data as plain text.\n  The 'preview' mode can handle large JSON documents up to 500 MiB. It shows a preview of the data, and allows to\n  transform, sort, filter, format, or compact the data.\n\n- `{String[]} modes`\n\n  Create a box in the editor menu where the user can switch between the specified modes. Available values: see option `mode`.\n\n- `{String} name`\n\n  Initial field name for the root node, is `undefined` by default. Can also be set using `JSONEditor.setName(name)`. Only applicable when `mode` is 'tree', 'view', or 'form'.\n\n- `{Object} schema`\n\n  Validate the JSON object against a JSON schema. A JSON schema describes the\n  structure that a JSON object must have, like required properties or the type\n  that a value must have.\n\n  See [http://json-schema.org/](http://json-schema.org/) for more information.\n\n  Also see the option `onValidate` for custom validation.\n\n- `{Object} schemaRefs`\n\n  Schemas that are referenced using the `$ref` property from the JSON schema that are set in the `schema` option,\n  the object structure in the form of `{reference_key: schemaObject}`\n\n- `{boolean} allowSchemaSuggestions`\n\n  Enables autocomplete suggestions based on the JSON schema. `false` by default. when enabled and schema is configured, the editor will suggest text completions based on the schema properties, examples and enums.\n  \n  **limitation**: the completions will be presented only for a valid json.\n\n  Only applicable when `mode` is 'code'.\n\n- `{boolean} search`\n\n  Enables a search box in the upper right corner of the JSONEditor. `true` by default. Only applicable when `mode` is 'tree', 'view', or 'form'.\n\n- `{Number} indentation`\n\n  Number of indentation spaces. `2` by default. Only applicable when `mode` is 'code', 'text', or 'preview'.\n\n- `{String} theme`\n\n  Set the Ace editor theme, uses included 'ace/theme/jsoneditor' by default. Please note that only the default theme is included with JSONEditor, so if you specify another one you need to make sure it is loaded.\n\n- `{Object} templates`\n\n  Array of templates that will appear in the context menu, Each template is a json object precreated that can be added as a object value to any node in your document. \n\n  The following example allow you can create a \"Person\" node and a \"Address\" node, each one will appear in your context menu, once you selected the whole json object will be created.\n\n  ```js\n  var options = {\n    templates: [\n          {\n              text: 'Person',\n              title: 'Insert a Person Node',\n              className: 'jsoneditor-type-object',\n              field: 'PersonTemplate',\n              value: {\n                  'firstName': 'John',\n                  'lastName': 'Do',\n                  'age': 28\n              }\n          },\n          {\n              text: 'Address',\n              title: 'Insert a Address Node',\n              field: 'AddressTemplate',\n              value: {\n                  'street': \"\",\n                  'city': \"\",\n                  'state': \"\",\n                  'ZIP code': \"\"\n              }\n          }\n      ]\n  }\n  ```\n\n- `{Object} autocomplete`\n\n  *autocomplete* will enable this feature in your editor in tree mode, the object have the following **subelements**:\n\n  - `{string} filter`\n  - `{Function} filter`\n\n     Indicate the filter method of the autocomplete. Default to `start`.\n\n     - `start`         : Match your input from the start, e.g. `ap` match `apple` but `pl` does not.\n     - `contain`       : Contain your input or not, e.g. `pl` match `apple` too.\n     - Custom Function : Define custom filter rule with signature `function(token, match, config)`. The `match` parameter can be either a string or an object with `text` and `value` properties. Return `true` if the option matches the input token.\n\n  - `{string} trigger`\n\n     Indicate the way to trigger autocomplete menu. Default to `keydown`\n\n     - `keydown` : When you type something in the field or value, it will trigger autocomplete.\n     - `focus`   : When you focus in the field or value, it will trigger the autocomplete.\n\n  - `{number[]} confirmKeys`\n\n     Indicate the KeyCodes for trigger confirm completion, by default those keys are:  `[39, 35, 9]` which are the code for [right, end, tab]\n\n  - `{boolean} caseSensitive`\n\n     Indicate if the autocomplete is going to be strict case-sensitive to match the options.\n\n  - `{Function} getOptions (text: string, path: string[], input: string, editor: JSONEditor)`\n\n     This function will return your possible options for create the autocomplete selection, you can control dynamically which options you want to display according to the current active editing node.\n     \n     *Parameters:*\n\n     - `text`   : The text in the current node part. (basically the text that the user is editing)\n     - `path`   : The path of the node that is being edited as an array with strings.\n     - `input`  : Can be \"field\" or \"value\" depending if the user is editing a field name or a value of a node.\n     - `editor` : The editor instance object that is being edited.\n\n     *Returns:*\n\n     - Can return an array with autocomplete options. Options can be either strings (e.g. `['apple','cranberry','raspberry','pie']`) or objects with `text` and `value` properties (e.g. `[{text: 'Apple', value: 'apple'}, {text: 'Cranberry', value: 'cranberry'}]`)\n     - Can return `null` when there are no autocomplete options.\n     - Can return an object `{startFrom: number, options: (string|object)[]}`. Here `startFrom` determines the start character from where the existing text will be replaced. `startFrom` is `0` by default, replacing the whole text. Options can be strings or objects as described above.\n     - Can return a `Promise` resolving one of the return types above to support asynchronously retrieving a list with options.\n\n- `{boolean} mainMenuBar`\n\n  Adds main menu bar - Contains format, sort, transform, search etc. functionality. `true` by default. Applicable in all types of `mode`.\n\n- `{boolean} navigationBar`\n\n  Adds navigation bar to the menu - the navigation bar visualize the current position on the tree structure as well as allows breadcrumbs navigation. `true` by default. Only applicable when `mode` is 'tree', 'form' or 'view'.\n\n- `{boolean} statusBar`\n\n  Adds status bar to the bottom of the editor - the status bar shows the cursor position and a count of the selected characters. `true` by default. Only applicable when `mode` is 'code', 'text', or 'preview'.\n\n- `{boolean} | {Array} showErrorTable`\n\n  Automatically expand error table above the status bar on error or validation error if `mode` matches an array item. Alternatively used as a boolean value. Default value is `['text', 'preview']`.\n\n- `{function} onTextSelectionChange(start, end, text)`\n\n  Set a callback function triggered when a text is selected in the JSONEditor.\n\n  callback signature should be:\n  ```js\n  /**\n  * @param {{row:Number, column:Number}} start Selection start position\n  * @param {{row:Number, column:Number}} end   Selected end position\n  * @param {String} text selected text\n  */\n  function onTextSelectionChange(start, end, text) {\n    ...\n  }\n  ```\n  Only applicable when `mode` is 'code' or 'text'.\n\n- `{function} onSelectionChange(start, end)`\n\n  Set a callback function triggered when Nodes are selected in the JSONEditor.\n\n  callback signature should be:\n  ```js\n  /**\n  * @typedef {{value: String|Object|Number|Boolean, path: Array.<String|Number>}} SerializableNode\n  * \n  * @param {SerializableNode=} start\n  * @param {SerializableNode=} end\n  */\n  function onSelectionChange(start, end) {\n    ...\n  }\n  ```\n  Only applicable when `mode` is 'tree'.\n  \n- `{function} onEvent({ field, path, value? }, event)`\n\n  Set a callback function that will be triggered when an event will occur in \n  a JSON field or value.\n  \n  In case of field event, node information will be \n\n  ```\n  {\n    field: string,\n    path: {string|number}[]\n  }\n  ```\n\n  In case of value event, node information will be\n\n  ```\n  {\n    field: string,\n    path: {string|number}[],\n    value: string\n  }\n  ```\n\n  signature should be:\n  ```js\n  /**\n  * @param {Node} the Node where event has been triggered \n  identified by {field: string, path: {string|number}[] [, value: string]}`\n  * @param {event} the event fired\n  */\n  function onEvent(node, event) {\n    ...\n  }\n  ```\n  Only applicable when `mode` is 'form', 'tree' or 'view'.  \n\n- `{function} onFocus({ type: 'focus', target })`\n  Callback method, triggered when the editor comes into focus, \n  passing an object `{type, target}`, Applicable for all modes.\n\n- `{function} onBlur({ type: 'blur', target })`\n  Callback method, triggered when the editor goes out of focus, \n  passing an object `{type, target}`, Applicable for all modes.\n\n- `{boolean} colorPicker`\n\n  If `true` (default), values containing a color name or color code will have a color picker rendered on their left side.\n\n- `{function} onColorPicker(parent, color, onChange)`\n\n  Callback function triggered when the user clicks a color.\n  Can be used to implement a custom color picker.\n  The callback is invoked with three arguments:\n  `parent` is an HTML element where the color picker can be attached,\n  `color` is the current color,\n  `onChange(newColor)` is a callback which has to be invoked with the new color selected in the color picker.\n  JSONEditor comes with a built-in color picker, powered by [vanilla-picker](https://github.com/Sphinxxxx/vanilla-picker).\n\n  A simple example of `onColorPicker` using `vanilla-picker`:\n\n  ```js\n  var options = {\n    onColorPicker: function (parent, color, onChange) {\n      new VanillaPicker({\n        parent: parent,\n        color: color,\n        onDone: function (color) {\n          onChange(color.hex)\n        }\n      }).show();\n    }\n  }\n  ```\n\n- `{boolean | function({field, value, path}) -> boolean} timestampTag`\n\n  If `true` (default), a tag with the date/time of a timestamp is displayed\n  right from values containing a timestamp. By default, a value is \n  considered a timestamp when it is an integer number with a value larger \n  than Jan 1th 2000, `946684800000`.\n  \n  When `timestampTag` a is a function, a timestamp tag will be displayed when \n  this function returns `true`, and no timestamp is displayed when the function\n  returns `false`. When the function returns a non-boolean value like `null`\n  or `undefined`, JSONEditor will fallback on the built-in rules to determine\n  whether or not to show a timestamp.   \n  \n  The function is invoked with an object as first parameter:\n  \n  ```\n  {\n    field: string,\n    value: string,\n    path: string[]\n  }\n  ```\n\n  Whether a value is a timestamp can be determined implicitly based on \n  the `value`, or explicitly based on `field` or `path`. You can for example\n  test whether a field name contains a string like: `'date'` or `'time'`.\n\n  Example:\n  \n  ```js\n  var options = {\n    timestampTag: function ({ field, value, path }) {\n      if (field === 'dateCreated') {\n        return true\n      }\n\n      return false\n    }\n  }\n  ```\n  \n  Only applicable for modes `tree`, `form`, and `view`.\n\n- `{ function({field, value, path}) -> string|null } timestampFormat`\n\n  Customizing the way formating the timestamp. Called when a value is timestamp after `timestampTag`. If it returns null, the timestamp would be formatted with default setting (`new Date(value).toISOString()`).\n  \n  parameter:\n  \n  ```\n  {\n    field: string,\n    value: string,\n    path: string[]\n  }\n  ```\n\n  Example:\n  \n  ```js\n  var options = {\n    timestampFormat: function ({ field, value, path }) {\n      if (field === 'customTime') {\n        return new Date(value*1000).toString()\n      }\n\n      return null\n    }\n  }\n  ```\n  \n  Only applicable for modes `tree`, `form`, and `view`.\n\n- `{string} language`\n\n  The default language comes from the browser navigator, but you can force a specific language. So use here string as 'en' or 'pt-BR'. Built-in languages: `en`, `es` `zh-CN`, `pt-BR`, `tr`, `ja`, `fr-FR`, `de`, `ru`, `ko`. Other translations can be specified via the option `languages`.\n\n- `{Object} languages`\n\n  You can override existing translations or provide a new translation for a specific language. To do it provide an object at languages with language and the keys/values to be inserted. For example:\n\n  ```\n  'languages': {\n    'pt-BR': {\n      'auto': 'Automático testing'\n    },\n    'en': {\n      'auto': 'Auto testing'\n    }\n  }\n  ```\n\n  All available fields for translation can be found in the source file `src/js/i18n.js`.\n\n- `{HTMLElement} modalAnchor`\n\n  The container element where modals (like for sorting and filtering) are attached: an overlay will be created on top\n  of this container, and the modal will be created in the center of this container.\n\n- `{HTMLElement} popupAnchor`\n\n  The container element where popups (for example drop down menus, for JSON Schema error\n  tooltips, and color pickers) will be absolutely positioned. \n  By default, this is the root DIV element of the editor itself. \n  \n  When the JSONEditor is inside a DIV element which hides overflowing contents \n  (CSS `overflow: auto` or `overflow: hidden`), tooltips will be visible only partly. \n  In this case, a `popupAnchor` outside of the element without hidden overflow will allow \n  the tooltips to be visible when overflowing the DIV element of the JSONEditor.\n\n- `{boolean} enableSort`\n\n  Enable sorting of arrays and object properties. Only applicable for mode 'tree'. `true` by default.\n\n- `{boolean} enableTransform`\n\n  Enable filtering, sorting, and transforming JSON using a [JMESPath](http://jmespath.org/) query. Only applicable for mode 'tree'. `true` by default.\n\n- `{Number} maxVisibleChilds`\n\n  Number of children allowed for a given node before the \"show more / show all\" message appears (in 'tree', 'view', or 'form' modes). `100` by default.\n  \n- `{ function(json: JSON, queryOptions: QueryOptions) -> string } createQuery`\n\n  Create a query string based on query options filled in the Transform Wizard in the Transform modal. \n  Normally used in combination with `executeQuery`.\n  The input for the function are the entered query options and the current JSON, and the output\n  must be a string containing the query. This query will be executed using `executeQuery`.\n  \n  The query options have the following structure:\n  \n  ```\n  interface QueryOptions {\n    filter?: {\n      field: string | '@'\n      relation: '==' | '!=' | '<' | '<=' | '>' | '>='\n      value: string\n    }\n    sort?: {\n      field: string | '@'\n      direction: 'asc' | 'desc'\n    }\n    projection?: {\n      fields: string[]\n    }\n  }\n  ```\n  \n  Note that there is a special case `'@'` for `filter.field` and `sort.field`.\n  It means that the field itself is selected, for example when having an array containing numbers.\n  \n  A usage example can be found in `examples/23_custom_query_language.html`.\n  \n\n- `{ function(json: JSON, query: string) -> JSON } executeQuery`\n\n  Replace the build-in query language used in the Transform modal with a custom language.\n  Normally used in combination with `createQuery`.\n  The input for the function is the current JSON and a query string, and output must be the transformed JSON.\n\n  A usage example can be found in `examples/23_custom_query_language.html`.\n\n- `{string} queryDescription` \n\n  A text description displayed on top of the Transform modal. \n  Can be used to explain a custom query language implemented via `createQuery` and `executeQuery`.\n  The text can contain HTML code like a link to a web page.\n  \n  A usage example can be found in `examples/23_custom_query_language.html`.\n\n\n### Methods\n\n#### `JSONEditor.collapseAll()`\n\nCollapse all fields. Only applicable for mode 'tree', 'view', and 'form'.\n\n#### `JSONEditor.destroy()`\n\nDestroy the editor. Clean up DOM, event listeners, and web workers.\n\n#### `JSONEditor.expandAll()`\n\nExpand all fields. Only applicable for mode 'tree', 'view', and 'form'.\n\n#### `JSONEditor.expand(options)`\n\nExpand/collapse a given JSON node. Only applicable for mode 'tree', 'view' and 'form'.\n\n*`options` fields:*\n\n- `{Array.<String>} path`\n\n  Path for the node to expand/collapse. Required.\n\n- `{Boolean} isExpand`\n\n  When true, expand the node. Else collapse it. Required.\n\n- `{Boolean} recursive`\n\n  When true, expand/collapse child nodes recursively. Optional.\n\n- `{Boolean} withPath`\n\n  When true, expand/collapse all nodes of `path` itself. Optional.\n\n#### `JSONEditor.focus()`\n\nSet focus to the JSONEditor.\n\n#### `JSONEditor.get()`\n\nGet JSON data.\n\nThis method throws an exception when the editor does not contain valid JSON,\nwhich can be the case when the editor is in mode `code`, `text`, or `preview`.\n\n*Returns:*\n\n- `{JSON} json`\n\n  JSON data from the JSONEditor.\n\n#### `JSONEditor.getMode()`\n\nRetrieve the current mode of the editor.\n\n*Returns:*\n\n- `{String} mode`\n\n  Current mode of the editor, for example `tree` or `code`.\n\n#### `JSONEditor.getName()`\n\nRetrieve the current field name of the root node.\n\n*Returns:*\n\n- `{String | undefined} name`\n\n  Current field name of the root node, or undefined if not set.\n\n#### `JSONEditor.getNodesByRange(start, end)`\n\nA utility function for getting a list of `SerializableNode` under certain range.\n\nThis function can be used as complementary to `getSelection` and `onSelectionChange` if a list of __all__ the selected nodes is required.\n\n*Parameters:*\n\n- `{path: Array.<String>} start`\n\n  Path for the first node in range\n\n- `{path: Array.<String>} end`\n\n  Path for the last node in range\n\n#### `JSONEditor.getSelection()`\n\nGet the current selected nodes, Only applicable for mode 'tree'.\n\n*Returns:*\n\n- `{start:SerializableNode, end: SerializableNode}`\n\n#### `JSONEditor.getText()`\n\nGet JSON data as string.\n\n*Returns:*\n\n- `{String} jsonString`\n\n  Contents of the editor as string. When the editor is in code `text`, `code` or `preview`,\n  the returned text is returned as-is. For the other modes, the returned text\n  is a compacted string. In order to get the JSON formatted with a certain\n  number of spaces, use `JSON.stringify(JSONEditor.get(), null, 2)`.\n\n#### `JSONEditor.getTextSelection()`\n\nGet the current selected text with the selection range, Only applicable for mode 'text' and 'code'.\n\n*Returns:*\n\n- `{start:{row:Number, column:Number},end:{row:Number, column:Number},text:String} selection`\n\n\n#### `JSONEditor.refresh()`\n\nForce the editor to refresh the user interface and update all rendered HTML. This can be useful for example when using `onClassName` and the returned class name depends on external factors.\n\n\n#### `JSONEditor.set(json)`\n\nSet JSON data.\nResets the state of the editor (expanded nodes, search, selection).\nSee also `JSONEditor.update(json)`.\n\n*Parameters:*\n\n- `{JSON} json`\n\n  JSON data to be displayed in the JSONEditor.\n\n#### `JSONEditor.setMode(mode)`\n\nSwitch mode. Mode `code` requires the [Ace editor](https://ace.c9.io/).\n\n*Parameters:*\n\n- `{String} mode`\n\n  Available values: `tree`, `view`, `form`, `code`, `text`, `preview`.\n\n#### `JSONEditor.setName(name)`\n\nSet a field name for the root node.\n\n*Parameters:*\n\n- `{String | undefined} name`\n\n  Field name of the root node. If undefined, the current name will be removed.\n\n#### `JSONEditor.setSchema(schema [,schemaRefs])`\n\nSet a JSON schema for validation of the JSON object. See also option `schema`.\nSee [http://json-schema.org/](http://json-schema.org/) for more information on the JSON schema definition.\n\n*Parameters:*\n\n- `{Object} schema`\n\n  A JSON schema.\n\n- `{Object} schemaRefs`\n\n  Optional, Schemas that are referenced using the `$ref` property from the JSON schema, the object structure in the form of `{reference_key: schemaObject}`\n\n#### `JSONEditor.setSelection(start, end)`\n\nSet selection for a range of nodes, Only applicable for mode 'tree'.\n\n- If no parameters sent - the current selection will be removed, if exists.\n- For single node selecion send only the `start` parameter.\n- If the nodes are not from the same level the first common parent will be selected\n\n*Parameters:*\n\n- `{path: Array.<String>} start`\n\n  Path for the start node\n\n- `{path: Array.<String>} end`\n\n  Path for the end node\n\n#### `JSONEditor.setText(jsonString)`\n\nSet text data in the editor.\n\nThis method throws an exception when the provided jsonString does not contain\nvalid JSON and the editor is in mode `tree`, `view`, or `form`.\n\n*Parameters:*\n\n- `{String} jsonString`\n\n  Contents of the editor as string.\n\n#### `JSONEditor.setTextSelection(startPos, endPos)`\n\nSet text selection for a range, Only applicable for mode 'text' and 'code'.\n\n*Parameters:*\n\n- `{row:Number, column:Number} startPos`\n\n  Position for selection start\n\n- `{row:Number, column:Number} endPos`\n\n  Position for selection end\n\n#### `JSONEditor.update(json)`\n\nReplace JSON data when the new data contains changes.\nIn modes `tree`, `form`, and `view`, the state of the editor will be maintained (expanded nodes, search, selection).\nSee also `JSONEditor.set(json)`.\n\n*Parameters:*\n\n- `{JSON} json`\n\n  JSON data to be displayed in the JSONEditor.\n\n#### `JSONEditor.updateText (json)`\n\nReplace text data when the new data contains changes.\nIn modes `tree`, `form`, and `view`, the state of the editor will be maintained (expanded nodes, search, selection).\nAlso see `JSONEditor.setText(jsonString)`.\n\nThis method throws an exception when the provided jsonString does not contain\nvalid JSON and the editor is in mode `tree`, `view`, or `form`.\n\n*Parameters:*\n\n- `{String} jsonString`\n\n  Contents of the editor as string.\n\n#### `JSONEditor.validate()`\n\nValidate the JSON document against the configured JSON schema or custom validator.\nSee also the `onValidationError` callback.\n\n*Returns:*\n\n- `{Promise<ValidationError[]>} errorsPromise`\n\n  Returns a promise which resolves with the current validation errors, \n  or an empty list when there are no errors. The `ValidationError` contains\n  a `type`, `path`, and `message`.\n\n\n### Static properties\n\n- `{string[]} JSONEditor.VALID_OPTIONS`\n\n  An array with the names of all known options.\n\n- `{object} ace`\n\n  Access to the bundled Ace editor, via the [`brace` library](https://github.com/thlorenz/brace).\n  Ace is used in code mode.\n  Same as `var ace = require('brace');`.\n\n- `{function} Ajv`\n\n  Access to the bundled [`ajv` library](https://github.com/epoberezkin/ajv), used for JSON schema validation.\n  Same as `var Ajv = require('ajv');`.\n\n- `{function} VanillaPicker`\n\n  Access to the bundled [`vanilla-picker` library](https://github.com/Sphinxxxx/vanilla-picker), used as color picker.\n  Same as `var VanillaPicker = require('vanilla-picker');`.\n\n\n### Examples\n\nA tree editor:\n\n```js\nvar options = {\n    \"mode\": \"tree\",\n    \"search\": true\n};\nvar editor = new JSONEditor(container, options);\nvar json = {\n    \"Array\": [1, 2, 3],\n    \"Boolean\": true,\n    \"Null\": null,\n    \"Number\": 123,\n    \"Object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"String\": \"Hello World\"\n};\neditor.set(json);\neditor.expandAll();\n\nvar json = editor.get(json);\n```\n\nA text editor:\n\n```js\nvar options = {\n    \"mode\": \"text\",\n    \"indentation\": 2\n};\nvar editor = new JSONEditor(container, options);\nvar json = {\n    \"Array\": [1, 2, 3],\n    \"Boolean\": true,\n    \"Null\": null,\n    \"Number\": 123,\n    \"Object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"String\": \"Hello World\"\n};\neditor.set(json);\n\nvar json = editor.get();\n```\n\n## JSON parsing and stringification\n\nIn general, to parse or stringify JSON data, the browsers built in JSON parser can be used. To create a formatted string from a JSON object, use:\n\n```js\nvar formattedString = JSON.stringify(json, null, 2);\n```\n\nto create a compacted string from a JSON object, use:\n\n```js\nvar compactString = JSON.stringify(json);\n```\n\nTo parse a String to a JSON object, use:\n\n```js\nvar json = JSON.parse(string);\n```\n"
  },
  {
    "path": "docs/shortcut_keys.md",
    "content": "# Shortcut keys\n\n## Tree Editor\n\nKey                     | Description\n----------------------- | ------------------------------------------------\nAlt+Arrows              | Move the caret up/down/left/right between fields\nCtrl+Shift+Arrow Up/Down| Select multiple fields\nShift+Alt+Arrows        | Move current field or selected fields up/down/left/right\nCtrl+D                  | Duplicate field\nCtrl+Del                | Remove field\nCtrl+Enter              | Open link when on a field containing an url\nCtrl+Ins                | Insert a new field with type auto\nCtrl+Shift+Ins          | Append a new field with type auto\nCtrl+E                  | Expand or collapse field\nAlt+End                 | Move the caret to the last field\nCtrl+F                  | Find\nF3, Ctrl+G              | Find next\nShift+F3, Ctrl+Shift+G  | Find previous\nAlt+Home                | Move the caret to the first field\nCtrl+M                  | Show actions menu\nCtrl+Z                  | Undo last action\nCtrl+Shift+Z            | Redo\n\n\n## Code Editor\n\nThe code editor is powered by [Ace Editor](http://ace.c9.io/). This editor's\nshortcut keys are described here:\n\nhttps://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts\n\nAdditionally, there are shortcut keys to format/compact the code:\n\nKey                     | Description\n----------------------- | ------------------------------------------------\nCtrl+I                  | Format JSON data, set proper indentation\nCtrl+Shift+I            | Compact JSON data, remove all whitespace\n"
  },
  {
    "path": "docs/styling.md",
    "content": "# Styling Reference\n\nDocumentation for writing custom JSON Editor styles.\n\n## Node\nNode is the fundamental unit that makes up the hierarchical JSON display in the Form, Tree, and View modes. It can be\ncustomized with several classes that reflect its type and state.\n\n- `jsoneditor-field`: the property name\n- `jsoneditor-value`: the value of the property\n  - The value element will have one of the following classes depending on its type:\n    - `jsoneditor-null`\n    - `jsoneditor-undefined`\n    - `jsoneditor-number`\n    - `jsoneditor-string`\n    - `jsoneditor-string jsoneditor-color-value`\n    - `jsoneditor-boolean`\n    - `jsoneditor-regexp`\n    - `jsoneditor-array`\n    - `jsoneditor-object`\n    - `jsoneditor-url`\n  - `jsoneditor-is-default`: applied to the value element when the value matches the default from the schema\n  - `jsoneditor-is-not-default`: applied to the value element when the value does not match the default from the schema\n- `jsoneditor-schema-error`: the warning icon that appears when the Node has a schema validation error\n  - `jsoneditor-popover`: the popover that appears when hovering over the schema validation error warning icon\n"
  },
  {
    "path": "docs/usage.md",
    "content": "# Usage\n\n### Install\n\nInstall via npm:\n\n    npm install jsoneditor\n\nAlternatively, you can use another JavaScript package manager like https://yarnpkg.com/, or a CDN such as https://cdnjs.com/ or https://www.jsdelivr.com/.\n\n## Load\n\nTo implement JSONEditor in a web application, load the javascript and css file\nin the head of the HTML page:\n\n```html\n<link href=\"jsoneditor/dist/jsoneditor.min.css\" rel=\"stylesheet\" type=\"text/css\">\n<script src=\"jsoneditor/dist/jsoneditor.min.js\"></script>\n```\n\nHere you'll have to change the urls `jsoneditor/dist/jsoneditor.min.js` and `jsoneditor/dist/jsoneditor.min.css` to match the place where you've downloaded the library, or fill in the URL of the CDN you're using.\n\n## Use\n\nIn the body, create a div element with an id and a size:\n\n```html\n<div id=\"jsoneditor\" style=\"width: 400px; height: 400px;\"></div>\n```\n\nAfter the page is loaded, load the editor with javascript:\n\n```js\nvar container = document.getElementById(\"jsoneditor\");\nvar options = {\n    mode: 'tree'\n};\nvar editor = new JSONEditor(container, options);\n```\n\nTo set JSON data in the editor:\n\n```js\nvar json = {\n    \"Array\": [1, 2, 3],\n    \"Boolean\": true,\n    \"Null\": null,\n    \"Number\": 123,\n    \"Object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"String\": \"Hello World\"\n};\neditor.set(json);\n```\n\nTo get JSON data from the editor:\n\n```js\nvar json = editor.get();\n```\n\n\n## Full Example\n\n```html\n<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n    <!-- when using the mode \"code\", it's important to specify charset utf-8 -->\n    <meta charset=\"utf-8\">\n\n    <link href=\"jsoneditor/dist/jsoneditor.min.css\" rel=\"stylesheet\" type=\"text/css\">\n    <script src=\"jsoneditor/dist/jsoneditor.min.js\"></script>\n</head>\n<body>\n<p>\n    <button onclick=\"setJSON();\">Set JSON</button>\n    <button onclick=\"getJSON();\">Get JSON</button>\n</p>\n<div id=\"jsoneditor\" style=\"width: 400px; height: 400px;\"></div>\n\n<script>\n    // create the editor\n    var container = document.getElementById(\"jsoneditor\");\n    var editor = new JSONEditor(container);\n\n    // set json\n    function setJSON () {\n        var json = {\n            \"Array\": [1, 2, 3],\n            \"Boolean\": true,\n            \"Null\": null,\n            \"Number\": 123,\n            \"Object\": {\"a\": \"b\", \"c\": \"d\"},\n            \"String\": \"Hello World\"\n        };\n        editor.set(json);\n    }\n\n    // get json\n    function getJSON() {\n        var json = editor.get();\n        alert(JSON.stringify(json, null, 2));\n    }\n</script>\n</body>\n</html>\n```\n\nFor more examples, see the\n[examples section](https://github.com/josdejong/jsoneditor/tree/master/examples).\n"
  },
  {
    "path": "examples/01_basic_usage.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Basic usage</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n<p>\n  <button id=\"setJSON\">Set JSON</button>\n  <button id=\"getJSON\">Get JSON</button>\n</p>\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const options = {}\n  const editor = new JSONEditor(container, options)\n\n  // set json\n  document.getElementById('setJSON').onclick = function () {\n    const json = {\n      'array': [1, 2, 3],\n      'boolean': true,\n      'color': '#82b92c',\n      'null': null,\n      'number': 123,\n      'object': {'a': 'b', 'c': 'd'},\n      'time': 1575599819000,\n      'string': 'Hello World',\n      'onlineDemo': 'https://jsoneditoronline.org/'\n    }\n    editor.set(json)\n  }\n\n  // get json\n  document.getElementById('getJSON').onclick = function () {\n    const json = editor.get()\n    alert(JSON.stringify(json, null, 2))\n  }\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/02_viewer.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Viewer</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n\n  <style type=\"text/css\">\n    body {\n      font: 11pt arial;\n    }\n    #jsoneditor {\n      width: 500px;\n    }\n  </style>\n</head>\n<body>\n<p>\n  This editor is read-only (mode='viewer').\n</p>\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const container = document.getElementById('jsoneditor')\n\n  const options = {\n    mode: 'view'\n  }\n\n  const json = {\n    'array': [1, 2, 3],\n    'boolean': true,\n    'null': null,\n    'number': 123,\n    'object': {'a': 'b', 'c': 'd'},\n    'string': 'Hello World'\n  }\n\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/03_switch_mode.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <!-- when using the mode \"code\", it's important to specify charset utf-8 -->\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Switch mode</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const container = document.getElementById('jsoneditor')\n\n  const options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onModeChange: function (newMode, oldMode) {\n      console.log('Mode switched from', oldMode, 'to', newMode)\n    }\n  }\n\n  const json = {\n    \"array\": [1, 2, 3],\n    \"boolean\": true,\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\"\n  }\n\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/04_load_and_save.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Load and save</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <script src=\"https://bgrins.github.io/filereader.js/filereader.js\"></script>\n  <script src=\"https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js\"></script>\n\n  <style>\n    html, body {\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n<h1>Load and save JSON documents</h1>\n<p>\n  This examples uses HTML5 to load/save local files.\n  Powered by <a href=\"http://bgrins.github.io/filereader.js/\">FileReader.js</a> and\n  <a href=\"https://github.com/eligrey/FileSaver.js\">FileSaver.js</a>.<br>\n  Only supported on modern browsers (Chrome, FireFox, IE10+, Safari 6.1+, Opera 15+).\n</p>\n<p>\n  Load a JSON document: <input type=\"file\" id=\"loadDocument\" value=\"Load\"/>\n</p>\n<p>\n  Save a JSON document: <input type=\"button\" id=\"saveDocument\" value=\"Save\" />\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  const editor = new JSONEditor(document.getElementById('jsoneditor'))\n\n  // Load a JSON document\n  FileReaderJS.setupInput(document.getElementById('loadDocument'), {\n    readAsDefault: 'Text',\n    on: {\n      load: function (event, file) {\n        editor.setText(event.target.result)\n      }\n    }\n  })\n\n  // Save a JSON document\n  document.getElementById('saveDocument').onclick = function () {\n    // Save Dialog\n    let fname = window.prompt(\"Save as...\")\n\n    // Check json extension in file name\n    if (fname.indexOf(\".\") === -1) {\n      fname = fname + \".json\"\n    } else {\n      if (fname.split('.').pop().toLowerCase() === \"json\") {\n        // Nothing to do\n      } else {\n        fname = fname.split('.')[0] + \".json\"\n      }\n    }\n    const blob = new Blob([editor.getText()], {type: 'application/json;charset=utf-8'})\n    saveAs(blob, fname)\n  }\n</script>\n</body>\n</html>\n\n\n"
  },
  {
    "path": "examples/05_custom_fields_editable.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Custom editable fields</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n    }\n  </style>\n</head>\n<body>\n  <p>\n    In this example:\n  </p>\n  <ul>\n    <li>the field <code>_id</code> and its value are read-only</li>\n    <li>the field <code>name</code> is read-only but has an editable value</li>\n    <li>the field <code>age</code> and its value are editable</li>\n  </ul>\n\n  <div id=\"jsoneditor\"></div>\n\n  <script>\n    const container = document.getElementById('jsoneditor')\n\n    const options = {\n      onEditable: function (node) {\n        // node is an object like:\n        //   {\n        //     field: 'FIELD',\n        //     value: 'VALUE',\n        //     path: ['PATH', 'TO', 'NODE']\n        //   }\n        switch (node.field) {\n          case '_id':\n            return false\n\n          case 'name':\n            return {\n              field: false,\n              value: true\n            }\n\n          default:\n            return true\n        }\n      }\n    }\n\n    const json = {\n      _id: 123456,\n      name: 'John',\n      age: 32\n    }\n\n    const editor = new JSONEditor(container, options, json)\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/06_custom_styling.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n\n    <title>JSONEditor | Custom styling</title>\n\n    <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n    <script src=\"../dist/jsoneditor.js\"></script>\n\n    <style type=\"text/css\">\n        #jsoneditor {\n            width: 500px;\n            height: 500px;\n        }\n\n        p {\n            width: 500px;\n            font-family: \"DejaVu Sans\", sans-serif;\n        }\n    </style>\n\n    <link href=\"./css/darktheme.css\" rel=\"stylesheet\" type=\"text/css\">\n\n</head>\n<body>\n<p>\n    This example demonstrates how to customize the look of JSONEditor,\n    the editor below has a dark theme. Note that the example isn't worked\n    out for the mode <code>code</code>. To do that, you can load and configure\n    a custom theme for the Ace editor.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const options = {\n    modes: ['tree', 'text']\n  }\n  const json = {\n    'array': [1, 2, 3],\n    'boolean': true,\n    'null': null,\n    'number': 123,\n    'object': {'a': 'b', 'c': 'd'},\n    'string': 'Hello World'\n  }\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/07_json_schema_validation.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | JSON schema validation</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      width: 600px;\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 100%;\n      height: 500px;\n    }\n\n    /* custom bold styling for non-default JSON schema values */\n    .jsoneditor-is-not-default {\n      font-weight: bold;\n    }\n  </style>\n</head>\n<body>\n<h1>JSON schema validation</h1>\n<p>\n  This example demonstrates JSON schema validation. The JSON object in this example must contain properties like <code>firstName</code> and <code>lastName</code>, can can optionally have a property <code>age</code> which must be a positive integer.\n</p>\n<p>\n  See <a href=\"http://json-schema.org/\" target=\"_blank\">http://json-schema.org/</a> for more information.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const schema = {\n    \"title\": \"Employee\",\n    \"description\": \"Object containing employee details\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"firstName\": {\n        \"title\": \"First Name\",\n        \"description\": \"The given name.\",\n        \"examples\": [\n          \"John\"\n        ],\n        \"type\": \"string\"\n      },\n      \"lastName\": {\n        \"title\": \"Last Name\",\n        \"description\": \"The family name.\",\n        \"examples\": [\n          \"Smith\"\n        ],\n        \"type\": \"string\"\n      },\n      \"gender\": {\n        \"title\": \"Gender\",\n        \"enum\": [\"male\", \"female\"]\n      },\n      \"availableToHire\": {\n        \"type\": \"boolean\",\n        \"default\": false\n      },\n      \"age\": {\n        \"description\": \"Age in years\",\n        \"type\": \"integer\",\n        \"minimum\": 0,\n        \"examples\": [28, 32]\n      },\n      \"job\": {\n        \"$ref\": \"job\"\n      }\n    },\n    \"required\": [\"firstName\", \"lastName\"]\n  }\n\n  const job = {\n    \"title\": \"Job description\",\n    \"type\": \"object\",\n    \"required\": [\"address\"],\n    \"properties\": {\n      \"company\": {\n        \"type\": \"string\",\n        \"examples\": [\n          \"ACME\",\n          \"Dexter Industries\"\n        ]\n      },\n      \"role\": {\n        \"description\": \"Job title.\",\n        \"type\": \"string\",\n        \"examples\": [\n          \"Human Resources Coordinator\",\n          \"Software Developer\"\n        ],\n        \"default\": \"Software Developer\"\n      },\n      \"address\": {\n        \"type\": \"string\"\n      },\n      \"salary\": {\n        \"type\": \"number\",\n        \"minimum\": 120,\n        \"examples\": [100, 110, 120]\n      }\n    }\n  }\n\n  const json = {\n    firstName: 'John',\n    lastName: 'Doe',\n    gender: null,\n    age: \"28\",\n    availableToHire: true,\n    job: {\n      company: 'freelance',\n      role: 'developer',\n      salary: 100\n    }\n  }\n\n  const options = {\n    schema: schema,\n    schemaRefs: {\"job\": job},\n    mode: 'tree',\n    modes: ['code', 'text', 'tree', 'preview']\n  }\n\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/08_custom_ace.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Custom Ace</title>\n\n  <!-- load a custom version of Ace editor -->\n  <script src=\"https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js\"></script>\n\n  <!-- load the minimalist version of JSONEditor, which doesn't have Ace embedded -->\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor-minimalist.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n\n    body, html {\n      font-family: \"DejaVu Sans\", sans-serif;\n    }\n\n    p, li {\n      width: 500px;\n      font-size: 10.5pt;\n    }\n\n    code {\n      background: #f5f5f5;\n    }\n  </style>\n\n</head>\n<body>\n<h1>Custom Ace editor</h1>\n<p>\n  This example demonstrates how to load a custom version of Ace editor into JSONEditor.\n</p>\n<p>\n  By default, JSONEditor <code>code</code> mode loads the following Ace plugins:\n</p>\n<ul>\n  <li>ace/mode/json</li>\n  <li>ace/ext/searchbox</li>\n  <li>ace/theme/jsoneditor</li>\n</ul>\n<p>\n  The jsoneditor theme comes embedded with JSONEditor. The other two plugins (json and searchbox) must be available in the folder of the custom Ace editor, or already be loaded via a script tag.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const options = {\n    modes: ['text', 'code', 'tree', 'form', 'view'],\n    mode: 'code',\n    ace: ace\n  }\n  const json = {\n    'array': [1, 2, 3],\n    'boolean': true,\n    'null': null,\n    'number': 123,\n    'object': {'a': 'b', 'c': 'd'},\n    'string': 'Hello World'\n  }\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/09_readonly_text_mode.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <!-- when using the mode \"code\", it's important to specify charset utf-8 -->\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Switch mode</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const container = document.getElementById('jsoneditor')\n\n  const options = {\n    mode: 'text',\n    modes: ['text', 'code'],\n    onEditable: function (node) {\n      if (!node.path) {\n        // In modes code and text, node is empty: no path, field, or value\n        // returning false makes the text area read-only\n        return false;\n      }\n    },\n    onModeChange: function (newMode, oldMode) {\n      console.log('Mode switched from', oldMode, 'to', newMode)\n    }\n  }\n\n  const json = {\n    \"array\": [1, 2, 3],\n    \"boolean\": true,\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\"\n  }\n\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/10_templates.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Item templates</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      width: 600px;\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 100%;\n      height: 500px;\n    }\n\n  </style>\n</head>\n<body>\n<h1>Item templates</h1>\n<p>\n  Using item templates, the options in the context menu under \"insert\" and \"append\" can be extended with extra options, containing a domain specific template like a \"Person\", \"Contact\", \"Order\", \"Address\", etc.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const json = [\n    {\n      firstName: 'John',\n      lastName: 'Doe',\n      age: 28\n    }\n  ]\n\n  const options = {\n    templates: [\n      {\n        text: 'Person',\n        title: 'Insert a Person Node',\n        className: 'jsoneditor-type-object',\n        field: 'PersonTemplate',\n        value: {\n          'firstName': 'John',\n          'lastName': 'Do',\n          'age': 28\n        }\n      },\n      {\n        text: 'Address',\n        title: 'Insert a Address Node',\n        field: 'AddressTemplate',\n        value: {\n          'street': '',\n          'city': '',\n          'state': '',\n          'ZIP code': ''\n        }\n      }\n    ]\n  }\n\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const editor = new JSONEditor(container, options, json)\n  editor.expandAll()\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/11_autocomplete_basic.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Auto Complete</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n\n    p {\n      width: 500px;\n      font-family: \"DejaVu Sans\", sans-serif;\n    }\n  </style>\n\n</head>\n<body>\n<p>\n  This example demonstrates how to autocomplete works, options available are: 'apple','cranberry','raspberry','pie', 'mango', 'mandarine', 'melon', 'appleton'.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const options = {\n    autocomplete: {\n      getOptions: function () {\n        return ['apple', 'cranberry', 'raspberry', 'pie', 'mango', 'mandarine', 'melon', 'appleton'];\n      }\n    }\n  }\n  const json = {\n    'array': [{'field1':'v1', 'field2':'v2'}, 2, 3],\n    'boolean': true,\n    'null': null,\n    'number': 123,\n    'object': {'a': 'b', 'c': 'd'},\n    'string': 'Hello World'\n  }\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/12_autocomplete_dynamic.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Dynamic Auto Complete</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n  <script src=\"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n\n    p {\n      width: 500px;\n      font-family: \"DejaVu Sans\", sans-serif;\n    }\n  </style>\n\n</head>\n<body>\n<p>\n  This example demonstrates how to autocomplete works, options available are dynamics and consist in all the strings found in the json\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const options = {\n    autocomplete: {\n      applyTo:['value'],\n      filter: 'contain',\n      trigger: 'focus',\n      getOptions: function (text, path, input, editor) {\n        return new Promise(function (resolve, reject) {\n          const options = extractUniqueWords(editor.get())\n          if (options.length > 0) {\n            resolve(options)\n          } else {\n            reject()\n          }\n        })\n      }\n    }\n  }\n\n  // helper function to extract all unique words in the keys and values of a JSON object\n  function extractUniqueWords (json) {\n    return _.uniq(_.flatMapDeep(json, function (value, key) {\n      return _.isObject(value)\n              ? [key]\n              : [key, String(value)]\n    }))\n  }\n\n  const json = {\n    'array': [{'field1':'v1', 'field2':'v2'}, 2, 3],\n    'boolean': true,\n    'null': null,\n    'number': 123,\n    'object': {'a': 'b', 'c': 'd'},\n    'string': 'Hello World'\n  }\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/13_autocomplete_advanced.html",
    "content": "﻿<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Advanced Auto Complete</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n  <script src=\"https://unpkg.com/jsonpath@0.2.11/jsonpath.min.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n\n    p {\n      width: 500px;\n      font-family: \"DejaVu Sans\", sans-serif;\n    }\n  </style>\n\n</head>\n<body>\n<p>\n  This example demonstrates how to autocomplete works with an ActivationChar option, press \"*\" in any value and continue with autocompletion.\n  The autocomplete returns the posible jsonpaths of the existing json document, for example <code>*object.a</code>.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const activationChar = '*'\n  const options = {\n    autocomplete: {\n      confirmKeys: [39, 35, 9, 190], // Confirm Autocomplete Keys: [right, end, tab, '.']  // By default are only [right, end, tab]\n      caseSensitive: false,\n\n      getOptions: function (text, path, input, editor) {\n        if (!text.startsWith(activationChar) || input !== 'value') return []\n        let data = {}\n        let startFrom = 0\n        const lastPoint = text.lastIndexOf('.')\n        const jsonObj = editor.get()\n        if ((lastPoint > 0) && (text.length > 1)) {\n          data = jsonpath.query(jsonObj, '$.' + text.substring(activationChar.length, lastPoint))\n          if (data.length > 0) {\n            data = data[0]\n          } else {\n            data = {}\n          }\n          // Indicate that autocompletion should start after the . (ignoring the first part)\n          startFrom = text.lastIndexOf('.') + 1\n        } else {\n          data = jsonObj\n        }\n\n        const optionsStr = YaskON.stringify(data, null, activationChar)\n        const options = optionsStr.split('\\n')\n        return { startFrom: startFrom, options: options }\n      }\n    }\n  }\n\n  // helper function to auto complete paths of a JSON object\n  const YaskON = {\n    // Return first level json paths by the node 'o'\n    stringify: function (o, prefix, activationChar) {\n      prefix = prefix || ''\n      switch (typeof o) {\n        case 'object':\n          let output = ''\n          if (Array.isArray(o)) {\n            o.forEach(function (e, index) {\n              output += activationChar + prefix + '[' + index + ']' + '\\n'\n            }.bind(this))\n            return output\n          }\n          output = ''\n          for (let k in o) {\n            if (o.hasOwnProperty(k)) {\n              if (prefix === '') output += this.stringify(o[k], k, activationChar)\n            }\n          }\n          if (prefix !== '') output += activationChar + prefix + '\\n'\n          return output\n        case 'function':\n          return ''\n        default:\n          return prefix + '\\n'\n      }\n    }\n  }\n\n  const json = {\n    'array': [{ 'field1': 'v1', 'field2': 'v2' }, 2, 3],\n    'boolean': true,\n    'null': null,\n    'number': 123,\n    'object': { 'a': 'b', 'c': 'd' },\n    'string': 'Hello World'\n  }\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/14_translate.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <title>JSONEditor | Translate</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n\n<body>\n<p>\n  JSONEditor has support for multiple languages (i18n), in this case uses <code>pt-BR</code>.\n</p>\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const options = {\n    // switch between pt-BR or en for testing forcing a language\n    // leave blank to get language\n    'language': 'pt-BR',\n    'languages': {\n      'pt-BR': {\n        'auto': 'Automático testing'\n      },\n      'en': {\n        'auto': 'Auto testing'\n      },\n      'newlang': {\n        'auto': 'Auto new lang'\n      }\n    }\n  }\n  const editor = new JSONEditor(container, options)\n\n  const json = {\n    'array': [1, 2, 3],\n    'boolean': true,\n    'null': null,\n    'number': 123,\n    'object': { 'a': 'b', 'c': 'd' },\n    'string': 'Hello World'\n  }\n  editor.set(json)\n</script>\n</body>\n\n</html>"
  },
  {
    "path": "examples/15_selection_api.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n      padding-left: 40px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    code.multiline {\n      display: block;\n      white-space: pre-wrap;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Selection indication was done using the <code>on[Text]SelectionChange</code> listeners.<br/>\n  you can try the following calls in the console of your browser:<br/>\n  <code class=\"multiline\">\n    // text and code modes:\n    editor.getTextSelection()\n    editor.setTextSelection(startPos, endPos)\n    // tree mode:\n    editor.getSelection()\n    editor.setSelection(startNode, endNode)\n  </code>\n</p>\n\n<form>\n  <div id=\"jsoneditor\"></div>\n  <div id=\"textModeSelection\" style=\"display:none;\">\n    <b>Selection:</b><div id=\"textRange\"></div>\n    <b>Text:</b><div id=\"selectedText\"></div>\n  </div>\n  <div id=\"treeModeSelection\">\n    <b>Selection:</b>\n    <div id=\"selectedNodes\"></div>\n  </div>\n</form>\n\n<script>\n  const container = document.getElementById('jsoneditor')\n\n  const options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onChange: function () {\n      console.log('change')\n    },\n    onModeChange: function (mode) {\n      const treeMode = document.getElementById('treeModeSelection')\n      const textMode = document.getElementById('textModeSelection')\n\n      treeMode.style.display = textMode.style.display = 'none'\n\n      if (mode === 'code' || mode === 'text') {\n        textMode.style.display = 'inline'\n      } else {\n        treeMode.style.display = 'inline'\n      }\n    },\n    indentation: 4,\n    escapeUnicode: true,\n    onTextSelectionChange: function(start, end, text) {\n      const rangeEl = document.getElementById('textRange')\n      rangeEl.innerHTML = 'start: ' + JSON.stringify(start) + ', end: ' + JSON.stringify(end)\n      const textEl = document.getElementById('selectedText')\n      textEl.innerHTML = text\n    },\n    onSelectionChange: function(start, end) {\n      const nodesEl = document.getElementById('selectedNodes')\n      nodesEl.innerHTML = ''\n      if (start) {\n        nodesEl.innerHTML = ('start: '  + JSON.stringify(start))\n        if (end) {\n          nodesEl.innerHTML += ('<br/>end: '  + JSON.stringify(end))\n        }\n      }\n    }\n  }\n\n  const json = {\n    \"array\": [1, 2, [3,4,5]],\n    \"boolean\": true,\n    \"htmlcode\": '&quot;',\n    \"escaped_unicode\": '\\\\u20b9',\n    \"unicode\": '\\u20b9,\\uD83D\\uDCA9',\n    \"return\": '\\n',\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\",\n    \"url\": \"http://jsoneditoronline.org\"\n  }\n\n  window.editor = new JSONEditor(container, options, json)\n\n  console.log('json', json)\n  console.log('string', JSON.stringify(json))\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/16_synchronize_editors.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Synchronize two editors</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font-family: sans-serif;\n    }\n\n    #jsoneditor1,\n    #jsoneditor2 {\n      width: 500px;\n      height: 500px;\n      margin-right: 10px;\n      display: inline-block;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Keep two editors synchronized using <code>onChangeText</code> and <code>updateText</code>.\n</p>\n<p>\n  This can be done too with <code>onChangeJSON</code> and <code>update</code>, which can only be used in\n  modes <code>tree</code>, <code>form</code> (and <code>view</code>).\n</p>\n\n<div id=\"jsoneditor1\"></div>\n<div id=\"jsoneditor2\"></div>\n\n<script>\n  // create editor 1\n  const  editor1 = new JSONEditor(document.getElementById('jsoneditor1'), {\n    onChangeText: function (jsonString) {\n      editor2.updateText(jsonString)\n    }\n  })\n\n  // create editor 2\n  const  editor2 = new JSONEditor(document.getElementById('jsoneditor2'), {\n    onChangeText: function (jsonString) {\n      editor1.updateText(jsonString)\n    }\n  })\n\n  // set initial data in both editors\n  const  json = {\n    'array': [1, 2, 3],\n    'boolean': true,\n    'null': null,\n    'number': 123,\n    'object': {'a': 'b', 'c': 'd'},\n    'string': 'Hello World'\n  }\n  editor1.set(json)\n  editor2.set(json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/17_on_event_api.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n      padding-left: 40px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  When clicking on a JSON field or value, a log message will be shown in\n  console.\n</p>\n\n<form>\n  <div id=\"jsoneditor\"></div>\n</form>\n\n<script>\n  const container = document.getElementById('jsoneditor')\n\n  const options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    name: \"jsonContent\",\n    onEvent: function(node, event) {\n      if (node.value !== undefined) {\n        console.log(event.type + ' event ' +\n          'on value ' + JSON.stringify(node.value) + ' ' +\n          'at path ' + JSON.stringify(node.path)\n        )\n      } else {\n        console.log(event.type + ' event ' +\n          'on field ' + JSON.stringify(node.field) + ' ' +\n          'at path ' + JSON.stringify(node.path)\n        )\n      }\n    }\n  }\n\n  const json = {\n    \"array\": [1, 2, [3,4,5]],\n    \"boolean\": true,\n    \"htmlcode\": '&quot;',\n    \"escaped_unicode\": '\\\\u20b9',\n    \"unicode\": '\\u20b9,\\uD83D\\uDCA9',\n    \"return\": '\\n',\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\", \"e\": [1, 2, 3]},\n    \"string\": \"Hello World\",\n    \"url\": \"http://jsoneditoronline.org\",\n    \"[0]\": \"zero\"\n  }\n\n  window.editor = new JSONEditor(container, options, json)\n\n  console.log('json', json)\n  console.log('string', JSON.stringify(json))\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/18_custom_validation.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Custom validation</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      width: 600px;\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 100%;\n      height: 500px;\n    }\n\n  </style>\n</head>\n<body>\n<h1>Custom validation</h1>\n<p>\n  This example demonstrates how to run custom validation on a JSON object.\n  The validation is available in all modes.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const json = {\n    team: [\n      {\n        name: 'Joe',\n        age: 17\n      },\n      {\n        name: 'Sarah',\n        age: 13\n      },\n      {\n        name: 'Jack'\n      }\n    ]\n  }\n\n  const options = {\n    mode: 'tree',\n    modes: ['code', 'text', 'tree', 'preview'],\n    onValidate: function (json) {\n      // rules:\n      // - team, names, and ages must be filled in and be of correct type\n      // - a team must have 4 members\n      // - at lease one member of the team must be adult\n      const errors = []\n\n      if (json && Array.isArray(json.team)) {\n        // check whether each team member has name and age filled in correctly\n        json.team.forEach(function (member, index) {\n          if (typeof member !== 'object') {\n            errors.push({path: ['team', index], message: 'Member must be an object with properties \"name\" and \"age\"'})\n          }\n\n          if ('name' in member) {\n            if (typeof member.name !== 'string') {\n              errors.push({path: ['team', index, 'name'], message: 'Name must be a string'})\n            }\n          } else {\n            errors.push({path: ['team', index], message: 'Required property \"name\"\" missing'})\n          }\n\n          if ('age' in member) {\n            if (typeof member.age !== 'number') {\n              errors.push({path: ['team', index, 'age'], message: 'Age must be a number'})\n            }\n          } else {\n            errors.push({path: ['team', index], message: 'Required property \"age\" missing'})\n          }\n        })\n\n        // check whether the team consists of exactly four members\n        if (json.team.length !== 4) {\n          errors.push({path: ['team'], message: 'A team must have 4 members'})\n        }\n\n        // check whether there is at least one adult member in the team\n        const adults = json.team.filter(function (member) {\n          return member ? member.age >= 18 : false\n        })\n        if (adults.length === 0) {\n          errors.push({path: ['team'], message: 'A team must have at least one adult person (age >= 18)'})\n        }\n      } else {\n        errors.push({path: [], message: 'Required property \"team\" missing or not an Array'})\n      }\n\n      return errors\n    }\n  }\n\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const editor = new JSONEditor(container, options, json)\n  editor.expandAll()\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/19_custom_validation_async.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Custom validation (asynchronous)</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      width: 600px;\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 100%;\n      height: 500px;\n    }\n\n  </style>\n</head>\n<body>\n<h1>Asynchronous custom validation</h1>\n<p>\n  This example demonstrates how to run asynchronous custom validation on a JSON object.\n  The names are checked asynchronously and the results \"come in\" half a second later.\n  Known names in this example are 'Joe', 'Harry', 'Megan'. For other names, a validation error will be displayed.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const json = {\n    customers: [\n      {name: 'Joe'},\n      {name: 'Sarah'},\n      {name: 'Harry'},\n    ]\n  }\n\n  const options = {\n    mode: 'tree',\n    modes: ['code', 'text', 'tree', 'preview'],\n    onValidate: function (json) {\n      // in this validation function we fake sending a request to a server\n      // to validate the existence of customers\n      if (json && Array.isArray(json.customers)) {\n        return Promise\n            .all(json.customers.map(function (customer, index) {\n              return isExistingCustomer(customer && customer.name).then(function (exists) {\n                if (!exists) {\n                  return {\n                    path: ['customers', index],\n                    message: 'Customer ' + customer.name + ' doesn\\'t exist in our database'\n                  }\n                }\n                else {\n                  return null\n                }\n              })\n            }))\n            .then(function (errors) {\n              return errors.filter(function (error) {\n                return error != null\n              })\n            })\n      }\n      else {\n        return null\n      }\n    }\n  }\n\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const editor = new JSONEditor(container, options, json)\n  editor.expandAll()\n\n  // this function fakes a request (asynchronous) to a server to validate the existence of a customer\n  function isExistingCustomer (customerName) {\n    return new Promise(function (resolve, reject) {\n      setTimeout(function () {\n        const customers = ['Joe', 'Harry', 'Megan']\n        const exists = customers.indexOf(customerName) !== -1\n        resolve(exists)\n      }, 500)\n    })\n  }\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/20_custom_css_style_for_nodes.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <script\n    src=\"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js\"\n    integrity=\"sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==\"\n    crossorigin=\"anonymous\"\n  ></script>\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 100%;\n      padding-left: 10px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #containerLeft {\n      display: inline-block;\n      width: 500px;\n      height: 500px;\n      margin-right: 10px;\n    }\n\n    #containerRight {\n      display: inline-block;\n      width: 500px;\n      height: 500px;\n    }\n    #containerRight .different_element {\n      background-color: #acee61;\n    }\n    #containerRight .different_element div.jsoneditor-field,\n    #containerRight .different_element div.jsoneditor-value {\n      color: red;\n    }\n\n    #containerLeft .different_element {\n      background-color: pink;\n    }\n    #containerLeft .different_element div.jsoneditor-field,\n    #containerLeft .different_element div.jsoneditor-value {\n      color: red;\n    }\n  </style>\n</head>\n<body>\n\n<h3>Custom class names</h3>\n<p>\n  This example highlights the differences between two JSON objects using the option <code>onClassName</code>.\n  Make a change in the left or right editor to see the changes update accordingly.\n</p>\n<p>\n  Please note that this is not a full-fledged, performant JSON diffing solution, it's just a small example to demonstrate <code>onClassName</code>.\n</p>\n<div id=\"wrapper\">\n  <div id=\"containerLeft\"></div>\n  <div id=\"containerRight\"></div>\n</div>\n\n<script>\n  const containerLeft = document.getElementById('containerLeft')\n  const containerRight = document.getElementById('containerRight')\n\n  function onClassName({ path, field, value }) {\n    const leftValue = _.get(jsonRight, path)\n    const rightValue = _.get(jsonLeft, path)\n\n    return _.isEqual(leftValue, rightValue)\n      ? 'the_same_element'\n      : 'different_element'\n  }\n\n  const optionsLeft = {\n    mode: 'tree',\n    onClassName: onClassName,\n    onChangeJSON: function (j) {\n      jsonLeft = j\n      window.editorRight.refresh()\n    }\n  }\n\n  const optionsRight = {\n    mode: 'tree',\n    onClassName: onClassName,\n    onChangeJSON: function (j) {\n      jsonRight = j\n      window.editorLeft.refresh()\n    }\n  }\n\n  let jsonLeft = {\n    \"arrayOfArrays\": [1, 2, 999, [3,4,5]],\n    \"someField\": true,\n    \"boolean\": true,\n    \"htmlcode\": '&quot;',\n    \"escaped_unicode\": '\\\\u20b9',\n    \"unicode\": '\\u20b9,\\uD83D\\uDCA9',\n    \"return\": '\\n',\n    \"null\": null,\n    \"thisObjectDoesntExistOnTheRight\" : {key: \"value\"},\n    \"number\": 123,\n    \"object\": {\"a\": \"b\",\"new\":4, \"c\": \"d\", \"e\": [1, 2, 3]},\n    \"string\": \"Hello World\",\n    \"url\": \"http://jsoneditoronline.org\",\n    \"[0]\": \"zero\"\n  }\n\n  let jsonRight = {\n    \"arrayOfArrays\": [1, 2, [3,4,5]],\n    \"boolean\": true,\n    \"htmlcode\": '&quot;',\n    \"escaped_unicode\": '\\\\u20b9',\n    \"thisFieldDoesntExistOnTheLeft\": 'foobar',\n    \"unicode\": '\\u20b9,\\uD83D\\uDCA9',\n    \"return\": '\\n',\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\",  \"c\": \"d\", \"e\": [1, 2, 3]},\n    \"string\": \"Hello World\",\n    \"url\": \"http://jsoneditoronline.org\",\n    \"[0]\": \"zero\"\n  }\n\n  window.editorLeft = new JSONEditor(containerLeft, optionsLeft, jsonLeft)\n  window.editorRight = new JSONEditor(containerRight, optionsRight, jsonRight)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/21_customize_context_menu.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Basic usage</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n\n    .submenu-highlight {\n      background-color: yellow !important;\n    }\n\n    .rainbow {\n      background: linear-gradient(to right, cyan, yellow, violet, green, orange, blue) !important;\n    }\n\n    .example-class > .jsoneditor-icon {\n      background-position: -168px -48px; /* warning triangle */\n    }\n\n    .dotty {\n      border-top : 1px dotted #e5e5e5 !important;\n    }\n  </style>\n</head>\n<body>\n<h1>Context Menu Customization</h1>\n<p>\n  This example demonstrates the use of the onCreateMenu callback option, which\n  allows you to customise context menus after they are created but before they\n  are shown to the user. You can alter/delete existing items as well as\n  adding new menu items. See the source code for this example for more\n  information.\n</p>\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n\n  const options = {\n    // onCreateMenu allows us to register a call back function to customise\n    // the context menu. The callback accpets two parameters, items and path.\n    // Items is an array containing the current menu items, and path\n    // (if present) contains the path of the current node (as an array).\n    // The callback should return the modified (or unmodified) list of menu\n    // items.\n\n    // Every time the user clicks on a context menu button, the menu\n    // is created from scratch and this callback is called.\n\n    onCreateMenu: function (items, node) {\n      const path = node.path\n\n      // log the current items and node for inspection\n      console.log('items:', items, 'node:', node)\n\n      // We are going to add a menu item which returns the current node path\n      // as a jq path selector ( https://stedolan.github.io/jq/ ). First we\n      // will create a function, and then We will connect this function to\n      // the menu item click property in a moment.\n\n      function pathTojq() {\n        let pathString = ''\n\n        path.forEach(function (segment, index) { // path is an array, loop through it\n          if (typeof segment == 'number') {  // format the selector for array indexs ...\n            pathString += '[' + segment + ']'\n          } else {  // ... or object keys\n            pathString += '.\"' + segment + '\"'\n          }\n        })\n\n        alert(pathString) // show it to the user.\n      }\n\n      // Create a new menu item. For our example, we only want to do this\n      // if there is a path (in the case of appendnodes (for new objects)\n      // path is null until a node is created)\n      if (path) {\n        // Each item in the items array represents a menu item,\n        // and requires the following details :\n\n        items.push({\n          text: 'jq Path', // the text for the menu item\n          title: 'Show the jq path for this node', // the HTML title attribute\n          className: 'example-class', // the css class name(s) for the menu item\n          click: pathTojq // the function to call when the menu item is clicked\n        })\n      }\n\n\n      // Now we will iterate through the menu items, which includes the items\n      // created by jsoneditor, and the new item we added above. In this\n      // example we will just alter the className property for the items, but\n      // you can alter any property (e.g. the click callback, text property etc.)\n      // for any item, or even delete the whole menu item.\n      items.forEach(function (item, index, items) {\n        if (\"submenu\" in item) {\n          // if the item has a submenu property, it is a submenu heading\n          // and contains another array of menu items. Let's colour\n          // that yellow...\n          items[index].className += ' submenu-highlight'\n        } else {\n          // if it's not a submenu heading, let's make it colorful\n          items[index].className += ' rainbow'\n        }\n      })\n\n      // note that the above loop isn't recursive, so it only alters the classes\n      // on the top-level menu items. To also process menu items in submenus\n      // you should iterate through any \"submenu\" arrays of items if the item has one.\n\n      // next, just for fun, let's remove any menu separators (again just at the\n      // top level menu). A menu separator is an item with a type : 'separator'\n      // property\n      items = items.filter(function (item) {\n        return item.type !== 'separator'\n      })\n\n      // finally we need to return the items array. If we don't, the menu\n      // will be empty.\n      return items\n    }\n  }\n\n  const json = {\n    'array': [1, 2, 3],\n    'boolean': true,\n    'color': '#82b92c',\n    'null': null,\n    'number': 123,\n    'object': {'a': 'b', 'c': 'd'},\n    'string': 'Hello World'\n  }\n\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/22_on_validation_event.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | onValidationError</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      width: 600px;\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 100%;\n      height: 500px;\n    }\n\n    #onValidationOutput {\n      margin: 5px;\n    }\n\n    /* custom bold styling for non-default JSON schema values */\n    .jsoneditor-is-not-default {\n      font-weight: bold;\n    }\n  </style>\n</head>\n<body>\n<h1>JSON schema validation</h1>\n<p>\n  This example demonstrates onValidationError callback.\n</p>\n\n<div id=\"jsoneditor\"></div>\n<div id=\"onValidationOutput\"></div>\n\n<script>\n  var schema = {\n    \"title\": \"Employee\",\n    \"description\": \"Object containing employee details\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"firstName\": {\n        \"title\": \"First Name\",\n        \"description\": \"The given name.\",\n        \"examples\": [\n          \"John\"\n        ],\n        \"type\": \"string\"\n      },\n      \"lastName\": {\n        \"title\": \"Last Name\",\n        \"description\": \"The family name.\",\n        \"examples\": [\n          \"Smith\"\n        ],\n        \"type\": \"string\"\n      },\n      \"gender\": {\n        \"title\": \"Gender\",\n        \"enum\": [\"male\", \"female\"]\n      },\n      \"availableToHire\": {\n        \"type\": \"boolean\",\n        \"default\": false\n      },\n      \"age\": {\n        \"description\": \"Age in years\",\n        \"type\": \"integer\",\n        \"minimum\": 0,\n        \"examples\": [28, 32]\n      },\n      \"job\": {\n        \"$ref\": \"job\"\n      }\n    },\n    \"required\": [\"firstName\", \"lastName\"]\n  };\n\n  var job = {\n    \"title\": \"Job description\",\n    \"type\": \"object\",\n    \"required\": [\"address\"],\n    \"properties\": {\n      \"company\": {\n        \"type\": \"string\",\n        \"examples\": [\n          \"ACME\",\n          \"Dexter Industries\"\n        ]\n      },\n      \"role\": {\n        \"description\": \"Job title.\",\n        \"type\": \"string\",\n        \"examples\": [\n          \"Human Resources Coordinator\",\n          \"Software Developer\"\n        ],\n        \"default\": \"Software Developer\"\n      },\n      \"address\": {\n        \"type\": \"string\"\n      },\n      \"salary\": {\n        \"type\": \"number\",\n        \"minimum\": 120,\n        \"examples\": [100, 110, 120]\n      }\n    }\n  };\n\n  var json = {\n    firstName: 'John',\n    lastName: 'Doe',\n    gender: null,\n    age: \"28\",\n    availableToHire: true,\n    job: {\n      company: 'freelance',\n      role: 'developer',\n      salary: 100\n    }\n  };\n\n  var options = {\n    schema: schema,\n    schemaRefs: {\"job\": job},\n    mode: 'code',\n    modes: ['code', 'text', 'tree', 'preview'],\n    onValidationError: function(errors) {\n      console.error('onValidationError', errors);\n      const outputEL = document.getElementById('onValidationOutput')\n      outputEL.innerHTML = '<code>onValidationError</code> was called with ' + errors.length + ' error' + (errors.length > 1 ? 's' : '') +  ' <br> ' +\n        'open the browser console to see the error objects';\n    },\n    onValidate: function (json) {\n      var errors = [];\n      if(!isNaN(json.age) && json.age < 30) {\n        errors.push({ path: ['age'], message: 'Member age must be 30 or higher' });\n      }\n      return errors;\n    }\n  };\n\n  // create the editor\n  var container = document.getElementById('jsoneditor');\n  var editor = new JSONEditor(container, options, json);\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/23_custom_query_language.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <title>JSONEditor | Custom query language</title>\n  <meta charset=\"utf-8\" />\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n  <script src=\"https://unpkg.com/lodash@4.17.15/lodash.min.js\"></script>\n\n  <style type=\"text/css\">\n    p {\n      max-width: 500px;\n      font-family: sans-serif;\n      font-size: 11pt;\n    }\n\n    code {\n      font-size: 11pt;\n      background: #e5e5e5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n\n    .warning {\n      color: red;\n    }\n  </style>\n</head>\n<body>\n<p>\n  This demo shows how to configure a custom query language.\n  Click on the \"Transform\" button and try it out.\n</p>\n<p>\n  This basic example uses lodash functions <code>filter</code>, <code>sort</code>, and <code>pick</code>,\n  but you can run any JavaScript code.\n</p>\n<p class=\"warning\">\n  WARNING: this example uses <code>new Function()</code> which can be dangerous when executed with arbitrary code.\n  Don't use it in production.\n</p>\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const container = document.getElementById('jsoneditor')\n  const options = {\n    createQuery: function (json, queryOptions) {\n      console.log('createQuery', queryOptions)\n\n      const { filter, sort, projection } = queryOptions\n      let query = 'data'\n\n      if (filter) {\n        // Note that the comparisons embrace type coercion,\n        // so a filter value like '5' (text) will match numbers like 5 too.\n        const getActualValue = filter.field !== '@'\n          ? `item => _.get(item, '${filter.field}')`\n          : `item => item`\n        query = `_.filter(${query}, ${getActualValue} ${filter.relation} '${filter.value}')`\n      }\n\n      if (sort) {\n        // The '@' field name is a special case,\n        // which means that the field itself is selected.\n        // For example when we have an array containing numbers.\n        query = sort.field !== '@'\n          ? `_.orderBy(${query}, '${sort.field}', '${sort.direction}')`\n          : `_.sortBy(${query}, '${sort.direction}')`\n      }\n\n      if (projection) {\n        // It is possible to make a util function \"pickFlat\"\n        // and use that when building the query to make it more readable.\n        if (projection.fields.length > 1) {\n          const fields = projection.fields.map(field => {\n            const name = _.last(field.split('.'))\n            return `  '${name}': _.get(item, '${field}')`\n          })\n          query = `_.map(${query}, item => ({\\n${fields.join(',\\n')}})\\n)`\n        } else {\n          const field = projection.fields[0]\n          query = `_.map(${query}, item => _.get(item, '${field}'))`\n        }\n      }\n\n      return query\n    },\n    executeQuery: function (json, query) {\n      console.log('executeQuery', query)\n\n      // WARNING: Using new Function() with arbitrary input can be dangerous! Be careful.\n      const execute = new Function('data', 'return ' + query)\n\n      return execute(json)\n    },\n    queryDescription: 'Enter a JavaScript query to filter, sort, or transform the JSON data.<br/>' +\n      'The <a href=\"https://lodash.com/\" target=\"_blank\">Lodash</a> library is available via <code>_</code> to facilitate this.'\n  }\n  const json = []\n  for (let i = 0; i < 100; i++) {\n    var longitude = 4 + i / 100\n    var latitude = 51 + i / 100\n\n    json.push({\n      name: 'Item ' + i,\n      id: String(i),\n      index: i,\n      time: new Date().toISOString(),\n      location: {\n        latitude: longitude,\n        longitude: latitude,\n        coordinates: [longitude, latitude]\n      },\n      random: Math.random()\n    })\n  }\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/24_new_window.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | New window</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n<p>\n  <button id=\"openNewEditor\">Open Editor in New Window</button>\n  <button id=\"setJSON\">Set JSON</button>\n  <button id=\"getJSON\">Get JSON</button>\n</p>\n\n<script>\n  let editor\n\n  function openNewEditor() {\n    const child = window.open(\"\", \"_blank\", \"width=400,height=400\")\n    child.document.title = 'JSONEditor | New window'\n    child.onunload = function () {\n      editor = undefined\n    }\n\n    // make the necessary styles available within the child window\n    // for JSONEditor\n    const baseUrl = window.location.href.slice(0, window.location.href.lastIndexOf('/'))\n    const jsonEditorStyles = child.document.createElement(\"link\")\n    jsonEditorStyles.setAttribute(\"href\", baseUrl + \"/../dist/jsoneditor.css\")\n    jsonEditorStyles.setAttribute(\"rel\", \"stylesheet\")\n    child.document.head.append(jsonEditorStyles)\n    // for vanilla-picker\n    const colorPickerStyles = JSONEditor.VanillaPicker.StyleElement.cloneNode(true)\n    child.document.head.append(colorPickerStyles)\n\n    const container = child.document.createElement(\"div\")\n    child.document.body.append(container)\n\n    // create the editor\n    const options = {\n      // Show sort and transform modals in the child window, not the parent.\n      modalAnchor: child.document.body\n    }\n    editor = new JSONEditor(container, options)\n  }\n\n  // create a new window\n  document.getElementById('openNewEditor').onclick = openNewEditor\n\n  // set json\n  document.getElementById('setJSON').onclick = function () {\n    if (!editor) {\n      openNewEditor()\n    }\n    const json = {\n      'array': [1, 2, 3],\n      'boolean': true,\n      'color': '#82b92c',\n      'null': null,\n      'number': 123,\n      'object': {'a': 'b', 'c': 'd'},\n      'time': 1575599819000,\n      'string': 'Hello World'\n    }\n    editor.set(json)\n  }\n\n  // get json\n  document.getElementById('getJSON').onclick = function () {\n    if (!editor) {\n      alert('No editor is open')\n    } else {\n      const json = editor.get()\n      alert(JSON.stringify(json, null, 2))\n    }\n  }\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/25_sync_node_expand.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Sync Node Expand</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor-left {\n      width: 45%;\n      height: 90%;\n      float: left;\n      margin-left: 2%;\n    }\n    #jsoneditor-right {\n      width: 45%;\n      height: 90%;\n      float: right;\n      margin-right: 2%;\n    }\n  </style>\n</head>\n<body>\n\n<div>\n    <div id=\"jsoneditor-left\"></div>\n    <div id=\"jsoneditor-right\"></div>\n</div>\n\n<script>\n  const left_json = {\n    'student_details': {\n      'name': {\n        'first_name': 'foo',\n        'last_name': 'bar'\n      },\n      'school': {\n        'name': 'foo',\n        'address': 'bar'\n      },\n      'contact': '434343',\n      'age': '39'\n    },\n    'marks': [50, 49, 36]\n  }\n  \n  const right_json = {\n    'marks': [50, 49],\n    'student_details': {\n      'name': 'foo bar',\n      'address': {\n        'street': 'foo',\n        'city': 'bar',\n        'zip': '444444'\n      },\n      'school': {\n        'name': 'foo',\n        'address': 'bar'\n      },\n      'age': '39',\n      'contact': ['434355', '343433', '324343']\n    }\n  }\n\n  const editor_left = new JSONEditor(document.getElementById('jsoneditor-left'), {\n      mode: \"tree\",\n      onExpand: (options) => {\n          if (editor_right) {\n              editor_right.expand(options)\n          }\n      }\n  }, left_json)\n\n  const editor_right = new JSONEditor(document.getElementById('jsoneditor-right'), {\n      mode: \"tree\",\n      onExpand: (options) => {\n          if (editor_left) {\n            editor_left.expand(options)\n          }\n      }\n  }, right_json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/26_autocomplete_by_schema.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Auto-completion by schema</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      width: 600px;\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 100%;\n      height: 500px;\n    }\n\n    /* custom bold styling for non-default JSON schema values */\n    .jsoneditor-is-not-default {\n      font-weight: bold;\n    }\n  </style>\n</head>\n<body>\n<h1>JSON autocompletion by schema</h1>\n<p>\n  This example demonstrates JSON autocompletion by schema. try to change the JSON properties and values and you'll get a suggestions that are based on the schema properties, examples and enums. \n</p>\n<p>\n  See <a href=\"http://json-schema.org/\" target=\"_blank\">http://json-schema.org/</a> for more information.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const schema = {\n    \"title\": \"Employee\",\n    \"description\": \"Object containing employee details\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"firstName\": {\n        \"title\": \"First Name\",\n        \"description\": \"The given name.\",\n        \"examples\": [\n          \"John\"\n        ],\n        \"type\": \"string\"\n      },\n      \"lastName\": {\n        \"title\": \"Last Name\",\n        \"description\": \"The family name.\",\n        \"examples\": [\n          \"Smith\"\n        ],\n        \"type\": \"string\"\n      },\n      \"gender\": {\n        \"title\": \"Gender\",\n        \"type\": \"string\",\n        \"enum\": [\"male\", \"female\"],\n        \"examples\": [\"male\", \"female\"],\n      },\n      \"availableToHire\": {\n        \"type\": \"boolean\",\n        \"default\": false\n      },\n      \"age\": {\n        \"description\": \"Age in years\",\n        \"type\": \"integer\",\n        \"minimum\": 0,\n        \"examples\": [28, 32]\n      },\n      \"job\": {\n        \"$ref\": \"job\"\n      },\n      \"profession\": {\n        \"oneOf\": [\n          {\n            \"$ref\": \"junior\"\n          },\n          {\n            \"$ref\": \"experienced\"\n          },\n          {\n            \"$ref\": \"senior\"\n          },\n        ]\n      },\n      \"publications\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\"academic\", \"professional\"]\n            },\n            \"journal\": {\n              \"type\": \"string\"\n            }\n          }\n        }\n      }\n    },\n    \"required\": [\"firstName\", \"lastName\"]\n  }\n\n  const job = {\n    \"title\": \"Job description\",\n    \"type\": \"object\",\n    \"required\": [\"address\"],\n    \"properties\": {\n      \"company\": {\n        \"type\": \"string\",\n        \"examples\": [\n          \"ACME\",\n          \"Dexter Industries\"\n        ]\n      },\n      \"role\": {\n        \"description\": \"Job title.\",\n        \"type\": \"string\",\n        \"examples\": [\n          \"Human Resources Coordinator\",\n          \"Software Developer\"\n        ],\n        \"default\": \"Software Developer\"\n      },\n      \"address\": {\n        \"type\": \"string\"\n      },\n      \"salary\": {\n        \"type\": \"number\",\n        \"minimum\": 120,\n        \"examples\": [100, 110, 120]\n      }\n    }\n  }\n\n  const junior = {\n    \"type\": \"object\",\n    \"properties\": {\n      \"level\": {\n      \"type\": \"string\",\n      \"enum\": [\"junior\"]\n      },\n      \"experience\": {\n        \"description\": \"years of experience\",\n        \"type\": \"number\",\n        \"minimum\": 0,\n        \"maximum\": 3,\n        \"examples\": [0,1,2,3]\n      }\n    }\n  }\n\n  const experienced = {\n    \"type\": \"object\",\n    \"properties\": {\n      \"level\": {\n        \"type\": \"string\",\n        \"enum\": [\"experienced\"]\n      },\n      \"experience\": {\n        \"description\": \"years of experience\",\n        \"type\": \"number\",\n        \"minimum\": 3,\n        \"maximum\": 7,\n        \"examples\": [3,4,5,6,7]\n      }\n    }\n  }\n\n  const senior = {\n    \"type\": \"object\",\n    \"properties\": {\n      \"level\": {\n        \"type\": \"string\",\n        \"enum\": [\"senior\"]\n      },\n      \"experience\": {\n        \"description\": \"years of experience\",\n        \"type\": \"number\",\n        \"minimum\": 7,\n        \"examples\": [7,8,9,10,11]\n      }\n    }\n  }\n\n  const json = {\n    firstName: \"John\",\n    lastName: \"Doe\",\n    gender: \"male\",\n    age: 28,\n    availableToHire: true,\n    job: {\n      company: \"freelance\",\n      role: \"developer\",\n      salary: 140,\n      address: \"Jerusalem\"\n    },\n    profession: {\n      level: \"senior\",\n      experience: 10\n    },\n    publications: [{\n      type: 'academic',\n      journal: 'MIT today'\n    },\n    {\n      type: 'professional',\n      journal: 'stack overflow'\n    }]\n  }\n\n  const options = {\n    schema: schema,\n    schemaRefs: {job: job, junior: junior, experienced: experienced, senior: senior},\n    allowSchemaSuggestions: true,\n    mode: 'code',\n    modes: ['code']\n  }\n\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const editor = new JSONEditor(container, options, json)\n  schema.properties.firstName.examples.push('David');\n  editor.setSchema(schema, {job: job, junior: junior, experienced: experienced, senior: senior});\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/27_autocomplete_by_schema_recursive_refs.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Auto-completion by schema</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      width: 600px;\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 100%;\n      height: 500px;\n    }\n\n    /* custom bold styling for non-default JSON schema values */\n    .jsoneditor-is-not-default {\n      font-weight: bold;\n    }\n  </style>\n</head>\n<body>\n<h1>JSON autocompletion by schema (recursive schema)</h1>\n<p>\n  This example demonstrates JSON autocompletion by schema. try to change the JSON properties and values and you'll get a suggestions that are based on the schema properties, examples and enums.\n  In this example the schema that in use is actually a recursive schema, meaninng it has a referance of a sub-schema that refer the same sub-schema again.\n</p>\n<p>\n  See <a href=\"http://json-schema.org/\" target=\"_blank\">http://json-schema.org/</a> for more information.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  const schema = {\n    \"oneOf\": [\n      {\n        \"$ref\": \"employeeSchema\"\n      }\n    ]\n  }\n  const employeeSchema = {\n    \"title\": \"Employee\",\n    \"description\": \"Object containing employee details\",\n    \"type\": \"object\",\n    \"additonalProperties\": false,\n    \"properties\": {\n      \"personalDetails\": {\n        \"$ref\": \"personal\"\n      },      \n      \"availableToHire\": {\n        \"type\": \"boolean\",\n        \"default\": false\n      },\n      \"job\": {\n        \"$ref\": \"job\"\n      },\n      \"profession\": {\n        \"oneOf\": [\n          {\n            \"$ref\": \"junior\"\n          },\n          {\n            \"$ref\": \"experienced\"\n          },\n          {\n            \"$ref\": \"senior\"\n          },\n        ]\n      },\n      \"reporters\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"$ref\" : \"employeeSchema\"\n        }\n      },\n      \"publications\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\"academic\", \"professional\"]\n            },\n            \"journal\": {\n              \"type\": \"string\"\n            }\n          }\n        }\n      }\n    },\n    \"required\": [\"personalDetails\"]\n  }\n\n  const personal = {\n    \"title\": \"Personal Details\",\n    \"type\": \"object\",\n    \"required\": [\"firstName\", \"lastName\"],\n    \"properties\": {\n      \"firstName\": {\n        \"title\": \"First Name\",\n        \"description\": \"The given name.\",\n        \"examples\": [\n          \"John\"\n        ],\n        \"type\": \"string\"\n      },\n      \"lastName\": {\n        \"title\": \"Last Name\",\n        \"description\": \"The family name.\",\n        \"examples\": [\n          \"Smith\"\n        ],\n        \"type\": \"string\"\n      },\n      \"gender\": {\n        \"title\": \"Gender\",\n        \"type\": \"string\",\n        \"enum\": [\"male\", \"female\"],\n        \"examples\": [\"male\", \"female\"],\n      },\n      \"age\": {\n        \"description\": \"Age in years\",\n        \"type\": \"integer\",\n        \"minimum\": 0,\n        \"examples\": [28, 32]\n      },\n    }\n  }\n\n  const job = {\n    \"title\": \"Job description\",\n    \"type\": \"object\",\n    \"required\": [\"address\"],\n    \"properties\": {\n      \"company\": {\n        \"type\": \"string\",\n        \"examples\": [\n          \"ACME\",\n          \"Dexter Industries\"\n        ]\n      },\n      \"role\": {\n        \"description\": \"Job title.\",\n        \"type\": \"string\",\n        \"examples\": [\n          \"Human Resources Coordinator\",\n          \"Software Developer\"\n        ],\n        \"default\": \"Software Developer\"\n      },\n      \"address\": {\n        \"type\": \"string\"\n      },\n      \"salary\": {\n        \"type\": \"number\",\n        \"minimum\": 120,\n        \"examples\": [100, 110, 120]\n      }\n    }\n  }\n\n  const junior = {\n    \"type\": \"object\",\n    \"properties\": {\n      \"level\": {\n      \"type\": \"string\",\n      \"enum\": [\"junior\"]\n      },\n      \"experience\": {\n        \"description\": \"years of experience\",\n        \"type\": \"number\",\n        \"minimum\": 0,\n        \"maximum\": 3,\n        \"examples\": [0,1,2,3]\n      }\n    }\n  }\n\n  const experienced = {\n    \"type\": \"object\",\n    \"properties\": {\n      \"level\": {\n        \"type\": \"string\",\n        \"enum\": [\"experienced\"]\n      },\n      \"experience\": {\n        \"description\": \"years of experience\",\n        \"type\": \"number\",\n        \"minimum\": 3,\n        \"maximum\": 7,\n        \"examples\": [3,4,5,6,7]\n      }\n    }\n  }\n\n  const senior = {\n    \"type\": \"object\",\n    \"properties\": {\n      \"level\": {\n        \"type\": \"string\",\n        \"enum\": [\"senior\"]\n      },\n      \"experience\": {\n        \"description\": \"years of experience\",\n        \"type\": \"number\",\n        \"minimum\": 7,\n        \"examples\": [7,8,9,10,11]\n      }\n    }\n  }\n\n  const json = {\n    personalDetails: {\n      firstName: \"John\",\n      lastName: \"Doe\",\n      gender: \"male\",\n      age: 28\n    },    \n    availableToHire: true,\n    job: {\n      company: \"freelance\",\n      role: \"developer\",\n      salary: 140,\n      address: \"Jerusalem\"\n    },\n    profession: {\n      level: \"senior\",\n      experience: 10\n    },\n    reporters: [{\n      personalDetails: {\n        firstName: \"John\",\n        lastName: \"Doe\",\n        gender: \"male\",\n        age: 28\n      },\n      job: {\n        company: \"freelance\",\n        role: \"developer\",\n        salary: 120,\n        address: \"New York\"\n      },\n      profession: {\n        level: \"junior\",\n        experience: 2\n      },\n    }],\n    publications: [{\n      type: 'academic',\n      journal: 'MIT today'\n    },\n    {\n      type: 'professional',\n      journal: 'stack overflow'\n    }]\n  }\n\n  const options = {\n    schema: schema,\n    schemaRefs: {job, junior, experienced, senior, personal, employeeSchema},\n    allowSchemaSuggestions: true,\n    mode: 'code',\n    modes: ['code']\n  }\n\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const editor = new JSONEditor(container, options, json)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/28_autocomplete_text_value_objects.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Auto Complete with Text/Value Objects</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      width: 600px;\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 100%;\n      height: 500px;\n    }\n\n    /* custom bold styling for non-default JSON schema values */\n    .jsoneditor-is-not-default {\n      font-weight: bold;\n    }\n  </style>\n\n</head>\n<body>\n<h1>Auto Complete with Text/Value Objects</h1>\n<p>\n  This example demonstrates the enhanced autocomplete functionality using objects with separate text (display) and value (actual value) properties. \n  You can search by either the company name or stock symbol. Try typing \"Apple\", \"Microsoft\", \"Google\", \"AAPL\", \"MSFT\", or \"GOOGL\". \n  The dropdown shows company names, but the selected value will be the stock symbol.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // Object array with text/value properties\n  const container = document.getElementById('jsoneditor')\n  const options = {\n    autocomplete: {\n      filter: 'contain', // Use contain filter for better search experience\n      getOptions: function () {\n        return [\n          { text: 'Apple Inc.', value: 'AAPL' },\n          { text: 'Microsoft Corporation', value: 'MSFT' },\n          { text: 'Alphabet Inc. (Google)', value: 'GOOGL' },\n          { text: 'Amazon.com Inc.', value: 'AMZN' },\n          { text: 'Tesla Inc.', value: 'TSLA' },\n          { text: 'Meta Platforms Inc. (Facebook)', value: 'META' },\n          { text: 'NVIDIA Corporation', value: 'NVDA' },\n          { text: 'Netflix Inc.', value: 'NFLX' },\n          { text: 'PayPal Holdings Inc.', value: 'PYPL' },\n          { text: 'Adobe Inc.', value: 'ADBE' }\n        ];\n      }\n    }\n  }\n  const json = {\n    'portfolio': ['AAPL', 'GOOGL'],\n    'watchlist': ['TSLA', 'META'],\n    'favorite_stock': 'MSFT',\n    'description': 'Search by company name or stock symbol - value stored is the symbol'\n  }\n  const editor = new JSONEditor(container, options, json)\n</script>\n\n</body>\n</html> "
  },
  {
    "path": "examples/29_autocomplete_multiple_fields.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n\n    <title>JSONEditor | Auto Complete with Multiple Searchable Fields</title>\n\n    <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\" />\n    <script src=\"../dist/jsoneditor.js\"></script>\n\n    <style type=\"text/css\">\n      body {\n        width: 600px;\n        font: 11pt sans-serif;\n      }\n      #jsoneditor {\n        width: 100%;\n        height: 500px;\n      }\n\n      /* custom bold styling for non-default JSON schema values */\n      .jsoneditor-is-not-default {\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <h1>Auto Complete with Multiple Searchable Fields</h1>\n    <p>\n      This example demonstrates advanced autocomplete functionality with custom\n      filtering that searches across multiple fields within each option.\n      Countries with multiple searchable fields - you can search by country\n      name, capital city, or country code. Try typing \"United States\",\n      \"Washington\", \"US\", \"Germany\", \"Berlin\", \"DE\", \"Japan\", \"Tokyo\", or \"JP\".\n    </p>\n\n    <div id=\"jsoneditor\"></div>\n\n    <script>\n      // Advanced autocomplete with multiple searchable fields\n      const container = document.getElementById(\"jsoneditor\");\n\n      // Country data with multiple searchable fields\n      const countries = [\n        { name: \"United States\", capital: \"Washington\", code: \"US\" },\n        { name: \"United Kingdom\", capital: \"London\", code: \"GB\" },\n        { name: \"Germany\", capital: \"Berlin\", code: \"DE\" },\n        { name: \"France\", capital: \"Paris\", code: \"FR\" },\n        { name: \"Japan\", capital: \"Tokyo\", code: \"JP\" },\n        { name: \"Australia\", capital: \"Canberra\", code: \"AU\" },\n        { name: \"Canada\", capital: \"Ottawa\", code: \"CA\" },\n        { name: \"Brazil\", capital: \"Brasília\", code: \"BR\" },\n        { name: \"India\", capital: \"New Delhi\", code: \"IN\" },\n        { name: \"China\", capital: \"Beijing\", code: \"CN\" },\n        { name: \"Italy\", capital: \"Rome\", code: \"IT\" },\n        { name: \"Spain\", capital: \"Madrid\", code: \"ES\" },\n        { name: \"Russia\", capital: \"Moscow\", code: \"RU\" },\n        { name: \"South Korea\", capital: \"Seoul\", code: \"KR\" },\n        { name: \"Mexico\", capital: \"Mexico City\", code: \"MX\" },\n      ];\n\n      const normalizeCase = (text = \"\", config) => {\n        return config.caseSensitive ? text : text.toLowerCase();\n      };\n\n      const options = {\n        autocomplete: {\n          getOptions: function () {\n            // Convert country data to text/value format for autocomplete\n            return countries.map((country) => ({\n              text: country.name,\n              value: country.code,\n              // Store additional searchable fields for custom filtering\n              _searchableFields: [country.name, country.capital, country.code],\n            }));\n          },\n          filter: function (token, match, config) {\n            const normalizedToken = normalizeCase(token, config);\n            // Custom filter that searches across multiple fields\n            if (typeof match === \"string\") {\n              // Handle simple string matches\n              const normalizedMatch = normalizeCase(match, config);\n              return normalizedMatch.indexOf(normalizedToken) > -1;\n            }\n\n            if (match && match._searchableFields) {\n              // Search across all searchable fields\n              return match._searchableFields.some((field) => {\n                const normalizedField = normalizeCase(field, config);\n                return normalizedField.indexOf(normalizedToken) > -1;\n              });\n            }\n\n            \n            const textMatch =\n              match.text && \n              normalizeCase(match.text, config).indexOf(normalizedToken) > -1;\n            const valueMatch =\n              match.value &&\n              normalizeCase(match.value, config).indexOf(normalizedToken) > -1;\n            return textMatch || valueMatch;\n          },\n        },\n      };\n\n      const json = {\n        user_countries: [\"US\", \"DE\"],\n        origin_country: \"JP\",\n        shipping_countries: [\"US\", \"CA\", \"GB\"],\n        description:\n          \"Search by country name, capital, or code - value stored is the country code\",\n      };\n\n      const editor = new JSONEditor(container, options, json);\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/css/darktheme.css",
    "content": "/* dark styling of the editor */\ndiv.jsoneditor,\ndiv.jsoneditor-menu {\n  border-color: #4b4b4b;\n}\ndiv.jsoneditor-menu {\n  background-color: #4b4b4b;\n}\ndiv.jsoneditor-tree,\ndiv.jsoneditor textarea.jsoneditor-text {\n  background-color: #666666;\n  color: #ffffff;\n}\ndiv.jsoneditor-field,\ndiv.jsoneditor-value {\n  color: #ffffff;\n}\ntable.jsoneditor-search div.jsoneditor-frame {\n  background: #808080;\n}\n\ntr.jsoneditor-highlight,\ntr.jsoneditor-selected {\n  background-color: #808080;\n}\n\ndiv.jsoneditor-field[contenteditable=true]:focus,\ndiv.jsoneditor-field[contenteditable=true]:hover,\ndiv.jsoneditor-value[contenteditable=true]:focus,\ndiv.jsoneditor-value[contenteditable=true]:hover,\ndiv.jsoneditor-field.jsoneditor-highlight,\ndiv.jsoneditor-value.jsoneditor-highlight {\n  background-color: #808080;\n  border-color: #808080;\n}\n\ndiv.jsoneditor-field.jsoneditor-highlight-active,\ndiv.jsoneditor-field.jsoneditor-highlight-active:focus,\ndiv.jsoneditor-field.jsoneditor-highlight-active:hover,\ndiv.jsoneditor-value.jsoneditor-highlight-active,\ndiv.jsoneditor-value.jsoneditor-highlight-active:focus,\ndiv.jsoneditor-value.jsoneditor-highlight-active:hover {\n  background-color: #b1b1b1;\n  border-color: #b1b1b1;\n}\n\ndiv.jsoneditor-tree button:focus {\n  background-color: #868686;\n}\n\n/* coloring of JSON in tree mode */\ndiv.jsoneditor-readonly {\n  color: #acacac;\n}\ndiv.jsoneditor td.jsoneditor-separator {\n  color: #acacac;\n}\ndiv.jsoneditor-value.jsoneditor-string {\n  color: #00ff88;\n}\ndiv.jsoneditor-value.jsoneditor-object,\ndiv.jsoneditor-value.jsoneditor-array {\n  color: #bababa;\n}\ndiv.jsoneditor-value.jsoneditor-number {\n  color: #ff4040;\n}\ndiv.jsoneditor-value.jsoneditor-boolean {\n  color: #ff8048;\n}\ndiv.jsoneditor-value.jsoneditor-null {\n  color: #49a7fc;\n}\ndiv.jsoneditor-value.jsoneditor-invalid {\n  color: white;\n}\n"
  },
  {
    "path": "examples/react_advanced_demo/.gitignore",
    "content": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "examples/react_advanced_demo/README.md",
    "content": "# JSONEditor React advanced demo\n\nThis project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).\n\n## Install\n\nInstall dependencies once:\n\n```\nnpm install\n```\n\n## Run\n\nTo run the demo:\n\n```\nnpm start\n```\n\nThis will open a development server at http://localhost:3000\n"
  },
  {
    "path": "examples/react_advanced_demo/package.json",
    "content": "{\n  \"name\": \"react_advanced_demo\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"jsoneditor\": \"latest\",\n    \"lodash\": \"4.17.23\",\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"18.2.0\",\n    \"react-scripts\": \"5.0.1\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test --env=jsdom\",\n    \"eject\": \"react-scripts eject\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/react_advanced_demo/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta name=\"theme-color\" content=\"#000000\">\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>JSONEditor | React advanced demo</title>\n  </head>\n  <body>\n    <noscript>\n      You need to enable JavaScript to run this app.\n    </noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "examples/react_advanced_demo/src/App.css",
    "content": ".app .contents {\n  width: 500px;\n  height: 400px;\n}\n\n.app .contents .mode {\n  padding: 10px 0;\n}\n\n.app .contents .code {\n  background: #f5f5f5;\n  overflow: auto;\n}"
  },
  {
    "path": "examples/react_advanced_demo/src/App.js",
    "content": "import React, { Component } from 'react';\n\nimport JSONEditorReact from './JSONEditorReact';\nimport './App.css';\n\nconst schema = {\n  title: 'Example Schema',\n  type: 'object',\n  properties: {\n    array: {\n      type: 'array',\n      items: {\n        type: 'number'\n      }\n    },\n    boolean: {\n      type: 'boolean'\n    },\n    number: {\n      type: 'number'\n    }\n  },\n  required: ['array', 'string', 'boolean']\n};\n\nconst json = {\n  'array': [1, 2, 3],\n  'boolean': true,\n  'null': null,\n  'number': 'four',\n  'object': {'a': 'b', 'c': 'd'},\n  'string': 'Hello World'\n};\n\nconst modes = ['tree', 'form', 'view', 'code', 'text'];\n\nclass App extends Component {\n  state = {\n    schema,\n    text: JSON.stringify(json, null, 2),\n    mode: 'tree'\n  };\n\n  render() {\n    return (\n      <div className=\"app\">\n        <h1>JSONEditor React advanced demo</h1>\n        <div className=\"contents\">\n          <div className=\"mode\">\n            mode: <select value={this.state.mode} onChange={this.onModeChangeSelect}>\n              {\n                modes.map(mode => <option key={mode} value={mode}>{mode}</option>)\n              }\n            </select>\n          </div>\n          <JSONEditorReact\n              schema={this.state.schema}\n              text={this.state.text}\n              mode={this.state.mode}\n              modes={modes}\n              indentation={4}\n              onChangeText={this.onChangeText}\n              onModeChange={this.onModeChange}\n          />\n          <div className=\"code\">\n            <pre>\n              <code>\n                {this.state.text}\n              </code>\n            </pre>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  onChangeText = (text) => {\n    this.setState({ text });\n  };\n\n  onModeChangeSelect = (event) => {\n    this.setState({ mode: event.target.value });\n  };\n\n  onModeChange = (mode) => {\n    this.setState({ mode });\n  };\n}\n\nexport default App;\n"
  },
  {
    "path": "examples/react_advanced_demo/src/App.test.js",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nit('renders without crashing', () => {\n  const div = document.createElement('div');\n  ReactDOM.render(<App />, div);\n  ReactDOM.unmountComponentAtNode(div);\n});\n"
  },
  {
    "path": "examples/react_advanced_demo/src/JSONEditorReact.css",
    "content": ".jsoneditor-react-container {\n  width: 100%;\n  height: 100%;\n}\n"
  },
  {
    "path": "examples/react_advanced_demo/src/JSONEditorReact.js",
    "content": "import React, {Component} from 'react';\nimport isEqual from 'lodash/isEqual';\nimport cloneDeep from 'lodash/cloneDeep';\n\nimport JSONEditor from 'jsoneditor';\nimport 'jsoneditor/dist/jsoneditor.css';\n\nimport './JSONEditorReact.css';\n\nexport default class JSONEditorReact extends Component {\n  componentDidMount () {\n    // copy all properties into options for the editor\n    // (except the properties for the JSONEditorReact component itself)\n    const options = Object.assign({}, this.props);\n    delete options.json;\n    delete options.text;\n\n    this.jsoneditor = new JSONEditor(this.container, options);\n\n    if ('json' in this.props) {\n      this.jsoneditor.set(this.props.json);\n    }\n    if ('text' in this.props) {\n      this.jsoneditor.setText(this.props.text);\n    }\n    this.schema = cloneDeep(this.props.schema);\n    this.schemaRefs = cloneDeep(this.props.schemaRefs);\n  }\n\n  componentDidUpdate() {\n    if ('json' in this.props) {\n      this.jsoneditor.update(this.props.json);\n    }\n\n    if ('text' in this.props) {\n      this.jsoneditor.updateText(this.props.text);\n    }\n\n    if ('mode' in this.props) {\n      this.jsoneditor.setMode(this.props.mode);\n    }\n\n    // store a clone of the schema to keep track on when it actually changes.\n    // (When using a PureComponent all of this would be redundant)\n    const schemaChanged = !isEqual(this.props.schema, this.schema);\n    const schemaRefsChanged = !isEqual(this.props.schemaRefs, this.schemaRefs);\n    if (schemaChanged || schemaRefsChanged) {\n      this.schema = cloneDeep(this.props.schema);\n      this.schemaRefs = cloneDeep(this.props.schemaRefs);\n      this.jsoneditor.setSchema(this.props.schema, this.props.schemaRefs);\n    }\n  }\n\n  componentWillUnmount () {\n    if (this.jsoneditor) {\n      this.jsoneditor.destroy();\n    }\n  }\n\n  render() {\n    return (\n        <div className=\"jsoneditor-react-container\" ref={elem => this.container = elem} />\n    );\n  }\n}\n"
  },
  {
    "path": "examples/react_advanced_demo/src/index.css",
    "content": "body {\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "examples/react_advanced_demo/src/index.js",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nconst container = document.getElementById('root')\nconst root = createRoot(container)\nroot.render(<App />);\n"
  },
  {
    "path": "examples/react_demo/.gitignore",
    "content": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "examples/react_demo/README.md",
    "content": "# JSONEditor React demo\n\nThis project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).\n\n## Install\n\nInstall dependencies once:\n\n```\nnpm install\n```\n\n## Run\n\nTo run the demo:\n\n```\nnpm start\n```\n\nThis will open a development server at http://localhost:3000\n"
  },
  {
    "path": "examples/react_demo/package.json",
    "content": "{\n  \"name\": \"react_demo\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"jsoneditor\": \"latest\",\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"18.2.0\",\n    \"react-scripts\": \"5.0.1\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test --env=jsdom\",\n    \"eject\": \"react-scripts eject\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/react_demo/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta name=\"theme-color\" content=\"#000000\">\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>JSONEditor | React demo</title>\n  </head>\n  <body>\n    <noscript>\n      You need to enable JavaScript to run this app.\n    </noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "examples/react_demo/src/App.css",
    "content": ".app .contents {\n  width: 500px;\n  height: 400px;\n}\n\n.app .contents .menu {\n  padding: 10px 0;\n}\n\n.app .contents .code {\n  background: #f5f5f5;\n}"
  },
  {
    "path": "examples/react_demo/src/App.js",
    "content": "import React, { Component } from 'react';\n\nimport JSONEditorDemo from './JSONEditorDemo';\nimport './App.css';\n\nclass App extends Component {\n  state = {\n    json: {\n      'array': [1, 2, 3],\n      'boolean': true,\n      'null': null,\n      'number': 123,\n      'object': {'a': 'b', 'c': 'd'},\n      'string': 'Hello World'\n    }\n  };\n\n  render() {\n    return (\n      <div className=\"app\">\n        <h1>JSONEditor React demo</h1>\n        <div className=\"contents\">\n          <div className=\"menu\">\n            <button onClick={this.updateTime}>\n              Create/update a field \"time\"\n            </button>\n          </div>\n          <JSONEditorDemo\n              json={this.state.json}\n              onChangeJSON={this.onChangeJSON}\n          />\n          <div className=\"code\">\n            <pre>\n              <code>\n                {JSON.stringify(this.state.json, null, 2)}\n              </code>\n            </pre>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  onChangeJSON = (json) => {\n    this.setState({ json });\n  };\n\n  updateTime = () => {\n    const time = new Date().toISOString();\n\n    this.setState({\n      json: Object.assign({}, this.state.json, { time })\n    })\n  };\n}\n\nexport default App;\n"
  },
  {
    "path": "examples/react_demo/src/App.test.js",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nit('renders without crashing', () => {\n  const div = document.createElement('div');\n  ReactDOM.render(<App />, div);\n  ReactDOM.unmountComponentAtNode(div);\n});\n"
  },
  {
    "path": "examples/react_demo/src/JSONEditorDemo.css",
    "content": ".jsoneditor-react-container {\n  width: 100%;\n  height: 100%;\n}\n"
  },
  {
    "path": "examples/react_demo/src/JSONEditorDemo.js",
    "content": "import React, {Component} from 'react';\n\nimport JSONEditor from 'jsoneditor';\nimport 'jsoneditor/dist/jsoneditor.css';\n\nimport './JSONEditorDemo.css';\n\nexport default class JSONEditorDemo extends Component {\n  componentDidMount () {\n    const options = {\n      mode: 'tree',\n      onChangeJSON: this.props.onChangeJSON\n    };\n\n    this.jsoneditor = new JSONEditor(this.container, options);\n    this.jsoneditor.set(this.props.json);\n  }\n\n  componentWillUnmount () {\n    if (this.jsoneditor) {\n      this.jsoneditor.destroy();\n    }\n  }\n\n  componentDidUpdate() {\n    this.jsoneditor.update(this.props.json);\n  }\n\n  render() {\n    return (\n        <div className=\"jsoneditor-react-container\" ref={elem => this.container = elem} />\n    );\n  }\n}\n"
  },
  {
    "path": "examples/react_demo/src/index.css",
    "content": "body {\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "examples/react_demo/src/index.js",
    "content": "import React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nconst container = document.getElementById('root')\nconst root = createRoot(container)\nroot.render(<App />);\n\n"
  },
  {
    "path": "examples/requirejs_demo/requirejs_demo.html",
    "content": "<!DOCTYPE HTML>\n<html>\n<head>\n  <title>JSONEditor | Require.js demo</title>\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../dist/jsoneditor.css\">\n  <script data-main=\"scripts/main\" src=\"https://requirejs.org/docs/release/2.3.6/minified/require.js\"></script>\n</head>\n<body>\n<p>\n  <button id=\"setJSON\">Set JSON</button>\n  <button id=\"getJSON\">Get JSON</button>\n</p>\n<div id=\"jsoneditor\"></div>\n</body>\n</html>\n"
  },
  {
    "path": "examples/requirejs_demo/scripts/main.js",
    "content": "const module = '../../../dist/jsoneditor'\nrequire([module], function (JSONEditor) {\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const editor = new JSONEditor(container)\n\n  // set json\n  document.getElementById('setJSON').onclick = function () {\n    const json = {\n      array: [1, 2, 3],\n      boolean: true,\n      null: null,\n      number: 123,\n      object: { a: 'b', c: 'd' },\n      string: 'Hello World'\n    }\n    editor.set(json)\n  }\n\n  // get json\n  document.getElementById('getJSON').onclick = function () {\n    const json = editor.get()\n    window.alert(JSON.stringify(json, null, 2))\n  }\n})\n"
  },
  {
    "path": "greenkeeper.json",
    "content": "{\n  \"groups\": {\n    \"default\": {\n      \"packages\": [\n        \"examples/react_advanced_demo/package.json\",\n        \"examples/react_demo/package.json\",\n        \"package.json\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "gulpfile.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst gulp = require('gulp')\nconst log = require('fancy-log')\nconst format = require('date-format')\nconst concatCss = require('gulp-concat-css')\nconst minifyCSS = require('gulp-clean-css')\nconst sass = require('gulp-sass')(require('sass'))\nconst { mkdirp } = require('mkdirp')\nconst webpack = require('webpack')\nconst uglify = require('uglify-js')\nconst btoa = require('btoa')\n\nconst NAME = 'jsoneditor'\nconst NAME_MINIMALIST = 'jsoneditor-minimalist'\nconst ENTRY = './src/js/JSONEditor.js'\nconst HEADER = './src/js/header.js'\nconst IMAGE = './src/scss/img/jsoneditor-icons.svg'\nconst DOCS = './src/docs/*'\nconst DIST = path.join(__dirname, 'dist')\n\n// generate banner with today's date and correct version\nfunction createBanner () {\n  const today = format.asString('yyyy-MM-dd', new Date()) // today, formatted as yyyy-MM-dd\n  const version = require('./package.json').version // math.js version\n\n  return String(fs.readFileSync(HEADER))\n    .replace('@@date', today)\n    .replace('@@version', version)\n}\n\nconst bannerPlugin = new webpack.BannerPlugin({\n  banner: createBanner(),\n  entryOnly: true,\n  raw: true\n})\n\nconst webpackConfigModule = {\n  rules: [\n    {\n      test: /\\.m?js$/,\n      exclude: /node_modules/,\n      use: {\n        loader: 'babel-loader'\n      }\n    },\n    {\n      test: /\\.js$/,\n      use: ['source-map-loader'],\n      enforce: 'pre'\n    }\n  ]\n}\n\n// create a single instance of the compiler to allow caching\nconst compiler = webpack({\n  entry: ENTRY,\n  target: ['web', 'es5'],\n  output: {\n    library: 'JSONEditor',\n    libraryTarget: 'umd',\n    path: DIST,\n    filename: NAME + '.js'\n  },\n  plugins: [bannerPlugin],\n  optimization: {\n    // We no not want to minimize our code.\n    minimize: false\n  },\n  module: webpackConfigModule,\n  resolve: {\n    extensions: ['.js'],\n    mainFields: ['main'] // pick ES5 version of vanilla-picker\n  },\n  cache: true\n})\n\n// create a single instance of the compiler to allow caching\nconst compilerMinimalist = webpack({\n  entry: ENTRY,\n  target: ['web', 'es5'],\n  output: {\n    library: 'JSONEditor',\n    libraryTarget: 'umd',\n    path: DIST,\n    filename: NAME_MINIMALIST + '.js'\n  },\n  module: webpackConfigModule,\n  plugins: [\n    bannerPlugin,\n    new webpack.IgnorePlugin({ resourceRegExp: /^ace-builds/ }),\n    new webpack.IgnorePlugin({ resourceRegExp: /worker-json-data-url/ }),\n    new webpack.IgnorePlugin({ resourceRegExp: /^ajv/ }),\n    new webpack.IgnorePlugin({ resourceRegExp: /^vanilla-picker/ })\n  ],\n  optimization: {\n    // We no not want to minimize our code.\n    minimize: false\n  },\n  cache: true\n})\n\nfunction minify (name) {\n  const code = String(fs.readFileSync(DIST + '/' + name + '.js'))\n  const result = uglify.minify({ [name + '.js']: code }, {\n    sourceMap: {\n      url: name + '.map'\n    },\n    output: {\n      comments: /@license/,\n      max_line_len: 64000 // extra large because we have embedded code for workers\n    }\n  })\n\n  if (result.error) {\n    throw result.error\n  }\n\n  const fileMin = DIST + '/' + name + '.min.js'\n  const fileMap = DIST + '/' + name + '.map'\n\n  fs.writeFileSync(fileMin, result.code)\n  fs.writeFileSync(fileMap, result.map)\n\n  log('Minified ' + fileMin)\n  log('Mapped ' + fileMap)\n}\n\n// make dist folder structure\ngulp.task('mkdir', function (done) {\n  mkdirp.sync(DIST)\n  mkdirp.sync(DIST + '/img')\n\n  done()\n})\n\n// Create an embedded version of the json worker code: a data url\ngulp.task('embed-json-worker', function (done) {\n  const workerBundleFile = './node_modules/ace-builds/src-noconflict/worker-json.js'\n  const workerEmbeddedFile = './src/js/generated/worker-json-data-url.js'\n  const workerScript = String(fs.readFileSync(workerBundleFile))\n\n  const workerDataUrl = 'data:application/javascript;base64,' + btoa(workerScript)\n\n  fs.writeFileSync(workerEmbeddedFile, 'module.exports = \\'' + workerDataUrl + '\\'\\n')\n\n  done()\n})\n\n// bundle javascript\ngulp.task('bundle', function (done) {\n  // update the banner contents (has a date in it which should stay up to date)\n  bannerPlugin.banner = createBanner()\n\n  compiler.run(function (err, stats) {\n    if (err) {\n      log(err)\n    }\n\n    log('bundled ' + NAME + '.js')\n\n    done()\n  })\n})\n\n// bundle minimalist version of javascript\ngulp.task('bundle-minimalist', function (done) {\n  // update the banner contents (has a date in it which should stay up to date)\n  bannerPlugin.banner = createBanner()\n\n  compilerMinimalist.run(function (err, stats) {\n    if (err) {\n      log(err)\n    }\n\n    log('bundled ' + NAME_MINIMALIST + '.js')\n\n    done()\n  })\n})\n\n// bundle css\ngulp.task('bundle-css', function (done) {\n  const concatOptions = { rebaseUrls: false }\n  const minifyOptions = { rebase: false }\n  gulp\n    .src(['src/scss/jsoneditor.scss'])\n    .pipe(\n      sass({\n        // importer: tildeImporter\n      })\n    )\n    .pipe(concatCss(NAME + '.css', concatOptions))\n    .pipe(gulp.dest(DIST))\n    .pipe(concatCss(NAME + '.min.css', concatOptions))\n    .pipe(minifyCSS(minifyOptions))\n    .pipe(gulp.dest(DIST))\n  done()\n})\n\n// create a folder img and copy the icons\ngulp.task('copy-img', function (done) {\n  gulp.src(IMAGE).pipe(gulp.dest(DIST + '/img'))\n  log('Copied images')\n\n  done()\n})\n\n// create a folder img and copy the icons\ngulp.task('copy-docs', function (done) {\n  gulp.src(DOCS).pipe(gulp.dest(DIST))\n  log('Copied doc')\n\n  done()\n})\n\ngulp.task('minify', function (done) {\n  minify(NAME)\n\n  done()\n})\n\ngulp.task('minify-minimalist', function (done) {\n  minify(NAME_MINIMALIST)\n\n  done()\n})\n\n// The watch task (to automatically rebuild when the source code changes)\n// Does only generate jsoneditor.js and jsoneditor.css, and copy the image\n// Does NOT minify the code and does NOT generate the minimalist version\ngulp.task('watch', gulp.series('bundle', 'bundle-css', 'copy-img', function () {\n  gulp.watch(['src/**/*'], gulp.series('bundle', 'bundle-css', 'copy-img'))\n}))\n\n// The default task (called when you run `gulp`)\ngulp.task('default', gulp.series(\n  'mkdir',\n  'embed-json-worker',\n  gulp.parallel(\n    'copy-img',\n    'copy-docs',\n    'bundle-css',\n    gulp.series('bundle', 'minify'),\n    gulp.series('bundle-minimalist', 'minify-minimalist')\n  )\n))\n"
  },
  {
    "path": "index.js",
    "content": "module.exports = require('./dist/jsoneditor')\n"
  },
  {
    "path": "misc/how_to_publish.md",
    "content": "# How to publish jsoneditor\n\nThis document describes the steps required to publish a new version of jsoneditor.\n\n\n## Update version number\n\nUpdate the version number in package.json.\n\nUpdate package-lock.json:\n\n    npm install\n\n\n## Update history\n\nUpdate the date and version number in the file HISTORY.md. Verify whether all\nchanges in the new version are described.\n\n\n## Test the library\n\nRun the unit tests and validate whether all tests pass:\n\n    npm test\n\n\n## Build library\n\nBuild the build (jsoneditor.js, jsoneditor.css, ...) files by running:\n\n    npm run build-and-test\n\nAfter the build is complete, verify if the files are updated and contain the\ncorrect date and version number in the header.\n\n\n## Test\n\nTest whether the npm library is ok by opening some examples, and check whether\nthe files under `dists` are created and have contents.\n\n\n## Commit\n\n- Commit the final code.\n- Merge the develop branch into the master branch.\n- Push to github.\n\nIf everything is well, create a tag for the new version, like:\n\n    git tag v1.2.4\n    git push --tags\n\n\n## Publish\n\nPublish to npm:\n\n    npm publish\n\n\n## Test published library\n\nInstall the libraries locally and test whether they work correctly:\n\n    cd tmp-folder\n    npm install jsoneditor\n\n\n## Done\n\nCongrats, be proud.\n\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"jsoneditor\",\n  \"version\": \"10.4.2\",\n  \"main\": \"./dist/jsoneditor.min.js\",\n  \"description\": \"A web-based tool to view, edit, format, and validate JSON\",\n  \"tags\": [\n    \"json\",\n    \"editor\",\n    \"viewer\",\n    \"formatter\"\n  ],\n  \"author\": \"Jos de Jong <wjosdejong@gmail.com>\",\n  \"license\": \"Apache-2.0\",\n  \"homepage\": \"https://jsoneditoronline.org\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/josdejong/jsoneditor.git\"\n  },\n  \"bugs\": \"https://github.com/josdejong/jsoneditor/issues\",\n  \"scripts\": {\n    \"build\": \"gulp\",\n    \"build-and-test\": \"npm run build && npm test && npm run lint\",\n    \"minify\": \"gulp minify\",\n    \"start\": \"gulp watch\",\n    \"test\": \"mocha test --require @babel/register\",\n    \"lint\": \"standard --env=mocha\",\n    \"format\": \"standard --env=mocha --fix\",\n    \"prepublishOnly\": \"npm run build-and-test\"\n  },\n  \"dependencies\": {\n    \"ace-builds\": \"^1.36.2\",\n    \"ajv\": \"^6.12.6\",\n    \"javascript-natural-sort\": \"^0.7.1\",\n    \"jmespath\": \"^0.16.0\",\n    \"json-source-map\": \"^0.6.1\",\n    \"jsonrepair\": \"^3.8.1\",\n    \"picomodal\": \"^3.0.0\",\n    \"vanilla-picker\": \"^2.12.3\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"7.28.4\",\n    \"@babel/preset-env\": \"7.28.3\",\n    \"@babel/register\": \"7.28.3\",\n    \"babel-loader\": \"10.0.0\",\n    \"btoa\": \"1.2.1\",\n    \"date-format\": \"4.0.14\",\n    \"fancy-log\": \"2.0.0\",\n    \"gulp\": \"5.0.1\",\n    \"gulp-clean-css\": \"4.3.0\",\n    \"gulp-concat-css\": \"3.1.0\",\n    \"gulp-sass\": \"6.0.1\",\n    \"jsdom\": \"27.0.0\",\n    \"json-loader\": \"0.5.7\",\n    \"mkdirp\": \"3.0.1\",\n    \"mocha\": \"11.7.4\",\n    \"sass\": \"1.93.2\",\n    \"source-map-loader\": \"5.0.0\",\n    \"standard\": \"17.1.2\",\n    \"uglify-js\": \"3.19.3\",\n    \"webpack\": \"5.105.0\"\n  },\n  \"files\": [\n    \"dist\",\n    \"docs\",\n    \"examples\",\n    \"src\",\n    \"HISTORY.md\",\n    \"index.js\",\n    \"LICENSE\",\n    \"NOTICE\",\n    \"README.md\"\n  ],\n  \"standard\": {\n    \"ignore\": [\n      \"src/js/assets\",\n      \"examples/react*\"\n    ]\n  }\n}\n"
  },
  {
    "path": "src/docs/which files do I need.md",
    "content": "# Which files do I need?\n\nEhhh, that's quite some files in this dist folder. Which files do I need?\n\n\n## Full version\n\nIf you're not sure which version to use, use the full version.\n\nWhich files are needed when using the full version?\n\n- jsoneditor.min.js\n- jsoneditor.map (optional, for debugging purposes only)\n- jsoneditor.min.css\n- img/jsoneditor-icons.svg\n\n\n## Minimalist version\n\nThe minimalist version has excluded the following libraries:\n\n- `ace` (via `brace`), used for the code editor.\n- `ajv`, used for JSON schema validation.\n- `vanilla-picker`, used as color picker.\n\nThis reduces the the size of the minified and gzipped JavaScript file\nfrom about 210 kB to about 70 kB (one third).\n\nWhen to use the minimalist version?\n\n- If you don't need the mode \"code\" and don't need JSON schema validation.\n- Or if you want to provide `ace` and/or `ajv` yourself via the configuration\n  options, for example when you already use Ace in other parts of your\n  web application too and don't want to bundle the library twice.\n- You don't need the color picker, or want to provide your own\n  color picker using `onColorPicker`.\n\nWhich files are needed when using the minimalist version?\n\n- jsoneditor-minimalist.min.js\n- jsoneditor-minimalist.map (optional, for debugging purposes only)\n- jsoneditor.min.css\n- img/jsoneditor-icons.svg\n\n"
  },
  {
    "path": "src/js/ContextMenu.js",
    "content": "'use strict'\n\nimport { createAbsoluteAnchor } from './createAbsoluteAnchor'\nimport { addClassName, getSelection, removeClassName, setSelection } from './util'\nimport { translate } from './i18n'\n\n/**\n * A context menu\n * @param {Object[]} items    Array containing the menu structure\n *                            TODO: describe structure\n * @param {Object} [options]  Object with options. Available options:\n *                            {function} close        Callback called when the\n *                                                    context menu is being closed.\n *                            {boolean} limitHeight   Whether ContextMenu height should be\n *                                                    limited or not.\n * @constructor\n */\nexport class ContextMenu {\n  constructor (items, options) {\n    this.dom = {}\n\n    const me = this\n    const dom = this.dom\n    this.anchor = undefined\n    this.items = items\n    this.eventListeners = {}\n    this.selection = undefined // holds the selection before the menu was opened\n    this.onClose = options ? options.close : undefined\n    this.limitHeight = options ? options.limitHeight : false\n\n    // create root element\n    const root = document.createElement('div')\n    root.className = 'jsoneditor-contextmenu-root'\n    dom.root = root\n\n    // create a container element\n    const menu = document.createElement('div')\n    menu.className = 'jsoneditor-contextmenu'\n    dom.menu = menu\n    root.appendChild(menu)\n\n    // create a list to hold the menu items\n    const list = document.createElement('ul')\n    list.className = 'jsoneditor-menu'\n    menu.appendChild(list)\n    dom.list = list\n    dom.items = [] // list with all buttons\n\n    // create a (non-visible) button to set the focus to the menu\n    const focusButton = document.createElement('button')\n    focusButton.type = 'button'\n    dom.focusButton = focusButton\n    const li = document.createElement('li')\n    li.style.overflow = 'hidden'\n    li.style.height = '0'\n    li.appendChild(focusButton)\n    list.appendChild(li)\n\n    function createMenuItems (list, domItems, items) {\n      items.forEach(item => {\n        if (item.type === 'separator') {\n          // create a separator\n          const separator = document.createElement('div')\n          separator.className = 'jsoneditor-separator'\n          const li = document.createElement('li')\n          li.appendChild(separator)\n          list.appendChild(li)\n        } else {\n          const domItem = {}\n\n          // create a menu item\n          const li = document.createElement('li')\n          list.appendChild(li)\n\n          // create a button in the menu item\n          const button = document.createElement('button')\n          button.type = 'button'\n          button.className = item.className\n          domItem.button = button\n          if (item.title) {\n            button.title = item.title\n          }\n          if (item.click) {\n            button.onclick = event => {\n              event.preventDefault()\n              me.hide()\n              item.click()\n            }\n          }\n          li.appendChild(button)\n\n          // create the contents of the button\n          if (item.submenu) {\n            // add the icon to the button\n            const divIcon = document.createElement('div')\n            divIcon.className = 'jsoneditor-icon'\n            button.appendChild(divIcon)\n            const divText = document.createElement('div')\n            divText.className = 'jsoneditor-text' +\n                (item.click ? '' : ' jsoneditor-right-margin')\n            divText.appendChild(document.createTextNode(item.text))\n            button.appendChild(divText)\n\n            let buttonSubmenu\n            if (item.click) {\n              // submenu and a button with a click handler\n              button.className += ' jsoneditor-default'\n\n              const buttonExpand = document.createElement('button')\n              buttonExpand.type = 'button'\n              domItem.buttonExpand = buttonExpand\n              buttonExpand.className = 'jsoneditor-expand'\n              const buttonExpandInner = document.createElement('div')\n              buttonExpandInner.className = 'jsoneditor-expand'\n              buttonExpand.appendChild(buttonExpandInner)\n              li.appendChild(buttonExpand)\n              if (item.submenuTitle) {\n                buttonExpand.title = item.submenuTitle\n              }\n\n              buttonSubmenu = buttonExpand\n            } else {\n              // submenu and a button without a click handler\n              const divExpand = document.createElement('div')\n              divExpand.className = 'jsoneditor-expand'\n              button.appendChild(divExpand)\n\n              buttonSubmenu = button\n            }\n\n            // attach a handler to expand/collapse the submenu\n            buttonSubmenu.onclick = event => {\n              event.preventDefault()\n              me._onExpandItem(domItem)\n              buttonSubmenu.focus()\n            }\n\n            // create the submenu\n            const domSubItems = []\n            domItem.subItems = domSubItems\n            const ul = document.createElement('ul')\n            domItem.ul = ul\n            ul.className = 'jsoneditor-menu'\n            ul.style.height = '0'\n            li.appendChild(ul)\n            createMenuItems(ul, domSubItems, item.submenu)\n          } else {\n            // no submenu, just a button with clickhandler\n            const icon = document.createElement('div')\n            icon.className = 'jsoneditor-icon'\n            button.appendChild(icon)\n\n            const text = document.createElement('div')\n            text.className = 'jsoneditor-text'\n            text.appendChild(document.createTextNode(translate(item.text)))\n            button.appendChild(text)\n          }\n\n          domItems.push(domItem)\n        }\n      })\n    }\n    createMenuItems(list, this.dom.items, items)\n\n    // TODO: when the editor is small, show the submenu on the right instead of inline?\n\n    // calculate the max height of the menu with one submenu expanded\n    this.maxHeight = 0 // height in pixels\n    items.forEach(item => {\n      const height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24\n      me.maxHeight = Math.max(me.maxHeight, height)\n    })\n  }\n\n  /**\n   * Get the currently visible buttons\n   * @return {Array.<HTMLElement>} buttons\n   * @private\n   */\n  _getVisibleButtons () {\n    const buttons = []\n    const me = this\n    this.dom.items.forEach(item => {\n      buttons.push(item.button)\n      if (item.buttonExpand) {\n        buttons.push(item.buttonExpand)\n      }\n      if (item.subItems && item === me.expandedItem) {\n        item.subItems.forEach(subItem => {\n          buttons.push(subItem.button)\n          if (subItem.buttonExpand) {\n            buttons.push(subItem.buttonExpand)\n          }\n          // TODO: change to fully recursive method\n        })\n      }\n    })\n\n    return buttons\n  }\n\n  /**\n   * Attach the menu to an anchor\n   * @param {HTMLElement} anchor    Anchor where the menu will be attached as sibling.\n   * @param {HTMLElement} frame     The root of the JSONEditor window\n   * @param {Boolean=} ignoreParent ignore anchor parent in regard to the calculation of the position, needed when the parent position is absolute\n   */\n  show (anchor, frame, ignoreParent) {\n    this.hide()\n\n    // determine whether to display the menu below or above the anchor\n    let showBelow = true\n    const parent = anchor.parentNode\n    const anchorRect = anchor.getBoundingClientRect()\n    const parentRect = parent.getBoundingClientRect()\n    const frameRect = frame.getBoundingClientRect()\n\n    const me = this\n    this.dom.absoluteAnchor = createAbsoluteAnchor(anchor, frame, () => {\n      me.hide()\n    })\n\n    if (anchorRect.bottom + this.maxHeight < frameRect.bottom) {\n      // fits below -> show below\n    } else if (anchorRect.top - this.maxHeight > frameRect.top) {\n      // fits above -> show above\n      showBelow = false\n    } else {\n      // doesn't fit above nor below -> show below\n    }\n\n    const topGap = ignoreParent ? 0 : (anchorRect.top - parentRect.top)\n\n    // position the menu\n    if (showBelow) {\n      // display the menu below the anchor\n      const anchorHeight = anchor.offsetHeight\n      this.dom.menu.style.left = '0'\n      this.dom.menu.style.top = topGap + anchorHeight + 'px'\n      this.dom.menu.style.bottom = ''\n    } else {\n      // display the menu above the anchor\n      this.dom.menu.style.left = '0'\n      this.dom.menu.style.top = ''\n      this.dom.menu.style.bottom = '0px'\n    }\n\n    if (this.limitHeight) {\n      const margin = 10 // make sure there is a little margin left\n      const maxPossibleMenuHeight = showBelow\n        ? frameRect.bottom - anchorRect.bottom - margin\n        : anchorRect.top - frameRect.top - margin\n      this.dom.list.style.maxHeight = maxPossibleMenuHeight + 'px'\n      this.dom.list.style.overflowY = 'auto'\n    }\n\n    // attach the menu to the temporary, absolute anchor\n    // parent.insertBefore(this.dom.root, anchor);\n    this.dom.absoluteAnchor.appendChild(this.dom.root)\n\n    // move focus to the first button in the context menu\n    this.selection = getSelection()\n    this.anchor = anchor\n    setTimeout(() => {\n      me.dom.focusButton.focus()\n    }, 0)\n\n    if (ContextMenu.visibleMenu) {\n      ContextMenu.visibleMenu.hide()\n    }\n    ContextMenu.visibleMenu = this\n  }\n\n  /**\n   * Hide the context menu if visible\n   */\n  hide () {\n    // remove temporary absolutely positioned anchor\n    if (this.dom.absoluteAnchor) {\n      this.dom.absoluteAnchor.destroy()\n      delete this.dom.absoluteAnchor\n    }\n\n    // remove the menu from the DOM\n    if (this.dom.root.parentNode) {\n      this.dom.root.parentNode.removeChild(this.dom.root)\n      if (this.onClose) {\n        this.onClose()\n      }\n    }\n\n    if (ContextMenu.visibleMenu === this) {\n      ContextMenu.visibleMenu = undefined\n    }\n  }\n\n  /**\n   * Expand a submenu\n   * Any currently expanded submenu will be hided.\n   * @param {Object} domItem\n   * @private\n   */\n  _onExpandItem (domItem) {\n    const me = this\n    const alreadyVisible = (domItem === this.expandedItem)\n\n    // hide the currently visible submenu\n    const expandedItem = this.expandedItem\n    if (expandedItem) {\n      // var ul = expandedItem.ul;\n      expandedItem.ul.style.height = '0'\n      expandedItem.ul.style.padding = ''\n      setTimeout(() => {\n        if (me.expandedItem !== expandedItem) {\n          expandedItem.ul.style.display = ''\n          removeClassName(expandedItem.ul.parentNode, 'jsoneditor-selected')\n        }\n      }, 300) // timeout duration must match the css transition duration\n      this.expandedItem = undefined\n    }\n\n    if (!alreadyVisible) {\n      const ul = domItem.ul\n      ul.style.display = 'block'\n      // eslint-disable-next-line no-unused-expressions\n      ul.clientHeight // force a reflow in Firefox\n      setTimeout(() => {\n        if (me.expandedItem === domItem) {\n          let childsHeight = 0\n          for (let i = 0; i < ul.childNodes.length; i++) {\n            childsHeight += ul.childNodes[i].clientHeight\n          }\n          ul.style.height = childsHeight + 'px'\n          ul.style.padding = '5px 10px'\n        }\n      }, 0)\n      addClassName(ul.parentNode, 'jsoneditor-selected')\n      this.expandedItem = domItem\n    }\n  }\n\n  /**\n   * Handle onkeydown event\n   * @param {Event} event\n   * @private\n   */\n  _onKeyDown (event) {\n    const target = event.target\n    const keynum = event.which\n    let handled = false\n    let buttons, targetIndex, prevButton, nextButton\n\n    if (keynum === 27) { // ESC\n      // hide the menu on ESC key\n\n      // restore previous selection and focus\n      if (this.selection) {\n        setSelection(this.selection)\n      }\n      if (this.anchor) {\n        this.anchor.focus()\n      }\n\n      this.hide()\n\n      handled = true\n    } else if (keynum === 9) { // Tab\n      if (!event.shiftKey) { // Tab\n        buttons = this._getVisibleButtons()\n        targetIndex = buttons.indexOf(target)\n        if (targetIndex === buttons.length - 1) {\n          // move to first button\n          buttons[0].focus()\n          handled = true\n        }\n      } else { // Shift+Tab\n        buttons = this._getVisibleButtons()\n        targetIndex = buttons.indexOf(target)\n        if (targetIndex === 0) {\n          // move to last button\n          buttons[buttons.length - 1].focus()\n          handled = true\n        }\n      }\n    } else if (keynum === 37) { // Arrow Left\n      if (target.className === 'jsoneditor-expand') {\n        buttons = this._getVisibleButtons()\n        targetIndex = buttons.indexOf(target)\n        prevButton = buttons[targetIndex - 1]\n        if (prevButton) {\n          prevButton.focus()\n        }\n      }\n      handled = true\n    } else if (keynum === 38) { // Arrow Up\n      buttons = this._getVisibleButtons()\n      targetIndex = buttons.indexOf(target)\n      prevButton = buttons[targetIndex - 1]\n      if (prevButton && prevButton.className === 'jsoneditor-expand') {\n        // skip expand button\n        prevButton = buttons[targetIndex - 2]\n      }\n      if (!prevButton) {\n        // move to last button\n        prevButton = buttons[buttons.length - 1]\n      }\n      if (prevButton) {\n        prevButton.focus()\n      }\n      handled = true\n    } else if (keynum === 39) { // Arrow Right\n      buttons = this._getVisibleButtons()\n      targetIndex = buttons.indexOf(target)\n      nextButton = buttons[targetIndex + 1]\n      if (nextButton && nextButton.className === 'jsoneditor-expand') {\n        nextButton.focus()\n      }\n      handled = true\n    } else if (keynum === 40) { // Arrow Down\n      buttons = this._getVisibleButtons()\n      targetIndex = buttons.indexOf(target)\n      nextButton = buttons[targetIndex + 1]\n      if (nextButton && nextButton.className === 'jsoneditor-expand') {\n        // skip expand button\n        nextButton = buttons[targetIndex + 2]\n      }\n      if (!nextButton) {\n        // move to first button\n        nextButton = buttons[0]\n      }\n      if (nextButton) {\n        nextButton.focus()\n        handled = true\n      }\n      handled = true\n    }\n    // TODO: arrow left and right\n\n    if (handled) {\n      event.stopPropagation()\n      event.preventDefault()\n    }\n  }\n}\n\n// currently displayed context menu, a singleton. We may only have one visible context menu\nContextMenu.visibleMenu = undefined\n"
  },
  {
    "path": "src/js/ErrorTable.js",
    "content": "/**\n * Show errors and schema warnings in a clickable table view\n * @param {Object} config\n * @property {boolean} errorTableVisible\n * @property {function (boolean) : void} onToggleVisibility\n * @property {function (number)} [onFocusLine]\n * @property {function (number)} onChangeHeight\n * @constructor\n */\nexport class ErrorTable {\n  constructor (config) {\n    this.errorTableVisible = config.errorTableVisible\n    this.onToggleVisibility = config.onToggleVisibility\n    this.onFocusLine = config.onFocusLine || (() => {})\n    this.onChangeHeight = config.onChangeHeight\n\n    this.dom = {}\n\n    const validationErrorsContainer = document.createElement('div')\n    validationErrorsContainer.className = 'jsoneditor-validation-errors-container'\n    this.dom.validationErrorsContainer = validationErrorsContainer\n\n    const additionalErrorsIndication = document.createElement('div')\n    additionalErrorsIndication.style.display = 'none'\n    additionalErrorsIndication.className = 'jsoneditor-additional-errors fadein'\n    additionalErrorsIndication.textContent = 'Scroll for more \\u25BF'\n    this.dom.additionalErrorsIndication = additionalErrorsIndication\n    validationErrorsContainer.appendChild(additionalErrorsIndication)\n\n    const validationErrorIcon = document.createElement('span')\n    validationErrorIcon.className = 'jsoneditor-validation-error-icon'\n    validationErrorIcon.style.display = 'none'\n    this.dom.validationErrorIcon = validationErrorIcon\n\n    const validationErrorCount = document.createElement('span')\n    validationErrorCount.className = 'jsoneditor-validation-error-count'\n    validationErrorCount.style.display = 'none'\n    this.dom.validationErrorCount = validationErrorCount\n\n    this.dom.parseErrorIndication = document.createElement('span')\n    this.dom.parseErrorIndication.className = 'jsoneditor-parse-error-icon'\n    this.dom.parseErrorIndication.style.display = 'none'\n  }\n\n  getErrorTable () {\n    return this.dom.validationErrorsContainer\n  }\n\n  getErrorCounter () {\n    return this.dom.validationErrorCount\n  }\n\n  getWarningIcon () {\n    return this.dom.validationErrorIcon\n  }\n\n  getErrorIcon () {\n    return this.dom.parseErrorIndication\n  }\n\n  toggleTableVisibility () {\n    this.errorTableVisible = !this.errorTableVisible\n    this.onToggleVisibility(this.errorTableVisible)\n  }\n\n  setErrors (errors, errorLocations) {\n    // clear any previous errors\n    if (this.dom.validationErrors) {\n      this.dom.validationErrors.parentNode.removeChild(this.dom.validationErrors)\n      this.dom.validationErrors = null\n      this.dom.additionalErrorsIndication.style.display = 'none'\n    }\n\n    // create the table with errors\n    // keep default behavior for parse errors\n    if (this.errorTableVisible && errors.length > 0) {\n      const validationErrors = document.createElement('div')\n      validationErrors.className = 'jsoneditor-validation-errors'\n\n      const table = document.createElement('table')\n      table.className = 'jsoneditor-text-errors'\n      validationErrors.appendChild(table)\n\n      const tbody = document.createElement('tbody')\n      table.appendChild(tbody)\n\n      errors.forEach(error => {\n        let line\n\n        if (!isNaN(error.line)) {\n          line = error.line\n        } else if (error.dataPath) {\n          const errLoc = errorLocations.find(loc => loc.path === error.dataPath)\n          if (errLoc) {\n            line = errLoc.line + 1\n          }\n        }\n\n        const trEl = document.createElement('tr')\n        trEl.className = !isNaN(line) ? 'jump-to-line' : ''\n        if (error.type === 'error') {\n          trEl.className += ' parse-error'\n        } else {\n          trEl.className += ' validation-error'\n        }\n\n        const td1 = document.createElement('td')\n        const button = document.createElement('button')\n        button.className = 'jsoneditor-schema-error'\n        td1.appendChild(button)\n        trEl.appendChild(td1)\n\n        const td2 = document.createElement('td')\n        td2.style = 'white-space: nowrap;'\n        td2.textContent = (!isNaN(line) ? ('Ln ' + line) : '')\n        trEl.appendChild(td2)\n\n        if (typeof error === 'string') {\n          const td34 = document.createElement('td')\n          td34.colSpan = 2\n          const pre = document.createElement('pre')\n          pre.appendChild(document.createTextNode(error))\n          td34.appendChild(pre)\n          trEl.appendChild(td34)\n        } else {\n          const td3 = document.createElement('td')\n          td3.appendChild(document.createTextNode(error.dataPath || ''))\n          trEl.appendChild(td3)\n\n          const td4 = document.createElement('td')\n          const pre = document.createElement('pre')\n          pre.appendChild(document.createTextNode(error.message.replace(/<br>/gi, '\\n')))\n          td4.appendChild(pre)\n          trEl.appendChild(td4)\n        }\n\n        trEl.onclick = () => {\n          this.onFocusLine(line)\n        }\n\n        tbody.appendChild(trEl)\n      })\n\n      this.dom.validationErrors = validationErrors\n      this.dom.validationErrorsContainer.appendChild(validationErrors)\n      this.dom.additionalErrorsIndication.title = errors.length + ' errors total'\n\n      if (this.dom.validationErrorsContainer.clientHeight < this.dom.validationErrorsContainer.scrollHeight) {\n        this.dom.additionalErrorsIndication.style.display = 'block'\n        this.dom.validationErrorsContainer.onscroll = () => {\n          this.dom.additionalErrorsIndication.style.display =\n              (this.dom.validationErrorsContainer.clientHeight > 0 && this.dom.validationErrorsContainer.scrollTop === 0) ? 'block' : 'none'\n        }\n      } else {\n        this.dom.validationErrorsContainer.onscroll = undefined\n      }\n\n      const height = this.dom.validationErrorsContainer.clientHeight + (this.dom.statusBar ? this.dom.statusBar.clientHeight : 0)\n      // this.content.style.marginBottom = (-height) + 'px';\n      // this.content.style.paddingBottom = height + 'px';\n      this.onChangeHeight(height)\n    } else {\n      this.onChangeHeight(0)\n    }\n\n    // update the status bar\n    const validationErrorsCount = errors.filter(error => error.type !== 'error').length\n    if (validationErrorsCount > 0) {\n      this.dom.validationErrorCount.style.display = 'inline'\n      this.dom.validationErrorCount.innerText = validationErrorsCount\n      this.dom.validationErrorCount.onclick = this.toggleTableVisibility.bind(this)\n\n      this.dom.validationErrorIcon.style.display = 'inline'\n      this.dom.validationErrorIcon.title = validationErrorsCount + ' schema validation error(s) found'\n      this.dom.validationErrorIcon.onclick = this.toggleTableVisibility.bind(this)\n    } else {\n      this.dom.validationErrorCount.style.display = 'none'\n      this.dom.validationErrorIcon.style.display = 'none'\n    }\n\n    // update the parse error icon\n    const hasParseErrors = errors.some(error => error.type === 'error')\n    if (hasParseErrors) {\n      const line = errors[0].line\n      this.dom.parseErrorIndication.style.display = 'block'\n      this.dom.parseErrorIndication.title = !isNaN(line)\n        ? ('parse error on line ' + line)\n        : 'parse error - check that the json is valid'\n      this.dom.parseErrorIndication.onclick = this.toggleTableVisibility.bind(this)\n    } else {\n      this.dom.parseErrorIndication.style.display = 'none'\n    }\n  }\n}\n"
  },
  {
    "path": "src/js/FocusTracker.js",
    "content": "'use strict'\n\n/**\n * @constructor FocusTracker\n * A custom focus tracker for a DOM element with complex internal DOM structure\n * @param  {[Object]} config    A set of configurations for the FocusTracker\n *                {DOM Object} target *    The DOM object to track (required)\n *                {Function}   onFocus     onFocus callback\n *                {Function}   onBlur      onBlur callback\n *\n * @return\n */\n\nexport class FocusTracker {\n  constructor (config) {\n    this.target = config.target || null\n    if (!this.target) {\n      throw new Error('FocusTracker constructor called without a \"target\" to track.')\n    }\n\n    this.onFocus = (typeof config.onFocus === 'function') ? config.onFocus : null\n    this.onBlur = (typeof config.onBlur === 'function') ? config.onBlur : null\n    this._onClick = this._onEvent.bind(this)\n    this._onKeyUp = function (event) {\n      if (event.which === 9 || event.keyCode === 9) {\n        this._onEvent(event)\n      }\n    }.bind(this)\n    this._onBlur = this._onEvent.bind(this)\n\n    this.focusFlag = false\n    this.firstEventFlag = true\n\n    /*\n      Adds required (click and keyup) event listeners to the 'document' object\n      to track the focus of the given 'target'\n     */\n    if (this.onFocus || this.onBlur) {\n      document.addEventListener('click', this._onClick)\n      document.addEventListener('keyup', this._onKeyUp)\n      document.addEventListener('blur', this._onBlur)\n    }\n  }\n\n  /**\n     * Removes the event listeners on the 'document' object\n     * that were added to track the focus of the given 'target'\n     */\n  destroy () {\n    document.removeEventListener('click', this._onClick)\n    document.removeEventListener('keyup', this._onKeyUp)\n    document.removeEventListener('blur', this._onBlur)\n    this._onEvent({ target: document.body }) // calling _onEvent with body element in the hope that the FocusTracker is added to an element inside the body tag\n  }\n\n  /**\n     * Tracks the focus of the target and calls the onFocus and onBlur\n     * event callbacks if available.\n     * @param {Event} [event]  The 'click' or 'keyup' event object,\n     *                          from the respective events set on\n     *              document object\n     * @private\n     */\n\n  _onEvent (event) {\n    const target = event.target\n    let focusFlag\n    if (target === this.target) {\n      focusFlag = true\n    } else if (this.target.contains(target) || this.target.contains(document.activeElement)) {\n      focusFlag = true\n    } else {\n      focusFlag = false\n    }\n\n    if (focusFlag) {\n      if (!this.focusFlag) {\n        // trigger the onFocus callback\n        if (this.onFocus) {\n          this.onFocus({ type: 'focus', target: this.target })\n        }\n        this.focusFlag = true\n      }\n    } else {\n      if (this.focusFlag || this.firstEventFlag) {\n        // trigger the onBlur callback\n        if (this.onBlur) {\n          this.onBlur({ type: 'blur', target: this.target })\n        }\n        this.focusFlag = false\n\n        /*\n          When switching from one mode to another in the editor, the FocusTracker gets recreated.\n          At that time, this.focusFlag will be init to 'false' and will fail the above if condition, when blur occurs\n          this.firstEventFlag is added to overcome that issue\n         */\n        if (this.firstEventFlag) {\n          this.firstEventFlag = false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/js/Highlighter.js",
    "content": "'use strict'\n\n/**\n * The highlighter can highlight/unhighlight a node, and\n * animate the visibility of a context menu.\n * @constructor Highlighter\n */\nexport class Highlighter {\n  constructor () {\n    this.locked = false\n  }\n\n  /**\n   * Hightlight given node and its childs\n   * @param {Node} node\n   */\n  highlight (node) {\n    if (this.locked) {\n      return\n    }\n\n    if (this.node !== node) {\n      // unhighlight current node\n      if (this.node) {\n        this.node.setHighlight(false)\n      }\n\n      // highlight new node\n      this.node = node\n      this.node.setHighlight(true)\n    }\n\n    // cancel any current timeout\n    this._cancelUnhighlight()\n  }\n\n  /**\n   * Unhighlight currently highlighted node.\n   * Will be done after a delay\n   */\n  unhighlight () {\n    if (this.locked) {\n      return\n    }\n\n    const me = this\n    if (this.node) {\n      this._cancelUnhighlight()\n\n      // do the unhighlighting after a small delay, to prevent re-highlighting\n      // the same node when moving from the drag-icon to the contextmenu-icon\n      // or vice versa.\n      this.unhighlightTimer = setTimeout(() => {\n        me.node.setHighlight(false)\n        me.node = undefined\n        me.unhighlightTimer = undefined\n      }, 0)\n    }\n  }\n\n  /**\n   * Cancel an unhighlight action (if before the timeout of the unhighlight action)\n   * @private\n   */\n  _cancelUnhighlight () {\n    if (this.unhighlightTimer) {\n      clearTimeout(this.unhighlightTimer)\n      this.unhighlightTimer = undefined\n    }\n  }\n\n  /**\n   * Lock highlighting or unhighlighting nodes.\n   * methods highlight and unhighlight do not work while locked.\n   */\n  lock () {\n    this.locked = true\n  }\n\n  /**\n   * Unlock highlighting or unhighlighting nodes\n   */\n  unlock () {\n    this.locked = false\n  }\n}\n"
  },
  {
    "path": "src/js/History.js",
    "content": "/**\n * Keep track on any history, be able\n * @param {function} onChange\n * @param {function} calculateItemSize\n * @param {number} limit    Maximum size of all items in history\n * @constructor\n */\nexport class History {\n  constructor (onChange, calculateItemSize, limit) {\n    this.onChange = onChange\n    this.calculateItemSize = calculateItemSize || (() => 1)\n    this.limit = limit\n\n    this.items = []\n    this.index = -1\n  }\n\n  add (item) {\n    // limit number of items in history so that the total size doesn't\n    // always keep at least one item in memory\n    while (this._calculateHistorySize() > this.limit && this.items.length > 1) {\n      this.items.shift()\n      this.index--\n    }\n\n    // cleanup any redo action that are not valid anymore\n    this.items = this.items.slice(0, this.index + 1)\n\n    this.items.push(item)\n    this.index++\n\n    this.onChange()\n  }\n\n  _calculateHistorySize () {\n    const calculateItemSize = this.calculateItemSize\n    let totalSize = 0\n\n    this.items.forEach(item => {\n      totalSize += calculateItemSize(item)\n    })\n\n    return totalSize\n  }\n\n  undo () {\n    if (!this.canUndo()) {\n      return\n    }\n\n    this.index--\n\n    this.onChange()\n\n    return this.items[this.index]\n  }\n\n  redo () {\n    if (!this.canRedo()) {\n      return\n    }\n\n    this.index++\n\n    this.onChange()\n\n    return this.items[this.index]\n  }\n\n  canUndo () {\n    return this.index > 0\n  }\n\n  canRedo () {\n    return this.index < this.items.length - 1\n  }\n\n  clear () {\n    this.items = []\n    this.index = -1\n\n    this.onChange()\n  }\n}\n"
  },
  {
    "path": "src/js/JSONEditor.js",
    "content": "'use strict'\n\nconst ace = require('./ace') // may be undefined in case of minimalist bundle\nconst VanillaPicker = require('./vanilla-picker') // may be undefined in case of minimalist bundle\nconst { treeModeMixins } = require('./treemode')\nconst { textModeMixins } = require('./textmode')\nconst { previewModeMixins } = require('./previewmode')\nconst { clear, extend, getInnerText, getInternetExplorerVersion, parse } = require('./util')\nconst { tryRequireAjv } = require('./tryRequireAjv')\nconst { showTransformModal } = require('./showTransformModal')\nconst { showSortModal } = require('./showSortModal')\n\nconst Ajv = tryRequireAjv()\n\nif (typeof Promise === 'undefined') {\n  console.error('Promise undefined. Please load a Promise polyfill in the browser in order to use JSONEditor')\n}\n\n/**\n * @constructor JSONEditor\n * @param {Element} container    Container element\n * @param {Object}  [options]    Object with options. available options:\n *                               {String} mode        Editor mode. Available values:\n *                                                    'tree' (default), 'view',\n *                                                    'form', 'text', and 'code'.\n *                               {function} onChange  Callback method, triggered\n *                                                    on change of contents.\n *                                                    Does not pass the contents itself.\n *                                                    See also `onChangeJSON` and\n *                                                    `onChangeText`.\n *                               {function} onChangeJSON  Callback method, triggered\n *                                                        in modes on change of contents,\n *                                                        passing the changed contents\n *                                                        as JSON.\n *                                                        Only applicable for modes\n *                                                        'tree', 'view', and 'form'.\n *                               {function} onChangeText  Callback method, triggered\n *                                                        in modes on change of contents,\n *                                                        passing the changed contents\n *                                                        as stringified JSON.\n *                               {function} onError   Callback method, triggered\n *                                                    when an error occurs\n *                               {Boolean} search     Enable search box.\n *                                                    True by default\n *                                                    Only applicable for modes\n *                                                    'tree', 'view', and 'form'\n *                               {Boolean} history    Enable history (undo/redo).\n *                                                    True by default\n *                                                    Only applicable for modes\n *                                                    'tree', 'view', and 'form'\n *                               {String} name        Field name for the root node.\n *                                                    Only applicable for modes\n *                                                    'tree', 'view', and 'form'\n *                               {Number} indentation     Number of indentation\n *                                                        spaces. 4 by default.\n *                                                        Only applicable for\n *                                                        modes 'text' and 'code'\n *                               {boolean} escapeUnicode  If true, unicode\n *                                                        characters are escaped.\n *                                                        false by default.\n *                               {boolean} sortObjectKeys If true, object keys are\n *                                                        sorted before display.\n *                                                        false by default.\n *                               {function} onSelectionChange Callback method,\n *                                                            triggered on node selection change\n *                                                            Only applicable for modes\n *                                                            'tree', 'view', and 'form'\n *                               {function} onTextSelectionChange Callback method,\n *                                                                triggered on text selection change\n *                                                                Only applicable for modes\n *                               {HTMLElement} modalAnchor        The anchor element to apply an\n *                                                                overlay and display the modals in a\n *                                                                centered location.\n *                                                                Defaults to document.body\n *                                                                'text' and 'code'\n *                               {function} onEvent Callback method, triggered\n *                                                  when an event occurs in\n *                                                  a JSON field or value.\n *                                                  Only applicable for\n *                                                  modes 'form', 'tree' and\n *                                                  'view'\n *                               {function} onFocus  Callback method, triggered\n *                                                   when the editor comes into focus,\n *                                                   passing an object {type, target},\n *                                                   Applicable for all modes\n *                               {function} onBlur   Callback method, triggered\n *                                                   when the editor goes out of focus,\n *                                                   passing an object {type, target},\n *                                                   Applicable for all modes\n *                               {function} onClassName Callback method, triggered\n *                                                  when a Node DOM is rendered. Function returns\n *                                                  a css class name to be set on a node.\n *                                                  Only applicable for\n *                                                  modes 'form', 'tree' and\n *                                                  'view'\n *                               {Number} maxVisibleChilds Number of children allowed for a node\n *                                                         in 'tree', 'view', or 'form' mode before\n *                                                         the \"show more/show all\" buttons appear.\n *                                                         100 by default.\n *\n * @param {Object | undefined} json JSON object\n */\nfunction JSONEditor (container, options, json) {\n  if (!(this instanceof JSONEditor)) {\n    throw new Error('JSONEditor constructor called without \"new\".')\n  }\n\n  // check for unsupported browser (IE8 and older)\n  const ieVersion = getInternetExplorerVersion()\n  if (ieVersion !== -1 && ieVersion < 9) {\n    throw new Error('Unsupported browser, IE9 or newer required. ' +\n        'Please install the newest version of your browser.')\n  }\n\n  if (options) {\n    // check for deprecated options\n    if (options.error) {\n      console.warn('Option \"error\" has been renamed to \"onError\"')\n      options.onError = options.error\n      delete options.error\n    }\n    if (options.change) {\n      console.warn('Option \"change\" has been renamed to \"onChange\"')\n      options.onChange = options.change\n      delete options.change\n    }\n    if (options.editable) {\n      console.warn('Option \"editable\" has been renamed to \"onEditable\"')\n      options.onEditable = options.editable\n      delete options.editable\n    }\n\n    // warn if onChangeJSON is used when mode can be `text` or `code`\n    if (options.onChangeJSON) {\n      if (options.mode === 'text' || options.mode === 'code' ||\n          (options.modes && (options.modes.indexOf('text') !== -1 || options.modes.indexOf('code') !== -1))) {\n        console.warn('Option \"onChangeJSON\" is not applicable to modes \"text\" and \"code\". ' +\n            'Use \"onChangeText\" or \"onChange\" instead.')\n      }\n    }\n\n    // validate options\n    if (options) {\n      Object.keys(options).forEach(option => {\n        if (JSONEditor.VALID_OPTIONS.indexOf(option) === -1) {\n          console.warn('Unknown option \"' + option + '\". This option will be ignored')\n        }\n      })\n    }\n  }\n\n  if (arguments.length) {\n    this._create(container, options, json)\n  }\n}\n\n/**\n * Configuration for all registered modes. Example:\n * {\n *     tree: {\n *         mixin: TreeEditor,\n *         data: 'json'\n *     },\n *     text: {\n *         mixin: TextEditor,\n *         data: 'text'\n *     }\n * }\n *\n * @type { Object.<String, {mixin: Object, data: String} > }\n */\nJSONEditor.modes = {}\n\n// debounce interval for JSON schema validation in milliseconds\nJSONEditor.prototype.DEBOUNCE_INTERVAL = 150\n\nJSONEditor.VALID_OPTIONS = [\n  'ajv', 'schema', 'schemaRefs', 'templates',\n  'ace', 'theme', 'autocomplete',\n  'onChange', 'onChangeJSON', 'onChangeText', 'onExpand',\n  'onEditable', 'onError', 'onEvent', 'onModeChange', 'onNodeName', 'onValidate', 'onCreateMenu',\n  'onSelectionChange', 'onTextSelectionChange', 'onClassName',\n  'onFocus', 'onBlur',\n  'colorPicker', 'onColorPicker',\n  'timestampTag', 'timestampFormat',\n  'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',\n  'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform', 'limitDragging',\n  'maxVisibleChilds', 'onValidationError',\n  'modalAnchor', 'popupAnchor',\n  'createQuery', 'executeQuery', 'queryDescription',\n  'allowSchemaSuggestions', 'showErrorTable'\n]\n\n/**\n * Create the JSONEditor\n * @param {Element} container    Container element\n * @param {Object}  [options]    See description in constructor\n * @param {Object | undefined} json JSON object\n * @private\n */\nJSONEditor.prototype._create = function (container, options, json) {\n  this.container = container\n  this.options = options || {}\n  this.json = json || {}\n\n  const mode = this.options.mode || (this.options.modes && this.options.modes[0]) || 'tree'\n  this.setMode(mode)\n}\n\n/**\n * Destroy the editor. Clean up DOM, event listeners, and web workers.\n */\nJSONEditor.prototype.destroy = () => {}\n\n/**\n * Set JSON object in editor\n * @param {Object | undefined} json      JSON data\n */\nJSONEditor.prototype.set = function (json) {\n  this.json = json\n}\n\n/**\n * Get JSON from the editor\n * @returns {Object} json\n */\nJSONEditor.prototype.get = function () {\n  return this.json\n}\n\n/**\n * Set string containing JSON for the editor\n * @param {String | undefined} jsonText\n */\nJSONEditor.prototype.setText = function (jsonText) {\n  this.json = parse(jsonText)\n}\n\n/**\n * Get stringified JSON contents from the editor\n * @returns {String} jsonText\n */\nJSONEditor.prototype.getText = function () {\n  return JSON.stringify(this.json)\n}\n\n/**\n * Set a field name for the root node.\n * @param {String | undefined} name\n */\nJSONEditor.prototype.setName = function (name) {\n  if (!this.options) {\n    this.options = {}\n  }\n  this.options.name = name\n}\n\n/**\n * Get the field name for the root node.\n * @return {String | undefined} name\n */\nJSONEditor.prototype.getName = function () {\n  return this.options && this.options.name\n}\n\n/**\n * Change the mode of the editor.\n * JSONEditor will be extended with all methods needed for the chosen mode.\n * @param {String} mode     Available modes: 'tree' (default), 'view', 'form',\n *                          'text', and 'code'.\n */\nJSONEditor.prototype.setMode = function (mode) {\n  // if the mode is the same as current mode (and it's not the first time), do nothing.\n  if (mode === this.options.mode && this.create) {\n    return\n  }\n\n  const container = this.container\n  const options = extend({}, this.options)\n  const oldMode = options.mode\n\n  options.mode = mode\n  const config = JSONEditor.modes[mode]\n  if (!config) {\n    throw new Error('Unknown mode \"' + options.mode + '\"')\n  }\n\n  const asText = (config.data === 'text')\n  const name = this.getName()\n  const data = this[asText ? 'getText' : 'get']() // get text or json\n\n  this.destroy()\n  clear(this)\n  extend(this, config.mixin)\n  this.create(container, options)\n\n  this.setName(name)\n  this[asText ? 'setText' : 'set'](data) // set text or json\n\n  if (typeof config.load === 'function') {\n    try {\n      config.load.call(this)\n    } catch (err) {\n      console.error(err)\n    }\n  }\n\n  if (typeof options.onModeChange === 'function' && mode !== oldMode) {\n    try {\n      options.onModeChange(mode, oldMode)\n    } catch (err) {\n      console.error(err)\n    }\n  }\n}\n\n/**\n * Get the current mode\n * @return {string}\n */\nJSONEditor.prototype.getMode = function () {\n  return this.options.mode\n}\n\n/**\n * Throw an error. If an error callback is configured in options.error, this\n * callback will be invoked. Else, a basic alert window with the error message\n * will be shown to the user.\n * @param {Error} err\n * @private\n */\nJSONEditor.prototype._onError = function (err) {\n  if (this.options && typeof this.options.onError === 'function') {\n    this.options.onError(err)\n  } else {\n    window.alert(err.toString())\n  }\n}\n\n/**\n * Set a JSON schema for validation of the JSON object.\n * To remove the schema, call JSONEditor.setSchema(null)\n * @param {Object | null} schema\n * @param {Object.<string, Object>=} schemaRefs Schemas that are referenced using the `$ref` property from the JSON schema that are set in the `schema` option,\n +  the object structure in the form of `{reference_key: schemaObject}`\n */\nJSONEditor.prototype.setSchema = function (schema, schemaRefs) {\n  // compile a JSON schema validator if a JSON schema is provided\n  if (schema) {\n    let ajv\n    try {\n      // grab ajv from options if provided, else create a new instance\n      if (this.options.ajv) {\n        ajv = this.options.ajv\n      } else {\n        ajv = Ajv({\n          allErrors: true,\n          verbose: true,\n          schemaId: 'auto',\n          $data: true\n        })\n\n        // support both draft-04 and draft-06 alongside the latest draft-07\n        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'))\n        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'))\n      }\n    } catch (err) {\n      console.warn('Failed to create an instance of Ajv, JSON Schema validation is not available. Please use a JSONEditor bundle including Ajv, or pass an instance of Ajv as via the configuration option `ajv`.')\n    }\n\n    if (ajv) {\n      if (schemaRefs) {\n        for (const ref in schemaRefs) {\n          ajv.removeSchema(ref) // When updating a schema - old refs has to be removed first\n          if (schemaRefs[ref]) {\n            ajv.addSchema(schemaRefs[ref], ref)\n          }\n        }\n        this.options.schemaRefs = schemaRefs\n      }\n      this.validateSchema = ajv.compile(schema)\n\n      // add schema to the options, so that when switching to an other mode,\n      // the set schema is not lost\n      this.options.schema = schema\n      this.options.schemaRefs = schemaRefs\n\n      // validate now\n      this.validate()\n    }\n\n    this.refresh() // update DOM\n  } else {\n    // remove current schema\n    this.validateSchema = null\n    this.options.schema = null\n    this.options.schemaRefs = null\n    this.validate() // to clear current error messages\n    this.refresh() // update DOM\n  }\n\n  if (typeof this._onSchemaChange === 'function') {\n    this._onSchemaChange(schema, schemaRefs)\n  }\n}\n\n/**\n * Validate current JSON object against the configured JSON schema\n * Throws an exception when no JSON schema is configured\n */\nJSONEditor.prototype.validate = () => {\n  // must be implemented by treemode and textmode\n  return Promise.resolve([])\n}\n\n/**\n * Refresh the rendered contents\n */\nJSONEditor.prototype.refresh = () => {\n  // can be implemented by treemode and textmode\n}\n\n/**\n * Register a plugin with one ore multiple modes for the JSON Editor.\n *\n * A mode is described as an object with properties:\n *\n * - `mode: String`           The name of the mode.\n * - `mixin: Object`          An object containing the mixin functions which\n *                            will be added to the JSONEditor. Must contain functions\n *                            create, get, getText, set, and setText. May have\n *                            additional functions.\n *                            When the JSONEditor switches to a mixin, all mixin\n *                            functions are added to the JSONEditor, and then\n *                            the function `create(container, options)` is executed.\n * - `data: 'text' | 'json'`  The type of data that will be used to load the mixin.\n * - `[load: function]`       An optional function called after the mixin\n *                            has been loaded.\n *\n * @param {Object | Array} mode  A mode object or an array with multiple mode objects.\n */\nJSONEditor.registerMode = mode => {\n  let i, prop\n\n  if (Array.isArray(mode)) {\n    // multiple modes\n    for (i = 0; i < mode.length; i++) {\n      JSONEditor.registerMode(mode[i])\n    }\n  } else {\n    // validate the new mode\n    if (!('mode' in mode)) throw new Error('Property \"mode\" missing')\n    if (!('mixin' in mode)) throw new Error('Property \"mixin\" missing')\n    if (!('data' in mode)) throw new Error('Property \"data\" missing')\n    const name = mode.mode\n    if (name in JSONEditor.modes) {\n      throw new Error('Mode \"' + name + '\" already registered')\n    }\n\n    // validate the mixin\n    if (typeof mode.mixin.create !== 'function') {\n      throw new Error('Required function \"create\" missing on mixin')\n    }\n    const reserved = ['setMode', 'registerMode', 'modes']\n    for (i = 0; i < reserved.length; i++) {\n      prop = reserved[i]\n      if (prop in mode.mixin) {\n        throw new Error('Reserved property \"' + prop + '\" not allowed in mixin')\n      }\n    }\n\n    JSONEditor.modes[name] = mode\n  }\n}\n\n// register tree, text, and preview modes\nJSONEditor.registerMode(treeModeMixins)\nJSONEditor.registerMode(textModeMixins)\nJSONEditor.registerMode(previewModeMixins)\n\n// expose some of the libraries that can be used customized\nJSONEditor.ace = ace\nJSONEditor.Ajv = Ajv\nJSONEditor.VanillaPicker = VanillaPicker\n\n// expose some utils (this is undocumented, unofficial)\nJSONEditor.showTransformModal = showTransformModal\nJSONEditor.showSortModal = showSortModal\nJSONEditor.getInnerText = getInnerText\n\n// default export for TypeScript ES6 projects\nJSONEditor.default = JSONEditor\n\nmodule.exports = JSONEditor\n"
  },
  {
    "path": "src/js/ModeSwitcher.js",
    "content": "'use strict'\n\nimport { ContextMenu } from './ContextMenu'\nimport { translate } from './i18n'\n\n/**\n * Create a select box to be used in the editor menu's, which allows to switch mode\n * @param {HTMLElement} container\n * @param {String[]} modes  Available modes: 'code', 'form', 'text', 'tree', 'view', 'preview'\n * @param {String} current  Available modes: 'code', 'form', 'text', 'tree', 'view', 'preview'\n * @param {function(mode: string)} onSwitch  Callback invoked on switch\n * @constructor\n */\nexport class ModeSwitcher {\n  constructor (container, modes, current, onSwitch) {\n    // available modes\n    const availableModes = {\n      code: {\n        text: translate('modeCodeText'),\n        title: translate('modeCodeTitle'),\n        click: function () {\n          onSwitch('code')\n        }\n      },\n      form: {\n        text: translate('modeFormText'),\n        title: translate('modeFormTitle'),\n        click: function () {\n          onSwitch('form')\n        }\n      },\n      text: {\n        text: translate('modeTextText'),\n        title: translate('modeTextTitle'),\n        click: function () {\n          onSwitch('text')\n        }\n      },\n      tree: {\n        text: translate('modeTreeText'),\n        title: translate('modeTreeTitle'),\n        click: function () {\n          onSwitch('tree')\n        }\n      },\n      view: {\n        text: translate('modeViewText'),\n        title: translate('modeViewTitle'),\n        click: function () {\n          onSwitch('view')\n        }\n      },\n      preview: {\n        text: translate('modePreviewText'),\n        title: translate('modePreviewTitle'),\n        click: function () {\n          onSwitch('preview')\n        }\n      }\n    }\n\n    // list the selected modes\n    const items = []\n    for (let i = 0; i < modes.length; i++) {\n      const mode = modes[i]\n      const item = availableModes[mode]\n      if (!item) {\n        throw new Error('Unknown mode \"' + mode + '\"')\n      }\n\n      item.className = 'jsoneditor-type-modes' + ((current === mode) ? ' jsoneditor-selected' : '')\n      items.push(item)\n    }\n\n    // retrieve the title of current mode\n    const currentMode = availableModes[current]\n    if (!currentMode) {\n      throw new Error('Unknown mode \"' + current + '\"')\n    }\n    const currentTitle = currentMode.text\n\n    // create the html element\n    const box = document.createElement('button')\n    box.type = 'button'\n    box.className = 'jsoneditor-modes jsoneditor-separator'\n    box.textContent = currentTitle + ' \\u25BE'\n    box.title = translate('modeEditorTitle')\n    box.onclick = () => {\n      const menu = new ContextMenu(items)\n      menu.show(box, container)\n    }\n\n    const frame = document.createElement('div')\n    frame.className = 'jsoneditor-modes'\n    frame.style.position = 'relative'\n    frame.appendChild(box)\n\n    container.appendChild(frame)\n\n    this.dom = {\n      container,\n      box,\n      frame\n    }\n  }\n\n  /**\n   * Set focus to switcher\n   */\n  focus () {\n    this.dom.box.focus()\n  }\n\n  /**\n   * Destroy the ModeSwitcher, remove from DOM\n   */\n  destroy () {\n    if (this.dom && this.dom.frame && this.dom.frame.parentNode) {\n      this.dom.frame.parentNode.removeChild(this.dom.frame)\n    }\n    this.dom = null\n  }\n}\n"
  },
  {
    "path": "src/js/Node.js",
    "content": "'use strict'\n\nimport naturalSort from 'javascript-natural-sort'\nimport { createAbsoluteAnchor } from './createAbsoluteAnchor'\nimport { ContextMenu } from './ContextMenu'\nimport { appendNodeFactory } from './appendNodeFactory'\nimport { showMoreNodeFactory } from './showMoreNodeFactory'\nimport { showSortModal } from './showSortModal'\nimport { showTransformModal } from './showTransformModal'\nimport {\n  addClassName,\n  addEventListener,\n  debounce,\n  escapeUnicodeChars,\n  findUniqueName,\n  getAbsoluteLeft,\n  getAbsoluteTop,\n  getInnerText,\n  getType,\n  isTimestamp,\n  isUrl,\n  isValidColor,\n  makeFieldTooltip,\n  parse,\n  parsePath,\n  parseString,\n  removeAllClassNames,\n  removeClassName,\n  removeEventListener,\n  selectContentEditable,\n  setEndOfContentEditable,\n  stripFormatting,\n  textDiff\n} from './util'\nimport { translate } from './i18n'\nimport { DEFAULT_MODAL_ANCHOR } from './constants'\n\n/**\n * @constructor Node\n * Create a new Node\n * @param {./treemode} editor\n * @param {Object} [params] Can contain parameters:\n *                          {string}  field\n *                          {boolean} fieldEditable\n *                          {*}       value\n *                          {String}  type  Can have values 'auto', 'array',\n *                                          'object', or 'string'.\n */\nexport class Node {\n  constructor (editor, params) {\n    /** @type {./treemode} */\n    this.editor = editor\n    this.dom = {}\n    this.expanded = false\n\n    if (params && (params instanceof Object)) {\n      this.setField(params.field, params.fieldEditable)\n      if ('value' in params) {\n        this.setValue(params.value, params.type)\n      }\n      if ('internalValue' in params) {\n        this.setInternalValue(params.internalValue)\n      }\n    } else {\n      this.setField('')\n      this.setValue(null)\n    }\n\n    this._debouncedOnChangeValue = debounce(this._onChangeValue.bind(this), Node.prototype.DEBOUNCE_INTERVAL)\n    this._debouncedOnChangeField = debounce(this._onChangeField.bind(this), Node.prototype.DEBOUNCE_INTERVAL)\n\n    // starting value for visible children\n    this.visibleChilds = this.getMaxVisibleChilds()\n  }\n\n  getMaxVisibleChilds () {\n    return (this.editor && this.editor.options && this.editor.options.maxVisibleChilds)\n      ? this.editor.options.maxVisibleChilds\n      : DEFAULT_MAX_VISIBLE_CHILDS\n  }\n\n  /**\n   * Determine whether the field and/or value of this node are editable\n   * @private\n   */\n  _updateEditability () {\n    this.editable = {\n      field: true,\n      value: true\n    }\n\n    if (this.editor) {\n      this.editable.field = this.editor.options.mode === 'tree'\n      this.editable.value = this.editor.options.mode !== 'view'\n\n      if ((this.editor.options.mode === 'tree' || this.editor.options.mode === 'form') &&\n          (typeof this.editor.options.onEditable === 'function')) {\n        const getValue = this.getValue.bind(this)\n        const editable = this.editor.options.onEditable({\n          field: this.field,\n          get value () {\n            return getValue()\n          },\n          path: this.getPath()\n        })\n\n        if (typeof editable === 'boolean') {\n          this.editable.field = editable\n          this.editable.value = editable\n        } else if (typeof editable === 'object' && editable !== null) {\n          if (typeof editable.field === 'boolean') this.editable.field = editable.field\n          if (typeof editable.value === 'boolean') this.editable.value = editable.value\n        } else {\n          console.error(\n            'Invalid return value for function onEditable.',\n            'Actual value:', editable, '.',\n            'Either a boolean or object { field: boolean, value: boolean } expected.')\n\n          this.editable.field = false\n          this.editable.value = false\n        }\n      }\n    }\n  }\n\n  /**\n   * Get the path of this node\n   * @return {{string|number}[]} Array containing the path to this node.\n   * Element is a number if is the index of an array, a string otherwise.\n   */\n  getPath () {\n    let node = this\n    const path = []\n    while (node) {\n      const field = node.getName()\n      if (field !== undefined) {\n        path.unshift(field)\n      }\n      node = node.parent\n    }\n    return path\n  }\n\n  /**\n   * Get the internal path of this node, a list with the child indexes.\n   * @return {String[]} Array containing the internal path to this node\n   */\n  getInternalPath () {\n    let node = this\n    const internalPath = []\n    while (node) {\n      if (node.parent) {\n        internalPath.unshift(node.getIndex())\n      }\n      node = node.parent\n    }\n    return internalPath\n  }\n\n  /**\n   * Get node serializable name\n   * @returns {String|Number}\n   */\n  getName () {\n    return !this.parent\n      ? undefined // do not add an (optional) field name of the root node\n      : (this.parent.type !== 'array')\n          ? this.field\n          : this.index\n  }\n\n  /**\n   * Find child node by serializable path\n   * @param {Array<String>} path\n   */\n  findNodeByPath (path) {\n    if (!path) {\n      return\n    }\n\n    if (path.length === 0) {\n      return this\n    }\n\n    if (path.length && this.childs && this.childs.length) {\n      for (let i = 0; i < this.childs.length; ++i) {\n        if (('' + path[0]) === ('' + this.childs[i].getName())) {\n          return this.childs[i].findNodeByPath(path.slice(1))\n        }\n      }\n    }\n  }\n\n  /**\n   * Find child node by an internal path: the indexes of the childs nodes\n   * @param {Array<String>} internalPath\n   * @return {Node | undefined} Returns the node if the path exists.\n   *                            Returns undefined otherwise.\n   */\n  findNodeByInternalPath (internalPath) {\n    if (!internalPath) {\n      return undefined\n    }\n\n    let node = this\n    for (let i = 0; i < internalPath.length && node; i++) {\n      const childIndex = internalPath[i]\n      node = node.childs[childIndex]\n    }\n\n    return node\n  }\n\n  /**\n   * @typedef {{value: String|Object|Number|Boolean, path: Array.<String|Number>}} SerializableNode\n   *\n   * Returns serializable representation for the node\n   * @return {SerializableNode}\n   */\n  serialize () {\n    return {\n      value: this.getValue(),\n      path: this.getPath()\n    }\n  }\n\n  /**\n   * Find a Node from a JSON path like '.items[3].name'\n   * @param {string} jsonPath\n   * @return {Node | null} Returns the Node when found, returns null if not found\n   */\n  findNode (jsonPath) {\n    const path = parsePath(jsonPath)\n    let node = this\n    while (node && path.length > 0) {\n      const prop = path.shift()\n      if (typeof prop === 'number') {\n        if (node.type !== 'array') {\n          throw new Error('Cannot get child node at index ' + prop + ': node is no array')\n        }\n        node = node.childs[prop]\n      } else { // string\n        if (node.type !== 'object') {\n          throw new Error('Cannot get child node ' + prop + ': node is no object')\n        }\n        node = node.childs.filter(child => child.field === prop)[0]\n      }\n    }\n\n    return node\n  }\n\n  /**\n   * Find all parents of this node. The parents are ordered from root node towards\n   * the original node.\n   * @return {Array.<Node>}\n   */\n  findParents () {\n    const parents = []\n    let parent = this.parent\n    while (parent) {\n      parents.unshift(parent)\n      parent = parent.parent\n    }\n    return parents\n  }\n\n  /**\n   *\n   * @param {{dataPath: string, keyword: string, message: string, params: Object, schemaPath: string} | null} error\n   * @param {Node} [child]  When this is the error of a parent node, pointing\n   *                        to an invalid child node, the child node itself\n   *                        can be provided. If provided, clicking the error\n   *                        icon will set focus to the invalid child node.\n   */\n  setError (error, child) {\n    this.error = error\n    this.errorChild = child\n\n    if (this.dom && this.dom.tr) {\n      this.updateError()\n    }\n  }\n\n  /**\n   * Render the error\n   */\n  updateError () {\n    const error = this.fieldError || this.valueError || this.error\n    let tdError = this.dom.tdError\n    if (error && this.dom && this.dom.tr) {\n      addClassName(this.dom.tr, 'jsoneditor-validation-error')\n\n      if (!tdError) {\n        tdError = document.createElement('td')\n        this.dom.tdError = tdError\n        this.dom.tdValue.parentNode.appendChild(tdError)\n      }\n\n      const button = document.createElement('button')\n      button.type = 'button'\n      button.className = 'jsoneditor-button jsoneditor-schema-error'\n\n      const destroy = () => {\n        if (this.dom.popupAnchor) {\n          this.dom.popupAnchor.destroy() // this will trigger the onDestroy callback\n        }\n      }\n\n      const onDestroy = () => {\n        delete this.dom.popupAnchor\n      }\n\n      const createPopup = (destroyOnMouseOut) => {\n        const frame = this.editor.frame\n        this.dom.popupAnchor = createAbsoluteAnchor(button, this.editor.getPopupAnchor(), onDestroy, destroyOnMouseOut)\n\n        const popupWidth = 200 // must correspond to what's configured in the CSS\n        const buttonRect = button.getBoundingClientRect()\n        const frameRect = frame.getBoundingClientRect()\n        const position = (frameRect.width - buttonRect.x > (popupWidth / 2 + 20))\n          ? 'jsoneditor-above'\n          : 'jsoneditor-left'\n\n        const popover = document.createElement('div')\n        popover.className = 'jsoneditor-popover ' + position\n        popover.appendChild(document.createTextNode(error.message))\n        this.dom.popupAnchor.appendChild(popover)\n      }\n\n      button.onmouseover = () => {\n        if (!this.dom.popupAnchor) {\n          createPopup(true)\n        }\n      }\n      button.onfocus = () => {\n        destroy()\n        createPopup(false)\n      }\n      button.onblur = () => {\n        destroy()\n      }\n\n      // when clicking the error icon, expand all nodes towards the invalid\n      // child node, and set focus to the child node\n      const child = this.errorChild\n      if (child) {\n        button.onclick = function showInvalidNode () {\n          child.findParents().forEach(parent => {\n            parent.expand(false)\n          })\n\n          child.scrollTo(() => {\n            child.focus()\n          })\n        }\n      }\n\n      // apply the error message to the node\n      while (tdError.firstChild) {\n        tdError.removeChild(tdError.firstChild)\n      }\n      tdError.appendChild(button)\n    } else {\n      if (this.dom.tr) {\n        removeClassName(this.dom.tr, 'jsoneditor-validation-error')\n      }\n\n      if (tdError) {\n        this.dom.tdError.parentNode.removeChild(this.dom.tdError)\n        delete this.dom.tdError\n      }\n    }\n  }\n\n  /**\n   * Get the index of this node: the index in the list of childs where this\n   * node is part of\n   * @return {number | null} Returns the index, or null if this is the root node\n   */\n  getIndex () {\n    if (this.parent) {\n      const index = this.parent.childs.indexOf(this)\n      return index !== -1 ? index : null\n    } else {\n      return -1\n    }\n  }\n\n  /**\n   * Set parent node\n   * @param {Node} parent\n   */\n  setParent (parent) {\n    this.parent = parent\n  }\n\n  /**\n   * Set field\n   * @param {String}  field\n   * @param {boolean} [fieldEditable]\n   */\n  setField (field, fieldEditable) {\n    this.field = field\n    this.previousField = field\n    this.fieldEditable = (fieldEditable === true)\n  }\n\n  /**\n   * Get field\n   * @return {String}\n   */\n  getField () {\n    if (this.field === undefined) {\n      this._getDomField()\n    }\n\n    return this.field\n  }\n\n  /**\n   * Set value. Value is a JSON structure or an element String, Boolean, etc.\n   * @param {*} value\n   * @param {String} [type]  Specify the type of the value. Can be 'auto',\n   *                         'array', 'object', or 'string'\n   */\n  setValue (value, type) {\n    let childValue, child\n    let i, j\n    const updateDom = false\n    const previousChilds = this.childs\n\n    this.type = this._getType(value)\n\n    // check if type corresponds with the provided type\n    if (type && type !== this.type) {\n      if (type === 'string' && this.type === 'auto') {\n        this.type = type\n      } else {\n        throw new Error('Type mismatch: ' +\n            'cannot cast value of type \"' + this.type +\n            ' to the specified type \"' + type + '\"')\n      }\n    }\n\n    if (this.type === 'array') {\n      // array\n      if (!this.childs) {\n        this.childs = []\n      }\n\n      for (i = 0; i < value.length; i++) {\n        childValue = value[i]\n        if (childValue !== undefined && !(childValue instanceof Function)) {\n          if (i < this.childs.length) {\n            // reuse existing child, keep its state\n            child = this.childs[i]\n\n            child.fieldEditable = false\n            child.index = i\n            child.setValue(childValue)\n          } else {\n            // create a new child\n            child = new Node(this.editor, {\n              value: childValue\n            })\n            const visible = i < this.getMaxVisibleChilds()\n            this.appendChild(child, visible, updateDom)\n          }\n        }\n      }\n\n      // cleanup redundant childs\n      // we loop backward to prevent issues with shifting index numbers\n      for (j = this.childs.length; j >= value.length; j--) {\n        this.removeChild(this.childs[j], updateDom)\n      }\n    } else if (this.type === 'object') {\n      // object\n      if (!this.childs) {\n        this.childs = []\n      }\n\n      // cleanup redundant childs\n      // we loop backward to prevent issues with shifting index numbers\n      for (j = this.childs.length - 1; j >= 0; j--) {\n        if (!hasOwnProperty(value, this.childs[j].field)) {\n          this.removeChild(this.childs[j], updateDom)\n        }\n      }\n\n      i = 0\n      for (const childField in value) {\n        if (hasOwnProperty(value, childField)) {\n          childValue = value[childField]\n          if (childValue !== undefined && !(childValue instanceof Function)) {\n            const child = this.findChildByProperty(childField)\n            if (child) {\n              // reuse existing child, keep its state\n              child.setField(childField, true)\n              child.setValue(childValue)\n            } else {\n              // create a new child, append to the end\n              const newChild = new Node(this.editor, {\n                field: childField,\n                value: childValue\n              })\n              const visible = i < this.getMaxVisibleChilds()\n              this.appendChild(newChild, visible, updateDom)\n            }\n          }\n          i++\n        }\n      }\n      this.value = ''\n\n      // sort object keys during initialization. Must not trigger an onChange action\n      if (this.editor.options.sortObjectKeys === true) {\n        const triggerAction = false\n        this.sort([], 'asc', triggerAction)\n      }\n    } else {\n      // value\n      this.hideChilds()\n\n      delete this.append\n      delete this.showMore\n      delete this.expanded\n      delete this.childs\n\n      this.value = value\n    }\n\n    // recreate the DOM if switching from an object/array to auto/string or vice versa\n    // needed to recreated the expand button for example\n    if (Array.isArray(previousChilds) !== Array.isArray(this.childs)) {\n      this.recreateDom()\n    }\n\n    this.updateDom({ updateIndexes: true })\n\n    this.previousValue = this.value // used only to check for changes in DOM vs JS model\n  }\n\n  /**\n   * Set internal value\n   * @param {*} internalValue  Internal value structure keeping type,\n   *                           order and duplicates in objects\n   */\n  setInternalValue (internalValue) {\n    let childValue, child, visible\n    let i, j\n    const notUpdateDom = false\n    const previousChilds = this.childs\n\n    this.type = internalValue.type\n\n    if (internalValue.type === 'array') {\n      // array\n      if (!this.childs) {\n        this.childs = []\n      }\n\n      for (i = 0; i < internalValue.childs.length; i++) {\n        childValue = internalValue.childs[i]\n        if (childValue !== undefined && !(childValue instanceof Function)) {\n          if (i < this.childs.length) {\n            // reuse existing child, keep its state\n            child = this.childs[i]\n\n            child.fieldEditable = false\n            child.index = i\n            child.setInternalValue(childValue)\n          } else {\n            // create a new child\n            child = new Node(this.editor, {\n              internalValue: childValue\n            })\n            visible = i < this.getMaxVisibleChilds()\n            this.appendChild(child, visible, notUpdateDom)\n          }\n        }\n      }\n\n      // cleanup redundant childs\n      // we loop backward to prevent issues with shifting index numbers\n      for (j = this.childs.length; j >= internalValue.childs.length; j--) {\n        this.removeChild(this.childs[j], notUpdateDom)\n      }\n    } else if (internalValue.type === 'object') {\n      // object\n      if (!this.childs) {\n        this.childs = []\n      }\n\n      for (i = 0; i < internalValue.childs.length; i++) {\n        childValue = internalValue.childs[i]\n        if (childValue !== undefined && !(childValue instanceof Function)) {\n          if (i < this.childs.length) {\n            // reuse existing child, keep its state\n            child = this.childs[i]\n\n            delete child.index\n            child.setField(childValue.field, true)\n            child.setInternalValue(childValue.value)\n          } else {\n            // create a new child\n            child = new Node(this.editor, {\n              field: childValue.field,\n              internalValue: childValue.value\n            })\n            visible = i < this.getMaxVisibleChilds()\n            this.appendChild(child, visible, notUpdateDom)\n          }\n        }\n      }\n\n      // cleanup redundant childs\n      // we loop backward to prevent issues with shifting index numbers\n      for (j = this.childs.length; j >= internalValue.childs.length; j--) {\n        this.removeChild(this.childs[j], notUpdateDom)\n      }\n    } else {\n      // value\n      this.hideChilds()\n\n      delete this.append\n      delete this.showMore\n      delete this.expanded\n      delete this.childs\n\n      this.value = internalValue.value\n    }\n\n    // recreate the DOM if switching from an object/array to auto/string or vice versa\n    // needed to recreated the expand button for example\n    if (Array.isArray(previousChilds) !== Array.isArray(this.childs)) {\n      this.recreateDom()\n    }\n\n    this.updateDom({ updateIndexes: true })\n\n    this.previousValue = this.value // used only to check for changes in DOM vs JS model\n  }\n\n  /**\n   * Remove the DOM of this node and it's childs and recreate it again\n   */\n  recreateDom () {\n    if (this.dom && this.dom.tr && this.dom.tr.parentNode) {\n      const domAnchor = this._detachFromDom()\n\n      this.clearDom()\n\n      this._attachToDom(domAnchor)\n    } else {\n      this.clearDom()\n    }\n  }\n\n  /**\n   * Get value. Value is a JSON structure\n   * @return {*} value\n   */\n  getValue () {\n    if (this.type === 'array') {\n      const arr = []\n      this.childs.forEach(child => {\n        arr.push(child.getValue())\n      })\n      return arr\n    } else if (this.type === 'object') {\n      const obj = {}\n      this.childs.forEach(child => {\n        obj[child.getField()] = child.getValue()\n      })\n      return obj\n    } else {\n      if (this.value === undefined) {\n        this._getDomValue()\n      }\n\n      return this.value\n    }\n  }\n\n  /**\n   * Get internal value, a structure which maintains ordering and duplicates in objects\n   * @return {*} value\n   */\n  getInternalValue () {\n    if (this.type === 'array') {\n      return {\n        type: this.type,\n        childs: this.childs.map(child => child.getInternalValue())\n      }\n    } else if (this.type === 'object') {\n      return {\n        type: this.type,\n        childs: this.childs.map(child => ({\n          field: child.getField(),\n          value: child.getInternalValue()\n        }))\n      }\n    } else {\n      if (this.value === undefined) {\n        this._getDomValue()\n      }\n\n      return {\n        type: this.type,\n        value: this.value\n      }\n    }\n  }\n\n  /**\n   * Get the nesting level of this node\n   * @return {Number} level\n   */\n  getLevel () {\n    return (this.parent ? this.parent.getLevel() + 1 : 0)\n  }\n\n  /**\n   * Get jsonpath of the current node\n   * @return {Node[]} Returns an array with nodes\n   */\n  getNodePath () {\n    const path = this.parent ? this.parent.getNodePath() : []\n    path.push(this)\n    return path\n  }\n\n  /**\n   * Create a clone of a node\n   * The complete state of a clone is copied, including whether it is expanded or\n   * not. The DOM elements are not cloned.\n   * @return {Node} clone\n   */\n  clone () {\n    const clone = new Node(this.editor)\n    clone.type = this.type\n    clone.field = this.field\n    clone.fieldInnerText = this.fieldInnerText\n    clone.fieldEditable = this.fieldEditable\n    clone.previousField = this.previousField\n    clone.value = this.value\n    clone.valueInnerText = this.valueInnerText\n    clone.previousValue = this.previousValue\n    clone.expanded = this.expanded\n    clone.visibleChilds = this.visibleChilds\n\n    if (this.childs) {\n      // an object or array\n      const cloneChilds = []\n      this.childs.forEach(child => {\n        const childClone = child.clone()\n        childClone.setParent(clone)\n        cloneChilds.push(childClone)\n      })\n      clone.childs = cloneChilds\n    } else {\n      // a value\n      clone.childs = undefined\n    }\n\n    return clone\n  }\n\n  /**\n   * Expand this node and optionally its childs.\n   * @param {boolean} [recurse] Optional recursion, true by default. When\n   *                            true, all childs will be expanded recursively\n   */\n  expand (recurse) {\n    if (!this.childs) {\n      return\n    }\n\n    // set this node expanded\n    this.expanded = true\n    if (this.dom.expand) {\n      this.dom.expand.className = 'jsoneditor-button jsoneditor-expanded'\n    }\n\n    this.showChilds()\n\n    if (recurse !== false) {\n      this.childs.forEach(child => {\n        child.expand(recurse)\n      })\n    }\n\n    // update the css classes of table row, and fire onClassName etc\n    this.updateDom({ recurse: false })\n  }\n\n  /**\n   * Collapse this node and optionally its childs.\n   * @param {boolean} [recurse] Optional recursion, true by default. When\n   *                            true, all childs will be collapsed recursively\n   */\n  collapse (recurse) {\n    if (!this.childs) {\n      return\n    }\n\n    this.hideChilds()\n\n    // collapse childs in case of recurse\n    if (recurse !== false) {\n      this.childs.forEach(child => {\n        child.collapse(recurse)\n      })\n    }\n\n    // make this node collapsed\n    if (this.dom.expand) {\n      this.dom.expand.className = 'jsoneditor-button jsoneditor-collapsed'\n    }\n    this.expanded = false\n\n    // update the css classes of table row, and fire onClassName etc\n    this.updateDom({ recurse: false })\n  }\n\n  /**\n   * Recursively show all childs when they are expanded\n   */\n  showChilds () {\n    const childs = this.childs\n    if (!childs) {\n      return\n    }\n    if (!this.expanded) {\n      return\n    }\n\n    const tr = this.dom.tr\n    let nextTr\n    const table = tr ? tr.parentNode : undefined\n    if (table) {\n      // show row with append button\n      const append = this.getAppendDom()\n      if (!append.parentNode) {\n        nextTr = tr.nextSibling\n        if (nextTr) {\n          table.insertBefore(append, nextTr)\n        } else {\n          table.appendChild(append)\n        }\n      }\n\n      // show childs\n      const iMax = Math.min(this.childs.length, this.visibleChilds)\n      nextTr = this._getNextTr()\n      for (let i = 0; i < iMax; i++) {\n        const child = this.childs[i]\n        if (!child.getDom().parentNode) {\n          table.insertBefore(child.getDom(), nextTr)\n        }\n        child.showChilds()\n      }\n\n      // show \"show more childs\" if limited\n      const showMore = this.getShowMoreDom()\n      nextTr = this._getNextTr()\n      if (!showMore.parentNode) {\n        table.insertBefore(showMore, nextTr)\n      }\n      this.showMore.updateDom() // to update the counter\n    }\n  }\n\n  _getNextTr () {\n    if (this.showMore && this.showMore.getDom().parentNode) {\n      return this.showMore.getDom()\n    }\n\n    if (this.append && this.append.getDom().parentNode) {\n      return this.append.getDom()\n    }\n  }\n\n  /**\n   * Hide the node with all its childs\n   * @param {{resetVisibleChilds: boolean}} [options]\n   */\n  hide (options) {\n    const tr = this.dom.tr\n    const table = tr ? tr.parentNode : undefined\n    if (table) {\n      table.removeChild(tr)\n    }\n\n    if (this.dom.popupAnchor) {\n      this.dom.popupAnchor.destroy()\n    }\n\n    this.hideChilds(options)\n  }\n\n  /**\n   * Recursively hide all childs\n   * @param {{resetVisibleChilds: boolean}} [options]\n   */\n  hideChilds (options) {\n    const childs = this.childs\n    if (!childs) {\n      return\n    }\n    if (!this.expanded) {\n      return\n    }\n\n    // hide append row\n    const append = this.getAppendDom()\n    if (append.parentNode) {\n      append.parentNode.removeChild(append)\n    }\n\n    // hide childs\n    this.childs.forEach(child => {\n      child.hide()\n    })\n\n    // hide \"show more\" row\n    const showMore = this.getShowMoreDom()\n    if (showMore.parentNode) {\n      showMore.parentNode.removeChild(showMore)\n    }\n\n    // reset max visible childs\n    if (!options || options.resetVisibleChilds) {\n      this.visibleChilds = this.getMaxVisibleChilds()\n    }\n  }\n\n  /**\n   * set custom css classes on a node\n   */\n  _updateCssClassName () {\n    if (this.dom.field &&\n      this.editor &&\n      this.editor.options &&\n      typeof this.editor.options.onClassName === 'function' &&\n      this.dom.tree) {\n      removeAllClassNames(this.dom.tree)\n      const getValue = this.getValue.bind(this)\n      const addClasses = this.editor.options.onClassName({\n        path: this.getPath(),\n        field: this.field,\n        get value () {\n          return getValue()\n        }\n      }) || ''\n      addClassName(this.dom.tree, 'jsoneditor-values ' + addClasses)\n    }\n  }\n\n  recursivelyUpdateCssClassesOnNodes () {\n    this._updateCssClassName()\n    if (Array.isArray(this.childs)) {\n      for (let i = 0; i < this.childs.length; i++) {\n        this.childs[i].recursivelyUpdateCssClassesOnNodes()\n      }\n    }\n  }\n\n  /**\n   * Goes through the path from the node to the root and ensures that it is expanded\n   */\n  expandTo () {\n    let currentNode = this.parent\n    while (currentNode) {\n      if (!currentNode.expanded) {\n        currentNode.expand()\n      }\n      currentNode = currentNode.parent\n    }\n  }\n\n  /**\n   * Add a new child to the node.\n   * Only applicable when Node value is of type array or object\n   * @param {Node} node\n   * @param {boolean} [visible] If true (default), the child will be rendered\n   * @param {boolean} [updateDom]  If true (default), the DOM of both parent\n   *                               node and appended node will be updated\n   *                               (child count, indexes)\n   */\n  appendChild (node, visible, updateDom) {\n    if (this._hasChilds()) {\n      // adjust the link to the parent\n      node.setParent(this)\n      node.fieldEditable = (this.type === 'object')\n      if (this.type === 'array') {\n        node.index = this.childs.length\n      }\n      if (this.type === 'object' && node.field === undefined) {\n        // initialize field value if needed\n        node.setField('')\n      }\n      this.childs.push(node)\n\n      if (this.expanded && visible !== false) {\n        // insert into the DOM, before the appendRow\n        const newTr = node.getDom()\n        const nextTr = this._getNextTr()\n        const table = nextTr ? nextTr.parentNode : undefined\n        if (nextTr && table) {\n          table.insertBefore(newTr, nextTr)\n        }\n\n        node.showChilds()\n\n        this.visibleChilds++\n      }\n\n      if (updateDom !== false) {\n        this.updateDom({ updateIndexes: true })\n        node.updateDom({ recurse: true })\n      }\n    }\n  }\n\n  /**\n   * Move a node from its current parent to this node\n   * Only applicable when Node value is of type array or object\n   * @param {Node} node\n   * @param {Node} beforeNode\n   * @param {boolean} [updateDom]  If true (default), the DOM of both parent\n   *                               node and appended node will be updated\n   *                               (child count, indexes)\n   */\n  moveBefore (node, beforeNode, updateDom) {\n    if (this._hasChilds()) {\n      // create a temporary row, to prevent the scroll position from jumping\n      // when removing the node\n      const tbody = (this.dom.tr) ? this.dom.tr.parentNode : undefined\n      let trTemp\n      if (tbody) {\n        trTemp = document.createElement('tr')\n        trTemp.style.height = tbody.clientHeight + 'px'\n        tbody.appendChild(trTemp)\n      }\n\n      if (node.parent) {\n        node.parent.removeChild(node)\n      }\n\n      if (beforeNode instanceof AppendNode || !beforeNode) {\n        // the this.childs.length + 1 is to reckon with the node that we're about to add\n        if (this.childs.length + 1 > this.visibleChilds) {\n          const lastVisibleNode = this.childs[this.visibleChilds - 1]\n          this.insertBefore(node, lastVisibleNode, updateDom)\n        } else {\n          const visible = true\n          this.appendChild(node, visible, updateDom)\n        }\n      } else {\n        this.insertBefore(node, beforeNode, updateDom)\n      }\n\n      if (tbody && trTemp) {\n        tbody.removeChild(trTemp)\n      }\n    }\n  }\n\n  /**\n   * Insert a new child before a given node\n   * Only applicable when Node value is of type array or object\n   * @param {Node} node\n   * @param {Node} beforeNode\n   * @param {boolean} [updateDom]  If true (default), the DOM of both parent\n   *                               node and appended node will be updated\n   *                               (child count, indexes)\n   */\n  insertBefore (node, beforeNode, updateDom) {\n    if (this._hasChilds()) {\n      this.visibleChilds++\n\n      // initialize field value if needed\n      if (this.type === 'object' && node.field === undefined) {\n        node.setField('')\n      }\n\n      if (beforeNode === this.append) {\n        // append to the child nodes\n\n        // adjust the link to the parent\n        node.setParent(this)\n        node.fieldEditable = (this.type === 'object')\n        this.childs.push(node)\n      } else {\n        // insert before a child node\n        const index = this.childs.indexOf(beforeNode)\n        if (index === -1) {\n          throw new Error('Node not found')\n        }\n\n        // adjust the link to the parent\n        node.setParent(this)\n        node.fieldEditable = (this.type === 'object')\n        this.childs.splice(index, 0, node)\n      }\n\n      if (this.expanded) {\n        // insert into the DOM\n        const newTr = node.getDom()\n        const nextTr = beforeNode.getDom()\n        const table = nextTr ? nextTr.parentNode : undefined\n        if (nextTr && table) {\n          table.insertBefore(newTr, nextTr)\n        }\n\n        node.showChilds()\n        this.showChilds()\n      }\n\n      if (updateDom !== false) {\n        this.updateDom({ updateIndexes: true })\n        node.updateDom({ recurse: true })\n      }\n    }\n  }\n\n  /**\n   * Insert a new child before a given node\n   * Only applicable when Node value is of type array or object\n   * @param {Node} node\n   * @param {Node} afterNode\n   */\n  insertAfter (node, afterNode) {\n    if (this._hasChilds()) {\n      const index = this.childs.indexOf(afterNode)\n      const beforeNode = this.childs[index + 1]\n      if (beforeNode) {\n        this.insertBefore(node, beforeNode)\n      } else {\n        this.appendChild(node)\n      }\n    }\n  }\n\n  /**\n   * Search in this node\n   * Searches are case insensitive.\n   * @param {String} text\n   * @param {Node[]} [results] Array where search results will be added\n   *                           used to count and limit the results whilst iterating\n   * @return {Node[]} results  Array with nodes containing the search text\n   */\n  search (text, results) {\n    if (!Array.isArray(results)) {\n      results = []\n    }\n    let index\n    const search = text ? text.toLowerCase() : undefined\n\n    // delete old search data\n    delete this.searchField\n    delete this.searchValue\n\n    // search in field\n    if (this.field !== undefined && results.length <= this.MAX_SEARCH_RESULTS) {\n      const field = String(this.field).toLowerCase()\n      index = field.indexOf(search)\n      if (index !== -1) {\n        this.searchField = true\n        results.push({\n          node: this,\n          elem: 'field'\n        })\n      }\n\n      // update dom\n      this._updateDomField()\n    }\n\n    // search in value\n    if (this._hasChilds()) {\n      // array, object\n\n      // search the nodes childs\n      if (this.childs) {\n        this.childs.forEach(child => {\n          child.search(text, results)\n        })\n      }\n    } else {\n      // string, auto\n      if (this.value !== undefined && results.length <= this.MAX_SEARCH_RESULTS) {\n        const value = String(this.value).toLowerCase()\n        index = value.indexOf(search)\n        if (index !== -1) {\n          this.searchValue = true\n          results.push({\n            node: this,\n            elem: 'value'\n          })\n        }\n\n        // update dom\n        this._updateDomValue()\n      }\n    }\n\n    return results\n  }\n\n  /**\n   * Move the scroll position such that this node is in the visible area.\n   * The node will not get the focus\n   * @param {function(boolean)} [callback]\n   */\n  scrollTo (callback) {\n    this.expandPathToNode()\n\n    if (this.dom.tr && this.dom.tr.parentNode) {\n      this.editor.scrollTo(this.dom.tr.offsetTop, callback)\n    }\n  }\n\n  /**\n   * if the node is not visible, expand its parents\n   */\n  expandPathToNode () {\n    let node = this\n    const recurse = false\n    while (node && node.parent) {\n      // expand visible childs of the parent if needed\n      const index = node.parent.type === 'array'\n        ? node.index\n        : node.parent.childs.indexOf(node)\n      while (node.parent.visibleChilds < index + 1) {\n        node.parent.visibleChilds += this.getMaxVisibleChilds()\n      }\n\n      // expand the parent itself\n      node.parent.expand(recurse)\n      node = node.parent\n    }\n  }\n\n  /**\n   * Set focus to this node\n   * @param {String} [elementName]  The field name of the element to get the\n   *                                focus available values: 'drag', 'menu',\n   *                                'expand', 'field', 'value' (default)\n   */\n  focus (elementName) {\n    Node.focusElement = elementName\n\n    if (this.dom.tr && this.dom.tr.parentNode) {\n      const dom = this.dom\n\n      switch (elementName) {\n        case 'drag':\n          if (dom.drag) {\n            dom.drag.focus()\n          } else {\n            dom.menu.focus()\n          }\n          break\n\n        case 'menu':\n          dom.menu.focus()\n          break\n\n        case 'expand':\n          if (this._hasChilds()) {\n            dom.expand.focus()\n          } else if (dom.field && this.fieldEditable) {\n            dom.field.focus()\n            selectContentEditable(dom.field)\n          } else if (dom.value && !this._hasChilds()) {\n            dom.value.focus()\n            selectContentEditable(dom.value)\n          } else {\n            dom.menu.focus()\n          }\n          break\n\n        case 'field':\n          if (dom.field && this.fieldEditable) {\n            dom.field.focus()\n            selectContentEditable(dom.field)\n          } else if (dom.value && !this._hasChilds()) {\n            dom.value.focus()\n            selectContentEditable(dom.value)\n          } else if (this._hasChilds()) {\n            dom.expand.focus()\n          } else {\n            dom.menu.focus()\n          }\n          break\n\n        case 'value':\n        default:\n          if (dom.select) {\n            // enum select box\n            dom.select.focus()\n          } else if (dom.value && !this._hasChilds()) {\n            dom.value.focus()\n            selectContentEditable(dom.value)\n          } else if (dom.field && this.fieldEditable) {\n            dom.field.focus()\n            selectContentEditable(dom.field)\n          } else if (this._hasChilds()) {\n            dom.expand.focus()\n          } else {\n            dom.menu.focus()\n          }\n          break\n      }\n    }\n  }\n\n  /**\n   * Check if given node is a child. The method will check recursively to find\n   * this node.\n   * @param {Node} node\n   * @return {boolean} containsNode\n   */\n  containsNode (node) {\n    if (this === node) {\n      return true\n    }\n\n    const childs = this.childs\n    if (childs) {\n      // TODO: use the js5 Array.some() here?\n      for (let i = 0, iMax = childs.length; i < iMax; i++) {\n        if (childs[i].containsNode(node)) {\n          return true\n        }\n      }\n    }\n\n    return false\n  }\n\n  /**\n   * Remove a child from the node.\n   * Only applicable when Node value is of type array or object\n   * @param {Node} node   The child node to be removed;\n   * @param {boolean} [updateDom]  If true (default), the DOM of the parent\n   *                               node will be updated (like child count)\n   * @return {Node | undefined} node  The removed node on success,\n   *                                             else undefined\n   */\n  removeChild (node, updateDom) {\n    if (this.childs) {\n      const index = this.childs.indexOf(node)\n\n      if (index !== -1) {\n        if (index < this.visibleChilds && this.expanded) {\n          this.visibleChilds--\n        }\n\n        node.hide()\n\n        // delete old search results\n        delete node.searchField\n        delete node.searchValue\n\n        const removedNode = this.childs.splice(index, 1)[0]\n        removedNode.parent = null\n\n        if (updateDom !== false) {\n          this.updateDom({ updateIndexes: true })\n        }\n\n        return removedNode\n      }\n    }\n\n    return undefined\n  }\n\n  /**\n   * Remove a child node node from this node\n   * This method is equal to Node.removeChild, except that _remove fire an\n   * onChange event.\n   * @param {Node} node\n   * @private\n   */\n  _remove (node) {\n    this.removeChild(node)\n  }\n\n  /**\n   * Change the type of the value of this Node\n   * @param {String} newType\n   */\n  changeType (newType) {\n    const oldType = this.type\n\n    if (oldType === newType) {\n      // type is not changed\n      return\n    }\n\n    if ((newType === 'string' || newType === 'auto') &&\n        (oldType === 'string' || oldType === 'auto')) {\n      // this is an easy change\n      this.type = newType\n    } else {\n      // change from array to object, or from string/auto to object/array\n      const domAnchor = this._detachFromDom()\n\n      // delete the old DOM\n      this.clearDom()\n\n      // adjust the field and the value\n      this.type = newType\n\n      // adjust childs\n      if (newType === 'object') {\n        if (!this.childs) {\n          this.childs = []\n        }\n\n        this.childs.forEach(child => {\n          child.clearDom()\n          delete child.index\n          child.fieldEditable = true\n          if (child.field === undefined) {\n            child.field = ''\n          }\n        })\n\n        if (oldType === 'string' || oldType === 'auto') {\n          this.expanded = true\n        }\n      } else if (newType === 'array') {\n        if (!this.childs) {\n          this.childs = []\n        }\n\n        this.childs.forEach((child, index) => {\n          child.clearDom()\n          child.fieldEditable = false\n          child.index = index\n        })\n\n        if (oldType === 'string' || oldType === 'auto') {\n          this.expanded = true\n        }\n      } else {\n        this.expanded = false\n      }\n\n      this._attachToDom(domAnchor)\n    }\n\n    if (newType === 'auto' || newType === 'string') {\n      // cast value to the correct type\n      if (newType === 'string') {\n        this.value = String(this.value)\n      } else {\n        this.value = parseString(String(this.value))\n      }\n\n      this.focus()\n    }\n\n    this.updateDom({ updateIndexes: true })\n  }\n\n  /**\n   * Test whether the JSON contents of this node are deep equal to provided JSON object.\n   * @param {*} json\n   */\n  deepEqual (json) {\n    let i\n\n    if (this.type === 'array') {\n      if (!Array.isArray(json)) {\n        return false\n      }\n\n      if (this.childs.length !== json.length) {\n        return false\n      }\n\n      for (i = 0; i < this.childs.length; i++) {\n        if (!this.childs[i].deepEqual(json[i])) {\n          return false\n        }\n      }\n    } else if (this.type === 'object') {\n      if (typeof json !== 'object' || !json) {\n        return false\n      }\n\n      // we reckon with the order of the properties too.\n      const props = Object.keys(json)\n      if (this.childs.length !== props.length) {\n        return false\n      }\n      for (i = 0; i < props.length; i++) {\n        const child = this.childs[i]\n        if (child.field !== props[i] || !child.deepEqual(json[child.field])) {\n          return false\n        }\n      }\n    } else {\n      if (this.value !== json) {\n        return false\n      }\n    }\n\n    return true\n  }\n\n  /**\n   * Retrieve value from DOM\n   * @private\n   */\n  _getDomValue () {\n    this._clearValueError()\n\n    if (this.dom.value && this.type !== 'array' && this.type !== 'object') {\n      this.valueInnerText = getInnerText(this.dom.value)\n\n      if (this.valueInnerText === '' && this.dom.value.innerHTML !== '') {\n        // When clearing the contents, often a <br/> remains, messing up the\n        // styling of the empty text box. Therefore we remove the <br/>\n        this.dom.value.textContent = ''\n      }\n    }\n\n    if (this.valueInnerText !== undefined) {\n      try {\n        // retrieve the value\n        let value\n        if (this.type === 'string') {\n          value = this._unescapeHTML(this.valueInnerText)\n        } else {\n          const str = this._unescapeHTML(this.valueInnerText)\n          value = parseString(str)\n        }\n        if (value !== this.value) {\n          this.value = value\n          this._debouncedOnChangeValue()\n        }\n      } catch (err) {\n        // keep the previous value\n        this._setValueError(translate('cannotParseValueError'))\n      }\n    }\n  }\n\n  /**\n   * Show a local error in case of invalid value\n   * @param {string} message\n   * @private\n   */\n  _setValueError (message) {\n    this.valueError = {\n      message\n    }\n    this.updateError()\n  }\n\n  _clearValueError () {\n    if (this.valueError) {\n      this.valueError = null\n      this.updateError()\n    }\n  }\n\n  /**\n   * Show a local error in case of invalid or duplicate field\n   * @param {string} message\n   * @private\n   */\n  _setFieldError (message) {\n    this.fieldError = {\n      message\n    }\n    this.updateError()\n  }\n\n  _clearFieldError () {\n    if (this.fieldError) {\n      this.fieldError = null\n      this.updateError()\n    }\n  }\n\n  /**\n   * Handle a changed value\n   * @private\n   */\n  _onChangeValue () {\n    // get current selection, then override the range such that we can select\n    // the added/removed text on undo/redo\n    const oldSelection = this.editor.getDomSelection()\n    if (oldSelection.range) {\n      const undoDiff = textDiff(String(this.value), String(this.previousValue))\n      oldSelection.range.startOffset = undoDiff.start\n      oldSelection.range.endOffset = undoDiff.end\n    }\n    const newSelection = this.editor.getDomSelection()\n    if (newSelection.range) {\n      const redoDiff = textDiff(String(this.previousValue), String(this.value))\n      newSelection.range.startOffset = redoDiff.start\n      newSelection.range.endOffset = redoDiff.end\n    }\n\n    this.editor._onAction('editValue', {\n      path: this.getInternalPath(),\n      oldValue: this.previousValue,\n      newValue: this.value,\n      oldSelection,\n      newSelection\n    })\n\n    this.previousValue = this.value\n  }\n\n  /**\n   * Handle a changed field\n   * @private\n   */\n  _onChangeField () {\n    // get current selection, then override the range such that we can select\n    // the added/removed text on undo/redo\n    const oldSelection = this.editor.getDomSelection()\n    const previous = this.previousField || ''\n    if (oldSelection.range) {\n      const undoDiff = textDiff(this.field, previous)\n      oldSelection.range.startOffset = undoDiff.start\n      oldSelection.range.endOffset = undoDiff.end\n    }\n    const newSelection = this.editor.getDomSelection()\n    if (newSelection.range) {\n      const redoDiff = textDiff(previous, this.field)\n      newSelection.range.startOffset = redoDiff.start\n      newSelection.range.endOffset = redoDiff.end\n    }\n\n    this.editor._onAction('editField', {\n      parentPath: this.parent.getInternalPath(),\n      index: this.getIndex(),\n      oldValue: this.previousField,\n      newValue: this.field,\n      oldSelection,\n      newSelection\n    })\n\n    this.previousField = this.field\n  }\n\n  /**\n   * Update dom value:\n   * - the text color of the value, depending on the type of the value\n   * - the height of the field, depending on the width\n   * - background color in case it is empty\n   * @private\n   */\n  _updateDomValue () {\n    const domValue = this.dom.value\n    if (domValue) {\n      const classNames = ['jsoneditor-value']\n\n      // set text color depending on value type\n      const value = this.value\n      const valueType = (this.type === 'auto') ? getType(value) : this.type\n      const valueIsUrl = valueType === 'string' && isUrl(value)\n      classNames.push('jsoneditor-' + valueType)\n      if (valueIsUrl) {\n        classNames.push('jsoneditor-url')\n      }\n\n      // visual styling when empty\n      const isEmpty = (String(this.value) === '' && this.type !== 'array' && this.type !== 'object')\n      if (isEmpty) {\n        classNames.push('jsoneditor-empty')\n      }\n\n      // highlight when there is a search result\n      if (this.searchValueActive) {\n        classNames.push('jsoneditor-highlight-active')\n      }\n      if (this.searchValue) {\n        classNames.push('jsoneditor-highlight')\n      }\n\n      domValue.className = classNames.join(' ')\n\n      // update title\n      if (valueType === 'array' || valueType === 'object') {\n        const count = this.childs ? this.childs.length : 0\n        domValue.title = this.type + ' containing ' + count + ' items'\n      } else if (valueIsUrl && this.editable.value) {\n        domValue.title = translate('openUrl')\n      } else {\n        domValue.title = ''\n      }\n\n      // show checkbox when the value is a boolean\n      if (valueType === 'boolean' && this.editable.value) {\n        if (!this.dom.checkbox) {\n          this.dom.checkbox = document.createElement('input')\n          this.dom.checkbox.type = 'checkbox'\n          this.dom.tdCheckbox = document.createElement('td')\n          this.dom.tdCheckbox.className = 'jsoneditor-tree'\n          this.dom.tdCheckbox.appendChild(this.dom.checkbox)\n\n          this.dom.tdValue.parentNode.insertBefore(this.dom.tdCheckbox, this.dom.tdValue)\n        }\n\n        this.dom.checkbox.checked = this.value\n      } else {\n        // cleanup checkbox when displayed\n        if (this.dom.tdCheckbox) {\n          this.dom.tdCheckbox.parentNode.removeChild(this.dom.tdCheckbox)\n          delete this.dom.tdCheckbox\n          delete this.dom.checkbox\n        }\n      }\n\n      // create select box when this node has an enum object\n      if (this.enum && this.editable.value) {\n        if (!this.dom.select) {\n          this.dom.select = document.createElement('select')\n          this.id = this.field + '_' + new Date().getUTCMilliseconds()\n          this.dom.select.id = this.id\n          this.dom.select.name = this.dom.select.id\n\n          // Create the default empty option\n          const defaultOption = document.createElement('option')\n          defaultOption.value = ''\n          defaultOption.textContent = '--'\n          this.dom.select.appendChild(defaultOption)\n\n          // Iterate all enum values and add them as options\n          this._updateEnumOptions()\n\n          this.dom.tdSelect = document.createElement('td')\n          this.dom.tdSelect.className = 'jsoneditor-tree'\n          this.dom.tdSelect.appendChild(this.dom.select)\n          this.dom.tdValue.parentNode.insertBefore(this.dom.tdSelect, this.dom.tdValue)\n        }\n\n        // Select the matching value\n        this.dom.select.value = (this.enum.indexOf(this.value) !== -1)\n          ? this.value\n          : '' // default\n\n        // If the enum is inside a composite type display\n        // both the simple input and the dropdown field\n        if (this.schema && (\n          !hasOwnProperty(this.schema, 'oneOf') &&\n          !hasOwnProperty(this.schema, 'anyOf') &&\n          !hasOwnProperty(this.schema, 'allOf'))\n        ) {\n          this.valueFieldHTML = this.dom.tdValue.innerHTML\n          this.dom.tdValue.style.visibility = 'hidden'\n          this.dom.tdValue.textContent = ''\n        } else {\n          delete this.valueFieldHTML\n        }\n      } else {\n        // cleanup select box when displayed, and attach the editable div instead\n        if (this.dom.tdSelect) {\n          this.dom.tdSelect.parentNode.removeChild(this.dom.tdSelect)\n          delete this.dom.tdSelect\n          delete this.dom.select\n          this.dom.tdValue.innerHTML = this.valueFieldHTML\n          this.dom.tdValue.style.visibility = ''\n          delete this.valueFieldHTML\n\n          this.dom.tdValue.appendChild(this.dom.value)\n        }\n      }\n\n      // show color picker when value is a color\n      if (this.editor.options.colorPicker &&\n          typeof value === 'string' &&\n          isValidColor(value)) {\n        if (!this.dom.color) {\n          this.dom.color = document.createElement('div')\n          this.dom.color.className = 'jsoneditor-color'\n\n          this.dom.tdColor = document.createElement('td')\n          this.dom.tdColor.className = 'jsoneditor-tree'\n          this.dom.tdColor.appendChild(this.dom.color)\n\n          this.dom.tdValue.parentNode.insertBefore(this.dom.tdColor, this.dom.tdValue)\n        }\n\n        // update styling of value and color background\n        addClassName(this.dom.value, 'jsoneditor-color-value')\n        if (!this.editable.value) {\n          addClassName(this.dom.color, 'jsoneditor-color-readonly')\n        } else {\n          removeClassName(this.dom.color, 'jsoneditor-color-readonly')\n        }\n        this.dom.color.style.backgroundColor = value\n      } else {\n        // cleanup color picker when displayed\n        this._deleteDomColor()\n      }\n\n      // show date tag when value is a timestamp in milliseconds\n      if (this._showTimestampTag()) {\n        if (!this.dom.date) {\n          this.dom.date = document.createElement('div')\n          this.dom.date.className = 'jsoneditor-date'\n          this.dom.value.parentNode.appendChild(this.dom.date)\n        }\n\n        let title = null\n        if (typeof this.editor.options.timestampFormat === 'function') {\n          title = this.editor.options.timestampFormat({\n            field: this.field,\n            value: this.value,\n            path: this.getPath()\n          })\n        }\n        if (!title) {\n          this.dom.date.textContent = new Date(value).toISOString()\n        } else {\n          while (this.dom.date.firstChild) {\n            this.dom.date.removeChild(this.dom.date.firstChild)\n          }\n          this.dom.date.appendChild(document.createTextNode(title))\n        }\n        this.dom.date.title = new Date(value).toString()\n      } else {\n        // cleanup date tag\n        if (this.dom.date) {\n          this.dom.date.parentNode.removeChild(this.dom.date)\n          delete this.dom.date\n        }\n      }\n\n      // strip formatting from the contents of the editable div\n      stripFormatting(domValue)\n\n      this._updateDomDefault()\n    }\n  }\n\n  _updateEnumOptions () {\n    if (!this.enum || !this.dom.select) {\n      return\n    }\n\n    // clear the existing options\n    this.dom.select.innerHTML = ''\n\n    // Iterate all enum values and add them as options\n    for (let i = 0; i < this.enum.length; i++) {\n      const option = document.createElement('option')\n      option.value = this.enum[i]\n      option.textContent = this.enum[i]\n      this.dom.select.appendChild(option)\n    }\n  }\n\n  _deleteDomColor () {\n    if (this.dom.color) {\n      this.dom.tdColor.parentNode.removeChild(this.dom.tdColor)\n      delete this.dom.tdColor\n      delete this.dom.color\n\n      removeClassName(this.dom.value, 'jsoneditor-color-value')\n    }\n  }\n\n  /**\n   * Update dom field:\n   * - the text color of the field, depending on the text\n   * - the height of the field, depending on the width\n   * - background color in case it is empty\n   * @private\n   */\n  _updateDomField () {\n    const domField = this.dom.field\n    if (domField) {\n      const tooltip = makeFieldTooltip(this.schema, this.editor.options.language)\n      if (tooltip) {\n        domField.title = tooltip\n      }\n\n      // make background color lightgray when empty\n      const isEmpty = (String(this.field) === '' && this.parent && this.parent.type !== 'array')\n      if (isEmpty) {\n        addClassName(domField, 'jsoneditor-empty')\n      } else {\n        removeClassName(domField, 'jsoneditor-empty')\n      }\n\n      // highlight when there is a search result\n      if (this.searchFieldActive) {\n        addClassName(domField, 'jsoneditor-highlight-active')\n      } else {\n        removeClassName(domField, 'jsoneditor-highlight-active')\n      }\n      if (this.searchField) {\n        addClassName(domField, 'jsoneditor-highlight')\n      } else {\n        removeClassName(domField, 'jsoneditor-highlight')\n      }\n\n      // strip formatting from the contents of the editable div\n      stripFormatting(domField)\n    }\n  }\n\n  /**\n   * Retrieve field from DOM\n   * @param {boolean} [forceUnique]  If true, the field name will be changed\n   *                                 into a unique name in case it is a duplicate.\n   * @private\n   */\n  _getDomField (forceUnique) {\n    this._clearFieldError()\n\n    if (this.dom.field && this.fieldEditable) {\n      this.fieldInnerText = getInnerText(this.dom.field)\n\n      if (this.fieldInnerText === '' && this.dom.field.innerHTML !== '') {\n        // When clearing the contents, often a <br/> remains, messing up the\n        // styling of the empty text box. Therefore we remove the <br/>\n        this.dom.field.textContent = ''\n      }\n    }\n\n    if (this.fieldInnerText !== undefined) {\n      try {\n        let field = this._unescapeHTML(this.fieldInnerText)\n\n        const existingFieldNames = this.parent.getFieldNames(this)\n        const isDuplicate = existingFieldNames.indexOf(field) !== -1\n\n        if (!isDuplicate) {\n          if (field !== this.field) {\n            this.field = field\n            this._debouncedOnChangeField()\n          }\n        } else {\n          if (forceUnique) {\n            // fix duplicate field: change it into a unique name\n            field = findUniqueName(field, existingFieldNames)\n            if (field !== this.field) {\n              this.field = field\n\n              // TODO: don't debounce but resolve right away, and cancel current debounce\n              this._debouncedOnChangeField()\n            }\n          } else {\n            this._setFieldError(translate('duplicateFieldError'))\n          }\n        }\n      } catch (err) {\n        // keep the previous field value\n        this._setFieldError(translate('cannotParseFieldError'))\n      }\n    }\n  }\n\n  /**\n   * Update the value of the schema default element in the DOM.\n   * @private\n   * @returns {undefined}\n   */\n  _updateDomDefault () {\n    // Short-circuit if schema is missing, has no default, or if Node has children\n    if (!this.schema || this.schema.default === undefined || this._hasChilds()) {\n      return\n    }\n\n    // select either enum dropdown (select) or input value\n    const inputElement = this.dom.select\n      ? this.dom.select\n      : this.dom.value\n\n    if (!inputElement) {\n      return\n    }\n\n    if (this.value === this.schema.default) {\n      inputElement.title = translate('default')\n      addClassName(inputElement, 'jsoneditor-is-default')\n      removeClassName(inputElement, 'jsoneditor-is-not-default')\n    } else {\n      inputElement.removeAttribute('title')\n      removeClassName(inputElement, 'jsoneditor-is-default')\n      addClassName(inputElement, 'jsoneditor-is-not-default')\n    }\n  }\n\n  /**\n   * Test whether to show a timestamp tag or not\n   * @return {boolean} Returns true when the value is a timestamp\n   */\n  _showTimestampTag () {\n    if (typeof this.value !== 'number') {\n      return false\n    }\n\n    const timestampTag = this.editor.options.timestampTag\n    if (typeof timestampTag === 'function') {\n      const result = timestampTag({\n        field: this.field,\n        value: this.value,\n        path: this.getPath()\n      })\n\n      if (typeof result === 'boolean') {\n        return result\n      } else {\n        return isTimestamp(this.field, this.value)\n      }\n    } else if (timestampTag === true) {\n      return isTimestamp(this.field, this.value)\n    } else {\n      return false\n    }\n  }\n\n  /**\n   * Clear the dom of the node\n   */\n  clearDom () {\n    // TODO: hide the node first?\n    // this.hide();\n    // TODO: recursively clear dom?\n\n    this.dom = {}\n  }\n\n  /**\n   * Get the HTML DOM TR element of the node.\n   * The dom will be generated when not yet created\n   * @return {Element} tr    HTML DOM TR Element\n   */\n  getDom () {\n    const dom = this.dom\n    if (dom.tr) {\n      return dom.tr\n    }\n\n    this._updateEditability()\n\n    // create row\n    dom.tr = document.createElement('tr')\n    dom.tr.node = this\n\n    if (this.editor.options.mode === 'tree') { // note: we take here the global setting\n      const tdDrag = document.createElement('td')\n      if (this.editable.field) {\n        // create draggable area\n        if (this.parent) {\n          const domDrag = document.createElement('button')\n          domDrag.type = 'button'\n          dom.drag = domDrag\n          domDrag.className = 'jsoneditor-button jsoneditor-dragarea'\n          domDrag.title = translate('drag')\n          tdDrag.appendChild(domDrag)\n        }\n      }\n      dom.tr.appendChild(tdDrag)\n\n      // create context menu\n      const tdMenu = document.createElement('td')\n      const menu = document.createElement('button')\n      menu.type = 'button'\n      dom.menu = menu\n      menu.className = 'jsoneditor-button jsoneditor-contextmenu-button'\n      menu.title = translate('actionsMenu')\n      tdMenu.appendChild(dom.menu)\n      dom.tr.appendChild(tdMenu)\n    }\n\n    // create tree and field\n    const tdField = document.createElement('td')\n    dom.tr.appendChild(tdField)\n    dom.tree = this._createDomTree()\n    tdField.appendChild(dom.tree)\n\n    this.updateDom({ updateIndexes: true })\n\n    return dom.tr\n  }\n\n  /**\n   * Test whether a Node is rendered and visible\n   * @returns {boolean}\n   */\n  isVisible () {\n    return (this.dom && this.dom.tr && this.dom.tr.parentNode) || false\n  }\n\n  /**\n   * Test if this node is a sescendant of an other node\n   * @param {Node} node\n   * @return {boolean} isDescendant\n   * @private\n   */\n  isDescendantOf (node) {\n    let n = this.parent\n    while (n) {\n      if (n === node) {\n        return true\n      }\n      n = n.parent\n    }\n\n    return false\n  }\n\n  /**\n   * Create an editable field\n   * @return {Element} domField\n   * @private\n   */\n  _createDomField () {\n    return document.createElement('div')\n  }\n\n  /**\n   * Set highlighting for this node and all its childs.\n   * Only applied to the currently visible (expanded childs)\n   * @param {boolean} highlight\n   */\n  setHighlight (highlight) {\n    if (this.dom.tr) {\n      if (highlight) {\n        addClassName(this.dom.tr, 'jsoneditor-highlight')\n      } else {\n        removeClassName(this.dom.tr, 'jsoneditor-highlight')\n      }\n\n      if (this.append) {\n        this.append.setHighlight(highlight)\n      }\n\n      if (this.childs) {\n        this.childs.forEach(child => {\n          child.setHighlight(highlight)\n        })\n      }\n    }\n  }\n\n  /**\n   * Select or deselect a node\n   * @param {boolean} selected\n   * @param {boolean} [isFirst]\n   */\n  setSelected (selected, isFirst) {\n    this.selected = selected\n\n    if (this.dom.tr) {\n      if (selected) {\n        addClassName(this.dom.tr, 'jsoneditor-selected')\n      } else {\n        removeClassName(this.dom.tr, 'jsoneditor-selected')\n      }\n\n      if (isFirst) {\n        addClassName(this.dom.tr, 'jsoneditor-first')\n      } else {\n        removeClassName(this.dom.tr, 'jsoneditor-first')\n      }\n\n      if (this.append) {\n        this.append.setSelected(selected)\n      }\n\n      if (this.showMore) {\n        this.showMore.setSelected(selected)\n      }\n\n      if (this.childs) {\n        this.childs.forEach(child => {\n          child.setSelected(selected)\n        })\n      }\n    }\n  }\n\n  /**\n   * Update the value of the node. Only primitive types are allowed, no Object\n   * or Array is allowed.\n   * @param {String | Number | Boolean | null} value\n   */\n  updateValue (value) {\n    this.value = value\n    this.previousValue = value\n    this.valueError = undefined\n    this.updateDom()\n  }\n\n  /**\n   * Update the field of the node.\n   * @param {String} field\n   */\n  updateField (field) {\n    this.field = field\n    this.previousField = field\n    this.fieldError = undefined\n    this.updateDom()\n  }\n\n  /**\n   * Update the HTML DOM, optionally recursing through the childs\n   * @param {Object} [options] Available parameters:\n   *                          {boolean} [recurse]         If true, the\n   *                          DOM of the childs will be updated recursively.\n   *                          False by default.\n   *                          {boolean} [updateIndexes]   If true, the childs\n   *                          indexes of the node will be updated too. False by\n   *                          default.\n   */\n  updateDom (options) {\n    // update level indentation\n    const domTree = this.dom.tree\n    if (domTree) {\n      domTree.style.marginLeft = this.getLevel() * 24 + 'px'\n    }\n\n    // apply field to DOM\n    const domField = this.dom.field\n    if (domField) {\n      if (this.fieldEditable) {\n        // parent is an object\n        domField.contentEditable = this.editable.field\n        domField.spellcheck = false\n        domField.className = 'jsoneditor-field'\n      } else {\n        // parent is an array this is the root node\n        domField.contentEditable = false\n        domField.className = 'jsoneditor-readonly'\n      }\n\n      let fieldText\n      if (this.index !== undefined) {\n        fieldText = this.index\n      } else if (this.field !== undefined) {\n        fieldText = this.field\n      } else {\n        const schema = this.editor.options.schema\n          ? Node._findSchema(this.editor.options.schema, this.editor.options.schemaRefs || {}, this.getPath())\n          : undefined\n\n        if (schema && schema.title) {\n          fieldText = schema.title\n        } else if (this._hasChilds()) {\n          fieldText = this.type\n        } else {\n          fieldText = ''\n        }\n      }\n\n      const escapedField = this._escapeHTML(fieldText)\n      if (\n        document.activeElement !== domField &&\n        escapedField !== this._unescapeHTML(getInnerText(domField))\n      ) {\n        // only update if it not has the focus or when there is an actual change,\n        // else you would needlessly loose the caret position when changing tabs\n        // or whilst typing\n        domField.innerHTML = escapedField\n      }\n      this._updateSchema()\n      this._updateEnumOptions()\n    }\n\n    // apply value to DOM\n    const domValue = this.dom.value\n    if (domValue) {\n      if (this.type === 'array' || this.type === 'object') {\n        this.updateNodeName()\n      } else {\n        const escapedValue = this._escapeHTML(this.value)\n        if (\n          document.activeElement !== domValue &&\n          escapedValue !== this._unescapeHTML(getInnerText(domValue))\n        ) {\n          // only update if it not has the focus or when there is an actual change,\n          // else you would needlessly loose the caret position when changing tabs\n          // or whilst typing\n          domValue.innerHTML = escapedValue\n        }\n      }\n    }\n\n    // apply styling to the table row\n    const tr = this.dom.tr\n    if (tr) {\n      if (this.type === 'array' || this.type === 'object') {\n        addClassName(tr, 'jsoneditor-expandable')\n\n        if (this.expanded) {\n          addClassName(tr, 'jsoneditor-expanded')\n          removeClassName(tr, 'jsoneditor-collapsed')\n        } else {\n          addClassName(tr, 'jsoneditor-collapsed')\n          removeClassName(tr, 'jsoneditor-expanded')\n        }\n      } else {\n        removeClassName(tr, 'jsoneditor-expandable')\n        removeClassName(tr, 'jsoneditor-expanded')\n        removeClassName(tr, 'jsoneditor-collapsed')\n      }\n    }\n\n    // update field and value\n    this._updateDomField()\n    this._updateDomValue()\n\n    // update childs indexes\n    if (options && options.updateIndexes === true) {\n      // updateIndexes is true or undefined\n      this._updateDomIndexes()\n    }\n\n    // update childs recursively\n    if (options && options.recurse === true) {\n      if (this.childs) {\n        this.childs.forEach(child => {\n          child.updateDom(options)\n        })\n      }\n    }\n\n    // update rendering of error\n    if (this.error) {\n      this.updateError()\n    }\n\n    // update row with append button\n    if (this.append) {\n      this.append.updateDom()\n    }\n\n    // update \"show more\" text at the bottom of large arrays\n    if (this.showMore) {\n      this.showMore.updateDom()\n    }\n\n    // fire onClassName\n    this._updateCssClassName()\n  }\n\n  /**\n   * Locate the JSON schema of the node and check for any enum type\n   * @private\n   */\n  _updateSchema () {\n    // Locating the schema of the node and checking for any enum type\n    if (this.editor && this.editor.options) {\n      // find the part of the json schema matching this nodes path\n      this.schema = this.editor.options.schema\n      // fix childSchema with $ref, and not display the select element on the child schema because of not found enum\n        ? Node._findSchema(this.editor.options.schema, this.editor.options.schemaRefs || {}, this.getPath())\n        : null\n      if (this.schema) {\n        this.enum = Node._findEnum(this.schema)\n      } else {\n        delete this.enum\n      }\n    }\n  }\n\n  /**\n   * Update the DOM of the childs of a node: update indexes and undefined field\n   * names.\n   * Only applicable when structure is an array or object\n   * @private\n   */\n  _updateDomIndexes () {\n    const domValue = this.dom.value\n    const childs = this.childs\n    if (domValue && childs) {\n      if (this.type === 'array') {\n        childs.forEach((child, index) => {\n          child.index = index\n          const childField = child.dom.field\n          if (childField) {\n            childField.textContent = index\n          }\n        })\n      } else if (this.type === 'object') {\n        childs.forEach(child => {\n          if (child.index !== undefined) {\n            delete child.index\n\n            if (child.field === undefined) {\n              child.field = ''\n            }\n          }\n        })\n      }\n    }\n  }\n\n  /**\n   * Create an editable value\n   * @private\n   */\n  _createDomValue () {\n    let domValue\n\n    if (this.type === 'array') {\n      domValue = document.createElement('div')\n      domValue.textContent = '[...]'\n    } else if (this.type === 'object') {\n      domValue = document.createElement('div')\n      domValue.textContent = '{...}'\n    } else {\n      if (!this.editable.value && isUrl(this.value)) {\n        // create a link in case of read-only editor and value containing an url\n        domValue = document.createElement('a')\n        domValue.href = this.value\n        domValue.innerHTML = this._escapeHTML(this.value)\n      } else {\n        // create an editable or read-only div\n        domValue = document.createElement('div')\n        domValue.contentEditable = this.editable.value\n        domValue.spellcheck = false\n        domValue.innerHTML = this._escapeHTML(this.value)\n      }\n    }\n\n    return domValue\n  }\n\n  /**\n   * Create an expand/collapse button\n   * @return {Element} expand\n   * @private\n   */\n  _createDomExpandButton () {\n    // create expand button\n    const expand = document.createElement('button')\n    expand.type = 'button'\n    if (this._hasChilds()) {\n      expand.className = this.expanded\n        ? 'jsoneditor-button jsoneditor-expanded'\n        : 'jsoneditor-button jsoneditor-collapsed'\n      expand.title = translate('expandTitle')\n    } else {\n      expand.className = 'jsoneditor-button jsoneditor-invisible'\n      expand.title = ''\n    }\n\n    return expand\n  }\n\n  /**\n   * Create a DOM tree element, containing the expand/collapse button\n   * @return {Element} domTree\n   * @private\n   */\n  _createDomTree () {\n    const dom = this.dom\n    const domTree = document.createElement('table')\n    const tbody = document.createElement('tbody')\n    domTree.style.borderCollapse = 'collapse' // TODO: put in css\n    domTree.className = 'jsoneditor-values'\n    domTree.appendChild(tbody)\n    const tr = document.createElement('tr')\n    tbody.appendChild(tr)\n\n    // create expand button\n    const tdExpand = document.createElement('td')\n    tdExpand.className = 'jsoneditor-tree'\n    tr.appendChild(tdExpand)\n    dom.expand = this._createDomExpandButton()\n    tdExpand.appendChild(dom.expand)\n    dom.tdExpand = tdExpand\n\n    // create the field\n    const tdField = document.createElement('td')\n    tdField.className = 'jsoneditor-tree'\n    tr.appendChild(tdField)\n    dom.field = this._createDomField()\n    tdField.appendChild(dom.field)\n    dom.tdField = tdField\n\n    // create a separator\n    const tdSeparator = document.createElement('td')\n    tdSeparator.className = 'jsoneditor-tree'\n    tr.appendChild(tdSeparator)\n    if (this.type !== 'object' && this.type !== 'array') {\n      tdSeparator.appendChild(document.createTextNode(':'))\n      tdSeparator.className = 'jsoneditor-separator'\n    }\n    dom.tdSeparator = tdSeparator\n\n    // create the value\n    const tdValue = document.createElement('td')\n    tdValue.className = 'jsoneditor-tree'\n    tr.appendChild(tdValue)\n    dom.value = this._createDomValue()\n    tdValue.appendChild(dom.value)\n    dom.tdValue = tdValue\n\n    return domTree\n  }\n\n  /**\n   * Handle an event. The event is caught centrally by the editor\n   * @param {Event} event\n   */\n  onEvent (event) {\n    const type = event.type\n    const target = event.target || event.srcElement\n    const dom = this.dom\n    const node = this\n    const expandable = this._hasChilds()\n\n    // check if mouse is on menu or on dragarea.\n    // If so, highlight current row and its childs\n    if (target === dom.drag || target === dom.menu) {\n      if (type === 'mouseover') {\n        this.editor.highlighter.highlight(this)\n      } else if (type === 'mouseout') {\n        this.editor.highlighter.unhighlight()\n      }\n    }\n\n    // context menu events\n    if (type === 'click' && target === dom.menu) {\n      const highlighter = node.editor.highlighter\n      highlighter.highlight(node)\n      highlighter.lock()\n      addClassName(dom.menu, 'jsoneditor-selected')\n      this.showContextMenu(dom.menu, () => {\n        removeClassName(dom.menu, 'jsoneditor-selected')\n        highlighter.unlock()\n        highlighter.unhighlight()\n      })\n    }\n\n    // expand events\n    if (type === 'click') {\n      if (target === dom.expand) {\n        if (expandable) {\n          const recurse = event.ctrlKey // with ctrl-key, expand/collapse all\n          this._onExpand(recurse)\n        }\n      }\n    }\n\n    if (\n      type === 'click' &&\n      (event.target === node.dom.tdColor || event.target === node.dom.color) &&\n      this.editable.value\n    ) {\n      this._showColorPicker()\n    }\n\n    // swap the value of a boolean when the checkbox displayed left is clicked\n    if (type === 'change' && target === dom.checkbox) {\n      this.dom.value.textContent = String(!this.value)\n      this._getDomValue()\n      this._updateDomDefault()\n    }\n\n    // update the value of the node based on the selected option\n    if (type === 'change' && target === dom.select) {\n      this.dom.value.innerHTML = this._escapeHTML(dom.select.value)\n      this._getDomValue()\n      this._updateDomValue()\n    }\n\n    // value events\n    const domValue = dom.value\n    if (target === domValue) {\n      // noinspection FallthroughInSwitchStatementJS\n      switch (type) {\n        case 'blur':\n        case 'change': {\n          this._getDomValue()\n          this._clearValueError()\n          this._updateDomValue()\n\n          const escapedValue = this._escapeHTML(this.value)\n          if (escapedValue !== this._unescapeHTML(getInnerText(domValue))) {\n            // only update when there is an actual change, else you loose the\n            // caret position when changing tabs or whilst typing\n            domValue.innerHTML = escapedValue\n          }\n          break\n        }\n\n        case 'input':\n          // this._debouncedGetDomValue(true); // TODO\n          this._getDomValue()\n          this._updateDomValue()\n          break\n\n        case 'keydown':\n        case 'mousedown':\n          // TODO: cleanup\n          this.editor.selection = this.editor.getDomSelection()\n          break\n\n        case 'click':\n          if (event.ctrlKey && this.editable.value) {\n            // if read-only, we use the regular click behavior of an anchor\n            if (isUrl(this.value)) {\n              event.preventDefault()\n              window.open(this.value, '_blank', 'noreferrer')\n            }\n          }\n          break\n\n        case 'keyup':\n          // this._debouncedGetDomValue(true); // TODO\n          this._getDomValue()\n          this._updateDomValue()\n          break\n\n        case 'cut':\n        case 'paste':\n          setTimeout(() => {\n            node._getDomValue()\n            node._updateDomValue()\n          }, 1)\n          break\n      }\n    }\n\n    // field events\n    const domField = dom.field\n    if (target === domField) {\n      switch (type) {\n        case 'blur': {\n          this._getDomField(true)\n          this._updateDomField()\n\n          const escapedField = this._escapeHTML(this.field)\n          if (escapedField !== this._unescapeHTML(getInnerText(domField))) {\n            // only update when there is an actual change, else you loose the\n            // caret position when changing tabs or whilst typing\n            domField.innerHTML = escapedField\n          }\n          break\n        }\n\n        case 'input':\n          this._getDomField()\n          this._updateSchema()\n          this._updateDomField()\n          this._updateDomValue()\n          break\n\n        case 'keydown':\n        case 'mousedown':\n          this.editor.selection = this.editor.getDomSelection()\n          break\n\n        case 'keyup':\n          this._getDomField()\n          this._updateDomField()\n          break\n\n        case 'cut':\n        case 'paste':\n          setTimeout(() => {\n            node._getDomField()\n            node._updateDomField()\n          }, 1)\n          break\n      }\n    }\n\n    // focus\n    // when clicked in whitespace left or right from the field or value, set focus\n    const domTree = dom.tree\n    if (domTree && target === domTree.parentNode && type === 'click' && !event.hasMoved) {\n      const left = (event.offsetX !== undefined)\n        ? (event.offsetX < (this.getLevel() + 1) * 24)\n        : (event.pageX < getAbsoluteLeft(dom.tdSeparator))// for FF\n      if (left || expandable) {\n        // node is expandable when it is an object or array\n        if (domField) {\n          setEndOfContentEditable(domField)\n          domField.focus()\n        }\n      } else {\n        if (domValue && !this.enum) {\n          setEndOfContentEditable(domValue)\n          domValue.focus()\n        }\n      }\n    }\n    if (((target === dom.tdExpand && !expandable) || target === dom.tdField || target === dom.tdSeparator) &&\n        (type === 'click' && !event.hasMoved)) {\n      if (domField) {\n        setEndOfContentEditable(domField)\n        domField.focus()\n      }\n    }\n\n    if (type === 'keydown') {\n      this.onKeyDown(event)\n    }\n\n    // fire after applying for example a change by clicking a checkbox\n    if (typeof this.editor.options.onEvent === 'function') {\n      this._onEvent(event)\n    }\n  }\n\n  /**\n   * Trigger external onEvent provided in options if node is a JSON field or\n   * value.\n   * Information provided depends on the element, value is only included if\n   * event occurs in a JSON value:\n   * {field: string, path: {string|number}[] [, value: string]}\n   * @param {Event} event\n   * @private\n   */\n  _onEvent (event) {\n    const element = event.target\n    const isField = element === this.dom.field\n    const isValue = (\n      element === this.dom.value ||\n      element === this.dom.checkbox ||\n      element === this.dom.select)\n\n    if (isField || isValue) {\n      const info = {\n        field: this.getField(),\n        path: this.getPath()\n      }\n\n      // For leaf values, include value\n      if (isValue && !this._hasChilds()) {\n        info.value = this.getValue()\n      }\n\n      this.editor.options.onEvent(info, event)\n    }\n  }\n\n  /**\n   * Key down event handler\n   * @param {Event} event\n   */\n  onKeyDown (event) {\n    const keynum = event.which || event.keyCode\n    const target = event.target || event.srcElement\n    const ctrlKey = event.ctrlKey\n    const shiftKey = event.shiftKey\n    const altKey = event.altKey\n    let handled = false\n    let prevNode, nextNode, nextDom, nextDom2\n    const editable = this.editor.options.mode === 'tree'\n    let oldSelection\n    let oldNextNode\n    let oldParent\n    let oldIndexRedo\n    let newIndexRedo\n    let oldParentPathRedo\n    let newParentPathRedo\n    let nodes\n    let multiselection\n    const selectedNodes = this.editor.multiselection.nodes.length > 0\n      ? this.editor.multiselection.nodes\n      : [this]\n    const firstNode = selectedNodes[0]\n    const lastNode = selectedNodes[selectedNodes.length - 1]\n\n    // console.log(ctrlKey, keynum, event.charCode); // TODO: cleanup\n    if (keynum === 13) { // Enter\n      if (target === this.dom.value) {\n        if (!this.editable.value || event.ctrlKey) {\n          if (isUrl(this.value)) {\n            window.open(this.value, '_blank', 'noreferrer')\n            handled = true\n          }\n        }\n      } else if (target === this.dom.expand) {\n        const expandable = this._hasChilds()\n        if (expandable) {\n          const recurse = event.ctrlKey // with ctrl-key, expand/collapse all\n          this._onExpand(recurse)\n          target.focus()\n          handled = true\n        }\n      }\n    } else if (keynum === 68) { // D\n      if (ctrlKey && editable) { // Ctrl+D\n        Node.onDuplicate(selectedNodes)\n        handled = true\n      }\n    } else if (keynum === 69) { // E\n      if (ctrlKey) { // Ctrl+E and Ctrl+Shift+E\n        this._onExpand(shiftKey) // recurse = shiftKey\n        target.focus() // TODO: should restore focus in case of recursing expand (which takes DOM offline)\n        handled = true\n      }\n    } else if (keynum === 77 && editable) { // M\n      if (ctrlKey) { // Ctrl+M\n        this.showContextMenu(target)\n        handled = true\n      }\n    } else if (keynum === 46 && editable) { // Del\n      if (ctrlKey) { // Ctrl+Del\n        Node.onRemove(selectedNodes)\n        handled = true\n      }\n    } else if (keynum === 45 && editable) { // Ins\n      if (ctrlKey && !shiftKey) { // Ctrl+Ins\n        this._onInsertBefore()\n        handled = true\n      } else if (ctrlKey && shiftKey) { // Ctrl+Shift+Ins\n        this._onInsertAfter()\n        handled = true\n      }\n    } else if (keynum === 35) { // End\n      if (altKey) { // Alt+End\n        // find the last node\n        const endNode = this._lastNode()\n        if (endNode) {\n          endNode.focus(Node.focusElement || this._getElementName(target))\n        }\n        handled = true\n      }\n    } else if (keynum === 36) { // Home\n      if (altKey) { // Alt+Home\n        // find the first node\n        const homeNode = this._firstNode()\n        if (homeNode) {\n          homeNode.focus(Node.focusElement || this._getElementName(target))\n        }\n        handled = true\n      }\n    } else if (keynum === 37) { // Arrow Left\n      if (altKey && !shiftKey) { // Alt + Arrow Left\n        // move to left element\n        const prevElement = this._previousElement(target)\n        if (prevElement) {\n          this.focus(this._getElementName(prevElement))\n        }\n        handled = true\n      } else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow left\n        if (lastNode.expanded) {\n          const appendDom = lastNode.getAppendDom()\n          nextDom = appendDom ? appendDom.nextSibling : undefined\n        } else {\n          const dom = lastNode.getDom()\n          nextDom = dom.nextSibling\n        }\n        if (nextDom) {\n          nextNode = Node.getNodeFromTarget(nextDom)\n          nextDom2 = nextDom.nextSibling\n          const nextNode2 = Node.getNodeFromTarget(nextDom2)\n          if (nextNode && nextNode instanceof AppendNode &&\n              !(lastNode.parent.childs.length === 1) &&\n              nextNode2 && nextNode2.parent) {\n            oldSelection = this.editor.getDomSelection()\n            oldParent = firstNode.parent\n            oldNextNode = oldParent.childs[lastNode.getIndex() + 1] || oldParent.append\n            oldIndexRedo = firstNode.getIndex()\n            newIndexRedo = nextNode2.getIndex()\n            oldParentPathRedo = oldParent.getInternalPath()\n            newParentPathRedo = nextNode2.parent.getInternalPath()\n\n            selectedNodes.forEach(node => {\n              nextNode2.parent.moveBefore(node, nextNode2)\n            })\n            this.focus(Node.focusElement || this._getElementName(target))\n\n            this.editor._onAction('moveNodes', {\n              count: selectedNodes.length,\n              fieldNames: selectedNodes.map(getField),\n\n              oldParentPath: oldParent.getInternalPath(),\n              newParentPath: firstNode.parent.getInternalPath(),\n              oldIndex: oldNextNode.getIndex(),\n              newIndex: firstNode.getIndex(),\n\n              oldIndexRedo,\n              newIndexRedo,\n              oldParentPathRedo,\n              newParentPathRedo,\n\n              oldSelection,\n              newSelection: this.editor.getDomSelection()\n            })\n          }\n        }\n      }\n    } else if (keynum === 38) { // Arrow Up\n      if (altKey && !shiftKey) { // Alt + Arrow Up\n        // find the previous node\n        prevNode = this._previousNode()\n        if (prevNode) {\n          this.editor.deselect(true)\n          prevNode.focus(Node.focusElement || this._getElementName(target))\n        }\n        handled = true\n      } else if (!altKey && ctrlKey && shiftKey && editable) { // Ctrl + Shift + Arrow Up\n        // select multiple nodes\n        prevNode = this._previousNode()\n        if (prevNode) {\n          multiselection = this.editor.multiselection\n          multiselection.start = multiselection.start || this\n          multiselection.end = prevNode\n          nodes = this.editor._findTopLevelNodes(multiselection.start, multiselection.end)\n\n          this.editor.select(nodes)\n          prevNode.focus('field') // select field as we know this always exists\n        }\n        handled = true\n      } else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Up\n        // find the previous node\n        prevNode = firstNode._previousNode()\n        if (prevNode && prevNode.parent) {\n          oldSelection = this.editor.getDomSelection()\n          oldParent = firstNode.parent\n          oldNextNode = oldParent.childs[lastNode.getIndex() + 1] || oldParent.append\n          oldIndexRedo = firstNode.getIndex()\n          newIndexRedo = prevNode.getIndex()\n          oldParentPathRedo = oldParent.getInternalPath()\n          newParentPathRedo = prevNode.parent.getInternalPath()\n\n          selectedNodes.forEach(node => {\n            prevNode.parent.moveBefore(node, prevNode)\n          })\n          this.focus(Node.focusElement || this._getElementName(target))\n\n          this.editor._onAction('moveNodes', {\n            count: selectedNodes.length,\n            fieldNames: selectedNodes.map(getField),\n\n            oldParentPath: oldParent.getInternalPath(),\n            newParentPath: firstNode.parent.getInternalPath(),\n            oldIndex: oldNextNode.getIndex(),\n            newIndex: firstNode.getIndex(),\n\n            oldIndexRedo,\n            newIndexRedo,\n            oldParentPathRedo,\n            newParentPathRedo,\n\n            oldSelection,\n            newSelection: this.editor.getDomSelection()\n          })\n        }\n        handled = true\n      }\n    } else if (keynum === 39) { // Arrow Right\n      if (altKey && !shiftKey) { // Alt + Arrow Right\n        // move to right element\n        const nextElement = this._nextElement(target)\n        if (nextElement) {\n          this.focus(this._getElementName(nextElement))\n        }\n        handled = true\n      } else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Right\n        const dom = firstNode.getDom()\n        const prevDom = dom.previousSibling\n        if (prevDom) {\n          prevNode = Node.getNodeFromTarget(prevDom)\n          if (prevNode && prevNode.parent && !prevNode.isVisible()) {\n            oldSelection = this.editor.getDomSelection()\n            oldParent = firstNode.parent\n            oldNextNode = oldParent.childs[lastNode.getIndex() + 1] || oldParent.append\n            oldIndexRedo = firstNode.getIndex()\n            newIndexRedo = prevNode.getIndex()\n            oldParentPathRedo = oldParent.getInternalPath()\n            newParentPathRedo = prevNode.parent.getInternalPath()\n\n            selectedNodes.forEach(node => {\n              prevNode.parent.moveBefore(node, prevNode)\n            })\n            this.focus(Node.focusElement || this._getElementName(target))\n\n            this.editor._onAction('moveNodes', {\n              count: selectedNodes.length,\n              fieldNames: selectedNodes.map(getField),\n\n              oldParentPath: oldParent.getInternalPath(),\n              newParentPath: firstNode.parent.getInternalPath(),\n              oldIndex: oldNextNode.getIndex(),\n              newIndex: firstNode.getIndex(),\n\n              oldIndexRedo,\n              newIndexRedo,\n              oldParentPathRedo,\n              newParentPathRedo,\n\n              oldSelection,\n              newSelection: this.editor.getDomSelection()\n            })\n          }\n        }\n      }\n    } else if (keynum === 40) { // Arrow Down\n      if (altKey && !shiftKey) { // Alt + Arrow Down\n        // find the next node\n        nextNode = this._nextNode()\n        if (nextNode) {\n          this.editor.deselect(true)\n          nextNode.focus(Node.focusElement || this._getElementName(target))\n        }\n        handled = true\n      } else if (!altKey && ctrlKey && shiftKey && editable) { // Ctrl + Shift + Arrow Down\n        // select multiple nodes\n        nextNode = this._nextNode()\n        if (nextNode) {\n          multiselection = this.editor.multiselection\n          multiselection.start = multiselection.start || this\n          multiselection.end = nextNode\n          nodes = this.editor._findTopLevelNodes(multiselection.start, multiselection.end)\n\n          this.editor.select(nodes)\n          nextNode.focus('field') // select field as we know this always exists\n        }\n        handled = true\n      } else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down\n        // find the 2nd next node and move before that one\n        if (lastNode.expanded) {\n          nextNode = lastNode.append ? lastNode.append._nextNode() : undefined\n        } else {\n          nextNode = lastNode._nextNode()\n        }\n\n        // when the next node is not visible, we've reached the \"showMore\" buttons\n        if (nextNode && !nextNode.isVisible()) {\n          nextNode = nextNode.parent.showMore\n        }\n\n        if (nextNode && nextNode instanceof AppendNode) {\n          nextNode = lastNode\n        }\n\n        const nextNode2 = nextNode && (nextNode._nextNode() || nextNode.parent.append)\n        if (nextNode2 && nextNode2.parent) {\n          oldSelection = this.editor.getDomSelection()\n          oldParent = firstNode.parent\n          oldNextNode = oldParent.childs[lastNode.getIndex() + 1] || oldParent.append\n          oldIndexRedo = firstNode.getIndex()\n          newIndexRedo = nextNode2.getIndex()\n          oldParentPathRedo = oldParent.getInternalPath()\n          newParentPathRedo = nextNode2.parent.getInternalPath()\n\n          selectedNodes.forEach(node => {\n            nextNode2.parent.moveBefore(node, nextNode2)\n          })\n          this.focus(Node.focusElement || this._getElementName(target))\n\n          this.editor._onAction('moveNodes', {\n            count: selectedNodes.length,\n            fieldNames: selectedNodes.map(getField),\n            oldParentPath: oldParent.getInternalPath(),\n            newParentPath: firstNode.parent.getInternalPath(),\n            oldParentPathRedo,\n            newParentPathRedo,\n            oldIndexRedo,\n            newIndexRedo,\n            oldIndex: oldNextNode.getIndex(),\n            newIndex: firstNode.getIndex(),\n            oldSelection,\n            newSelection: this.editor.getDomSelection()\n          })\n        }\n        handled = true\n      }\n    }\n\n    if (handled) {\n      event.preventDefault()\n      event.stopPropagation()\n    }\n  }\n\n  /**\n   * Handle the expand event, when clicked on the expand button\n   * @param {boolean} recurse   If true, child nodes will be expanded too\n   * @private\n   */\n  _onExpand (recurse) {\n    let table\n    let frame\n    let scrollTop\n\n    if (recurse) {\n      // Take the table offline\n      table = this.dom.tr.parentNode // TODO: not nice to access the main table like this\n      frame = table.parentNode\n      scrollTop = frame.scrollTop\n      frame.removeChild(table)\n    }\n\n    if (this.expanded) {\n      this.collapse(recurse)\n    } else {\n      this.expand(recurse)\n    }\n\n    if (recurse) {\n      // Put the table online again\n      frame.appendChild(table)\n      frame.scrollTop = scrollTop\n    }\n\n    if (typeof this.editor.options.onExpand === 'function') {\n      this.editor.options.onExpand({\n        path: this.getPath(),\n        isExpand: this.expanded,\n        recursive: recurse\n      })\n    }\n  }\n\n  /**\n   * Open a color picker to select a new color\n   * @private\n   */\n  _showColorPicker () {\n    if (typeof this.editor.options.onColorPicker === 'function' && this.dom.color) {\n      const node = this\n\n      // force deleting current color picker (if any)\n      node._deleteDomColor()\n      node.updateDom()\n\n      const colorAnchor = createAbsoluteAnchor(this.dom.color, this.editor.getPopupAnchor())\n\n      this.editor.options.onColorPicker(colorAnchor, this.value, function onChange (value) {\n        if (typeof value === 'string' && value !== node.value) {\n          // force recreating the color block, to cleanup any attached color picker\n          node._deleteDomColor()\n\n          node.value = value\n          node.updateDom()\n          node._debouncedOnChangeValue()\n        }\n      })\n    }\n  }\n\n  /**\n   * Get all field names of an object\n   * @param {Node} [excludeNode] Optional node to be excluded from the returned field names\n   * @return {string[]}\n   */\n  getFieldNames (excludeNode) {\n    if (this.type === 'object') {\n      return this.childs\n        .filter(child => child !== excludeNode)\n        .map(child => child.field)\n    }\n\n    return []\n  }\n\n  /**\n   * Handle insert before event\n   * @param {String} [field]\n   * @param {*} [value]\n   * @param {String} [type]   Can be 'auto', 'array', 'object', or 'string'\n   * @private\n   */\n  _onInsertBefore (field, value, type) {\n    const oldSelection = this.editor.getDomSelection()\n\n    const newNode = new Node(this.editor, {\n      field: (field !== undefined) ? field : '',\n      value: (value !== undefined) ? value : '',\n      type\n    })\n    newNode.expand(true)\n\n    const beforePath = this.getInternalPath()\n\n    this.parent.insertBefore(newNode, this)\n    this.editor.highlighter.unhighlight()\n    newNode.focus('field')\n    const newSelection = this.editor.getDomSelection()\n\n    this.editor._onAction('insertBeforeNodes', {\n      nodes: [newNode],\n      paths: [newNode.getInternalPath()],\n      beforePath,\n      parentPath: this.parent.getInternalPath(),\n      oldSelection,\n      newSelection\n    })\n  }\n\n  /**\n   * Handle insert after event\n   * @param {String} [field]\n   * @param {*} [value]\n   * @param {String} [type]   Can be 'auto', 'array', 'object', or 'string'\n   * @private\n   */\n  _onInsertAfter (field, value, type) {\n    const oldSelection = this.editor.getDomSelection()\n\n    const newNode = new Node(this.editor, {\n      field: (field !== undefined) ? field : '',\n      value: (value !== undefined) ? value : '',\n      type\n    })\n    newNode.expand(true)\n    this.parent.insertAfter(newNode, this)\n    this.editor.highlighter.unhighlight()\n    newNode.focus('field')\n    const newSelection = this.editor.getDomSelection()\n\n    this.editor._onAction('insertAfterNodes', {\n      nodes: [newNode],\n      paths: [newNode.getInternalPath()],\n      afterPath: this.getInternalPath(),\n      parentPath: this.parent.getInternalPath(),\n      oldSelection,\n      newSelection\n    })\n  }\n\n  /**\n   * Handle append event\n   * @param {String} [field]\n   * @param {*} [value]\n   * @param {String} [type]   Can be 'auto', 'array', 'object', or 'string'\n   * @private\n   */\n  _onAppend (field, value, type) {\n    const oldSelection = this.editor.getDomSelection()\n\n    const newNode = new Node(this.editor, {\n      field: (field !== undefined) ? field : '',\n      value: (value !== undefined) ? value : '',\n      type\n    })\n    newNode.expand(true)\n    this.parent.appendChild(newNode)\n    this.editor.highlighter.unhighlight()\n    newNode.focus('field')\n    const newSelection = this.editor.getDomSelection()\n\n    this.editor._onAction('appendNodes', {\n      nodes: [newNode],\n      paths: [newNode.getInternalPath()],\n      parentPath: this.parent.getInternalPath(),\n      oldSelection,\n      newSelection\n    })\n  }\n\n  /**\n   * Change the type of the node's value\n   * @param {String} newType\n   * @private\n   */\n  _onChangeType (newType) {\n    const oldType = this.type\n    if (newType !== oldType) {\n      const oldSelection = this.editor.getDomSelection()\n      this.changeType(newType)\n      const newSelection = this.editor.getDomSelection()\n\n      this.editor._onAction('changeType', {\n        path: this.getInternalPath(),\n        oldType,\n        newType,\n        oldSelection,\n        newSelection\n      })\n    }\n  }\n\n  /**\n   * Sort the child's of the node. Only applicable when the node has type 'object'\n   * or 'array'.\n   * @param {String[] | string} path  Path of the child value to be compared\n   * @param {String} direction        Sorting direction. Available values: \"asc\", \"desc\"\n   * @param {boolean} [triggerAction=true]  If true (default), a user action will be\n   *                                        triggered, creating an entry in history\n   *                                        and invoking onChange.\n   * @private\n   */\n  sort (path, direction, triggerAction = true) {\n    if (typeof path === 'string') {\n      path = parsePath(path)\n    }\n\n    if (!this._hasChilds()) {\n      return\n    }\n\n    this.hideChilds() // sorting is faster when the childs are not attached to the dom\n\n    // copy the childs array (the old one will be kept for an undo action\n    const oldChilds = this.childs\n    this.childs = this.childs.concat()\n\n    // sort the childs array\n    const order = (direction === 'desc') ? -1 : 1\n\n    if (this.type === 'object') {\n      this.childs.sort((a, b) => order * naturalSort(a.field, b.field))\n    } else { // this.type === 'array'\n      this.childs.sort((a, b) => {\n        const nodeA = a.getNestedChild(path)\n        const nodeB = b.getNestedChild(path)\n\n        if (!nodeA) {\n          return order\n        }\n        if (!nodeB) {\n          return -order\n        }\n\n        const valueA = nodeA.value\n        const valueB = nodeB.value\n\n        if (typeof valueA !== 'string' && typeof valueB !== 'string') {\n          // both values are a number, boolean, or null -> use simple, fast sorting\n          return valueA > valueB ? order : valueA < valueB ? -order : 0\n        }\n\n        return order * naturalSort(valueA, valueB)\n      })\n    }\n\n    // update the index numbering\n    this._updateDomIndexes()\n\n    this.showChilds()\n\n    if (triggerAction === true) {\n      this.editor._onAction('sort', {\n        path: this.getInternalPath(),\n        oldChilds,\n        newChilds: this.childs\n      })\n    }\n  }\n\n  /**\n   * Replace the value of the node, keep it's state\n   * @param {*} newValue\n   */\n  update (newValue) {\n    const oldValue = this.getInternalValue()\n\n    this.setValue(newValue)\n\n    this.editor._onAction('transform', {\n      path: this.getInternalPath(),\n      oldValue,\n      newValue: this.getInternalValue()\n    })\n  }\n\n  /**\n   * Remove this node from the DOM\n   * @returns {{table: Element, nextTr?: Element}}\n   *            Returns the DOM elements that which be used to attach the node\n   *            to the DOM again, see _attachToDom.\n   * @private\n   */\n  _detachFromDom () {\n    const table = this.dom.tr ? this.dom.tr.parentNode : undefined\n    let lastTr\n    if (this.expanded) {\n      lastTr = this.getAppendDom()\n    } else {\n      lastTr = this.getDom()\n    }\n    const nextTr = (lastTr && lastTr.parentNode) ? lastTr.nextSibling : undefined\n\n    this.hide({ resetVisibleChilds: false })\n\n    return {\n      table,\n      nextTr\n    }\n  }\n\n  /**\n   * Attach this node to the DOM again\n   * @param {{table: Element, nextTr?: Element}} domAnchor\n   *            The DOM elements returned by _detachFromDom.\n   * @private\n   */\n  _attachToDom (domAnchor) {\n    if (domAnchor.table) {\n      if (domAnchor.nextTr) {\n        domAnchor.table.insertBefore(this.getDom(), domAnchor.nextTr)\n      } else {\n        domAnchor.table.appendChild(this.getDom())\n      }\n    }\n\n    if (this.expanded) {\n      this.showChilds()\n    }\n  }\n\n  /**\n   * Transform the node given a JMESPath query.\n   * @param {String} query    JMESPath query to apply\n   * @private\n   */\n  transform (query) {\n    if (!this._hasChilds()) {\n      return\n    }\n\n    this.hideChilds() // sorting is faster when the childs are not attached to the dom\n\n    try {\n      const oldInternalValue = this.getInternalValue()\n\n      // apply the JMESPath query\n      const oldValue = this.getValue()\n      const newValue = this.editor.options.executeQuery(oldValue, query)\n      this.setValue(newValue)\n\n      const newInternalValue = this.getInternalValue()\n\n      this.editor._onAction('transform', {\n        path: this.getInternalPath(),\n        oldValue: oldInternalValue,\n        newValue: newInternalValue\n      })\n\n      this.showChilds()\n    } catch (err) {\n      this.showChilds()\n\n      this.editor._onError(err)\n    }\n  }\n\n  /**\n   * Make this object the root object of the ditor\n   */\n  extract () {\n    this.editor.node.hideChilds()\n    this.hideChilds()\n\n    try {\n      const oldInternalValue = this.editor.node.getInternalValue()\n      this.editor._setRoot(this)\n      const newInternalValue = this.editor.node.getInternalValue()\n\n      this.editor._onAction('transform', {\n        path: this.editor.node.getInternalPath(),\n        oldValue: oldInternalValue,\n        newValue: newInternalValue\n      })\n    } catch (err) {\n      this.editor._onError(err)\n    } finally {\n      this.updateDom({ recurse: true })\n      this.showChilds()\n    }\n  }\n\n  /**\n   * Get a nested child given a path with properties\n   * @param {String[]} path\n   * @returns {Node}\n   */\n  getNestedChild (path) {\n    let i = 0\n    let child = this\n\n    while (child && i < path.length) {\n      child = child.findChildByProperty(path[i])\n      i++\n    }\n\n    return child\n  }\n\n  /**\n   * Find a child by property name\n   * @param {string} prop\n   * @return {Node | undefined} Returns the child node when found, or undefined otherwise\n   */\n  findChildByProperty (prop) {\n    if (this.type !== 'object') {\n      return undefined\n    }\n\n    return this.childs.find(child => child.field === prop)\n  }\n\n  /**\n   * Create a table row with an append button.\n   * @return {HTMLElement | undefined} tr with the AppendNode contents\n   */\n  getAppendDom () {\n    if (!this.append) {\n      this.append = new AppendNode(this.editor)\n      this.append.setParent(this)\n    }\n    return this.append.getDom()\n  }\n\n  /**\n   * Create a table row with an showMore button and text\n   * @return {HTMLElement | undefined} tr with the AppendNode contents\n   */\n  getShowMoreDom () {\n    if (!this.showMore) {\n      this.showMore = new ShowMoreNode(this.editor, this)\n    }\n    return this.showMore.getDom()\n  }\n\n  /**\n   * Get the next sibling of current node\n   * @return {Node} nextSibling\n   */\n  nextSibling () {\n    const index = this.parent.childs.indexOf(this)\n    return this.parent.childs[index + 1] || this.parent.append\n  }\n\n  /**\n   * Get the previously rendered node\n   * @return {Node | null} previousNode\n   */\n  _previousNode () {\n    let prevNode = null\n    const dom = this.getDom()\n    if (dom && dom.parentNode) {\n      // find the previous field\n      let prevDom = dom\n      do {\n        prevDom = prevDom.previousSibling\n        prevNode = Node.getNodeFromTarget(prevDom)\n      }\n      while (prevDom && prevNode && (prevNode instanceof AppendNode && !prevNode.isVisible()))\n    }\n    return prevNode\n  }\n\n  /**\n   * Get the next rendered node\n   * @return {Node | null} nextNode\n   * @private\n   */\n  _nextNode () {\n    let nextNode = null\n    const dom = this.getDom()\n    if (dom && dom.parentNode) {\n      // find the previous field\n      let nextDom = dom\n      do {\n        nextDom = nextDom.nextSibling\n        nextNode = Node.getNodeFromTarget(nextDom)\n      }\n      while (nextDom && nextNode && (nextNode instanceof AppendNode && !nextNode.isVisible()))\n    }\n\n    return nextNode\n  }\n\n  /**\n   * Get the first rendered node\n   * @return {Node | null} firstNode\n   * @private\n   */\n  _firstNode () {\n    let firstNode = null\n    const dom = this.getDom()\n    if (dom && dom.parentNode) {\n      const firstDom = dom.parentNode.firstChild\n      firstNode = Node.getNodeFromTarget(firstDom)\n    }\n\n    return firstNode\n  }\n\n  /**\n   * Get the last rendered node\n   * @return {Node | null} lastNode\n   * @private\n   */\n  _lastNode () {\n    let lastNode = null\n    const dom = this.getDom()\n    if (dom && dom.parentNode) {\n      let lastDom = dom.parentNode.lastChild\n      lastNode = Node.getNodeFromTarget(lastDom)\n      while (lastDom && lastNode && !lastNode.isVisible()) {\n        lastDom = lastDom.previousSibling\n        lastNode = Node.getNodeFromTarget(lastDom)\n      }\n    }\n    return lastNode\n  }\n\n  /**\n   * Get the next element which can have focus.\n   * @param {Element} elem\n   * @return {Element | null} nextElem\n   * @private\n   */\n  _previousElement (elem) {\n    const dom = this.dom\n    // noinspection FallthroughInSwitchStatementJS\n    switch (elem) {\n      case dom.value:\n        if (this.fieldEditable) {\n          return dom.field\n        }\n      // intentional fall through\n      case dom.field:\n        if (this._hasChilds()) {\n          return dom.expand\n        }\n      // intentional fall through\n      case dom.expand:\n        return dom.menu\n      case dom.menu:\n        if (dom.drag) {\n          return dom.drag\n        }\n      // intentional fall through\n      default:\n        return null\n    }\n  }\n\n  /**\n   * Get the next element which can have focus.\n   * @param {Element} elem\n   * @return {Element | null} nextElem\n   * @private\n   */\n  _nextElement (elem) {\n    const dom = this.dom\n    // noinspection FallthroughInSwitchStatementJS\n    switch (elem) {\n      case dom.drag:\n        return dom.menu\n      case dom.menu:\n        if (this._hasChilds()) {\n          return dom.expand\n        }\n      // intentional fall through\n      case dom.expand:\n        if (this.fieldEditable) {\n          return dom.field\n        }\n      // intentional fall through\n      case dom.field:\n        if (!this._hasChilds()) {\n          return dom.value\n        }\n      // intentional fall through\n      default:\n        return null\n    }\n  }\n\n  /**\n   * Get the dom name of given element. returns null if not found.\n   * For example when element === dom.field, \"field\" is returned.\n   * @param {Element} element\n   * @return {String | null} elementName  Available elements with name: 'drag',\n   *                                      'menu', 'expand', 'field', 'value'\n   * @private\n   */\n  _getElementName (element) {\n    return Object.keys(this.dom)\n      .find(name => this.dom[name] === element)\n  }\n\n  /**\n   * Test if this node has childs. This is the case when the node is an object\n   * or array.\n   * @return {boolean} hasChilds\n   * @private\n   */\n  _hasChilds () {\n    return this.type === 'array' || this.type === 'object'\n  }\n\n  addTemplates (menu, append) {\n    const node = this\n    const templates = node.editor.options.templates\n    if (templates == null) return\n    if (templates.length) {\n      // create a separator\n      menu.push({\n        type: 'separator'\n      })\n    }\n    const appendData = (name, data) => {\n      node._onAppend(name, data)\n    }\n    const insertData = (name, data) => {\n      node._onInsertBefore(name, data)\n    }\n    templates.forEach(function (template) {\n      menu.push({\n        text: template.text,\n        className: (template.className || 'jsoneditor-type-object'),\n        title: template.title,\n        click: (append ? appendData.bind(this, template.field, template.value) : insertData.bind(this, template.field, template.value))\n      })\n    })\n  }\n\n  /**\n   * Show a contextmenu for this node\n   * @param {HTMLElement} anchor   Anchor element to attach the context menu to\n   *                               as sibling.\n   * @param {function} [onClose]   Callback method called when the context menu\n   *                               is being closed.\n   */\n  showContextMenu (anchor, onClose) {\n    const node = this\n    let items = []\n\n    if (this.editable.value) {\n      items.push({\n        text: translate('type'),\n        title: translate('typeTitle'),\n        className: 'jsoneditor-type-' + this.type,\n        submenu: [\n          {\n            text: translate('auto'),\n            className: 'jsoneditor-type-auto' +\n                (this.type === 'auto' ? ' jsoneditor-selected' : ''),\n            title: translate('autoType'),\n            click: function () {\n              node._onChangeType('auto')\n            }\n          },\n          {\n            text: translate('array'),\n            className: 'jsoneditor-type-array' +\n                (this.type === 'array' ? ' jsoneditor-selected' : ''),\n            title: translate('arrayType'),\n            click: function () {\n              node._onChangeType('array')\n            }\n          },\n          {\n            text: translate('object'),\n            className: 'jsoneditor-type-object' +\n                (this.type === 'object' ? ' jsoneditor-selected' : ''),\n            title: translate('objectType'),\n            click: function () {\n              node._onChangeType('object')\n            }\n          },\n          {\n            text: translate('string'),\n            className: 'jsoneditor-type-string' +\n                (this.type === 'string' ? ' jsoneditor-selected' : ''),\n            title: translate('stringType'),\n            click: function () {\n              node._onChangeType('string')\n            }\n          }\n        ]\n      })\n    }\n\n    if (this._hasChilds()) {\n      if (this.editor.options.enableSort) {\n        items.push({\n          text: translate('sort'),\n          title: translate('sortTitle', { type: this.type }),\n          className: 'jsoneditor-sort-asc',\n          click: function () {\n            node.showSortModal()\n          }\n        })\n      }\n\n      if (this.editor.options.enableTransform) {\n        items.push({\n          text: translate('transform'),\n          title: translate('transformTitle', { type: this.type }),\n          className: 'jsoneditor-transform',\n          click: function () {\n            node.showTransformModal()\n          }\n        })\n      }\n\n      if (this.parent) {\n        items.push({\n          text: translate('extract'),\n          title: translate('extractTitle', { type: this.type }),\n          className: 'jsoneditor-extract',\n          click: function () {\n            node.extract()\n          }\n        })\n      }\n    }\n\n    if (this.parent && this.parent._hasChilds()) {\n      if (items.length) {\n        // create a separator\n        items.push({\n          type: 'separator'\n        })\n      }\n\n      // create append button (for last child node only)\n      const childs = node.parent.childs\n      if (node === childs[childs.length - 1]) {\n        const appendSubmenu = [\n          {\n            text: translate('auto'),\n            className: 'jsoneditor-type-auto',\n            title: translate('autoType'),\n            click: function () {\n              node._onAppend('', '', 'auto')\n            }\n          },\n          {\n            text: translate('array'),\n            className: 'jsoneditor-type-array',\n            title: translate('arrayType'),\n            click: function () {\n              node._onAppend('', [])\n            }\n          },\n          {\n            text: translate('object'),\n            className: 'jsoneditor-type-object',\n            title: translate('objectType'),\n            click: function () {\n              node._onAppend('', {})\n            }\n          },\n          {\n            text: translate('string'),\n            className: 'jsoneditor-type-string',\n            title: translate('stringType'),\n            click: function () {\n              node._onAppend('', '', 'string')\n            }\n          }\n        ]\n        node.addTemplates(appendSubmenu, true)\n        items.push({\n          text: translate('appendText'),\n          title: translate('appendTitle'),\n          submenuTitle: translate('appendSubmenuTitle'),\n          className: 'jsoneditor-append',\n          click: function () {\n            node._onAppend('', '', 'auto')\n          },\n          submenu: appendSubmenu\n        })\n      }\n\n      // create insert button\n      const insertSubmenu = [\n        {\n          text: translate('auto'),\n          className: 'jsoneditor-type-auto',\n          title: translate('autoType'),\n          click: function () {\n            node._onInsertBefore('', '', 'auto')\n          }\n        },\n        {\n          text: translate('array'),\n          className: 'jsoneditor-type-array',\n          title: translate('arrayType'),\n          click: function () {\n            node._onInsertBefore('', [])\n          }\n        },\n        {\n          text: translate('object'),\n          className: 'jsoneditor-type-object',\n          title: translate('objectType'),\n          click: function () {\n            node._onInsertBefore('', {})\n          }\n        },\n        {\n          text: translate('string'),\n          className: 'jsoneditor-type-string',\n          title: translate('stringType'),\n          click: function () {\n            node._onInsertBefore('', '', 'string')\n          }\n        }\n      ]\n      node.addTemplates(insertSubmenu, false)\n      items.push({\n        text: translate('insert'),\n        title: translate('insertTitle'),\n        submenuTitle: translate('insertSub'),\n        className: 'jsoneditor-insert',\n        click: function () {\n          node._onInsertBefore('', '', 'auto')\n        },\n        submenu: insertSubmenu\n      })\n\n      if (this.editable.field) {\n        // create duplicate button\n        items.push({\n          text: translate('duplicateText'),\n          title: translate('duplicateField'),\n          className: 'jsoneditor-duplicate',\n          click: function () {\n            Node.onDuplicate(node)\n          }\n        })\n\n        // create remove button\n        items.push({\n          text: translate('removeText'),\n          title: translate('removeField'),\n          className: 'jsoneditor-remove',\n          click: function () {\n            Node.onRemove(node)\n          }\n        })\n      }\n    }\n\n    if (this.editor.options.onCreateMenu) {\n      const path = node.getPath()\n\n      items = this.editor.options.onCreateMenu(items, {\n        type: 'single',\n        path,\n        paths: [path]\n      })\n    }\n\n    const menu = new ContextMenu(items, { close: onClose })\n    menu.show(anchor, this.editor.getPopupAnchor())\n  }\n\n  /**\n   * Show sorting modal\n   */\n  showSortModal () {\n    const node = this\n    const container = this.editor.options.modalAnchor || DEFAULT_MODAL_ANCHOR\n    const json = this.getValue()\n\n    function onSort (sortedBy) {\n      const path = sortedBy.path\n      const pathArray = parsePath(path)\n\n      node.sortedBy = sortedBy\n      node.sort(pathArray, sortedBy.direction)\n    }\n\n    showSortModal(container, json, onSort, node.sortedBy)\n  }\n\n  /**\n   * Show transform modal\n   */\n  showTransformModal () {\n    const { modalAnchor, createQuery, executeQuery, queryDescription } = this.editor.options\n    const json = this.getValue()\n\n    showTransformModal({\n      container: modalAnchor || DEFAULT_MODAL_ANCHOR,\n      json,\n      queryDescription, // can be undefined\n      createQuery,\n      executeQuery,\n      onTransform: query => { this.transform(query) }\n    })\n  }\n\n  /**\n   * get the type of a value\n   * @param {*} value\n   * @return {String} type   Can be 'object', 'array', 'string', 'auto'\n   * @private\n   */\n  _getType (value) {\n    if (value instanceof Array) {\n      return 'array'\n    }\n    if (value instanceof Object) {\n      return 'object'\n    }\n    if (typeof (value) === 'string' && typeof (parseString(value)) !== 'string') {\n      return 'string'\n    }\n\n    return 'auto'\n  }\n\n  /**\n   * escape a text, such that it can be displayed safely in an HTML element\n   * @param {String} text\n   * @return {String} escapedText\n   * @private\n   */\n  _escapeHTML (text) {\n    if (typeof text !== 'string') {\n      return String(text)\n    } else {\n      const htmlEscaped = String(text)\n        .replace(/&/g, '&amp;') // must be replaced first!\n        .replace(/</g, '&lt;')\n        .replace(/>/g, '&gt;')\n        .replace(/ {2}/g, ' &nbsp;') // replace double space with an nbsp and space\n        .replace(/^ /, '&nbsp;') // space at start\n        .replace(/ $/, '&nbsp;') // space at end\n\n      const json = JSON.stringify(htmlEscaped)\n      let html = json.substring(1, json.length - 1)\n      if (this.editor.options.escapeUnicode === true) {\n        html = escapeUnicodeChars(html)\n      }\n      return html\n    }\n  }\n\n  /**\n   * unescape a string.\n   * @param {String} escapedText\n   * @return {String} text\n   * @private\n   */\n  _unescapeHTML (escapedText) {\n    const json = '\"' + this._escapeJSON(escapedText) + '\"'\n    const htmlEscaped = parse(json)\n\n    return htmlEscaped\n      .replace(/&lt;/g, '<')\n      .replace(/&gt;/g, '>')\n      .replace(/&nbsp;|\\u00A0/g, ' ')\n      .replace(/&amp;/g, '&') // must be replaced last\n  }\n\n  /**\n   * escape a text to make it a valid JSON string. The method will:\n   *   - replace unescaped double quotes with '\\\"'\n   *   - replace unescaped backslash with '\\\\'\n   *   - replace returns with '\\n'\n   * @param {String} text\n   * @return {String} escapedText\n   * @private\n   */\n  _escapeJSON (text) {\n    // TODO: replace with some smart regex (only when a new solution is faster!)\n    let escaped = ''\n    let i = 0\n    while (i < text.length) {\n      let c = text.charAt(i)\n      if (c === '\\n') {\n        escaped += '\\\\n'\n      } else if (c === '\\\\') {\n        escaped += c\n        i++\n\n        c = text.charAt(i)\n        if (c === '' || '\"\\\\/bfnrtu'.indexOf(c) === -1) {\n          escaped += '\\\\' // no valid escape character\n        }\n        escaped += c\n      } else if (c === '\"') {\n        escaped += '\\\\\"'\n      } else {\n        escaped += c\n      }\n      i++\n    }\n\n    return escaped\n  }\n\n  /**\n   * update the object name according to the callback onNodeName\n   * @private\n   */\n  updateNodeName () {\n    const count = this.childs ? this.childs.length : 0\n    let nodeName\n    if (this.type === 'object' || this.type === 'array') {\n      if (this.editor.options.onNodeName) {\n        try {\n          const getValue = this.getValue.bind(this)\n          nodeName = this.editor.options.onNodeName({\n            path: this.getPath(),\n            size: count,\n            type: this.type,\n            get value () {\n              return getValue()\n            }\n          })\n        } catch (err) {\n          console.error('Error in onNodeName callback: ', err)\n        }\n      }\n\n      this.dom.value.textContent = (this.type === 'object')\n        ? ('{' + (nodeName || count) + '}')\n        : ('[' + (nodeName || count) + ']')\n    }\n  }\n\n  /**\n   * update recursively the object's and its children's name.\n   * @private\n   */\n  recursivelyUpdateNodeName () {\n    if (this.expanded) {\n      this.updateNodeName()\n      if (this.childs !== 'undefined') {\n        let i\n        for (i in this.childs) {\n          this.childs[i].recursivelyUpdateNodeName()\n        }\n      }\n    }\n  }\n}\n\n// debounce interval for keyboard input in milliseconds\nNode.prototype.DEBOUNCE_INTERVAL = 150\n\n// search will stop iterating as soon as the max is reached\nNode.prototype.MAX_SEARCH_RESULTS = 999\n\n// default number of child nodes to display\nconst DEFAULT_MAX_VISIBLE_CHILDS = 100\n\n// stores the element name currently having the focus\nNode.focusElement = undefined\n\n/**\n * Select all text in an editable div after a delay of 0 ms\n * @param {Element} editableDiv\n */\nNode.select = editableDiv => {\n  setTimeout(() => {\n    selectContentEditable(editableDiv)\n  }, 0)\n}\n\n/**\n * DragStart event, fired on mousedown on the dragarea at the left side of a Node\n * @param {Node[] | Node} nodes\n * @param {Event} event\n */\nNode.onDragStart = (nodes, event) => {\n  if (!Array.isArray(nodes)) {\n    return Node.onDragStart([nodes], event)\n  }\n  if (nodes.length === 0) {\n    return\n  }\n\n  const firstNode = nodes[0]\n  const lastNode = nodes[nodes.length - 1]\n  const parent = firstNode.parent\n  const draggedNode = Node.getNodeFromTarget(event.target)\n  const editor = firstNode.editor\n\n  // in case of multiple selected nodes, offsetY prevents the selection from\n  // jumping when you start dragging one of the lower down nodes in the selection\n  const offsetY = getAbsoluteTop(draggedNode.dom.tr) - getAbsoluteTop(firstNode.dom.tr)\n\n  if (!editor.mousemove) {\n    editor.mousemove = addEventListener(event.view, 'mousemove', event => {\n      Node.onDrag(nodes, event)\n    })\n  }\n\n  if (!editor.mouseup) {\n    editor.mouseup = addEventListener(event.view, 'mouseup', event => {\n      Node.onDragEnd(nodes, event)\n    })\n  }\n\n  editor.highlighter.lock()\n  editor.drag = {\n    oldCursor: document.body.style.cursor,\n    oldSelection: editor.getDomSelection(),\n    oldPaths: nodes.map(getInternalPath),\n    oldParent: parent,\n    oldNextNode: parent.childs[lastNode.getIndex() + 1] || parent.append,\n    oldParentPathRedo: parent.getInternalPath(),\n    oldIndexRedo: firstNode.getIndex(),\n    mouseX: event.pageX,\n    offsetY,\n    level: firstNode.getLevel()\n  }\n  document.body.style.cursor = 'move'\n\n  event.preventDefault()\n}\n\n/**\n * Drag event, fired when moving the mouse while dragging a Node\n * @param {Node[] | Node} nodes\n * @param {Event} event\n */\nNode.onDrag = (nodes, event) => {\n  if (!Array.isArray(nodes)) {\n    return Node.onDrag([nodes], event)\n  }\n  if (nodes.length === 0) {\n    return\n  }\n\n  // TODO: this method has grown too large. Split it in a number of methods\n  const editor = nodes[0].editor\n  const mouseY = event.pageY - editor.drag.offsetY\n  const mouseX = event.pageX\n  let trPrev, trNext, trFirst, trLast, trRoot\n  let nodePrev, nodeNext\n  let topPrev, topFirst, bottomNext, heightNext\n  let moved = false\n\n  // TODO: add an ESC option, which resets to the original position\n\n  // move up/down\n  const firstNode = nodes[0]\n  const trThis = firstNode.dom.tr\n  let topThis = getAbsoluteTop(trThis)\n  const heightThis = trThis.offsetHeight\n  if (mouseY < topThis) {\n    // move up\n    trPrev = trThis\n    do {\n      trPrev = trPrev.previousSibling\n      nodePrev = Node.getNodeFromTarget(trPrev)\n      topPrev = trPrev ? getAbsoluteTop(trPrev) : 0\n    }\n    while (trPrev && mouseY < topPrev)\n\n    if (nodePrev && !nodePrev.parent) {\n      nodePrev = undefined\n    }\n\n    if (!nodePrev) {\n      // move to the first node\n      trRoot = trThis.parentNode.firstChild\n      trPrev = trRoot ? trRoot.nextSibling : undefined\n      nodePrev = Node.getNodeFromTarget(trPrev)\n      if (nodePrev === firstNode) {\n        nodePrev = undefined\n      }\n    }\n\n    if (nodePrev && nodePrev.isVisible()) {\n      // check if mouseY is really inside the found node\n      trPrev = nodePrev.dom.tr\n      topPrev = trPrev ? getAbsoluteTop(trPrev) : 0\n      if (mouseY > topPrev + heightThis) {\n        nodePrev = undefined\n      }\n    }\n\n    if (\n      nodePrev &&\n      (editor.options.limitDragging === false || nodePrev.parent === nodes[0].parent)\n    ) {\n      nodes.forEach(node => {\n        nodePrev.parent.moveBefore(node, nodePrev)\n      })\n      moved = true\n    }\n  } else {\n    // move down\n    const lastNode = nodes[nodes.length - 1]\n    trLast = (lastNode.expanded && lastNode.append) ? lastNode.append.getDom() : lastNode.dom.tr\n    trFirst = trLast ? trLast.nextSibling : undefined\n    if (trFirst) {\n      topFirst = getAbsoluteTop(trFirst)\n      trNext = trFirst\n      do {\n        nodeNext = Node.getNodeFromTarget(trNext)\n        if (trNext) {\n          bottomNext = trNext.nextSibling\n            ? getAbsoluteTop(trNext.nextSibling)\n            : 0\n          heightNext = trNext ? (bottomNext - topFirst) : 0\n\n          if (nodeNext &&\n              nodeNext.parent.childs.length === nodes.length &&\n              nodeNext.parent.childs[nodes.length - 1] === lastNode) {\n            // We are about to remove the last child of this parent,\n            // which will make the parents appendNode visible.\n            topThis += 27\n            // TODO: dangerous to suppose the height of the appendNode a constant of 27 px.\n          }\n\n          trNext = trNext.nextSibling\n        }\n      }\n      while (trNext && mouseY > topThis + heightNext)\n\n      if (nodeNext && nodeNext.parent) {\n        // calculate the desired level\n        const diffX = (mouseX - editor.drag.mouseX)\n        const diffLevel = Math.round(diffX / 24 / 2)\n        const level = editor.drag.level + diffLevel // desired level\n        let levelNext = nodeNext.getLevel() // level to be\n\n        // find the best fitting level (move upwards over the append nodes)\n        trPrev = nodeNext.dom.tr && nodeNext.dom.tr.previousSibling\n        while (levelNext < level && trPrev) {\n          nodePrev = Node.getNodeFromTarget(trPrev)\n\n          const isDraggedNode = nodes.some(node => node === nodePrev || nodePrev.isDescendantOf(node))\n\n          if (isDraggedNode) {\n            // neglect the dragged nodes themselves and their childs\n          } else if (nodePrev instanceof AppendNode) {\n            const childs = nodePrev.parent.childs\n            if (childs.length !== nodes.length || childs[nodes.length - 1] !== lastNode) {\n              // non-visible append node of a list of childs\n              // consisting of not only this node (else the\n              // append node will change into a visible \"empty\"\n              // text when removing this node).\n              nodeNext = Node.getNodeFromTarget(trPrev)\n              levelNext = nodeNext.getLevel()\n            } else {\n              break\n            }\n          } else {\n            break\n          }\n\n          trPrev = trPrev.previousSibling\n        }\n\n        if (nodeNext instanceof AppendNode && !nodeNext.isVisible() &&\n            nodeNext.parent.showMore.isVisible()) {\n          nodeNext = nodeNext._nextNode()\n        }\n\n        // move the node when its position is changed\n        if (\n          nodeNext &&\n          (editor.options.limitDragging === false || nodeNext.parent === nodes[0].parent) &&\n          nodeNext.dom.tr && nodeNext.dom.tr !== trLast.nextSibling\n        ) {\n          nodes.forEach(node => {\n            nodeNext.parent.moveBefore(node, nodeNext)\n          })\n          moved = true\n        }\n      }\n    }\n  }\n\n  if (moved) {\n    // update the dragging parameters when moved\n    editor.drag.mouseX = mouseX\n    editor.drag.level = firstNode.getLevel()\n  }\n\n  // auto scroll when hovering around the top of the editor\n  editor.startAutoScroll(mouseY)\n\n  event.preventDefault()\n}\n\n/**\n * Drag event, fired on mouseup after having dragged a node\n * @param {Node[] | Node} nodes\n * @param {Event} event\n */\nNode.onDragEnd = (nodes, event) => {\n  if (!Array.isArray(nodes)) {\n    return Node.onDrag([nodes], event)\n  }\n  if (nodes.length === 0) {\n    return\n  }\n\n  const firstNode = nodes[0]\n  const editor = firstNode.editor\n\n  // set focus to the context menu button of the first node\n  if (firstNode && firstNode.dom.menu) {\n    firstNode.dom.menu.focus()\n  }\n\n  const oldParentPath = editor.drag.oldParent.getInternalPath()\n  const newParentPath = firstNode.parent.getInternalPath()\n  const sameParent = editor.drag.oldParent === firstNode.parent\n  const oldIndex = editor.drag.oldNextNode.getIndex()\n  const newIndex = firstNode.getIndex()\n  const oldParentPathRedo = editor.drag.oldParentPathRedo\n\n  const oldIndexRedo = editor.drag.oldIndexRedo\n  const newIndexRedo = (sameParent && oldIndexRedo < newIndex)\n    ? (newIndex + nodes.length)\n    : newIndex\n\n  if (!sameParent || oldIndexRedo !== newIndex) {\n    // only register this action if the node is actually moved to another place\n    editor._onAction('moveNodes', {\n      count: nodes.length,\n      fieldNames: nodes.map(getField),\n\n      oldParentPath,\n      newParentPath,\n      oldIndex,\n      newIndex,\n\n      oldIndexRedo,\n      newIndexRedo,\n      oldParentPathRedo,\n      newParentPathRedo: null, // This is a hack, value will be filled in during undo\n\n      oldSelection: editor.drag.oldSelection,\n      newSelection: editor.getDomSelection()\n    })\n  }\n\n  document.body.style.cursor = editor.drag.oldCursor\n  editor.highlighter.unlock()\n  nodes.forEach(node => {\n    node.updateDom()\n\n    if (event.target !== node.dom.drag && event.target !== node.dom.menu) {\n      editor.highlighter.unhighlight()\n    }\n  })\n  delete editor.drag\n\n  if (editor.mousemove) {\n    removeEventListener(event.view, 'mousemove', editor.mousemove)\n    delete editor.mousemove\n  }\n  if (editor.mouseup) {\n    removeEventListener(event.view, 'mouseup', editor.mouseup)\n    delete editor.mouseup\n  }\n\n  // Stop any running auto scroll\n  editor.stopAutoScroll()\n\n  event.preventDefault()\n}\n\n/**\n * find an enum definition in a JSON schema, as property `enum` or inside\n * one of the schemas composites (`oneOf`, `anyOf`, `allOf`)\n * @param  {Object} schema\n * @return {Array | null} Returns the enum when found, null otherwise.\n * @private\n */\nNode._findEnum = schema => {\n  if (schema.enum) {\n    return schema.enum\n  }\n\n  const composite = schema.oneOf || schema.anyOf || schema.allOf\n  if (composite) {\n    const match = composite.filter(entry => entry.enum)\n    if (match.length > 0) {\n      return match[0].enum\n    }\n  }\n\n  return null\n}\n\n/**\n * Implementation for _findSchema\n * @param {Object} topLevelSchema\n * @param {Object} schemaRefs\n * @param {Array.<string | number>} path\n * @param {Object} currentSchema\n * @return {Object | boolean | null}\n * @private\n */\nNode._findOneSchema = (topLevelSchema, schemaRefs, path, currentSchema) => {\n  const nextPath = path.slice(1, path.length)\n  const nextKey = path[0]\n\n  if (typeof currentSchema === 'object' && '$ref' in currentSchema && typeof currentSchema.$ref === 'string') {\n    const ref = currentSchema.$ref\n    if (ref in schemaRefs) {\n      currentSchema = schemaRefs[ref]\n    } else if (ref.startsWith('#/')) {\n      const refPath = ref.substring(2).split('/')\n      currentSchema = topLevelSchema\n      for (const segment of refPath) {\n        if (segment in currentSchema) {\n          currentSchema = currentSchema[segment]\n        } else {\n          throw Error(`Unable to resolve reference ${ref}`)\n        }\n      }\n    } else if (ref.match(/#\\//g)?.length === 1) {\n      const [schemaUrl, relativePath] = ref.split('#/')\n      if (schemaUrl in schemaRefs) {\n        const referencedSchema = schemaRefs[schemaUrl]\n        const reference = { $ref: '#/'.concat(relativePath) }\n        const auxNextPath = []\n        auxNextPath.push(nextKey)\n        if (nextPath.length > 0) {\n          auxNextPath.push(...nextPath)\n        }\n        return Node._findSchema(referencedSchema, schemaRefs, auxNextPath, reference)\n      } else {\n        throw Error(`Unable to resolve reference ${ref}`)\n      }\n    } else {\n      throw Error(`Unable to resolve reference ${ref}`)\n    }\n  }\n\n  // We have no more path segments to resolve, return the currently found schema\n  // We do this here, after resolving references, in case of the leaf schema beeing a reference\n  if (nextKey === undefined) {\n    return currentSchema\n  }\n\n  if (typeof nextKey === 'string') {\n    if (typeof currentSchema.properties === 'object' && currentSchema.properties !== null && nextKey in currentSchema.properties) {\n      currentSchema = currentSchema.properties[nextKey]\n      return Node._findSchema(topLevelSchema, schemaRefs, nextPath, currentSchema)\n    }\n    if (typeof currentSchema.patternProperties === 'object' && currentSchema.patternProperties !== null) {\n      for (const prop in currentSchema.patternProperties) {\n        if (nextKey.match(prop)) {\n          currentSchema = currentSchema.patternProperties[prop]\n          return Node._findSchema(topLevelSchema, schemaRefs, nextPath, currentSchema)\n        }\n      }\n    }\n    if (typeof currentSchema.additionalProperties === 'object') {\n      currentSchema = currentSchema.additionalProperties\n      return Node._findSchema(topLevelSchema, schemaRefs, nextPath, currentSchema)\n    }\n    return null\n  }\n  if (typeof nextKey === 'number' && typeof currentSchema.items === 'object' && currentSchema.items !== null) {\n    currentSchema = currentSchema.items\n    return Node._findSchema(topLevelSchema, schemaRefs, nextPath, currentSchema)\n  }\n\n  return null\n}\n\n/**\n * Return the part of a JSON schema matching given path.\n *\n * Note that this attempts to find *a* schema matching the path, not necessarily\n * the best / most appropriate.  For example, oneOf vs. anyOf vs. allOf may\n * result in different schemas being applied in practice.\n *\n * @param {Object} topLevelSchema\n * @param {Object} schemaRefs\n * @param {Array.<string | number>} path\n * @param {Object} currentSchema\n * @return {Object | null}\n * @private\n */\nNode._findSchema = (topLevelSchema, schemaRefs, path, currentSchema = topLevelSchema) => {\n  let possibleSchemas = [currentSchema]\n  for (const subSchemas of [currentSchema.oneOf, currentSchema.anyOf, currentSchema.allOf]) {\n    if (Array.isArray(subSchemas)) {\n      possibleSchemas = possibleSchemas.concat(subSchemas)\n    }\n  }\n\n  let fallback = null\n  for (const schema of possibleSchemas) {\n    const result = Node._findOneSchema(topLevelSchema, schemaRefs, path, schema)\n    // Although we don't attempt to find the best / most appropriate schema, we\n    // can at least attempt to find something more specific than `true`.\n    if (result === true) {\n      fallback = true\n      continue\n    } else if (result !== null) {\n      return result\n    }\n  }\n\n  return fallback\n}\n\n/**\n * Remove nodes\n * @param {Node[] | Node} nodes\n */\nNode.onRemove = nodes => {\n  if (!Array.isArray(nodes)) {\n    return Node.onRemove([nodes])\n  }\n\n  if (nodes && nodes.length > 0) {\n    const firstNode = nodes[0]\n    const parent = firstNode.parent\n    const editor = firstNode.editor\n    const firstIndex = firstNode.getIndex()\n    editor.highlighter.unhighlight()\n\n    // adjust the focus\n    const oldSelection = editor.getDomSelection()\n    Node.blurNodes(nodes)\n    const newSelection = editor.getDomSelection()\n\n    // store the paths before removing them (needed for history)\n    const paths = nodes.map(getInternalPath)\n\n    // remove the nodes\n    nodes.forEach(node => {\n      node.parent._remove(node)\n    })\n\n    // store history action\n    editor._onAction('removeNodes', {\n      nodes,\n      paths,\n      parentPath: parent.getInternalPath(),\n      index: firstIndex,\n      oldSelection,\n      newSelection\n    })\n  }\n}\n\n/**\n * Duplicate nodes\n * duplicated nodes will be added right after the original nodes\n * @param {Node[] | Node} nodes\n */\nNode.onDuplicate = nodes => {\n  if (!Array.isArray(nodes)) {\n    return Node.onDuplicate([nodes])\n  }\n\n  if (nodes && nodes.length > 0) {\n    const lastNode = nodes[nodes.length - 1]\n    const parent = lastNode.parent\n    const editor = lastNode.editor\n\n    editor.deselect(editor.multiselection.nodes)\n\n    // duplicate the nodes\n    const oldSelection = editor.getDomSelection()\n    let afterNode = lastNode\n    const clones = nodes.map(node => {\n      const clone = node.clone()\n      if (node.parent.type === 'object') {\n        const existingFieldNames = node.parent.getFieldNames()\n        clone.field = findUniqueName(node.field, existingFieldNames)\n      }\n      parent.insertAfter(clone, afterNode)\n      afterNode = clone\n      return clone\n    })\n\n    // set selection to the duplicated nodes\n    if (nodes.length === 1) {\n      if (clones[0].parent.type === 'object') {\n        // when duplicating a single object property,\n        // set focus to the field and keep the original field name\n        clones[0].dom.field.innerHTML = nodes[0]._escapeHTML(nodes[0].field)\n        clones[0].focus('field')\n      } else {\n        clones[0].focus()\n      }\n    } else {\n      editor.select(clones)\n    }\n    const newSelection = editor.getDomSelection()\n\n    editor._onAction('duplicateNodes', {\n      paths: nodes.map(getInternalPath),\n      clonePaths: clones.map(getInternalPath),\n      afterPath: lastNode.getInternalPath(),\n      parentPath: parent.getInternalPath(),\n      oldSelection,\n      newSelection\n    })\n  }\n}\n\n/**\n * Find the node from an event target\n * @param {HTMLElement} target\n * @return {Node | undefined} node  or undefined when not found\n * @static\n */\nNode.getNodeFromTarget = target => {\n  while (target) {\n    if (target.node) {\n      return target.node\n    }\n    target = target.parentNode\n  }\n\n  return undefined\n}\n\n/**\n * Test whether target is a child of the color DOM of a node\n * @param {HTMLElement} target\n * @returns {boolean}\n */\nNode.targetIsColorPicker = target => {\n  const node = Node.getNodeFromTarget(target)\n\n  if (node) {\n    let parent = target && target.parentNode\n    while (parent) {\n      if (parent === node.dom.color) {\n        return true\n      }\n      parent = parent.parentNode\n    }\n  }\n\n  return false\n}\n\n/**\n * Remove the focus of given nodes, and move the focus to the (a) node before,\n * (b) the node after, or (c) the parent node.\n * @param {Array.<Node> | Node} nodes\n */\nNode.blurNodes = nodes => {\n  if (!Array.isArray(nodes)) {\n    Node.blurNodes([nodes])\n    return\n  }\n\n  const firstNode = nodes[0]\n  const parent = firstNode.parent\n  const firstIndex = firstNode.getIndex()\n\n  if (parent.childs[firstIndex + nodes.length]) {\n    parent.childs[firstIndex + nodes.length].focus()\n  } else if (parent.childs[firstIndex - 1]) {\n    parent.childs[firstIndex - 1].focus()\n  } else {\n    parent.focus()\n  }\n}\n\n// helper function to get the internal path of a node\nfunction getInternalPath (node) {\n  return node.getInternalPath()\n}\n\n// helper function to get the field of a node\nfunction getField (node) {\n  return node.getField()\n}\n\nfunction hasOwnProperty (object, key) {\n  return Object.prototype.hasOwnProperty.call(object, key)\n}\n\n// TODO: find a nicer solution to resolve this circular dependency between Node and AppendNode\n//       idea: introduce properties .isAppendNode and .isNode and use that instead of instanceof AppendNode checks\nconst AppendNode = appendNodeFactory(Node)\nconst ShowMoreNode = showMoreNodeFactory(Node)\n"
  },
  {
    "path": "src/js/NodeHistory.js",
    "content": "'use strict'\n\nimport { findUniqueName } from './util'\n\n/**\n * @constructor History\n * Store action history, enables undo and redo\n * @param {JSONEditor} editor\n */\nexport class NodeHistory {\n  constructor (editor) {\n    this.editor = editor\n    this.history = []\n    this.index = -1\n\n    this.clear()\n\n    // helper function to find a Node from a path\n    function findNode (path) {\n      return editor.node.findNodeByInternalPath(path)\n    }\n\n    // map with all supported actions\n    this.actions = {\n      editField: {\n        undo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          const node = parentNode.childs[params.index]\n          node.updateField(params.oldValue)\n        },\n        redo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          const node = parentNode.childs[params.index]\n          node.updateField(params.newValue)\n        }\n      },\n      editValue: {\n        undo: function (params) {\n          findNode(params.path).updateValue(params.oldValue)\n        },\n        redo: function (params) {\n          findNode(params.path).updateValue(params.newValue)\n        }\n      },\n      changeType: {\n        undo: function (params) {\n          findNode(params.path).changeType(params.oldType)\n        },\n        redo: function (params) {\n          findNode(params.path).changeType(params.newType)\n        }\n      },\n\n      appendNodes: {\n        undo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          params.paths.map(findNode).forEach(node => {\n            parentNode.removeChild(node)\n          })\n        },\n        redo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          params.nodes.forEach(node => {\n            parentNode.appendChild(node)\n          })\n        }\n      },\n      insertBeforeNodes: {\n        undo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          params.paths.map(findNode).forEach(node => {\n            parentNode.removeChild(node)\n          })\n        },\n        redo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          const beforeNode = findNode(params.beforePath)\n          params.nodes.forEach(node => {\n            parentNode.insertBefore(node, beforeNode)\n          })\n        }\n      },\n      insertAfterNodes: {\n        undo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          params.paths.map(findNode).forEach(node => {\n            parentNode.removeChild(node)\n          })\n        },\n        redo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          let afterNode = findNode(params.afterPath)\n          params.nodes.forEach(node => {\n            parentNode.insertAfter(node, afterNode)\n            afterNode = node\n          })\n        }\n      },\n      removeNodes: {\n        undo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          const beforeNode = parentNode.childs[params.index] || parentNode.append\n          params.nodes.forEach(node => {\n            parentNode.insertBefore(node, beforeNode)\n          })\n        },\n        redo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          params.paths.map(findNode).forEach(node => {\n            parentNode.removeChild(node)\n          })\n        }\n      },\n      duplicateNodes: {\n        undo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          params.clonePaths.map(findNode).forEach(node => {\n            parentNode.removeChild(node)\n          })\n        },\n        redo: function (params) {\n          const parentNode = findNode(params.parentPath)\n          let afterNode = findNode(params.afterPath)\n          const nodes = params.paths.map(findNode)\n          nodes.forEach(node => {\n            const clone = node.clone()\n            if (parentNode.type === 'object') {\n              const existingFieldNames = parentNode.getFieldNames()\n              clone.field = findUniqueName(node.field, existingFieldNames)\n            }\n            parentNode.insertAfter(clone, afterNode)\n            afterNode = clone\n          })\n        }\n      },\n      moveNodes: {\n        undo: function (params) {\n          const oldParentNode = findNode(params.oldParentPath)\n          const newParentNode = findNode(params.newParentPath)\n          const oldBeforeNode = oldParentNode.childs[params.oldIndex] || oldParentNode.append\n\n          // first copy the nodes, then move them\n          const nodes = newParentNode.childs.slice(params.newIndex, params.newIndex + params.count)\n\n          nodes.forEach((node, index) => {\n            node.field = params.fieldNames[index]\n            oldParentNode.moveBefore(node, oldBeforeNode)\n          })\n\n          // This is a hack to work around an issue that we don't know tha original\n          // path of the new parent after dragging, as the node is already moved at that time.\n          if (params.newParentPathRedo === null) {\n            params.newParentPathRedo = newParentNode.getInternalPath()\n          }\n        },\n        redo: function (params) {\n          const oldParentNode = findNode(params.oldParentPathRedo)\n          const newParentNode = findNode(params.newParentPathRedo)\n          const newBeforeNode = newParentNode.childs[params.newIndexRedo] || newParentNode.append\n\n          // first copy the nodes, then move them\n          const nodes = oldParentNode.childs.slice(params.oldIndexRedo, params.oldIndexRedo + params.count)\n\n          nodes.forEach((node, index) => {\n            node.field = params.fieldNames[index]\n            newParentNode.moveBefore(node, newBeforeNode)\n          })\n        }\n      },\n\n      sort: {\n        undo: function (params) {\n          const node = findNode(params.path)\n          node.hideChilds()\n          node.childs = params.oldChilds\n          node.updateDom({ updateIndexes: true })\n          node.showChilds()\n        },\n        redo: function (params) {\n          const node = findNode(params.path)\n          node.hideChilds()\n          node.childs = params.newChilds\n          node.updateDom({ updateIndexes: true })\n          node.showChilds()\n        }\n      },\n\n      transform: {\n        undo: function (params) {\n          findNode(params.path).setInternalValue(params.oldValue)\n\n          // TODO: would be nice to restore the state of the node and childs\n        },\n        redo: function (params) {\n          findNode(params.path).setInternalValue(params.newValue)\n\n          // TODO: would be nice to restore the state of the node and childs\n        }\n      }\n\n      // TODO: restore the original caret position and selection with each undo\n      // TODO: implement history for actions \"expand\", \"collapse\", \"scroll\", \"setDocument\"\n    }\n  }\n\n  /**\n   * The method onChange is executed when the History is changed, and can\n   * be overloaded.\n   */\n  onChange () {}\n\n  /**\n   * Add a new action to the history\n   * @param {String} action  The executed action. Available actions: \"editField\",\n   *                         \"editValue\", \"changeType\", \"appendNode\",\n   *                         \"removeNode\", \"duplicateNode\", \"moveNode\"\n   * @param {Object} params  Object containing parameters describing the change.\n   *                         The parameters in params depend on the action (for\n   *                         example for \"editValue\" the Node, old value, and new\n   *                         value are provided). params contains all information\n   *                         needed to undo or redo the action.\n   */\n  add (action, params) {\n    this.index++\n    this.history[this.index] = {\n      action,\n      params,\n      timestamp: new Date()\n    }\n\n    // remove redo actions which are invalid now\n    if (this.index < this.history.length - 1) {\n      this.history.splice(this.index + 1, this.history.length - this.index - 1)\n    }\n\n    // fire onchange event\n    this.onChange()\n  }\n\n  /**\n   * Clear history\n   */\n  clear () {\n    this.history = []\n    this.index = -1\n\n    // fire onchange event\n    this.onChange()\n  }\n\n  /**\n   * Check if there is an action available for undo\n   * @return {Boolean} canUndo\n   */\n  canUndo () {\n    return (this.index >= 0)\n  }\n\n  /**\n   * Check if there is an action available for redo\n   * @return {Boolean} canRedo\n   */\n  canRedo () {\n    return (this.index < this.history.length - 1)\n  }\n\n  /**\n   * Undo the last action\n   */\n  undo () {\n    if (this.canUndo()) {\n      const obj = this.history[this.index]\n      if (obj) {\n        const action = this.actions[obj.action]\n        if (action && action.undo) {\n          action.undo(obj.params)\n          if (obj.params.oldSelection) {\n            try {\n              this.editor.setDomSelection(obj.params.oldSelection)\n            } catch (err) {\n              console.error(err)\n            }\n          }\n        } else {\n          console.error(new Error('unknown action \"' + obj.action + '\"'))\n        }\n      }\n      this.index--\n\n      // fire onchange event\n      this.onChange()\n    }\n  }\n\n  /**\n   * Redo the last action\n   */\n  redo () {\n    if (this.canRedo()) {\n      this.index++\n\n      const obj = this.history[this.index]\n      if (obj) {\n        const action = this.actions[obj.action]\n        if (action && action.redo) {\n          action.redo(obj.params)\n          if (obj.params.newSelection) {\n            try {\n              this.editor.setDomSelection(obj.params.newSelection)\n            } catch (err) {\n              console.error(err)\n            }\n          }\n        } else {\n          console.error(new Error('unknown action \"' + obj.action + '\"'))\n        }\n      }\n\n      // fire onchange event\n      this.onChange()\n    }\n  }\n\n  /**\n   * Destroy history\n   */\n  destroy () {\n    this.editor = null\n\n    this.history = []\n    this.index = -1\n  }\n}\n"
  },
  {
    "path": "src/js/SchemaTextCompleter.js",
    "content": "'use strict'\n\nimport * as jsonMap from 'json-source-map'\nimport {\n  isArray,\n  isObject,\n  uniqueMergeArrays,\n  asyncExec\n} from './util'\n\n/**\n * SchemaTextCompleter class implements the ace ext-language_tools completer API,\n * and suggests completions for the text editor that are relative\n * to the cursor position and the json schema\n */\nexport class SchemaTextCompleter {\n  constructor (schema, schemaRefs) {\n    this.schema = schema\n    this.schemaRefs = schemaRefs || {}\n    this.suggestions = {}\n    this.suggestionsRefs = {}\n    this._buildSuggestions()\n  }\n\n  _buildSuggestions () {\n    this._handleSchemaEntry('', this.schema, this.suggestions)\n    for (const refName in this.schemaRefs) {\n      this.suggestionsRefs[refName] = {}\n      this._handleSchemaEntry('', this.schemaRefs[refName], this.suggestionsRefs[refName])\n    }\n  }\n\n  _handleRef (currectPath, refName, suggestionsObj) {\n    suggestionsObj[currectPath] = suggestionsObj[currectPath] || {}\n    suggestionsObj[currectPath].refs = suggestionsObj[currectPath].refs || []\n    suggestionsObj[currectPath].refs = uniqueMergeArrays(suggestionsObj[currectPath].refs, [refName])\n  }\n\n  _handleSchemaEntry (currectPath, schemaNode, suggestionsObj) {\n    if (!schemaNode) {\n      console.error('SchemaTextCompleter: schema node is missing for path', currectPath)\n      return\n    }\n    if (schemaNode.$ref) {\n      this._handleRef(currectPath, schemaNode.$ref, suggestionsObj)\n      return\n    }\n    const ofConditionEntry = this._checkOfConditon(schemaNode)\n    if (ofConditionEntry) {\n      this._handleOfCondition(currectPath, schemaNode[ofConditionEntry], suggestionsObj)\n      return\n    }\n    switch (schemaNode.type) {\n      case 'object':\n        this._handleObject(currectPath, schemaNode, suggestionsObj)\n        break\n      case 'string':\n      case 'number':\n      case 'integer':\n        this._handlePrimitive(currectPath, schemaNode, suggestionsObj)\n        break\n      case 'boolean':\n        this._handleBoolean(currectPath, schemaNode, suggestionsObj)\n        break\n      case 'array':\n        this._handleArray(currectPath, schemaNode, suggestionsObj)\n    }\n  }\n\n  _handleObject (currectPath, schemaNode, suggestionsObj) {\n    if (isObject(schemaNode.properties)) {\n      const props = Object.keys(schemaNode.properties)\n      suggestionsObj[currectPath] = suggestionsObj[currectPath] || {}\n      suggestionsObj[currectPath].props = suggestionsObj[currectPath].props || []\n      suggestionsObj[currectPath].props = uniqueMergeArrays(suggestionsObj[currectPath].props, props)\n      props.forEach((prop) => {\n        asyncExec(() => {\n          this._handleSchemaEntry(`${currectPath}/${prop}`, schemaNode.properties[prop], suggestionsObj)\n        })\n      })\n    }\n  }\n\n  _handlePrimitive (currectPath, schemaNode, suggestionsObj) {\n    suggestionsObj[currectPath] = suggestionsObj[currectPath] || {}\n    if (isArray(schemaNode.examples)) {\n      suggestionsObj[currectPath].examples = suggestionsObj[currectPath].examples || []\n      suggestionsObj[currectPath].examples = uniqueMergeArrays(suggestionsObj[currectPath].examples, schemaNode.examples)\n    }\n    if (isArray(schemaNode.enum)) {\n      suggestionsObj[currectPath].enum = suggestionsObj[currectPath].enum || []\n      suggestionsObj[currectPath].enum = uniqueMergeArrays(suggestionsObj[currectPath].enum, schemaNode.enum)\n    }\n  }\n\n  _handleBoolean (currectPath, schemaNode, suggestionsObj) {\n    if (!suggestionsObj[currectPath]) {\n      suggestionsObj[currectPath] = {\n        bool: [true, false]\n      }\n    }\n  }\n\n  _handleArray (currectPath, schemaNode, suggestionsObj) {\n    if (schemaNode.items) {\n      asyncExec(() => {\n        this._handleSchemaEntry(`${currectPath}/\\\\d+`, schemaNode.items, suggestionsObj)\n      })\n    }\n  }\n\n  _handleOfCondition (currectPath, schemaNode, suggestionsObj) {\n    if (schemaNode && schemaNode.length) {\n      schemaNode.forEach(schemaEntry => {\n        asyncExec(() => {\n          this._handleSchemaEntry(currectPath, schemaEntry, suggestionsObj)\n        })\n      })\n    }\n  }\n\n  _checkOfConditon (entry) {\n    if (!entry) {\n      return\n    }\n    if (entry.oneOf) {\n      return 'oneOf'\n    }\n    if (entry.anyOf) {\n      return 'anyOf'\n    }\n    if (entry.allOf) {\n      return 'allOf'\n    }\n  }\n\n  getCompletions (editor, session, pos, prefix, callback) {\n    try {\n      const map = jsonMap.parse(session.getValue())\n      const pointers = map.pointers || {}\n      const processCompletionsCallback = (suggestions) => {\n        let completions = []\n        let score = 0\n        const appendSuggesions = (type) => {\n          const typeTitle = {\n            props: 'property',\n            enum: 'enum',\n            bool: 'boolean',\n            examples: 'examples'\n          }\n          if (suggestions && suggestions[type]?.length) {\n            completions = completions.concat(suggestions[type].map(term => {\n              return {\n                caption: term + '',\n                meta: `schema [${typeTitle[type]}]`,\n                score: score++,\n                value: term + ''\n              }\n            }))\n          }\n        }\n        appendSuggesions('props')\n        appendSuggesions('enum')\n        appendSuggesions('bool')\n        appendSuggesions('examples')\n\n        if (completions.length) {\n          callback(null, completions)\n        }\n      }\n      Object.keys(pointers).forEach((ptr) => {\n        asyncExec(() => {\n          const matchPointersToPath = (pointer, currentSuggestions, path) => {\n            const option = Object.keys(currentSuggestions).reduce((last, key) => {\n              if (new RegExp(`^${path}${key}`).test(pointer)) {\n                if (!last || last.length < key.length) {\n                  return key\n                }\n              }\n              return last\n            }, null)\n            if (typeof option === 'string') {\n              if (currentSuggestions[option]?.refs?.length) {\n                const mergedSuggestions = {}\n                for (const idx in currentSuggestions[option].refs) {\n                  const refName = currentSuggestions[option].refs[idx]\n                  if (this.suggestionsRefs[refName]) {\n                    const refSuggestion = matchPointersToPath(pointer, this.suggestionsRefs[refName], `${path}${option}`)\n                    if (refSuggestion?.enum) {\n                      mergedSuggestions.enum = uniqueMergeArrays(mergedSuggestions.enum, refSuggestion.enum)\n                    }\n                    if (refSuggestion?.examples) {\n                      mergedSuggestions.examples = uniqueMergeArrays(mergedSuggestions.examples, refSuggestion.examples)\n                    }\n                    if (refSuggestion?.bool) {\n                      mergedSuggestions.bool = uniqueMergeArrays(mergedSuggestions.bool, refSuggestion.bool)\n                    }\n                    if (refSuggestion?.props) {\n                      mergedSuggestions.props = uniqueMergeArrays(mergedSuggestions.props, refSuggestion.props)\n                    }\n                  }\n                }\n                return mergedSuggestions\n              } else if (new RegExp(`^${path}${option}$`).test(pointer)) {\n                // console.log('SchemaTextCompleter: Text suggestion match', { path: pointer, schemaPath: `${path}${option}`, suggestions: currentSuggestions[option] })\n                return currentSuggestions[option]\n              }\n            }\n          }\n          let selectedPtr\n          if (pointers[ptr].key?.line === pos.row) {\n            if (pos.column >= pointers[ptr].key.column && pos.column <= pointers[ptr].keyEnd.column) {\n              selectedPtr = ptr.slice(0, ptr.lastIndexOf('/'))\n            }\n          }\n          if (pointers[ptr].value?.line === pos.row &&\n              pointers[ptr].value?.line === pointers[ptr].valueEnd?.line) { // multiline values are objects\n            if (pos.column >= pointers[ptr].value.column && pos.column <= pointers[ptr].valueEnd.column) {\n              selectedPtr = ptr\n            }\n          }\n          if (selectedPtr) {\n            const chosenCompletions = matchPointersToPath(selectedPtr, this.suggestions, '')\n            processCompletionsCallback(chosenCompletions)\n          }\n        })\n      })\n    } catch (e) {\n      // probably not valid json, ignore.\n    }\n  }\n}\n"
  },
  {
    "path": "src/js/SearchBox.js",
    "content": "'use strict'\nimport { translate } from './i18n'\n\n/**\n * @constructor SearchBox\n * Create a search box in given HTML container\n * @param {JSONEditor} editor    The JSON Editor to attach to\n * @param {Element} container               HTML container element of where to\n *                                          create the search box\n */\nexport class SearchBox {\n  constructor (editor, container) {\n    const searchBox = this\n\n    this.editor = editor\n    this.timeout = undefined\n    this.delay = 200 // ms\n    this.lastText = undefined\n    this.results = null\n\n    this.dom = {}\n    this.dom.container = container\n\n    const wrapper = document.createElement('div')\n    this.dom.wrapper = wrapper\n    wrapper.className = 'jsoneditor-search'\n    container.appendChild(wrapper)\n\n    const results = document.createElement('div')\n    this.dom.results = results\n    results.className = 'jsoneditor-results'\n    wrapper.appendChild(results)\n\n    const divInput = document.createElement('div')\n    this.dom.input = divInput\n    divInput.className = 'jsoneditor-frame'\n    divInput.title = translate('searchTitle')\n    wrapper.appendChild(divInput)\n\n    const refreshSearch = document.createElement('button')\n    refreshSearch.type = 'button'\n    refreshSearch.className = 'jsoneditor-refresh'\n    divInput.appendChild(refreshSearch)\n\n    const search = document.createElement('input')\n    search.type = 'text'\n    this.dom.search = search\n    search.oninput = event => {\n      searchBox._onDelayedSearch(event)\n    }\n    search.onchange = event => {\n      // For IE 9\n      searchBox._onSearch()\n    }\n    search.onkeydown = event => {\n      searchBox._onKeyDown(event)\n    }\n    search.onkeyup = event => {\n      searchBox._onKeyUp(event)\n    }\n    refreshSearch.onclick = event => {\n      search.select()\n    }\n\n    // TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819\n    divInput.appendChild(search)\n\n    const searchNext = document.createElement('button')\n    searchNext.type = 'button'\n    searchNext.title = translate('searchNextResultTitle')\n    searchNext.className = 'jsoneditor-next'\n    searchNext.onclick = () => {\n      searchBox.next()\n    }\n\n    divInput.appendChild(searchNext)\n\n    const searchPrevious = document.createElement('button')\n    searchPrevious.type = 'button'\n    searchPrevious.title = translate('searchPreviousResultTitle')\n    searchPrevious.className = 'jsoneditor-previous'\n    searchPrevious.onclick = () => {\n      searchBox.previous()\n    }\n\n    divInput.appendChild(searchPrevious)\n  }\n\n  /**\n   * Go to the next search result\n   * @param {boolean} [focus]   If true, focus will be set to the next result\n   *                            focus is false by default.\n   */\n  next (focus) {\n    if (this.results) {\n      let index = this.resultIndex !== null ? this.resultIndex + 1 : 0\n      if (index > this.results.length - 1) {\n        index = 0\n      }\n      this._setActiveResult(index, focus)\n    }\n  }\n\n  /**\n   * Go to the prevous search result\n   * @param {boolean} [focus]   If true, focus will be set to the next result\n   *                            focus is false by default.\n   */\n  previous (focus) {\n    if (this.results) {\n      const max = this.results.length - 1\n      let index = this.resultIndex !== null ? this.resultIndex - 1 : max\n      if (index < 0) {\n        index = max\n      }\n      this._setActiveResult(index, focus)\n    }\n  }\n\n  /**\n   * Set new value for the current active result\n   * @param {Number} index\n   * @param {boolean} [focus]   If true, focus will be set to the next result.\n   *                            focus is false by default.\n   * @private\n   */\n  _setActiveResult (index, focus) {\n    // de-activate current active result\n    if (this.activeResult) {\n      const prevNode = this.activeResult.node\n      const prevElem = this.activeResult.elem\n      if (prevElem === 'field') {\n        delete prevNode.searchFieldActive\n      } else {\n        delete prevNode.searchValueActive\n      }\n      prevNode.updateDom()\n    }\n\n    if (!this.results || !this.results[index]) {\n      // out of range, set to undefined\n      this.resultIndex = undefined\n      this.activeResult = undefined\n      return\n    }\n\n    this.resultIndex = index\n\n    // set new node active\n    const node = this.results[this.resultIndex].node\n    const elem = this.results[this.resultIndex].elem\n    if (elem === 'field') {\n      node.searchFieldActive = true\n    } else {\n      node.searchValueActive = true\n    }\n    this.activeResult = this.results[this.resultIndex]\n    node.updateDom()\n\n    // TODO: not so nice that the focus is only set after the animation is finished\n    node.scrollTo(() => {\n      if (focus) {\n        node.focus(elem)\n      }\n    })\n  }\n\n  /**\n   * Cancel any running onDelayedSearch.\n   * @private\n   */\n  _clearDelay () {\n    if (this.timeout !== undefined) {\n      clearTimeout(this.timeout)\n      delete this.timeout\n    }\n  }\n\n  /**\n   * Start a timer to execute a search after a short delay.\n   * Used for reducing the number of searches while typing.\n   * @param {Event} event\n   * @private\n   */\n  _onDelayedSearch (event) {\n    // execute the search after a short delay (reduces the number of\n    // search actions while typing in the search text box)\n    this._clearDelay()\n    const searchBox = this\n    this.timeout = setTimeout(event => {\n      searchBox._onSearch()\n    }, this.delay)\n  }\n\n  /**\n   * Handle onSearch event\n   * @param {boolean} [forceSearch]  If true, search will be executed again even\n   *                                 when the search text is not changed.\n   *                                 Default is false.\n   * @private\n   */\n  _onSearch (forceSearch) {\n    this._clearDelay()\n\n    const value = this.dom.search.value\n    const text = value.length > 0 ? value : undefined\n    if (text !== this.lastText || forceSearch) {\n      // only search again when changed\n      this.lastText = text\n      this.results = this.editor.search(text)\n      const MAX_SEARCH_RESULTS = this.results[0]\n        ? this.results[0].node.MAX_SEARCH_RESULTS\n        : Infinity\n\n      // try to maintain the current active result if this is still part of the new search results\n      let activeResultIndex = 0\n      if (this.activeResult) {\n        for (let i = 0; i < this.results.length; i++) {\n          if (this.results[i].node === this.activeResult.node) {\n            activeResultIndex = i\n            break\n          }\n        }\n      }\n\n      this._setActiveResult(activeResultIndex, false)\n\n      // display search results\n      if (text !== undefined) {\n        const resultCount = this.results.length\n        if (resultCount === 0) {\n          this.dom.results.textContent = 'no\\u00A0results'\n        } else if (resultCount === 1) {\n          this.dom.results.textContent = '1\\u00A0result'\n        } else if (resultCount > MAX_SEARCH_RESULTS) {\n          this.dom.results.textContent = MAX_SEARCH_RESULTS + '+\\u00A0results'\n        } else {\n          this.dom.results.textContent = resultCount + '\\u00A0results'\n        }\n      } else {\n        this.dom.results.textContent = ''\n      }\n    }\n  }\n\n  /**\n   * Handle onKeyDown event in the input box\n   * @param {Event} event\n   * @private\n   */\n  _onKeyDown (event) {\n    const keynum = event.which\n    if (keynum === 27) {\n      // ESC\n      this.dom.search.value = '' // clear search\n      this._onSearch()\n      event.preventDefault()\n      event.stopPropagation()\n    } else if (keynum === 13) {\n      // Enter\n      if (event.ctrlKey) {\n        // force to search again\n        this._onSearch(true)\n      } else if (event.shiftKey) {\n        // move to the previous search result\n        this.previous()\n      } else {\n        // move to the next search result\n        this.next()\n      }\n      event.preventDefault()\n      event.stopPropagation()\n    }\n  }\n\n  /**\n   * Handle onKeyUp event in the input box\n   * @param {Event} event\n   * @private\n   */\n  _onKeyUp (event) {\n    const keynum = event.keyCode\n    if (keynum !== 27 && keynum !== 13) {\n      // !show and !Enter\n      this._onDelayedSearch(event) // For IE 9\n    }\n  }\n\n  /**\n   * Clear the search results\n   */\n  clear () {\n    this.dom.search.value = ''\n    this._onSearch()\n  }\n\n  /**\n   * Refresh searchResults if there is a search value\n   */\n  forceSearch () {\n    this._onSearch(true)\n  }\n\n  /**\n   * Test whether the search box value is empty\n   * @returns {boolean} Returns true when empty.\n   */\n  isEmpty () {\n    return this.dom.search.value === ''\n  }\n\n  /**\n   * Destroy the search box\n   */\n  destroy () {\n    this.editor = null\n    this.dom.container.removeChild(this.dom.wrapper)\n    this.dom = null\n\n    this.results = null\n    this.activeResult = null\n\n    this._clearDelay()\n  }\n}\n"
  },
  {
    "path": "src/js/TreePath.js",
    "content": "'use strict'\n\nimport { ContextMenu } from './ContextMenu'\nimport { translate } from './i18n'\nimport { addClassName, removeClassName } from './util'\n\n/**\n * Creates a component that visualize path selection in tree based editors\n * @param {HTMLElement} container\n * @param {HTMLElement} root\n * @constructor\n */\nexport class TreePath {\n  constructor (container, root) {\n    if (container) {\n      this.root = root\n      this.path = document.createElement('div')\n      this.path.className = 'jsoneditor-treepath'\n      this.path.setAttribute('tabindex', 0)\n      this.contentMenuClicked = false\n      container.appendChild(this.path)\n      this.reset()\n    }\n  }\n\n  /**\n   * Reset component to initial status\n   */\n  reset () {\n    this.path.textContent = translate('selectNode')\n  }\n\n  /**\n   * Renders the component UI according to a given path objects\n   * @param {Array<{name: String, childs: Array}>} pathObjs a list of path objects\n   *\n   */\n  setPath (pathObjs) {\n    const me = this\n\n    this.path.textContent = ''\n\n    if (pathObjs && pathObjs.length) {\n      pathObjs.forEach((pathObj, idx) => {\n        const pathEl = document.createElement('span')\n        let sepEl\n        pathEl.className = 'jsoneditor-treepath-element'\n        pathEl.innerText = pathObj.name\n        pathEl.onclick = _onSegmentClick.bind(me, pathObj)\n\n        me.path.appendChild(pathEl)\n\n        if (pathObj.children.length) {\n          sepEl = document.createElement('span')\n          sepEl.className = 'jsoneditor-treepath-seperator'\n          sepEl.textContent = '\\u25BA'\n\n          sepEl.onclick = () => {\n            me.contentMenuClicked = true\n            const items = []\n            pathObj.children.forEach(child => {\n              items.push({\n                text: child.name,\n                className: 'jsoneditor-type-modes' + (pathObjs[idx + 1] + 1 && pathObjs[idx + 1].name === child.name ? ' jsoneditor-selected' : ''),\n                click: _onContextMenuItemClick.bind(me, pathObj, child.name)\n              })\n            })\n            const menu = new ContextMenu(items, { limitHeight: true })\n            menu.show(sepEl, me.root, true)\n          }\n\n          me.path.appendChild(sepEl)\n        }\n\n        if (idx === pathObjs.length - 1) {\n          const leftRectPos = (sepEl || pathEl).getBoundingClientRect().right\n          if (me.path.offsetWidth < leftRectPos) {\n            me.path.scrollLeft = leftRectPos\n          }\n\n          if (me.path.scrollLeft) {\n            const showAllBtn = document.createElement('span')\n            showAllBtn.className = 'jsoneditor-treepath-show-all-btn'\n            showAllBtn.title = 'show all path'\n            showAllBtn.textContent = '...'\n            showAllBtn.onclick = _onShowAllClick.bind(me, pathObjs)\n            me.path.insertBefore(showAllBtn, me.path.firstChild)\n          }\n        }\n      })\n    }\n\n    function _onShowAllClick (pathObjs) {\n      me.contentMenuClicked = false\n      addClassName(me.path, 'show-all')\n      me.path.style.width = me.path.parentNode.getBoundingClientRect().width - 10 + 'px'\n      me.path.onblur = () => {\n        if (me.contentMenuClicked) {\n          me.contentMenuClicked = false\n          me.path.focus()\n          return\n        }\n        removeClassName(me.path, 'show-all')\n        me.path.onblur = undefined\n        me.path.style.width = ''\n        me.setPath(pathObjs)\n      }\n    }\n\n    function _onSegmentClick (pathObj) {\n      if (this.selectionCallback) {\n        this.selectionCallback(pathObj)\n      }\n    }\n\n    function _onContextMenuItemClick (pathObj, selection) {\n      if (this.contextMenuCallback) {\n        this.contextMenuCallback(pathObj, selection)\n      }\n    }\n  }\n\n  /**\n   * set a callback function for selection of path section\n   * @param {Function} callback function to invoke when section is selected\n   */\n  onSectionSelected (callback) {\n    if (typeof callback === 'function') {\n      this.selectionCallback = callback\n    }\n  }\n\n  /**\n   * set a callback function for selection of path section\n   * @param {Function} callback function to invoke when section is selected\n   */\n  onContextMenuItemSelected (callback) {\n    if (typeof callback === 'function') {\n      this.contextMenuCallback = callback\n    }\n  }\n}\n"
  },
  {
    "path": "src/js/ace/index.js",
    "content": "let ace\nif (window.ace) {\n  // use the already loaded instance of Ace\n  ace = window.ace\n} else {\n  try {\n    // load Ace editor\n    ace = require('ace-builds/src-noconflict/ace')\n\n    // load required Ace plugins\n    require('ace-builds/src-noconflict/mode-json')\n    require('ace-builds/src-noconflict/ext-searchbox')\n    require('ace-builds/src-noconflict/ext-language_tools')\n    // embed Ace json worker\n    // https://github.com/ajaxorg/ace/issues/3913\n    const jsonWorkerDataUrl = require('../generated/worker-json-data-url')\n    ace.config.setModuleUrl('ace/mode/json_worker', jsonWorkerDataUrl)\n  } catch (err) {\n    // failed to load Ace (can be minimalist bundle).\n    // No worries, the editor will fall back to plain text if needed.\n  }\n}\n\nmodule.exports = ace\n"
  },
  {
    "path": "src/js/ace/theme-jsoneditor.js",
    "content": "/* ***** BEGIN LICENSE BLOCK *****\n * Distributed under the BSD license:\n *\n * Copyright (c) 2010, Ajax.org B.V.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *     * Redistributions of source code must retain the above copyright\n *       notice, this list of conditions and the following disclaimer.\n *     * Redistributions in binary form must reproduce the above copyright\n *       notice, this list of conditions and the following disclaimer in the\n *       documentation and/or other materials provided with the distribution.\n *     * Neither the name of Ajax.org B.V. nor the\n *       names of its contributors may be used to endorse or promote products\n *       derived from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY\n * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * ***** END LICENSE BLOCK ***** */\n\nwindow.ace.define('ace/theme/jsoneditor', ['require', 'exports', 'module', 'ace/lib/dom'], (acequire, exports, module) => {\n  exports.isDark = false\n  exports.cssClass = 'ace-jsoneditor'\n  exports.cssText = `.ace-jsoneditor .ace_gutter {\nbackground: #ebebeb;\ncolor: #333\n}\n\n.ace-jsoneditor.ace_editor {\nline-height: 1.3;\nbackground-color: #fff;\n}\n.ace-jsoneditor .ace_print-margin {\nwidth: 1px;\nbackground: #e8e8e8\n}\n.ace-jsoneditor .ace_scroller {\nbackground-color: #FFFFFF\n}\n.ace-jsoneditor .ace_text-layer {\ncolor: gray\n}\n.ace-jsoneditor .ace_variable {\ncolor: #1a1a1a\n}\n.ace-jsoneditor .ace_cursor {\nborder-left: 2px solid #000000\n}\n.ace-jsoneditor .ace_overwrite-cursors .ace_cursor {\nborder-left: 0px;\nborder-bottom: 1px solid #000000\n}\n.ace-jsoneditor .ace_marker-layer .ace_selection {\nbackground: lightgray\n}\n.ace-jsoneditor.ace_multiselect .ace_selection.ace_start {\nbox-shadow: 0 0 3px 0px #FFFFFF;\nborder-radius: 2px\n}\n.ace-jsoneditor .ace_marker-layer .ace_step {\nbackground: rgb(255, 255, 0)\n}\n.ace-jsoneditor .ace_marker-layer .ace_bracket {\nmargin: -1px 0 0 -1px;\nborder: 1px solid #BFBFBF\n}\n.ace-jsoneditor .ace_marker-layer .ace_active-line {\nbackground: #FFFBD1\n}\n.ace-jsoneditor .ace_gutter-active-line {\nbackground-color : #dcdcdc\n}\n.ace-jsoneditor .ace_marker-layer .ace_selected-word {\nborder: 1px solid lightgray\n}\n.ace-jsoneditor .ace_invisible {\ncolor: #BFBFBF\n}\n.ace-jsoneditor .ace_keyword,\n.ace-jsoneditor .ace_meta,\n.ace-jsoneditor .ace_support.ace_constant.ace_property-value {\ncolor: #AF956F\n}\n.ace-jsoneditor .ace_keyword.ace_operator {\ncolor: #484848\n}\n.ace-jsoneditor .ace_keyword.ace_other.ace_unit {\ncolor: #96DC5F\n}\n.ace-jsoneditor .ace_constant.ace_language {\ncolor: darkorange\n}\n.ace-jsoneditor .ace_constant.ace_numeric {\ncolor: red\n}\n.ace-jsoneditor .ace_constant.ace_character.ace_entity {\ncolor: #BF78CC\n}\n.ace-jsoneditor .ace_invalid {\ncolor: #FFFFFF;\nbackground-color: #FF002A;\n}\n.ace-jsoneditor .ace_fold {\nbackground-color: #AF956F;\nborder-color: #000000\n}\n.ace-jsoneditor .ace_storage,\n.ace-jsoneditor .ace_support.ace_class,\n.ace-jsoneditor .ace_support.ace_function,\n.ace-jsoneditor .ace_support.ace_other,\n.ace-jsoneditor .ace_support.ace_type {\ncolor: #C52727\n}\n.ace-jsoneditor .ace_string {\ncolor: green\n}\n.ace-jsoneditor .ace_comment {\ncolor: #BCC8BA\n}\n.ace-jsoneditor .ace_entity.ace_name.ace_tag,\n.ace-jsoneditor .ace_entity.ace_other.ace_attribute-name {\ncolor: #606060\n}\n.ace-jsoneditor .ace_markup.ace_underline {\ntext-decoration: underline\n}\n.ace-jsoneditor .ace_indent-guide {\nbackground: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==\") right repeat-y\n}`\n\n  const dom = acequire('../lib/dom')\n  dom.importCssString(exports.cssText, exports.cssClass)\n})\n"
  },
  {
    "path": "src/js/appendNodeFactory.js",
    "content": "'use strict'\n\nimport { ContextMenu } from './ContextMenu'\nimport { translate } from './i18n'\nimport { addClassName, removeClassName } from './util'\n\n/**\n * A factory function to create an AppendNode, which depends on a Node\n * @param {Node} Node\n */\nexport function appendNodeFactory (Node) {\n  /**\n   * @constructor AppendNode\n   * @extends Node\n   * @param {TreeEditor} editor\n   * Create a new AppendNode. This is a special node which is created at the\n   * end of the list with childs for an object or array\n   */\n  function AppendNode (editor) {\n    /** @type {TreeEditor} */\n    this.editor = editor\n    this.dom = {}\n  }\n\n  AppendNode.prototype = new Node()\n\n  /**\n   * Return a table row with an append button.\n   * @return {Element} dom   TR element\n   */\n  AppendNode.prototype.getDom = function () {\n    // TODO: implement a new solution for the append node\n    const dom = this.dom\n\n    if (dom.tr) {\n      return dom.tr\n    }\n\n    this._updateEditability()\n\n    // a row for the append button\n    const trAppend = document.createElement('tr')\n    trAppend.className = 'jsoneditor-append'\n    trAppend.node = this\n    dom.tr = trAppend\n\n    // TODO: consistent naming\n\n    if (this.editor.options.mode === 'tree') {\n      // a cell for the dragarea column\n      dom.tdDrag = document.createElement('td')\n\n      // create context menu\n      const tdMenu = document.createElement('td')\n      dom.tdMenu = tdMenu\n      const menu = document.createElement('button')\n      menu.type = 'button'\n      menu.className = 'jsoneditor-button jsoneditor-contextmenu-button'\n      menu.title = 'Click to open the actions menu (Ctrl+M)'\n      dom.menu = menu\n      tdMenu.appendChild(dom.menu)\n    }\n\n    // a cell for the contents (showing text 'empty')\n    const tdAppend = document.createElement('td')\n    const domText = document.createElement('div')\n    domText.appendChild(document.createTextNode('(' + translate('empty') + ')'))\n    domText.className = 'jsoneditor-readonly'\n    tdAppend.appendChild(domText)\n    dom.td = tdAppend\n    dom.text = domText\n\n    this.updateDom()\n\n    return trAppend\n  }\n\n  /**\n   * Append node doesn't have a path\n   * @returns {null}\n   */\n  AppendNode.prototype.getPath = () => null\n\n  /**\n   * Append node doesn't have an index\n   * @returns {null}\n   */\n  AppendNode.prototype.getIndex = () => null\n\n  /**\n   * Update the HTML dom of the Node\n   */\n  AppendNode.prototype.updateDom = function (options) {\n    const dom = this.dom\n    const tdAppend = dom.td\n    if (tdAppend) {\n      tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px'\n      // TODO: not so nice hard coded offset\n    }\n\n    const domText = dom.text\n    if (domText) {\n      domText.firstChild.nodeValue = '(' + translate('empty') + ' ' + this.parent.type + ')'\n    }\n\n    // attach or detach the contents of the append node:\n    // hide when the parent has childs, show when the parent has no childs\n    const trAppend = dom.tr\n    if (!this.isVisible()) {\n      if (dom.tr.firstChild) {\n        if (dom.tdDrag) {\n          trAppend.removeChild(dom.tdDrag)\n        }\n        if (dom.tdMenu) {\n          trAppend.removeChild(dom.tdMenu)\n        }\n        trAppend.removeChild(tdAppend)\n      }\n    } else {\n      if (!dom.tr.firstChild) {\n        if (dom.tdDrag) {\n          trAppend.appendChild(dom.tdDrag)\n        }\n        if (dom.tdMenu) {\n          trAppend.appendChild(dom.tdMenu)\n        }\n        trAppend.appendChild(tdAppend)\n      }\n    }\n  }\n\n  /**\n   * Check whether the AppendNode is currently visible.\n   * the AppendNode is visible when its parent has no childs (i.e. is empty).\n   * @return {boolean} isVisible\n   */\n  AppendNode.prototype.isVisible = function () {\n    return (this.parent.childs.length === 0)\n  }\n\n  /**\n   * Show a contextmenu for this node\n   * @param {HTMLElement} anchor   The element to attach the menu to.\n   * @param {function} [onClose]   Callback method called when the context menu\n   *                               is being closed.\n   */\n  AppendNode.prototype.showContextMenu = function (anchor, onClose) {\n    const node = this\n\n    const appendSubmenu = [\n      {\n        text: translate('auto'),\n        className: 'jsoneditor-type-auto',\n        title: translate('autoType'),\n        click: function () {\n          node._onAppend('', '', 'auto')\n        }\n      },\n      {\n        text: translate('array'),\n        className: 'jsoneditor-type-array',\n        title: translate('arrayType'),\n        click: function () {\n          node._onAppend('', [])\n        }\n      },\n      {\n        text: translate('object'),\n        className: 'jsoneditor-type-object',\n        title: translate('objectType'),\n        click: function () {\n          node._onAppend('', {})\n        }\n      },\n      {\n        text: translate('string'),\n        className: 'jsoneditor-type-string',\n        title: translate('stringType'),\n        click: function () {\n          node._onAppend('', '', 'string')\n        }\n      }\n    ]\n    node.addTemplates(appendSubmenu, true)\n    let items = [\n      // create append button\n      {\n        text: translate('appendText'),\n        title: translate('appendTitleAuto'),\n        submenuTitle: translate('appendSubmenuTitle'),\n        className: 'jsoneditor-insert',\n        click: function () {\n          node._onAppend('', '', 'auto')\n        },\n        submenu: appendSubmenu\n      }\n    ]\n\n    if (this.editor.options.onCreateMenu) {\n      const path = node.parent.getPath()\n\n      items = this.editor.options.onCreateMenu(items, {\n        type: 'append',\n        path,\n        paths: [path]\n      })\n    }\n\n    const menu = new ContextMenu(items, { close: onClose })\n    menu.show(anchor, this.editor.getPopupAnchor())\n  }\n\n  /**\n   * Handle an event. The event is caught centrally by the editor\n   * @param {Event} event\n   */\n  AppendNode.prototype.onEvent = function (event) {\n    const type = event.type\n    const target = event.target || event.srcElement\n    const dom = this.dom\n\n    // highlight the append nodes parent\n    const menu = dom.menu\n    if (target === menu) {\n      if (type === 'mouseover') {\n        this.editor.highlighter.highlight(this.parent)\n      } else if (type === 'mouseout') {\n        this.editor.highlighter.unhighlight()\n      }\n    }\n\n    // context menu events\n    if (type === 'click' && target === dom.menu) {\n      const highlighter = this.editor.highlighter\n      highlighter.highlight(this.parent)\n      highlighter.lock()\n      addClassName(dom.menu, 'jsoneditor-selected')\n      this.showContextMenu(dom.menu, () => {\n        removeClassName(dom.menu, 'jsoneditor-selected')\n        highlighter.unlock()\n        highlighter.unhighlight()\n      })\n    }\n\n    if (type === 'keydown') {\n      this.onKeyDown(event)\n    }\n  }\n\n  return AppendNode\n}\n"
  },
  {
    "path": "src/js/assets/jsonlint/README.md",
    "content": "The file jsonlint.js is copied from the following project:\n\nhttps://github.com/josdejong/jsonlint  at 85a19d7\n\nwhich is a fork of the (currently not maintained) project:\n\nhttps://github.com/zaach/jsonlint\n\nThe forked project contains some fixes to allow the file to be bundled with \nbrowserify. The file is copied in this project to prevent issues with linking \nto a github project from package.json, which is for example not supported \nby jspm. \n\nAs soon as zaach/jsonlint is being maintained again we can push the fix\nto the original library and use it as dependency again.\n"
  },
  {
    "path": "src/js/assets/jsonlint/jsonlint.js",
    "content": "/* Jison generated parser */\nvar jsonlint = (function(){\nvar parser = {trace: function trace() { },\nyy: {},\nsymbols_: {\"error\":2,\"JSONString\":3,\"STRING\":4,\"JSONNumber\":5,\"NUMBER\":6,\"JSONNullLiteral\":7,\"NULL\":8,\"JSONBooleanLiteral\":9,\"TRUE\":10,\"FALSE\":11,\"JSONText\":12,\"JSONValue\":13,\"EOF\":14,\"JSONObject\":15,\"JSONArray\":16,\"{\":17,\"}\":18,\"JSONMemberList\":19,\"JSONMember\":20,\":\":21,\",\":22,\"[\":23,\"]\":24,\"JSONElementList\":25,\"$accept\":0,\"$end\":1},\nterminals_: {2:\"error\",4:\"STRING\",6:\"NUMBER\",8:\"NULL\",10:\"TRUE\",11:\"FALSE\",14:\"EOF\",17:\"{\",18:\"}\",21:\":\",22:\",\",23:\"[\",24:\"]\"},\nproductions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],\nperformAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {\n\nvar $0 = $$.length - 1;\nswitch (yystate) {\ncase 1: // replace escaped characters with actual character\n          this.$ = yytext.replace(/\\\\(\\\\|\")/g, \"$\"+\"1\")\n                     .replace(/\\\\n/g,'\\n')\n                     .replace(/\\\\r/g,'\\r')\n                     .replace(/\\\\t/g,'\\t')\n                     .replace(/\\\\v/g,'\\v')\n                     .replace(/\\\\f/g,'\\f')\n                     .replace(/\\\\b/g,'\\b');\n        \nbreak;\ncase 2:this.$ = Number(yytext);\nbreak;\ncase 3:this.$ = null;\nbreak;\ncase 4:this.$ = true;\nbreak;\ncase 5:this.$ = false;\nbreak;\ncase 6:return this.$ = $$[$0-1];\nbreak;\ncase 13:this.$ = {};\nbreak;\ncase 14:this.$ = $$[$0-1];\nbreak;\ncase 15:this.$ = [$$[$0-2], $$[$0]];\nbreak;\ncase 16:this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];\nbreak;\ncase 17:this.$ = $$[$0-2]; $$[$0-2][$$[$0][0]] = $$[$0][1];\nbreak;\ncase 18:this.$ = [];\nbreak;\ncase 19:this.$ = $$[$0-1];\nbreak;\ncase 20:this.$ = [$$[$0]];\nbreak;\ncase 21:this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);\nbreak;\n}\n},\ntable: [{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}],\ndefaultActions: {16:[2,6]},\nparseError: function parseError(str, hash) {\n    throw new Error(str);\n},\nparse: function parse(input) {\n    var self = this,\n        stack = [0],\n        vstack = [null], // semantic value stack\n        lstack = [], // location stack\n        table = this.table,\n        yytext = '',\n        yylineno = 0,\n        yyleng = 0,\n        recovering = 0,\n        TERROR = 2,\n        EOF = 1;\n\n    //this.reductionCount = this.shiftCount = 0;\n\n    this.lexer.setInput(input);\n    this.lexer.yy = this.yy;\n    this.yy.lexer = this.lexer;\n    if (typeof this.lexer.yylloc == 'undefined')\n        this.lexer.yylloc = {};\n    var yyloc = this.lexer.yylloc;\n    lstack.push(yyloc);\n\n    if (typeof this.yy.parseError === 'function')\n        this.parseError = this.yy.parseError;\n\n    function popStack (n) {\n        stack.length = stack.length - 2*n;\n        vstack.length = vstack.length - n;\n        lstack.length = lstack.length - n;\n    }\n\n    function lex() {\n        var token;\n        token = self.lexer.lex() || 1; // $end = 1\n        // if token isn't its numeric value, convert\n        if (typeof token !== 'number') {\n            token = self.symbols_[token] || token;\n        }\n        return token;\n    }\n\n    var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;\n    while (true) {\n        // retreive state number from top of stack\n        state = stack[stack.length-1];\n\n        // use default actions if available\n        if (this.defaultActions[state]) {\n            action = this.defaultActions[state];\n        } else {\n            if (symbol == null)\n                symbol = lex();\n            // read action for current state and first input\n            action = table[state] && table[state][symbol];\n        }\n\n        // handle parse error\n        _handle_error:\n        if (typeof action === 'undefined' || !action.length || !action[0]) {\n\n            if (!recovering) {\n                // Report error\n                expected = [];\n                for (p in table[state]) if (this.terminals_[p] && p > 2) {\n                    expected.push(\"'\"+this.terminals_[p]+\"'\");\n                }\n                var errStr = '';\n                if (this.lexer.showPosition) {\n                    errStr = 'Parse error on line '+(yylineno+1)+\":\\n\"+this.lexer.showPosition()+\"\\nExpecting \"+expected.join(', ') + \", got '\" + this.terminals_[symbol]+ \"'\";\n                } else {\n                    errStr = 'Parse error on line '+(yylineno+1)+\": Unexpected \" +\n                                  (symbol == 1 /*EOF*/ ? \"end of input\" :\n                                              (\"'\"+(this.terminals_[symbol] || symbol)+\"'\"));\n                }\n                this.parseError(errStr,\n                    {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});\n            }\n\n            // just recovered from another error\n            if (recovering == 3) {\n                if (symbol == EOF) {\n                    throw new Error(errStr || 'Parsing halted.');\n                }\n\n                // discard current lookahead and grab another\n                yyleng = this.lexer.yyleng;\n                yytext = this.lexer.yytext;\n                yylineno = this.lexer.yylineno;\n                yyloc = this.lexer.yylloc;\n                symbol = lex();\n            }\n\n            // try to recover from error\n            while (1) {\n                // check for error recovery rule in this state\n                if ((TERROR.toString()) in table[state]) {\n                    break;\n                }\n                if (state == 0) {\n                    throw new Error(errStr || 'Parsing halted.');\n                }\n                popStack(1);\n                state = stack[stack.length-1];\n            }\n\n            preErrorSymbol = symbol; // save the lookahead token\n            symbol = TERROR;         // insert generic error symbol as new lookahead\n            state = stack[stack.length-1];\n            action = table[state] && table[state][TERROR];\n            recovering = 3; // allow 3 real symbols to be shifted before reporting a new error\n        }\n\n        // this shouldn't happen, unless resolve defaults are off\n        if (action[0] instanceof Array && action.length > 1) {\n            throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);\n        }\n\n        switch (action[0]) {\n\n            case 1: // shift\n                //this.shiftCount++;\n\n                stack.push(symbol);\n                vstack.push(this.lexer.yytext);\n                lstack.push(this.lexer.yylloc);\n                stack.push(action[1]); // push state\n                symbol = null;\n                if (!preErrorSymbol) { // normal execution/no error\n                    yyleng = this.lexer.yyleng;\n                    yytext = this.lexer.yytext;\n                    yylineno = this.lexer.yylineno;\n                    yyloc = this.lexer.yylloc;\n                    if (recovering > 0)\n                        recovering--;\n                } else { // error just occurred, resume old lookahead f/ before error\n                    symbol = preErrorSymbol;\n                    preErrorSymbol = null;\n                }\n                break;\n\n            case 2: // reduce\n                //this.reductionCount++;\n\n                len = this.productions_[action[1]][1];\n\n                // perform semantic action\n                yyval.$ = vstack[vstack.length-len]; // default to $$ = $1\n                // default location, uses first token for firsts, last for lasts\n                yyval._$ = {\n                    first_line: lstack[lstack.length-(len||1)].first_line,\n                    last_line: lstack[lstack.length-1].last_line,\n                    first_column: lstack[lstack.length-(len||1)].first_column,\n                    last_column: lstack[lstack.length-1].last_column\n                };\n                r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);\n\n                if (typeof r !== 'undefined') {\n                    return r;\n                }\n\n                // pop off stack\n                if (len) {\n                    stack = stack.slice(0,-1*len*2);\n                    vstack = vstack.slice(0, -1*len);\n                    lstack = lstack.slice(0, -1*len);\n                }\n\n                stack.push(this.productions_[action[1]][0]);    // push nonterminal (reduce)\n                vstack.push(yyval.$);\n                lstack.push(yyval._$);\n                // goto new state = table[STATE][NONTERMINAL]\n                newState = table[stack[stack.length-2]][stack[stack.length-1]];\n                stack.push(newState);\n                break;\n\n            case 3: // accept\n                return true;\n        }\n\n    }\n\n    return true;\n}};\n/* Jison generated lexer */\nvar lexer = (function(){\nvar lexer = ({EOF:1,\nparseError:function parseError(str, hash) {\n        if (this.yy.parseError) {\n            this.yy.parseError(str, hash);\n        } else {\n            throw new Error(str);\n        }\n    },\nsetInput:function (input) {\n        this._input = input;\n        this._more = this._less = this.done = false;\n        this.yylineno = this.yyleng = 0;\n        this.yytext = this.matched = this.match = '';\n        this.conditionStack = ['INITIAL'];\n        this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};\n        return this;\n    },\ninput:function () {\n        var ch = this._input[0];\n        this.yytext+=ch;\n        this.yyleng++;\n        this.match+=ch;\n        this.matched+=ch;\n        var lines = ch.match(/\\n/);\n        if (lines) this.yylineno++;\n        this._input = this._input.slice(1);\n        return ch;\n    },\nunput:function (ch) {\n        this._input = ch + this._input;\n        return this;\n    },\nmore:function () {\n        this._more = true;\n        return this;\n    },\nless:function (n) {\n        this._input = this.match.slice(n) + this._input;\n    },\npastInput:function () {\n        var past = this.matched.substr(0, this.matched.length - this.match.length);\n        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\\n/g, \"\");\n    },\nupcomingInput:function () {\n        var next = this.match;\n        if (next.length < 20) {\n            next += this._input.substr(0, 20-next.length);\n        }\n        return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\\n/g, \"\");\n    },\nshowPosition:function () {\n        var pre = this.pastInput();\n        var c = new Array(pre.length + 1).join(\"-\");\n        return pre + this.upcomingInput() + \"\\n\" + c+\"^\";\n    },\nnext:function () {\n        if (this.done) {\n            return this.EOF;\n        }\n        if (!this._input) this.done = true;\n\n        var token,\n            match,\n            tempMatch,\n            index,\n            col,\n            lines;\n        if (!this._more) {\n            this.yytext = '';\n            this.match = '';\n        }\n        var rules = this._currentRules();\n        for (var i=0;i < rules.length; i++) {\n            tempMatch = this._input.match(this.rules[rules[i]]);\n            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {\n                match = tempMatch;\n                index = i;\n                if (!this.options.flex) break;\n            }\n        }\n        if (match) {\n            lines = match[0].match(/\\n.*/g);\n            if (lines) this.yylineno += lines.length;\n            this.yylloc = {first_line: this.yylloc.last_line,\n                           last_line: this.yylineno+1,\n                           first_column: this.yylloc.last_column,\n                           last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}\n            this.yytext += match[0];\n            this.match += match[0];\n            this.yyleng = this.yytext.length;\n            this._more = false;\n            this._input = this._input.slice(match[0].length);\n            this.matched += match[0];\n            token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);\n            if (this.done && this._input) this.done = false;\n            if (token) return token;\n            else return;\n        }\n        if (this._input === \"\") {\n            return this.EOF;\n        } else {\n            this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\\n'+this.showPosition(), \n                    {text: \"\", token: null, line: this.yylineno});\n        }\n    },\nlex:function lex() {\n        var r = this.next();\n        if (typeof r !== 'undefined') {\n            return r;\n        } else {\n            return this.lex();\n        }\n    },\nbegin:function begin(condition) {\n        this.conditionStack.push(condition);\n    },\npopState:function popState() {\n        return this.conditionStack.pop();\n    },\n_currentRules:function _currentRules() {\n        return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;\n    },\ntopState:function () {\n        return this.conditionStack[this.conditionStack.length-2];\n    },\npushState:function begin(condition) {\n        this.begin(condition);\n    }});\nlexer.options = {};\nlexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {\n\nvar YYSTATE=YY_START\nswitch($avoiding_name_collisions) {\ncase 0:/* skip whitespace */\nbreak;\ncase 1:return 6\nbreak;\ncase 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4\nbreak;\ncase 3:return 17\nbreak;\ncase 4:return 18\nbreak;\ncase 5:return 23\nbreak;\ncase 6:return 24\nbreak;\ncase 7:return 22\nbreak;\ncase 8:return 21\nbreak;\ncase 9:return 10\nbreak;\ncase 10:return 11\nbreak;\ncase 11:return 8\nbreak;\ncase 12:return 14\nbreak;\ncase 13:return 'INVALID'\nbreak;\n}\n};\nlexer.rules = [/^(?:\\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\\.[0-9]+)?([eE][-+]?[0-9]+)?\\b)/,/^(?:\"(?:\\\\[\\\\\"bfnrt/]|\\\\u[a-fA-F0-9]{4}|[^\\\\\\0-\\x09\\x0a-\\x1f\"])*\")/,/^(?:\\{)/,/^(?:\\})/,/^(?:\\[)/,/^(?:\\])/,/^(?:,)/,/^(?::)/,/^(?:true\\b)/,/^(?:false\\b)/,/^(?:null\\b)/,/^(?:$)/,/^(?:.)/];\nlexer.conditions = {\"INITIAL\":{\"rules\":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],\"inclusive\":true}};\n\n\n;\nreturn lexer;})()\nparser.lexer = lexer;\nreturn parser;\n})();\nif (typeof require !== 'undefined' && typeof exports !== 'undefined') {\n  exports.parser = jsonlint;\n  exports.parse = jsonlint.parse.bind(jsonlint);\n}"
  },
  {
    "path": "src/js/assets/selectr/README.md",
    "content": "This is a copy of the Selectr project\n\nhttps://github.com/Mobius1/Selectr\n\nReason is that the project is not maintained and has some issues\nloading it via `require` in a webpack project.\n"
  },
  {
    "path": "src/js/assets/selectr/selectr.js",
    "content": "/*!\n * Selectr 2.4.13\n * http://mobius.ovh/docs/selectr\n *\n * Released under the MIT license\n */\n\n'use strict';\n\n/**\n * Event Emitter\n */\nvar Events = function() {};\n\n/**\n * Event Prototype\n * @type {Object}\n */\nEvents.prototype = {\n  /**\n   * Add custom event listener\n   * @param  {String} event Event type\n   * @param  {Function} func   Callback\n   * @return {Void}\n   */\n  on: function(event, func) {\n    this._events = this._events || {};\n    this._events[event] = this._events[event] || [];\n    this._events[event].push(func);\n  },\n\n  /**\n   * Remove custom event listener\n   * @param  {String} event Event type\n   * @param  {Function} func   Callback\n   * @return {Void}\n   */\n  off: function(event, func) {\n    this._events = this._events || {};\n    if (event in this._events === false) return;\n    this._events[event].splice(this._events[event].indexOf(func), 1);\n  },\n\n  /**\n   * Fire a custom event\n   * @param  {String} event Event type\n   * @return {Void}\n   */\n  emit: function(event /* , args... */ ) {\n    this._events = this._events || {};\n    if (event in this._events === false) return;\n    for (var i = 0; i < this._events[event].length; i++) {\n      this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));\n    }\n  }\n};\n\n/**\n * Event mixin\n * @param  {Object} obj\n * @return {Object}\n */\nEvents.mixin = function(obj) {\n  var props = ['on', 'off', 'emit'];\n  for (var i = 0; i < props.length; i++) {\n    if (typeof obj === 'function') {\n      obj.prototype[props[i]] = Events.prototype[props[i]];\n    } else {\n      obj[props[i]] = Events.prototype[props[i]];\n    }\n  }\n  return obj;\n};\n\n/**\n * Helpers\n * @type {Object}\n */\nvar util = {\n  escapeRegExp: function(str) {\n    // source from lodash 3.0.0\n    var _reRegExpChar = /[\\\\^$.*+?()[\\]{}|]/g;\n    var _reHasRegExpChar = new RegExp(_reRegExpChar.source);\n    return (str && _reHasRegExpChar.test(str)) ? str.replace(_reRegExpChar, '\\\\$&') : str;\n  },\n  extend: function(src, props) {\n    for (var prop in props) {\n      if (props.hasOwnProperty(prop)) {\n        var val = props[prop];\n        if (val && Object.prototype.toString.call(val) === \"[object Object]\") {\n          src[prop] = src[prop] || {};\n          util.extend(src[prop], val);\n        } else {\n          src[prop] = val;\n        }\n      }\n    }\n    return src;\n  },\n  each: function(a, b, c) {\n    if (\"[object Object]\" === Object.prototype.toString.call(a)) {\n      for (var d in a) {\n        if (Object.prototype.hasOwnProperty.call(a, d)) {\n          b.call(c, d, a[d], a);\n        }\n      }\n    } else {\n      for (var e = 0, f = a.length; e < f; e++) {\n        b.call(c, e, a[e], a);\n      }\n    }\n  },\n  createElement: function(e, a) {\n    var d = document,\n      el = d.createElement(e);\n    if (a && \"[object Object]\" === Object.prototype.toString.call(a)) {\n      var i;\n      for (i in a)\n        if (i in el) el[i] = a[i];\n        else if (\"html\" === i) el.innerHTML = a[i];\n        else el.setAttribute(i, a[i]);\n    }\n    return el;\n  },\n  hasClass: function(a, b) {\n    if (a)\n      return a.classList ? a.classList.contains(b) : !!a.className && !!a.className.match(new RegExp(\"(\\\\s|^)\" + b + \"(\\\\s|$)\"));\n  },\n  addClass: function(a, b) {\n    if (!util.hasClass(a, b)) {\n      if (a.classList) {\n        a.classList.add(b);\n      } else {\n        a.className = a.className.trim() + \" \" + b;\n      }\n    }\n  },\n  removeClass: function(a, b) {\n    if (util.hasClass(a, b)) {\n      if (a.classList) {\n        a.classList.remove(b);\n      } else {\n        a.className = a.className.replace(new RegExp(\"(^|\\\\s)\" + b.split(\" \").join(\"|\") + \"(\\\\s|$)\", \"gi\"), \" \");\n      }\n    }\n  },\n  closest: function(el, fn) {\n    return el && el !== document.body && (fn(el) ? el : util.closest(el.parentNode, fn));\n  },\n  isInt: function(val) {\n    return typeof val === 'number' && isFinite(val) && Math.floor(val) === val;\n  },\n  debounce: function(a, b, c) {\n    var d;\n    return function() {\n      var e = this,\n        f = arguments,\n        g = function() {\n          d = null;\n          if (!c) a.apply(e, f);\n        },\n        h = c && !d;\n      clearTimeout(d);\n      d = setTimeout(g, b);\n      if (h) {\n        a.apply(e, f);\n      }\n    };\n  },\n  rect: function(el, abs) {\n    var w = window;\n    var r = el.getBoundingClientRect();\n    var x = abs ? w.pageXOffset : 0;\n    var y = abs ? w.pageYOffset : 0;\n\n    return {\n      bottom: r.bottom + y,\n      height: r.height,\n      left: r.left + x,\n      right: r.right + x,\n      top: r.top + y,\n      width: r.width\n    };\n  },\n  includes: function(a, b) {\n    return a.indexOf(b) > -1;\n  },\n  startsWith: function(a, b) {\n    return a.substr( 0, b.length ) === b;\n  },\n  truncate: function(el) {\n    while (el.firstChild) {\n      el.removeChild(el.firstChild);\n    }\n  }\n};\n\n\nfunction isset(obj, prop) {\n  return obj.hasOwnProperty(prop) && (obj[prop] === true || obj[prop].length);\n}\n\n/**\n * Append an item to the list\n * @param  {Object} item\n * @param  {Object} custom\n * @return {Void}\n */\nfunction appendItem(item, parent, custom) {\n  if (item.parentNode) {\n    if (!item.parentNode.parentNode) {\n      parent.appendChild(item.parentNode);\n    }\n  } else {\n    parent.appendChild(item);\n  }\n\n  util.removeClass(item, \"excluded\");\n  if (!custom) {\n    // remove any <span> highlighting, without xss\n    item.textContent = item.textContent;\n  }\n}\n\n/**\n * Render the item list\n * @return {Void}\n */\nvar render = function() {\n  if (this.items.length) {\n    var f = document.createDocumentFragment();\n\n    if (this.config.pagination) {\n      var pages = this.pages.slice(0, this.pageIndex);\n\n      util.each(pages, function(i, items) {\n        util.each(items, function(j, item) {\n          appendItem(item, f, this.customOption);\n        }, this);\n      }, this);\n    } else {\n      util.each(this.items, function(i, item) {\n        appendItem(item, f, this.customOption);\n      }, this);\n    }\n\n    // highlight first selected option if any; first option otherwise\n    if (f.childElementCount) {\n      util.removeClass(this.items[this.navIndex], \"active\");\n      this.navIndex = (\n        f.querySelector(\".selectr-option.selected\") ||\n        f.querySelector(\".selectr-option\")\n      ).idx;\n      util.addClass(this.items[this.navIndex], \"active\");\n    }\n\n    this.tree.appendChild(f);\n  }\n};\n\n/**\n * Dismiss / close the dropdown\n * @param  {obj} e\n * @return {void}\n */\nvar dismiss = function(e) {\n  var target = e.target;\n  if (!this.container.contains(target) && (this.opened || util.hasClass(this.container, \"notice\"))) {\n    this.close();\n  }\n};\n\n/**\n * Build a list item from the HTMLOptionElement\n * @param  {int} i      HTMLOptionElement index\n * @param  {HTMLOptionElement} option\n * @param  {bool} group  Has parent optgroup\n * @return {void}\n */\nvar createItem = function(option, data) {\n  data = data || option;\n  var elementData =  {\n    class: \"selectr-option\",\n    role: \"treeitem\",\n    \"aria-selected\": false\n  };\n\n  if(this.customOption){\n    elementData.html = this.config.renderOption(data); // asume xss prevention in custom render function\n  } else{\n    elementData.textContent = option.textContent; // treat all as plain text\n  }\n  var opt = util.createElement(\"li\",elementData);\n\n\n  opt.idx = option.idx;\n\n  this.items.push(opt);\n\n  if (option.defaultSelected) {\n    this.defaultSelected.push(option.idx);\n  }\n\n  if (option.disabled) {\n    opt.disabled = true;\n    util.addClass(opt, \"disabled\");\n  }\n\n  return opt;\n};\n\n/**\n * Build the container\n * @return {Void}\n */\nvar build = function() {\n\n  this.requiresPagination = this.config.pagination && this.config.pagination > 0;\n\n  // Set width\n  if (isset(this.config, \"width\")) {\n    if (util.isInt(this.config.width)) {\n      this.width = this.config.width + \"px\";\n    } else {\n      if (this.config.width === \"auto\") {\n        this.width = \"100%\";\n      } else if (util.includes(this.config.width, \"%\")) {\n        this.width = this.config.width;\n      }\n    }\n  }\n\n  this.container = util.createElement(\"div\", {\n    class: \"selectr-container\"\n  });\n\n  // Custom className\n  if (this.config.customClass) {\n    util.addClass(this.container, this.config.customClass);\n  }\n\n  // Mobile device\n  if (this.mobileDevice) {\n    util.addClass(this.container, \"selectr-mobile\");\n  } else {\n    util.addClass(this.container, \"selectr-desktop\");\n  }\n\n  // Hide the HTMLSelectElement and prevent focus\n  this.el.tabIndex = -1;\n\n  // Native dropdown\n  if (this.config.nativeDropdown || this.mobileDevice) {\n    util.addClass(this.el, \"selectr-visible\");\n  } else {\n    util.addClass(this.el, \"selectr-hidden\");\n  }\n\n  this.selected = util.createElement(\"div\", {\n    class: \"selectr-selected\",\n    disabled: this.disabled,\n    tabIndex: 0,\n    \"aria-expanded\": false\n  });\n\n  this.label = util.createElement(this.el.multiple ? \"ul\" : \"span\", {\n    class: \"selectr-label\"\n  });\n\n  var dropdown = util.createElement(\"div\", {\n    class: \"selectr-options-container\"\n  });\n\n  this.tree = util.createElement(\"ul\", {\n    class: \"selectr-options\",\n    role: \"tree\",\n    \"aria-hidden\": true,\n    \"aria-expanded\": false\n  });\n\n  this.notice = util.createElement(\"div\", {\n    class: \"selectr-notice\"\n  });\n\n  this.el.setAttribute(\"aria-hidden\", true);\n\n  if (this.disabled) {\n    this.el.disabled = true;\n  }\n\n  if (this.el.multiple) {\n    util.addClass(this.label, \"selectr-tags\");\n    util.addClass(this.container, \"multiple\");\n\n    // Collection of tags\n    this.tags = [];\n\n    // Collection of selected values\n    // #93 defaultSelected = false did not work as expected\n    this.selectedValues = (this.config.defaultSelected) ? this.getSelectedProperties('value') : [];\n\n    // Collection of selected indexes\n    this.selectedIndexes = this.getSelectedProperties('idx');\n  } else {\n    // #93 defaultSelected = false did not work as expected\n    // these values were undefined\n    this.selectedValue = null;\n    this.selectedIndex = -1;\n  }\n\n  this.selected.appendChild(this.label);\n\n  if (this.config.clearable) {\n    this.selectClear = util.createElement(\"button\", {\n      class: \"selectr-clear\",\n      type: \"button\"\n    });\n\n    this.container.appendChild(this.selectClear);\n\n    util.addClass(this.container, \"clearable\");\n  }\n\n  if (this.config.taggable) {\n    var li = util.createElement('li', {\n      class: 'input-tag'\n    });\n    this.input = util.createElement(\"input\", {\n      class: \"selectr-tag-input\",\n      placeholder: this.config.tagPlaceholder,\n      tagIndex: 0,\n      autocomplete: \"off\",\n      autocorrect: \"off\",\n      autocapitalize: \"off\",\n      spellcheck: \"false\",\n      role: \"textbox\",\n      type: \"search\"\n    });\n\n    li.appendChild(this.input);\n    this.label.appendChild(li);\n    util.addClass(this.container, \"taggable\");\n\n    this.tagSeperators = [\",\"];\n    if (this.config.tagSeperators) {\n      this.tagSeperators = this.tagSeperators.concat(this.config.tagSeperators);\n      var _aTempEscapedSeperators = [];\n      for(var _nTagSeperatorStepCount = 0; _nTagSeperatorStepCount < this.tagSeperators.length; _nTagSeperatorStepCount++){\n        _aTempEscapedSeperators.push(util.escapeRegExp(this.tagSeperators[_nTagSeperatorStepCount]));\n      }\n      this.tagSeperatorsRegex = new RegExp(_aTempEscapedSeperators.join('|'),'i');\n    } else {\n      this.tagSeperatorsRegex = new RegExp(',','i');\n    }\n  }\n\n  if (this.config.searchable) {\n    this.input = util.createElement(\"input\", {\n      class: \"selectr-input\",\n      tagIndex: -1,\n      autocomplete: \"off\",\n      autocorrect: \"off\",\n      autocapitalize: \"off\",\n      spellcheck: \"false\",\n      role: \"textbox\",\n      type: \"search\",\n      placeholder: this.config.messages.searchPlaceholder\n    });\n    this.inputClear = util.createElement(\"button\", {\n      class: \"selectr-input-clear\",\n      type: \"button\"\n    });\n    this.inputContainer = util.createElement(\"div\", {\n      class: \"selectr-input-container\"\n    });\n\n    this.inputContainer.appendChild(this.input);\n    this.inputContainer.appendChild(this.inputClear);\n    dropdown.appendChild(this.inputContainer);\n  }\n\n  dropdown.appendChild(this.notice);\n  dropdown.appendChild(this.tree);\n\n  // List of items for the dropdown\n  this.items = [];\n\n  // Establish options\n  this.options = [];\n\n  // Check for options in the element\n  if (this.el.options.length) {\n    this.options = [].slice.call(this.el.options);\n  }\n\n  // Element may have optgroups so\n  // iterate element.children instead of element.options\n  var group = false,\n    j = 0;\n  if (this.el.children.length) {\n    util.each(this.el.children, function(i, element) {\n      if (element.nodeName === \"OPTGROUP\") {\n\n        group = util.createElement(\"ul\", {\n          class: \"selectr-optgroup\",\n          role: \"group\",\n          html: \"<li class='selectr-optgroup--label'>\" + element.label + \"</li>\"\n        });\n\n        util.each(element.children, function(x, el) {\n          el.idx = j;\n          group.appendChild(createItem.call(this, el, group));\n          j++;\n        }, this);\n      } else {\n        element.idx = j;\n        createItem.call(this, element);\n        j++;\n      }\n    }, this);\n  }\n\n  // Options defined by the data option\n  if (this.config.data && Array.isArray(this.config.data)) {\n    this.data = [];\n    var optgroup = false,\n      option;\n\n    group = false;\n    j = 0;\n\n    util.each(this.config.data, function(i, opt) {\n      // Check for group options\n      if (isset(opt, \"children\")) {\n        optgroup = util.createElement(\"optgroup\", {\n          label: opt.text\n        });\n\n        group = util.createElement(\"ul\", {\n          class: \"selectr-optgroup\",\n          role: \"group\",\n          html: \"<li class='selectr-optgroup--label'>\" + opt.text + \"</li>\"\n        });\n\n        util.each(opt.children, function(x, data) {\n          option = new Option(data.text, data.value, false, data.hasOwnProperty(\"selected\") && data.selected === true);\n\n          option.disabled = isset(data, \"disabled\");\n\n          this.options.push(option);\n\n          optgroup.appendChild(option);\n\n          option.idx = j;\n\n          group.appendChild(createItem.call(this, option, data));\n\n          this.data[j] = data;\n\n          j++;\n        }, this);\n\n        this.el.appendChild(optgroup);\n      } else {\n        option = new Option(opt.text, opt.value, false, opt.hasOwnProperty(\"selected\") && opt.selected === true);\n\n        option.disabled = isset(opt, \"disabled\");\n\n        this.options.push(option);\n\n        option.idx = j;\n\n        createItem.call(this, option, opt);\n\n        this.data[j] = opt;\n\n        j++;\n      }\n    }, this);\n  }\n\n  this.setSelected(true);\n\n  var first;\n  this.navIndex = 0;\n  for (var i = 0; i < this.items.length; i++) {\n    first = this.items[i];\n\n    if (!util.hasClass(first, \"disabled\")) {\n\n      util.addClass(first, \"active\");\n      this.navIndex = i;\n      break;\n    }\n  }\n\n  // Check for pagination / infinite scroll\n  if (this.requiresPagination) {\n    this.pageIndex = 1;\n\n    // Create the pages\n    this.paginate();\n  }\n\n  this.container.appendChild(this.selected);\n  this.container.appendChild(dropdown);\n\n  this.placeEl = util.createElement(\"div\", {\n    class: \"selectr-placeholder\"\n  });\n\n  // Set the placeholder\n  this.setPlaceholder();\n\n  this.selected.appendChild(this.placeEl);\n\n  // Disable if required\n  if (this.disabled) {\n    this.disable();\n  }\n\n  this.el.parentNode.insertBefore(this.container, this.el);\n  this.container.appendChild(this.el);\n};\n\n/**\n * Navigate through the dropdown\n * @param  {obj} e\n * @return {void}\n */\nvar navigate = function(e) {\n  e = e || window.event;\n\n  // Filter out the keys we don\"t want\n  if (!this.items.length || !this.opened || !util.includes([13, 38, 40], e.which)) {\n    this.navigating = false;\n    return;\n  }\n\n  e.preventDefault();\n\n  if (e.which === 13) {\n\n    if ( this.noResults || (this.config.taggable && this.input.value.length > 0) ) {\n      return false;\n    }\n\n    return this.change(this.navIndex);\n  }\n\n  var direction, prevEl = this.items[this.navIndex];\n  var lastIndex = this.navIndex;\n\n  switch (e.which) {\n    case 38:\n      direction = 0;\n      if (this.navIndex > 0) {\n        this.navIndex--;\n      }\n      break;\n    case 40:\n      direction = 1;\n      if (this.navIndex < this.items.length - 1) {\n        this.navIndex++;\n      }\n  }\n\n  this.navigating = true;\n\n\n  // Instead of wasting memory holding a copy of this.items\n  // with disabled / excluded options omitted, skip them instead\n  while (util.hasClass(this.items[this.navIndex], \"disabled\") || util.hasClass(this.items[this.navIndex], \"excluded\")) {\n    if (this.navIndex > 0 && this.navIndex < this.items.length -1) {\n      if (direction) {\n        this.navIndex++;\n      } else {\n        this.navIndex--;\n      }\n    } else {\n      this.navIndex = lastIndex;\n      break;\n    }\n\n    if (this.searching) {\n      if (this.navIndex > this.tree.lastElementChild.idx) {\n        this.navIndex = this.tree.lastElementChild.idx;\n        break;\n      } else if (this.navIndex < this.tree.firstElementChild.idx) {\n        this.navIndex = this.tree.firstElementChild.idx;\n        break;\n      }\n    }\n  }\n\n  // Autoscroll the dropdown during navigation\n  var r = util.rect(this.items[this.navIndex]);\n\n  if (!direction) {\n    if (this.navIndex === 0) {\n      this.tree.scrollTop = 0;\n    } else if (r.top - this.optsRect.top < 0) {\n      this.tree.scrollTop = this.tree.scrollTop + (r.top - this.optsRect.top);\n    }\n  } else {\n    if (this.navIndex === 0) {\n      this.tree.scrollTop = 0;\n    } else if ((r.top + r.height) > (this.optsRect.top + this.optsRect.height)) {\n      this.tree.scrollTop = this.tree.scrollTop + ((r.top + r.height) - (this.optsRect.top + this.optsRect.height));\n    }\n\n    // Load another page if needed\n    if (this.navIndex === this.tree.childElementCount - 1 && this.requiresPagination) {\n      load.call(this);\n    }\n  }\n\n  if (prevEl) {\n    util.removeClass(prevEl, \"active\");\n  }\n\n  util.addClass(this.items[this.navIndex], \"active\");\n};\n\n/**\n * Add a tag\n * @param  {HTMLElement} item\n */\nvar addTag = function(item) {\n  var that = this,\n    r;\n\n  var docFrag = document.createDocumentFragment();\n  var option = this.options[item.idx];\n  var data = this.data ? this.data[item.idx] : option;\n  var elementData = { class: \"selectr-tag\" };\n  if (this.customSelected){\n    elementData.html = this.config.renderSelection(data); // asume xss prevention in custom render function\n  } else {\n    elementData.textContent = option.textContent;\n  }\n  var tag = util.createElement(\"li\", elementData);\n  var btn = util.createElement(\"button\", {\n    class: \"selectr-tag-remove\",\n    type: \"button\"\n  });\n\n  tag.appendChild(btn);\n\n  // Set property to check against later\n  tag.idx = item.idx;\n  tag.tag = option.value;\n\n  this.tags.push(tag);\n\n  if (this.config.sortSelected) {\n\n    var tags = this.tags.slice();\n\n    // Deal with values that contain numbers\n    r = function(val, arr) {\n      val.replace(/(\\d+)|(\\D+)/g, function(that, $1, $2) {\n        arr.push([$1 || Infinity, $2 || \"\"]);\n      });\n    };\n\n    tags.sort(function(a, b) {\n      var x = [],\n        y = [],\n        ac, bc;\n      if (that.config.sortSelected === true) {\n        ac = a.tag;\n        bc = b.tag;\n      } else if (that.config.sortSelected === 'text') {\n        ac = a.textContent;\n        bc = b.textContent;\n      }\n\n      r(ac, x);\n      r(bc, y);\n\n      while (x.length && y.length) {\n        var ax = x.shift();\n        var by = y.shift();\n        var nn = (ax[0] - by[0]) || ax[1].localeCompare(by[1]);\n        if (nn) return nn;\n      }\n\n      return x.length - y.length;\n    });\n\n    util.each(tags, function(i, tg) {\n      docFrag.appendChild(tg);\n    });\n\n    this.label.innerHTML = \"\";\n\n  } else {\n    docFrag.appendChild(tag);\n  }\n\n  if (this.config.taggable) {\n    this.label.insertBefore(docFrag, this.input.parentNode);\n  } else {\n    this.label.appendChild(docFrag);\n  }\n};\n\n/**\n * Remove a tag\n * @param  {HTMLElement} item\n * @return {void}\n */\nvar removeTag = function(item) {\n  var tag = false;\n\n  util.each(this.tags, function(i, t) {\n    if (t.idx === item.idx) {\n      tag = t;\n    }\n  }, this);\n\n  if (tag) {\n    this.label.removeChild(tag);\n    this.tags.splice(this.tags.indexOf(tag), 1);\n  }\n};\n\n/**\n * Load the next page of items\n * @return {void}\n */\nvar load = function() {\n  var tree = this.tree;\n  var scrollTop = tree.scrollTop;\n  var scrollHeight = tree.scrollHeight;\n  var offsetHeight = tree.offsetHeight;\n  var atBottom = scrollTop >= (scrollHeight - offsetHeight);\n\n  if ((atBottom && this.pageIndex < this.pages.length)) {\n    var f = document.createDocumentFragment();\n\n    util.each(this.pages[this.pageIndex], function(i, item) {\n      appendItem(item, f, this.customOption);\n    }, this);\n\n    tree.appendChild(f);\n\n    this.pageIndex++;\n\n    this.emit(\"selectr.paginate\", {\n      items: this.items.length,\n      total: this.data.length,\n      page: this.pageIndex,\n      pages: this.pages.length\n    });\n  }\n};\n\n/**\n * Clear a search\n * @return {void}\n */\nvar clearSearch = function() {\n  if (this.config.searchable || this.config.taggable) {\n    this.input.value = null;\n    this.searching = false;\n    if (this.config.searchable) {\n      util.removeClass(this.inputContainer, \"active\");\n    }\n\n    if (util.hasClass(this.container, \"notice\")) {\n      util.removeClass(this.container, \"notice\");\n      util.addClass(this.container, \"open\");\n      this.input.focus();\n    }\n\n    util.each(this.items, function(i, item) {\n      // Items that didn't match need the class\n      // removing to make them visible again\n      util.removeClass(item, \"excluded\");\n      // Remove the span element for underlining matched items\n      if (!this.customOption) {\n        // without xss\n        item.textContent = item.textContent;\n      }\n    }, this);\n  }\n};\n\n/**\n * Query matching for searches.\n * Wraps matching text in a span.selectr-match.\n *\n * @param  {string} query\n * @param  {HTMLOptionElement} option element\n * @return {bool} true if matched; false otherwise\n */\nvar match = function(query, option) {\n  var text = option.textContent;\n  var RX = new RegExp( query, \"ig\" );\n  var result = RX.exec( text );\n  if (result) {\n    // #102 stop xss\n    option.innerHTML = \"\";\n    var span = document.createElement( \"span\" );\n    span.classList.add( \"selectr-match\" );\n    span.textContent = result[0];\n    option.appendChild( document.createTextNode( text.substring( 0, result.index ) ) );\n    option.appendChild( span );\n    option.appendChild( document.createTextNode( text.substring( RX.lastIndex ) ) );\n    return true;\n  }\n  return false;\n};\n\n// Main Lib\nvar Selectr = function(el, config) {\n\n  if (!el) {\n    throw new Error(\"You must supply either a HTMLSelectElement or a CSS3 selector string.\");\n  }\n\n  this.el = el;\n\n  // CSS3 selector string\n  if (typeof el === \"string\") {\n    this.el = document.querySelector(el);\n  }\n\n  if (this.el === null) {\n    throw new Error(\"The element you passed to Selectr can not be found.\");\n  }\n\n  if (this.el.nodeName.toLowerCase() !== \"select\") {\n    throw new Error(\"The element you passed to Selectr is not a HTMLSelectElement.\");\n  }\n\n  this.render(config);\n};\n\n/**\n * Render the instance\n * @param  {object} config\n * @return {void}\n */\nSelectr.prototype.render = function(config) {\n\n  if (this.rendered) return;\n\n  /**\n   * Default configuration options\n   * @type {Object}\n   */\n  var defaultConfig = {\n    /**\n     * Emulates browser behaviour by selecting the first option by default\n     * @type {Boolean}\n     */\n    defaultSelected: true,\n\n    /**\n     * Sets the width of the container\n     * @type {String}\n     */\n    width: \"auto\",\n\n    /**\n     * Enables/ disables the container\n     * @type {Boolean}\n     */\n    disabled: false,\n\n    /**\n     * Enables/ disables logic for mobile\n     * @type {Boolean}\n     */\n    disabledMobile: false,\n\n    /**\n     * Enables / disables the search function\n     * @type {Boolean}\n     */\n    searchable: true,\n\n    /**\n     * Enable disable the clear button\n     * @type {Boolean}\n     */\n    clearable: false,\n\n    /**\n     * Sort the tags / multiselect options\n     * @type {Boolean}\n     */\n    sortSelected: false,\n\n    /**\n     * Allow deselecting of select-one options\n     * @type {Boolean}\n     */\n    allowDeselect: false,\n\n    /**\n     * Close the dropdown when scrolling (@AlexanderReiswich, #11)\n     * @type {Boolean}\n     */\n    closeOnScroll: false,\n\n    /**\n     * Allow the use of the native dropdown (@jonnyscholes, #14)\n     * @type {Boolean}\n     */\n    nativeDropdown: false,\n\n    /**\n     * Allow the use of native typing behavior for toggling, searching, selecting\n     * @type {boolean}\n     */\n    nativeKeyboard: false,\n\n    /**\n     * Set the main placeholder\n     * @type {String}\n     */\n    placeholder: \"Select an option...\",\n\n    /**\n     * Allow the tagging feature\n     * @type {Boolean}\n     */\n    taggable: false,\n\n    /**\n     * Set the tag input placeholder (@labikmartin, #21, #22)\n     * @type {String}\n     */\n    tagPlaceholder: \"Enter a tag...\",\n\n    messages: {\n      noResults: \"No results.\",\n      noOptions: \"No options available.\",\n      maxSelections: \"A maximum of {max} items can be selected.\",\n      tagDuplicate: \"That tag is already in use.\",\n      searchPlaceholder: \"Search options...\"\n    }\n  };\n\n  // add instance reference (#87)\n  this.el.selectr = this;\n\n  // Merge defaults with user set config\n  this.config = util.extend(defaultConfig, config);\n\n  // Store type\n  this.originalType = this.el.type;\n\n  // Store tabIndex\n  this.originalIndex = this.el.tabIndex;\n\n  // Store defaultSelected options for form reset\n  this.defaultSelected = [];\n\n  // Store the original option count\n  this.originalOptionCount = this.el.options.length;\n\n  if (this.config.multiple || this.config.taggable) {\n    this.el.multiple = true;\n  }\n\n  // Disabled?\n  this.disabled = isset(this.config, \"disabled\");\n\n  this.opened = false;\n\n  if (this.config.taggable) {\n    this.config.searchable = false;\n  }\n\n  this.navigating = false;\n\n  this.mobileDevice = false;\n\n  if (!this.config.disabledMobile && /Android|webOS|iPhone|iPad|BlackBerry|Windows Phone|Opera Mini|IEMobile|Mobile/i.test(navigator.userAgent)) {\n    this.mobileDevice = true;\n  }\n\n  this.customOption = this.config.hasOwnProperty(\"renderOption\") && typeof this.config.renderOption === \"function\";\n  this.customSelected = this.config.hasOwnProperty(\"renderSelection\") && typeof this.config.renderSelection === \"function\";\n\n  this.supportsEventPassiveOption = this.detectEventPassiveOption();\n\n  // Enable event emitter\n  Events.mixin(this);\n\n  build.call(this);\n\n  this.bindEvents();\n\n  this.update();\n\n  this.optsRect = util.rect(this.tree);\n\n  this.rendered = true;\n\n  // Fixes macOS Safari bug #28\n  if (!this.el.multiple) {\n    this.el.selectedIndex = this.selectedIndex;\n  }\n\n  var that = this;\n  setTimeout(function() {\n    that.emit(\"selectr.init\");\n  }, 20);\n};\n\nSelectr.prototype.getSelected = function () {\n  var selected = this.el.querySelectorAll('option:checked');\n  return selected;\n};\n\nSelectr.prototype.getSelectedProperties = function (prop) {\n  var selected = this.getSelected();\n  var values = [].slice.call(selected)\n    .map(function(option) { return option[prop]; })\n    .filter(function(i) { return i!==null && i!==undefined; });\n  return values;\n};\n\n/**\n * Feature detection: addEventListener passive option\n * https://dom.spec.whatwg.org/#dom-addeventlisteneroptions-passive\n * https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md\n */\nSelectr.prototype.detectEventPassiveOption = function () {\n  var supportsPassiveOption = false;\n  try {\n    var opts = Object.defineProperty({}, 'passive', {\n      get: function() {\n        supportsPassiveOption = true;\n      }\n    });\n    window.addEventListener('test', null, opts);\n  } catch (e) {}\n  return supportsPassiveOption;\n};\n\n/**\n * Attach the required event listeners\n */\nSelectr.prototype.bindEvents = function() {\n\n  var that = this;\n\n  this.events = {};\n\n  this.events.dismiss = dismiss.bind(this);\n  this.events.navigate = navigate.bind(this);\n  this.events.reset = this.reset.bind(this);\n\n  if (this.config.nativeDropdown || this.mobileDevice) {\n\n    this.container.addEventListener(\"touchstart\", function(e) {\n      if (e.changedTouches[0].target === that.el) {\n        that.toggle();\n      }\n    }, this.supportsEventPassiveOption ? { passive: true } : false);\n\n    this.container.addEventListener(\"click\", function(e) {\n      if (e.target === that.el) {\n        that.toggle();\n      }\n    });\n\n    var getChangedOptions = function(last, current) {\n      var added=[], removed=last.slice(0);\n      var idx;\n      for (var i=0; i<current.length; i++) {\n        idx = removed.indexOf(current[i]);\n        if (idx > -1)\n          removed.splice(idx, 1);\n        else\n          added.push(current[i]);\n      }\n      return [added, removed];\n    };\n\n    // Listen for the change on the native select\n    // and update accordingly\n    this.el.addEventListener(\"change\", function(e) {\n      if (e.__selfTriggered) {\n        return;\n      }\n      if (that.el.multiple) {\n        var indexes = that.getSelectedProperties('idx');\n        var changes = getChangedOptions(that.selectedIndexes, indexes);\n\n        util.each(changes[0], function(i, idx) {\n          that.select(idx);\n        }, that);\n\n        util.each(changes[1], function(i, idx) {\n          that.deselect(idx);\n        }, that);\n\n      } else {\n        if (that.el.selectedIndex > -1) {\n          that.select(that.el.selectedIndex);\n        }\n      }\n    });\n\n  }\n\n  // Open the dropdown with Enter key if focused\n  if ( this.config.nativeDropdown ) {\n    this.container.addEventListener(\"keydown\", function(e) {\n      if (e.key === \"Enter\" && that.selected === document.activeElement) {\n        // show native dropdown\n        that.toggle();\n        // focus on it\n        setTimeout(function() {\n          that.el.focus();\n        }, 200);\n      }\n    });\n  }\n\n  // Non-native dropdown\n  this.selected.addEventListener(\"click\", function(e) {\n\n    if (!that.disabled) {\n      that.toggle();\n    }\n\n    e.preventDefault();\n  });\n\n  if ( this.config.nativeKeyboard ) {\n    var typing = '';\n    var typingTimeout = null;\n\n    this.selected.addEventListener(\"keydown\", function (e) {\n      // Do nothing if disabled, not focused, or modifier keys are pressed\n      if (\n        that.disabled ||\n        that.selected !== document.activeElement ||\n        (e.altKey || e.ctrlKey || e.metaKey)\n      ) {\n        return;\n      }\n\n      // Open the dropdown on [enter], [ ], [↓], and [↑] keys\n      if (\n        e.key === \" \" ||\n        (! that.opened && [\"Enter\", \"ArrowUp\", \"ArrowDown\"].indexOf(e.key) > -1)\n      ) {\n        that.toggle();\n        e.preventDefault();\n        e.stopPropagation();\n        return;\n      }\n\n      // Type to search if multiple; type to select otherwise\n      // make sure e.key is a single, printable character\n      // .length check is a short-circut to skip checking keys like \"ArrowDown\", etc.\n      // prefer \"codePoint\" methods; they work with the full range of unicode\n      if (\n        e.key.length <= 2 &&\n        String[String.fromCodePoint ? \"fromCodePoint\" : \"fromCharCode\"](\n          e.key[String.codePointAt ? \"codePointAt\" : \"charCodeAt\"]( 0 )\n        ) === e.key\n      ) {\n        if ( that.config.multiple ) {\n          that.open();\n          if ( that.config.searchable ) {\n            that.input.value = e.key;\n            that.input.focus();\n            that.search( null, true );\n          }\n        } else {\n          if ( typingTimeout ) {\n            clearTimeout( typingTimeout );\n          }\n          typing += e.key;\n          var found = that.search( typing, true );\n          if ( found && found.length ) {\n            that.clear();\n            that.setValue( found[0].value );\n          }\n          setTimeout(function () { typing = ''; }, 1000);\n        }\n        e.preventDefault();\n        e.stopPropagation();\n        return;\n      }\n    });\n\n    // Close the dropdown on [esc] key\n    this.container.addEventListener(\"keyup\", function (e) {\n      if ( that.opened && e.key === \"Escape\" ) {\n        that.close();\n        e.stopPropagation();\n\n        // keep focus so we can re-open easily if desired\n        that.selected.focus();\n      }\n    });\n  }\n\n  // Remove tag\n  this.label.addEventListener(\"click\", function(e) {\n    if (util.hasClass(e.target, \"selectr-tag-remove\")) {\n      that.deselect(e.target.parentNode.idx);\n    }\n  });\n\n  // Clear input\n  if (this.selectClear) {\n    this.selectClear.addEventListener(\"click\", this.clear.bind(this));\n  }\n\n  // Prevent text selection\n  this.tree.addEventListener(\"mousedown\", function(e) {\n    e.preventDefault();\n  });\n\n  // Select / deselect items\n  this.tree.addEventListener(\"click\", function(e) {\n    var item = util.closest(e.target, function(el) {\n      return el && util.hasClass(el, \"selectr-option\");\n    });\n\n    if (item) {\n      if (!util.hasClass(item, \"disabled\")) {\n        if (util.hasClass(item, \"selected\")) {\n          if (that.el.multiple || !that.el.multiple && that.config.allowDeselect) {\n            that.deselect(item.idx);\n          }\n        } else {\n          that.select(item.idx);\n        }\n\n        if (that.opened && !that.el.multiple) {\n          that.close();\n        }\n      }\n    }\n\n    e.preventDefault();\n    e.stopPropagation();\n  });\n\n  // Mouseover list items\n  this.tree.addEventListener(\"mouseover\", function(e) {\n    if (util.hasClass(e.target, \"selectr-option\")) {\n      if (!util.hasClass(e.target, \"disabled\")) {\n        util.removeClass(that.items[that.navIndex], \"active\");\n\n        util.addClass(e.target, \"active\");\n\n        that.navIndex = [].slice.call(that.items).indexOf(e.target);\n      }\n    }\n  });\n\n  // Searchable\n  if (this.config.searchable) {\n    // Show / hide the search input clear button\n\n    this.input.addEventListener(\"focus\", function(e) {\n      that.searching = true;\n    });\n\n    this.input.addEventListener(\"blur\", function(e) {\n      that.searching = false;\n    });\n\n    this.input.addEventListener(\"keyup\", function(e) {\n      that.search();\n\n      if (!that.config.taggable) {\n        // Show / hide the search input clear button\n        if (this.value.length) {\n          util.addClass(this.parentNode, \"active\");\n        } else {\n          util.removeClass(this.parentNode, \"active\");\n        }\n      }\n    });\n\n    // Clear the search input\n    this.inputClear.addEventListener(\"click\", function(e) {\n      that.input.value = null;\n      clearSearch.call(that);\n\n      if (!that.tree.childElementCount) {\n        render.call(that);\n      }\n    });\n  }\n\n  if (this.config.taggable) {\n    this.input.addEventListener(\"keyup\", function(e) {\n\n      that.search();\n\n      if (that.config.taggable && this.value.length) {\n        var _sVal = this.value.trim();\n\n        if (_sVal.length && (e.which === 13 || that.tagSeperatorsRegex.test(_sVal) )) {\n          var _sGrabbedTagValue = _sVal.replace(that.tagSeperatorsRegex, '');\n          _sGrabbedTagValue = util.escapeRegExp(_sGrabbedTagValue);\n          _sGrabbedTagValue = _sGrabbedTagValue.trim();\n\n          var _oOption;\n          if(_sGrabbedTagValue.length){\n            _oOption = that.add({\n              value: _sGrabbedTagValue,\n              textContent: _sGrabbedTagValue,\n              selected: true\n            }, true);\n          }\n\n          if(_oOption){\n            that.close();\n            clearSearch.call(that);\n          } else {\n            this.value = '';\n            that.setMessage(that.config.messages.tagDuplicate);\n          }\n        }\n      }\n    });\n  }\n\n  this.update = util.debounce(function() {\n    // Optionally close dropdown on scroll / resize (#11)\n    if (that.opened && that.config.closeOnScroll) {\n      that.close();\n    }\n    if (that.width) {\n      that.container.style.width = that.width;\n    }\n    that.invert();\n  }, 50);\n\n  if (this.requiresPagination) {\n    this.paginateItems = util.debounce(function() {\n      load.call(this);\n    }, 50);\n\n    this.tree.addEventListener(\"scroll\", this.paginateItems.bind(this));\n  }\n\n  // Dismiss when clicking outside the container\n  document.addEventListener(\"click\", this.events.dismiss);\n  window.addEventListener(\"keydown\", this.events.navigate);\n\n  window.addEventListener(\"resize\", this.update);\n  window.addEventListener(\"scroll\", this.update);\n\n  // remove event listeners on destroy()\n  this.on('selectr.destroy', function () {\n    document.removeEventListener(\"click\", this.events.dismiss);\n    window.removeEventListener(\"keydown\", this.events.navigate);\n    window.removeEventListener(\"resize\", this.update);\n    window.removeEventListener(\"scroll\", this.update);\n  });\n\n  // Listen for form.reset() (@ambrooks, #13)\n  if (this.el.form) {\n    this.el.form.addEventListener(\"reset\", this.events.reset);\n\n    // remove listener on destroy()\n    this.on('selectr.destroy', function () {\n      this.el.form.removeEventListener(\"reset\", this.events.reset);\n    });\n  }\n};\n\n/**\n * Check for selected options\n * @param {bool} reset\n */\nSelectr.prototype.setSelected = function(reset) {\n\n  // Select first option as with a native select-one element - #21, #24\n  if (!this.config.data && !this.el.multiple && this.el.options.length) {\n    // Browser has selected the first option by default\n    if (this.el.selectedIndex === 0) {\n      if (!this.el.options[0].defaultSelected && !this.config.defaultSelected) {\n        this.el.selectedIndex = -1;\n      }\n    }\n\n    this.selectedIndex = this.el.selectedIndex;\n\n    if (this.selectedIndex > -1) {\n      this.select(this.selectedIndex);\n    }\n  }\n\n  // If we're changing a select-one to select-multiple via the config\n  // and there are no selected options, the first option will be selected by the browser\n  // Let's prevent that here.\n  if (this.config.multiple && this.originalType === \"select-one\" && !this.config.data) {\n    if (this.el.options[0].selected && !this.el.options[0].defaultSelected) {\n      this.el.options[0].selected = false;\n    }\n  }\n\n  util.each(this.options, function(i, option) {\n    if (option.selected && option.defaultSelected) {\n      this.select(option.idx);\n    }\n  }, this);\n\n  if (this.config.selectedValue) {\n    this.setValue(this.config.selectedValue);\n  }\n\n  if (this.config.data) {\n\n\n    if (!this.el.multiple && this.config.defaultSelected && this.el.selectedIndex < 0 && this.config.data.length > 0) {\n      this.select(0);\n    }\n\n    var j = 0;\n    util.each(this.config.data, function(i, opt) {\n      // Check for group options\n      if (isset(opt, \"children\")) {\n        util.each(opt.children, function(x, item) {\n          if (item.hasOwnProperty(\"selected\") && item.selected === true) {\n            this.select(j);\n          }\n          j++;\n        }, this);\n      } else {\n        if (opt.hasOwnProperty(\"selected\") && opt.selected === true) {\n          this.select(j);\n        }\n        j++;\n      }\n    }, this);\n  }\n};\n\n/**\n * Destroy the instance\n * @return {void}\n */\nSelectr.prototype.destroy = function() {\n\n  if (!this.rendered) return;\n\n  this.emit(\"selectr.destroy\");\n\n  // Revert to select-single if programtically set to multiple\n  if (this.originalType === 'select-one') {\n    this.el.multiple = false;\n  }\n\n  if (this.config.data) {\n    this.el.innerHTML = \"\";\n  }\n\n  // Remove the className from select element\n  util.removeClass(this.el, 'selectr-hidden');\n\n  // Replace the container with the original select element\n  this.container.parentNode.replaceChild(this.el, this.container);\n\n  this.rendered = false;\n\n  // remove reference\n  delete this.el.selectr;\n};\n\n/**\n * Change an options state\n * @param  {Number} index\n * @return {void}\n */\nSelectr.prototype.change = function(index) {\n  var item = this.items[index],\n    option = this.options[index];\n\n  if (option.disabled) {\n    return;\n  }\n\n  if (option.selected && util.hasClass(item, \"selected\")) {\n    this.deselect(index);\n  } else {\n    this.select(index);\n  }\n\n  if (this.opened && !this.el.multiple) {\n    this.close();\n  }\n};\n\n/**\n * Select an option\n * @param  {Number} index\n * @return {void}\n */\nSelectr.prototype.select = function(index) {\n\n  var item = this.items[index],\n    options = [].slice.call(this.el.options),\n    option = this.options[index];\n\n  if (this.el.multiple) {\n    if (util.includes(this.selectedIndexes, index)) {\n      return false;\n    }\n\n    if (this.config.maxSelections && this.tags.length === this.config.maxSelections) {\n      this.setMessage(this.config.messages.maxSelections.replace(\"{max}\", this.config.maxSelections), true);\n      return false;\n    }\n\n    this.selectedValues.push(option.value);\n    this.selectedIndexes.push(index);\n\n    addTag.call(this, item);\n  } else {\n    var data = this.data ? this.data[index] : option;\n    if (this.customSelected) {\n      this.label.innerHTML = this.config.renderSelection(data);\n    } else {\n      // no xss\n      this.label.textContent = option.textContent;\n    }\n\n    this.selectedValue = option.value;\n    this.selectedIndex = index;\n\n    util.each(this.options, function(i, o) {\n      var opt = this.items[i];\n\n      if (i !== index) {\n        if (opt) {\n          util.removeClass(opt, \"selected\");\n        }\n        o.selected = false;\n        o.removeAttribute(\"selected\");\n      }\n    }, this);\n  }\n\n  if (!util.includes(options, option)) {\n    this.el.add(option);\n  }\n\n  item.setAttribute(\"aria-selected\", true);\n\n  util.addClass(item, \"selected\");\n  util.addClass(this.container, \"has-selected\");\n\n  option.selected = true;\n  option.setAttribute(\"selected\", \"\");\n\n  this.emit(\"selectr.change\", option);\n\n  this.emit(\"selectr.select\", option);\n\n  // fire native change event\n  if (\"createEvent\" in document) {\n    var evt = document.createEvent(\"HTMLEvents\");\n    evt.initEvent(\"change\", true, true);\n    evt.__selfTriggered = true;\n    this.el.dispatchEvent(evt);\n  } else {\n    this.el.fireEvent(\"onchange\");\n  }\n};\n\n/**\n * Deselect an option\n * @param  {Number} index\n * @return {void}\n */\nSelectr.prototype.deselect = function(index, force) {\n  var item = this.items[index],\n    option = this.options[index];\n\n  if (this.el.multiple) {\n    var selIndex = this.selectedIndexes.indexOf(index);\n    this.selectedIndexes.splice(selIndex, 1);\n\n    var valIndex = this.selectedValues.indexOf(option.value);\n    this.selectedValues.splice(valIndex, 1);\n\n    removeTag.call(this, item);\n\n    if (!this.tags.length) {\n      util.removeClass(this.container, \"has-selected\");\n    }\n  } else {\n\n    if (!force && !this.config.clearable && !this.config.allowDeselect) {\n      return false;\n    }\n\n    this.label.innerHTML = \"\";\n    this.selectedValue = null;\n\n    this.el.selectedIndex = this.selectedIndex = -1;\n\n    util.removeClass(this.container, \"has-selected\");\n  }\n\n\n  this.items[index].setAttribute(\"aria-selected\", false);\n\n  util.removeClass(this.items[index], \"selected\");\n\n  option.selected = false;\n\n  option.removeAttribute(\"selected\");\n\n  this.emit(\"selectr.change\", null);\n\n  this.emit(\"selectr.deselect\", option);\n\n  // fire native change event\n  if (\"createEvent\" in document) {\n    var evt = document.createEvent(\"HTMLEvents\");\n    evt.initEvent(\"change\", true, true);\n    evt.__selfTriggered = true;\n    this.el.dispatchEvent(evt);\n  } else {\n    this.el.fireEvent(\"onchange\");\n  }\n};\n\n/**\n * Programmatically set selected values\n * @param {String|Array} value - A string or an array of strings\n */\nSelectr.prototype.setValue = function(value) {\n  var isArray = Array.isArray(value);\n\n  if (!isArray) {\n    value = value.toString().trim();\n  }\n\n  // Can't pass array to select-one\n  if (!this.el.multiple && isArray) {\n    return false;\n  }\n\n  util.each(this.options, function(i, option) {\n    if (isArray && (value.indexOf(option.value) > -1) || option.value === value) {\n      this.change(option.idx);\n    }\n  }, this);\n};\n\n/**\n * Set the selected value(s)\n * @param  {bool} toObject Return only the raw values or an object\n * @param  {bool} toJson   Return the object as a JSON string\n * @return {mixed}         Array or String\n */\nSelectr.prototype.getValue = function(toObject, toJson) {\n  var value;\n\n  if (this.el.multiple) {\n    if (toObject) {\n      if (this.selectedIndexes.length) {\n        value = {};\n        value.values = [];\n        util.each(this.selectedIndexes, function(i, index) {\n          var option = this.options[index];\n          value.values[i] = {\n            value: option.value,\n            text: option.textContent\n          };\n        }, this);\n      }\n    } else {\n      value = this.selectedValues.slice();\n    }\n  } else {\n    if (toObject) {\n      var option = this.options[this.selectedIndex];\n      value = {\n        value: option.value,\n        text: option.textContent\n      };\n    } else {\n      value = this.selectedValue;\n    }\n  }\n\n  if (toObject && toJson) {\n    value = JSON.stringify(value);\n  }\n\n  return value;\n};\n\n/**\n * Add a new option or options\n * @param {object} data\n */\nSelectr.prototype.add = function(data, checkDuplicate) {\n  if (data) {\n    this.data = this.data || [];\n    this.items = this.items || [];\n    this.options = this.options || [];\n\n    if (Array.isArray(data)) {\n      // We have an array on items\n      util.each(data, function(i, obj) {\n        this.add(obj, checkDuplicate);\n      }, this);\n    }\n      // User passed a single object to the method\n    // or Selectr passed an object from an array\n    else if (\"[object Object]\" === Object.prototype.toString.call(data)) {\n\n      if (checkDuplicate) {\n        var dupe = false;\n\n        util.each(this.options, function(i, option) {\n          if (option.value.toLowerCase() === data.value.toLowerCase()) {\n            dupe = true;\n          }\n        });\n\n        if (dupe) {\n          return false;\n        }\n      }\n\n      var option = util.createElement('option', data);\n\n      this.data.push(data);\n\n      // fix for native iOS dropdown otherwise the native dropdown will be empty\n      if (this.mobileDevice) {\n        this.el.add(option);\n      }\n\n      // Add the new option to the list\n      this.options.push(option);\n\n      // Add the index for later use\n      option.idx = this.options.length > 0 ? this.options.length - 1 : 0;\n\n      // Create a new item\n      createItem.call(this, option);\n\n      // Select the item if required\n      if (data.selected) {\n        this.select(option.idx);\n      }\n\n      // We may have had an empty select so update\n      // the placeholder to reflect the changes.\n      this.setPlaceholder();\n\n      return option;\n    }\n\n    // Recount the pages\n    if (this.config.pagination) {\n      this.paginate();\n    }\n\n    return true;\n  }\n};\n\n/**\n * Remove an option or options\n * @param  {Mixed} o Array, integer (index) or string (value)\n * @return {Void}\n */\nSelectr.prototype.remove = function(o) {\n  var options = [];\n  if (Array.isArray(o)) {\n    util.each(o, function(i, opt) {\n      if (util.isInt(opt)) {\n        options.push(this.getOptionByIndex(opt));\n      } else if (typeof opt === \"string\") {\n        options.push(this.getOptionByValue(opt));\n      }\n    }, this);\n\n  } else if (util.isInt(o)) {\n    options.push(this.getOptionByIndex(o));\n  } else if (typeof o === \"string\") {\n    options.push(this.getOptionByValue(o));\n  }\n\n  if (options.length) {\n    var index;\n    util.each(options, function(i, option) {\n      index = option.idx;\n\n      // Remove the HTMLOptionElement\n      this.el.remove(option);\n\n      // Remove the reference from the option array\n      this.options.splice(index, 1);\n\n      // If the item has a parentNode (group element) it needs to be removed\n      // otherwise the render function will still append it to the dropdown\n      var parentNode = this.items[index].parentNode;\n\n      if (parentNode) {\n        parentNode.removeChild(this.items[index]);\n      }\n\n      // Remove reference from the items array\n      this.items.splice(index, 1);\n\n      // Reset the indexes\n      util.each(this.options, function(i, opt) {\n        opt.idx = i;\n        this.items[i].idx = i;\n      }, this);\n    }, this);\n\n    // We may have had an empty select now so update\n    // the placeholder to reflect the changes.\n    this.setPlaceholder();\n\n    // Recount the pages\n    if (this.config.pagination) {\n      this.paginate();\n    }\n  }\n};\n\n/**\n * Remove all options\n */\nSelectr.prototype.removeAll = function() {\n\n  // Clear any selected options\n  this.clear(true);\n\n  // Remove the HTMLOptionElements\n  util.each(this.el.options, function(i, option) {\n    this.el.remove(option);\n  }, this);\n\n  // Empty the dropdown\n  util.truncate(this.tree);\n\n  // Reset variables\n  this.items = [];\n  this.options = [];\n  this.data = [];\n\n  this.navIndex = 0;\n\n  if (this.requiresPagination) {\n    this.requiresPagination = false;\n\n    this.pageIndex = 1;\n    this.pages = [];\n  }\n\n  // Update the placeholder\n  this.setPlaceholder();\n};\n\n/**\n * Perform a search\n * @param {string}|{null} query The query string (taken from user input if null)\n * @param {boolean} anchor Anchor search to beginning of strings (defaults to false)?\n * @return {Array} Search results, as an array of {text, value} objects\n */\nSelectr.prototype.search = function( string, anchor ) {\n  if ( this.navigating ) {\n    return;\n  }\n\n  // we're only going to alter the DOM for \"live\" searches\n  var live = false;\n  if ( ! string ) {\n    string = this.input.value;\n    live = true;\n\n    // Remove message and clear dropdown\n    this.removeMessage();\n    util.truncate(this.tree);\n  }\n  var results = [];\n  var f = document.createDocumentFragment();\n\n  string = string.trim().toLowerCase();\n\n  if ( string.length > 0 ) {\n    var compare = anchor ? util.startsWith : util.includes;\n\n    util.each( this.options, function ( i, option ) {\n      var item = this.items[option.idx];\n      var matches = compare( option.textContent.trim().toLowerCase(), string );\n\n      if ( matches && !option.disabled ) {\n        results.push( { text: option.textContent, value: option.value } );\n        if ( live ) {\n          appendItem( item, f, this.customOption );\n          util.removeClass( item, \"excluded\" );\n\n          // Underline the matching results\n          if ( !this.customOption ) {\n            match( string, option );\n          }\n        }\n      } else if ( live ) {\n        util.addClass( item, \"excluded\" );\n      }\n    }, this);\n\n    if ( live ) {\n      // Append results\n      if ( !f.childElementCount ) {\n        if ( !this.config.taggable ) {\n          this.noResults = true;\n          this.setMessage( this.config.messages.noResults );\n        }\n      } else {\n        // Highlight top result (@binary-koan #26)\n        var prevEl = this.items[this.navIndex];\n        var firstEl = f.querySelector(\".selectr-option:not(.excluded)\");\n        this.noResults = false;\n\n        util.removeClass( prevEl, \"active\" );\n        this.navIndex = firstEl.idx;\n        util.addClass( firstEl, \"active\" );\n      }\n\n      this.tree.appendChild( f );\n    }\n  } else {\n    render.call(this);\n  }\n\n  return results;\n};\n\n/**\n * Toggle the dropdown\n * @return {void}\n */\nSelectr.prototype.toggle = function() {\n  if (!this.disabled) {\n    if (this.opened) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n};\n\n/**\n * Open the dropdown\n * @return {void}\n */\nSelectr.prototype.open = function() {\n\n  var that = this;\n\n  if (!this.options.length) {\n    return false;\n  }\n\n  if (!this.opened) {\n    this.emit(\"selectr.open\");\n  }\n\n  this.opened = true;\n\n  if (this.mobileDevice || this.config.nativeDropdown) {\n    util.addClass(this.container, \"native-open\");\n\n    if (this.config.data) {\n      // Dump the options into the select\n      // otherwise the native dropdown will be empty\n      util.each(this.options, function(i, option) {\n        this.el.add(option);\n      }, this);\n    }\n\n    return;\n  }\n\n  util.addClass(this.container, \"open\");\n\n  render.call(this);\n\n  this.invert();\n\n  this.tree.scrollTop = 0;\n\n  util.removeClass(this.container, \"notice\");\n\n  this.selected.setAttribute(\"aria-expanded\", true);\n\n  this.tree.setAttribute(\"aria-hidden\", false);\n  this.tree.setAttribute(\"aria-expanded\", true);\n\n  if (this.config.searchable && !this.config.taggable) {\n    setTimeout(function() {\n      that.input.focus();\n      // Allow tab focus\n      that.input.tabIndex = 0;\n    }, 10);\n  }\n};\n\n/**\n * Close the dropdown\n * @return {void}\n */\nSelectr.prototype.close = function() {\n\n  if (this.opened) {\n    this.emit(\"selectr.close\");\n  }\n\n  this.opened = false;\n  this.navigating = false;\n\n  if (this.mobileDevice || this.config.nativeDropdown) {\n    util.removeClass(this.container, \"native-open\");\n    return;\n  }\n\n  var notice = util.hasClass(this.container, \"notice\");\n\n  if (this.config.searchable && !notice) {\n    this.input.blur();\n    // Disable tab focus\n    this.input.tabIndex = -1;\n    this.searching = false;\n  }\n\n  if (notice) {\n    util.removeClass(this.container, \"notice\");\n    this.notice.textContent = \"\";\n  }\n\n  util.removeClass(this.container, \"open\");\n  util.removeClass(this.container, \"native-open\");\n\n  this.selected.setAttribute(\"aria-expanded\", false);\n\n  this.tree.setAttribute(\"aria-hidden\", true);\n  this.tree.setAttribute(\"aria-expanded\", false);\n\n  util.truncate(this.tree);\n  clearSearch.call(this);\n};\n\n\n/**\n * Enable the element\n * @return {void}\n */\nSelectr.prototype.enable = function() {\n  this.disabled = false;\n  this.el.disabled = false;\n\n  this.selected.tabIndex = this.originalIndex;\n\n  if (this.el.multiple) {\n    util.each(this.tags, function(i, t) {\n      t.lastElementChild.tabIndex = 0;\n    });\n  }\n\n  util.removeClass(this.container, \"selectr-disabled\");\n};\n\n/**\n * Disable the element\n * @param  {boolean} container Disable the container only (allow value submit with form)\n * @return {void}\n */\nSelectr.prototype.disable = function(container) {\n  if (!container) {\n    this.el.disabled = true;\n  }\n\n  this.selected.tabIndex = -1;\n\n  if (this.el.multiple) {\n    util.each(this.tags, function(i, t) {\n      t.lastElementChild.tabIndex = -1;\n    });\n  }\n\n  this.disabled = true;\n  util.addClass(this.container, \"selectr-disabled\");\n};\n\n\n/**\n * Reset to initial state\n * @return {void}\n */\nSelectr.prototype.reset = function() {\n  if (!this.disabled) {\n    this.clear();\n\n    this.setSelected(true);\n\n    util.each(this.defaultSelected, function(i, idx) {\n      this.select(idx);\n    }, this);\n\n    this.emit(\"selectr.reset\");\n  }\n};\n\n/**\n * Clear all selections\n * @return {void}\n */\nSelectr.prototype.clear = function(force, isClearLast) {\n\n  if (this.el.multiple) {\n    // Loop over the selectedIndexes so we don't have to loop over all the options\n    // which can be costly if there are a lot of them\n\n    if (this.selectedIndexes.length) {\n      // Copy the array or we'll get an error\n      var indexes = this.selectedIndexes.slice();\n\n      if (isClearLast) {\n        this.deselect(indexes.slice(-1)[0]);\n      } else {\n        util.each(indexes, function(i, idx) {\n          this.deselect(idx);\n        }, this);\n      }\n    }\n  } else {\n    if (this.selectedIndex > -1) {\n      this.deselect(this.selectedIndex, force);\n    }\n  }\n\n  this.emit(\"selectr.clear\");\n};\n\n/**\n * Return serialised data\n * @param  {boolean} toJson\n * @return {mixed} Returns either an object or JSON string\n */\nSelectr.prototype.serialise = function(toJson) {\n  var data = [];\n  util.each(this.options, function(i, option) {\n    var obj = {\n      value: option.value,\n      text: option.textContent\n    };\n\n    if (option.selected) {\n      obj.selected = true;\n    }\n    if (option.disabled) {\n      obj.disabled = true;\n    }\n    data[i] = obj;\n  });\n\n  return toJson ? JSON.stringify(data) : data;\n};\n\n/**\n * Localised version of serialise() method\n */\nSelectr.prototype.serialize = function(toJson) {\n  return this.serialise(toJson);\n};\n\n/**\n * Sets the placeholder\n * @param {String} placeholder\n */\nSelectr.prototype.setPlaceholder = function(placeholder) {\n  // Set the placeholder\n  placeholder = placeholder || this.config.placeholder || this.el.getAttribute(\"placeholder\");\n\n  if (!this.options.length) {\n    placeholder = this.config.messages.noOptions;\n  }\n\n  this.placeEl.innerHTML = placeholder;\n};\n\n/**\n * Paginate the option list\n * @return {Array}\n */\nSelectr.prototype.paginate = function() {\n  if (this.items.length) {\n    var that = this;\n\n    this.pages = this.items.map(function(v, i) {\n      return i % that.config.pagination === 0 ? that.items.slice(i, i + that.config.pagination) : null;\n    }).filter(function(pages) {\n      return pages;\n    });\n\n    return this.pages;\n  }\n};\n\n/**\n * Display a message\n * @param  {String} message The message\n */\nSelectr.prototype.setMessage = function(message, close) {\n  if (close) {\n    this.close();\n  }\n  util.addClass(this.container, \"notice\");\n  this.notice.textContent = message;\n};\n\n/**\n * Dismiss the current message\n */\nSelectr.prototype.removeMessage = function() {\n  util.removeClass(this.container, \"notice\");\n  this.notice.innerHTML = \"\";\n};\n\n/**\n * Keep the dropdown within the window\n * @return {void}\n */\nSelectr.prototype.invert = function() {\n  var rt = util.rect(this.selected),\n    oh = this.tree.parentNode.offsetHeight,\n    wh = window.innerHeight,\n    doInvert = rt.top + rt.height + oh > wh;\n\n  if (doInvert) {\n    util.addClass(this.container, \"inverted\");\n    this.isInverted = true;\n  } else {\n    util.removeClass(this.container, \"inverted\");\n    this.isInverted = false;\n  }\n\n  this.optsRect = util.rect(this.tree);\n};\n\n/**\n * Get an option via it's index\n * @param  {Integer} index The index of the HTMLOptionElement required\n * @return {HTMLOptionElement}\n */\nSelectr.prototype.getOptionByIndex = function(index) {\n  return this.options[index];\n};\n\n/**\n * Get an option via it's value\n * @param  {String} value The value of the HTMLOptionElement required\n * @return {HTMLOptionElement}\n */\nSelectr.prototype.getOptionByValue = function(value) {\n  var option = false;\n\n  for (var i = 0, l = this.options.length; i < l; i++) {\n    if (this.options[i].value.trim() === value.toString().trim()) {\n      option = this.options[i];\n      break;\n    }\n  }\n\n  return option;\n};\n\nmodule.exports = Selectr;\n"
  },
  {
    "path": "src/js/assets/selectr/selectr.scss",
    "content": "@use \"../../../scss/jsoneditor/variables\";\n\n/*!\n * Selectr 2.4.13\n * http://mobius.ovh/docs/selectr\n *\n * Released under the MIT license\n */\n.selectr-container {\n    position: relative;\n}\n\n.selectr-container li {\n    list-style: none;\n}\n\n.selectr-hidden {\n    position: absolute;\n    overflow: hidden;\n    clip: rect(0px, 0px, 0px, 0px);\n    width: 1px;\n    height: 1px;\n    margin: -1px;\n    padding: 0;\n    border: 0 none;\n}\n\n.selectr-visible {\n    position: absolute;\n    left: 0;\n    top: 0;\n    width: 100%;\n    height: 100%;\n    opacity: 0;\n    z-index: 11;\n}\n\n.selectr-desktop.multiple .selectr-visible {\n    display: none;\n}\n\n.selectr-desktop.multiple.native-open .selectr-visible {\n    top: 100%;\n    min-height: 200px !important;\n    height: auto;\n    opacity: 1;\n    display: block;\n}\n\n.selectr-container.multiple.selectr-mobile .selectr-selected {\n    z-index: 0;\n}\n\n.selectr-selected {\n    position: relative;\n    z-index: 1;\n    box-sizing: border-box;\n    width: 100%;\n    padding: 7px 28px 7px 14px;\n    cursor: pointer;\n    border: 1px solid  variables.$jse-grey;\n    border-radius: 3px;\n    background-color: variables.$jse-white;\n}\n\n.selectr-selected::before {\n    position: absolute;\n    top: 50%;\n    right: 10px;\n    width: 0;\n    height: 0;\n    content: '';\n    -o-transform: rotate(0deg) translate3d(0px, -50%, 0px);\n    -ms-transform: rotate(0deg) translate3d(0px, -50%, 0px);\n    -moz-transform: rotate(0deg) translate3d(0px, -50%, 0px);\n    -webkit-transform: rotate(0deg) translate3d(0px, -50%, 0px);\n    transform: rotate(0deg) translate3d(0px, -50%, 0px);\n    border-width: 4px 4px 0 4px;\n    border-style: solid;\n    border-color: #6c7a86 transparent transparent;\n}\n\n.selectr-container.open .selectr-selected::before,\n.selectr-container.native-open .selectr-selected::before {\n    border-width: 0 4px 4px 4px;\n    border-style: solid;\n    border-color: transparent transparent #6c7a86;\n}\n\n.selectr-label {\n    display: none;\n    overflow: hidden;\n    width: 100%;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n}\n\n.selectr-placeholder {\n    color: #6c7a86;\n}\n\n.selectr-tags {\n    margin: 0;\n    padding: 0;\n    white-space: normal;\n}\n\n.has-selected .selectr-tags {\n    margin: 0 0 -2px;\n}\n\n.selectr-tag {\n    list-style: none;\n    position: relative;\n    float: left;\n    padding: 2px 25px 2px 8px;\n    margin: 0 2px 2px 0;\n    cursor: default;\n    color: variables.$jse-white;\n    border: medium none;\n    border-radius: 10px;\n    background: #acb7bf none repeat scroll 0 0;\n}\n\n.selectr-container.multiple.has-selected .selectr-selected {\n    padding: 5px 28px 5px 5px;\n}\n\n.selectr-options-container {\n    position: absolute;\n    z-index: 10000;\n    top: calc(100% - 1px);\n    left: 0;\n    display: none;\n    box-sizing: border-box;\n    width: 100%;\n    border-width: 0 1px 1px;\n    border-style: solid;\n    border-color: transparent variables.$jse-grey variables.$jse-grey;\n    border-radius: 0 0 3px 3px;\n    background-color: variables.$jse-white;\n}\n\n.selectr-container.open .selectr-options-container {\n    display: block;\n}\n\n.selectr-input-container {\n    position: relative;\n    display: none;\n}\n\n.selectr-clear,\n.selectr-input-clear,\n.selectr-tag-remove {\n    position: absolute;\n    top: 50%;\n    right: 22px;\n    width: 20px;\n    height: 20px;\n    padding: 0;\n    cursor: pointer;\n    -o-transform: translate3d(0px, -50%, 0px);\n    -ms-transform: translate3d(0px, -50%, 0px);\n    -moz-transform: translate3d(0px, -50%, 0px);\n    -webkit-transform: translate3d(0px, -50%, 0px);\n    transform: translate3d(0px, -50%, 0px);\n    border: medium none;\n    background-color: transparent;\n    z-index: 11;\n}\n\n.selectr-clear,\n.selectr-input-clear {\n    display: none;\n}\n\n.selectr-container.has-selected .selectr-clear,\n.selectr-input-container.active .selectr-input-clear {\n    display: block;\n}\n\n.selectr-selected .selectr-tag-remove {\n    right: 2px;\n}\n\n.selectr-clear::before,\n.selectr-clear::after,\n.selectr-input-clear::before,\n.selectr-input-clear::after,\n.selectr-tag-remove::before,\n.selectr-tag-remove::after {\n    position: absolute;\n    top: 5px;\n    left: 9px;\n    width: 2px;\n    height: 10px;\n    content: ' ';\n    background-color: #6c7a86;\n}\n\n.selectr-tag-remove::before,\n.selectr-tag-remove::after {\n    top: 4px;\n    width: 3px;\n    height: 12px;\n    background-color: variables.$jse-white;\n}\n\n.selectr-clear:before,\n.selectr-input-clear::before,\n.selectr-tag-remove::before {\n    -o-transform: rotate(45deg);\n    -ms-transform: rotate(45deg);\n    -moz-transform: rotate(45deg);\n    -webkit-transform: rotate(45deg);\n    transform: rotate(45deg);\n}\n\n.selectr-clear:after,\n.selectr-input-clear::after,\n.selectr-tag-remove::after {\n    -o-transform: rotate(-45deg);\n    -ms-transform: rotate(-45deg);\n    -moz-transform: rotate(-45deg);\n    -webkit-transform: rotate(-45deg);\n    transform: rotate(-45deg);\n}\n\n.selectr-input-container.active,\n.selectr-input-container.active .selectr-clear {\n    display: block;\n}\n\n.selectr-input {\n    top: 5px;\n    left: 5px;\n    box-sizing: border-box;\n    width: calc(100% - 30px);\n    margin: 10px 15px;\n    padding: 7px 30px 7px 9px;\n    border: 1px solid  variables.$jse-grey;\n    border-radius: 3px;\n}\n\n.selectr-notice {\n    display: none;\n    box-sizing: border-box;\n    width: 100%;\n    padding: 8px 16px;\n    border-top: 1px solid  variables.$jse-grey;\n    border-radius: 0 0 3px 3px;\n    background-color: variables.$jse-white;\n}\n\n.selectr-container.notice .selectr-notice {\n    display: block;\n}\n\n.selectr-container.notice .selectr-selected {\n    border-radius: 3px 3px 0 0;\n}\n\n.selectr-options {\n    position: relative;\n    top: calc(100% + 2px);\n    display: none;\n    overflow-x: auto;\n    overflow-y: scroll;\n    max-height: 200px;\n    margin: 0;\n    padding: 0;\n}\n\n.selectr-container.open .selectr-options,\n.selectr-container.open .selectr-input-container,\n.selectr-container.notice .selectr-options-container {\n    display: block;\n}\n\n.selectr-option {\n    position: relative;\n    display: block;\n    padding: 5px 20px;\n    list-style: outside none none;\n    cursor: pointer;\n    font-weight: normal;\n}\n\n.selectr-options.optgroups > .selectr-option {\n    padding-left: 25px;\n}\n\n.selectr-optgroup {\n    font-weight: bold;\n    padding: 0;\n}\n\n.selectr-optgroup--label {\n    font-weight: bold;\n    margin-top: 10px;\n    padding: 5px 15px;\n}\n\n.selectr-match {\n    text-decoration: underline;\n}\n\n.selectr-option.selected {\n    background-color: #ddd;\n}\n\n.selectr-option.active {\n    color: variables.$jse-white;\n    background-color: #5897fb;\n}\n\n.selectr-option.disabled {\n    opacity: 0.4;\n}\n\n.selectr-option.excluded {\n    display: none;\n}\n\n.selectr-container.open .selectr-selected {\n    border-color:  variables.$jse-grey  variables.$jse-grey transparent  variables.$jse-grey;\n    border-radius: 3px 3px 0 0;\n}\n\n.selectr-container.open .selectr-selected::after {\n    -o-transform: rotate(180deg) translate3d(0px, 50%, 0px);\n    -ms-transform: rotate(180deg) translate3d(0px, 50%, 0px);\n    -moz-transform: rotate(180deg) translate3d(0px, 50%, 0px);\n    -webkit-transform: rotate(180deg) translate3d(0px, 50%, 0px);\n    transform: rotate(180deg) translate3d(0px, 50%, 0px);\n}\n\n.selectr-disabled {\n    opacity: .6;\n}\n\n.selectr-empty,\n.has-selected .selectr-placeholder {\n    display: none;\n}\n\n.has-selected .selectr-label {\n    display: block;\n}\n\n/* TAGGABLE */\n.taggable .selectr-selected {\n    padding: 4px 28px 4px 4px;\n}\n\n.taggable .selectr-selected::after {\n    display: table;\n    content: \" \";\n    clear: both;\n}\n\n.taggable .selectr-label {\n    width: auto;\n}\n\n.taggable .selectr-tags {\n    float: left;\n    display: block;\n}\n\n.taggable .selectr-placeholder {\n    display: none;\n}\n\n.input-tag {\n    float: left;\n    min-width: 90px;\n    width: auto;\n}\n\n.selectr-tag-input {\n    border: medium none;\n    padding: 3px 10px;\n    width: 100%;\n    font-family: inherit;\n    font-weight: inherit;\n    font-size: inherit;\n}\n\n.selectr-input-container.loading::after {\n    position: absolute;\n    top: 50%;\n    right: 20px;\n    width: 20px;\n    height: 20px;\n    content: '';\n    -o-transform: translate3d(0px, -50%, 0px);\n    -ms-transform: translate3d(0px, -50%, 0px);\n    -moz-transform: translate3d(0px, -50%, 0px);\n    -webkit-transform: translate3d(0px, -50%, 0px);\n    transform: translate3d(0px, -50%, 0px);\n\n    -o-transform-origin: 50% 0 0;\n    -ms-transform-origin: 50% 0 0;\n    -moz-transform-origin: 50% 0 0;\n    -webkit-transform-origin: 50% 0 0;\n    transform-origin: 50% 0 0;\n\n    -moz-animation: 500ms linear 0s normal forwards infinite running selectr-spin;\n    -webkit-animation: 500ms linear 0s normal forwards infinite running selectr-spin;\n    animation: 500ms linear 0s normal forwards infinite running selectr-spin;\n    border-width: 3px;\n    border-style: solid;\n    border-color: #aaa #ddd #ddd;\n    border-radius: 50%;\n}\n\n@-webkit-keyframes selectr-spin {\n    0% {\n        -webkit-transform: rotate(0deg) translate3d(0px, -50%, 0px);\n        transform: rotate(0deg) translate3d(0px, -50%, 0px);\n    }\n    100% {\n        -webkit-transform: rotate(360deg) translate3d(0px, -50%, 0px);\n        transform: rotate(360deg) translate3d(0px, -50%, 0px);\n    }\n}\n@keyframes selectr-spin {\n    0% {\n        -webkit-transform: rotate(0deg) translate3d(0px, -50%, 0px);\n        transform: rotate(0deg) translate3d(0px, -50%, 0px);\n    }\n    100% {\n        -webkit-transform: rotate(360deg) translate3d(0px, -50%, 0px);\n        transform: rotate(360deg) translate3d(0px, -50%, 0px);\n    }\n}\n.selectr-container.open.inverted .selectr-selected {\n    border-color: transparent  variables.$jse-grey  variables.$jse-grey;\n    border-radius: 0 0 3px 3px;\n}\n\n.selectr-container.inverted .selectr-options-container {\n    border-width: 1px 1px 0;\n    border-color:  variables.$jse-grey  variables.$jse-grey transparent;\n    border-radius: 3px 3px 0 0;\n    background-color: variables.$jse-white;\n}\n\n.selectr-container.inverted .selectr-options-container {\n    top: auto;\n    bottom: calc(100% - 1px);\n}\n\n.selectr-container ::-webkit-input-placeholder {\n    color: #6c7a86;\n    opacity: 1;\n}\n\n.selectr-container ::-moz-placeholder {\n    color: #6c7a86;\n    opacity: 1;\n}\n\n.selectr-container :-ms-input-placeholder {\n    color: #6c7a86;\n    opacity: 1;\n}\n\n.selectr-container ::placeholder {\n    color: #6c7a86;\n    opacity: 1;\n}\n"
  },
  {
    "path": "src/js/autocomplete.js",
    "content": "'use strict'\n\n// Helper functions for handling both string and object option formats\nconst getOptionText = (option) => {\n  if (option == null) return ''\n  return typeof option === 'string' ? option : (option.text || '')\n}\n\nconst getOptionValue = (option) => {\n  if (option == null) return ''\n  return typeof option === 'string' ? option : (option.value || option.text || '')\n}\n\nconst isObject = (value) => {\n  return value !== null && typeof value === 'object'\n}\n\nconst normalizeCase = (text = '', config) => {\n  return config.caseSensitive ? text : text.toLowerCase()\n}\n\nconst ensureStringOption = (option) => {\n  // Keep objects as-is, but convert primitives to strings to prevent breaking changes\n  return isObject(option) ? option : String(option)\n}\n\nconst getHighlightedTextParts = (token, row, config) => {\n  const rowText = getOptionText(row)\n  const rowValue = getOptionValue(row)\n  const tokenLower = normalizeCase(token, config)\n  const rowTextLower = normalizeCase(rowText, config)\n  const rowValueLower = normalizeCase(rowValue, config)\n\n  // Find the best match position for highlighting\n  let matchIndex = -1\n  const matchLength = token.length\n  let displayText = rowText\n\n  // Prefer text matches over value matches for display\n  if (rowTextLower.indexOf(tokenLower) > -1) {\n    matchIndex = rowTextLower.indexOf(tokenLower)\n    displayText = rowText\n  } else if (rowValueLower.indexOf(tokenLower) > -1) {\n    matchIndex = rowValueLower.indexOf(tokenLower)\n    displayText = rowText\n  }\n\n  if (matchIndex > -1) {\n    return {\n      beforeText: displayText.substring(0, matchIndex),\n      matchText: displayText.substring(matchIndex, matchIndex + matchLength),\n      afterText: displayText.substring(matchIndex + matchLength),\n      displayText\n    }\n  } else {\n    // No match found, return the display text as-is\n    return {\n      beforeText: '',\n      matchText: '',\n      afterText: displayText,\n      displayText\n    }\n  }\n}\n\n// Helper function to reduce duplication in filter functions\nconst matchesFilter = (token, match, config, matchFunction) => {\n  const normalizedToken = normalizeCase(token, config)\n\n  if (isObject(match)) {\n    // Check both text and value properties for object matches\n    const matchText = getOptionText(match)\n    const matchValue = getOptionValue(match)\n    const normalizedText = normalizeCase(matchText, config)\n    const normalizedValue = normalizeCase(matchValue, config)\n\n    return matchFunction(normalizedText, normalizedToken) ||\n           matchFunction(normalizedValue, normalizedToken)\n  } else {\n    // Handle simple string matches\n    const normalizedMatch = normalizeCase(String(match), config)\n    return matchFunction(normalizedMatch, normalizedToken)\n  }\n}\n\nconst defaultFilterFunction = {\n  start: function (token, match, config) {\n    return matchesFilter(token, match, config, (normalizedText, normalizedToken) =>\n      normalizedText.indexOf(normalizedToken) === 0\n    )\n  },\n  contain: function (token, match, config) {\n    return matchesFilter(token, match, config, (normalizedText, normalizedToken) =>\n      normalizedText.indexOf(normalizedToken) > -1\n    )\n  }\n}\n\nexport function autocomplete (config) {\n  config = config || {}\n  config.filter = config.filter || 'start'\n  config.trigger = config.trigger || 'keydown'\n  config.confirmKeys = config.confirmKeys || [39, 35, 9] // right, end, tab\n  config.caseSensitive = config.caseSensitive || false // autocomplete case sensitive\n\n  let fontSize = ''\n  let fontFamily = ''\n\n  const wrapper = document.createElement('div')\n  wrapper.style.position = 'relative'\n  wrapper.style.outline = '0'\n  wrapper.style.border = '0'\n  wrapper.style.margin = '0'\n  wrapper.style.padding = '0'\n\n  const dropDown = document.createElement('div')\n  dropDown.className = 'autocomplete dropdown'\n  dropDown.style.position = 'absolute'\n  dropDown.style.visibility = 'hidden'\n\n  let spacer\n  let leftSide // <-- it will contain the leftSide part of the textfield (the bit that was already autocompleted)\n  const createDropDownController = (elem, rs) => {\n    let rows = []\n    let ix = 0\n    let oldIndex = -1\n\n    // TODO: move this styling in JS to SCSS\n    const onMouseOver = function () { this.style.backgroundColor = '#ddd' }\n    const onMouseOut = function () { this.style.backgroundColor = '' }\n    const onMouseDown = function () { p.hide(); p.onmouseselection(this.__hint, p.rs) }\n\n    const p = {\n      rs,\n      hide: function () {\n        elem.style.visibility = 'hidden'\n        // rs.hideDropDown();\n      },\n      refresh: function (token, array) {\n        elem.style.visibility = 'hidden'\n        ix = 0\n        elem.textContent = ''\n        const vph = (window.innerHeight || document.documentElement.clientHeight)\n        const rect = elem.parentNode.getBoundingClientRect()\n        const distanceToTop = rect.top - 6 // heuristic give 6px\n        const distanceToBottom = vph - rect.bottom - 6 // distance from the browser border.\n\n        rows = []\n        const filterFn = typeof config.filter === 'function' ? config.filter : defaultFilterFunction[config.filter]\n\n        const filtered = !filterFn ? [] : array.filter(match => filterFn(token, match, config))\n\n        rows = filtered.map(row => {\n          const divRow = document.createElement('div')\n          divRow.className = 'item'\n          // divRow.style.color = config.color;\n          divRow.onmouseover = onMouseOver\n          divRow.onmouseout = onMouseOut\n          divRow.onmousedown = onMouseDown\n          divRow.__hint = row\n          divRow.textContent = ''\n\n          const { beforeText, matchText, afterText } = getHighlightedTextParts(token, row, config)\n\n          // Add text before match (if any)\n          if (beforeText) {\n            divRow.appendChild(document.createTextNode(beforeText))\n          }\n\n          // Add highlighted match (if any)\n          if (matchText) {\n            const b = document.createElement('b')\n            b.appendChild(document.createTextNode(matchText))\n            divRow.appendChild(b)\n          }\n\n          // Add text after match\n          if (afterText) {\n            divRow.appendChild(document.createTextNode(afterText))\n          }\n          elem.appendChild(divRow)\n          return divRow\n        })\n\n        if (rows.length === 0) {\n          return // nothing to show.\n        }\n\n        const firstRowValue = getOptionValue(rows[0].__hint)\n        const hasOptionText = isObject(rows[0].__hint) && 'text' in rows[0].__hint\n        if (rows.length === 1 && normalizeCase(token, config) === normalizeCase(firstRowValue, config) && !hasOptionText) {\n          return // do not show the dropDown if it has only one element which matches what we have just displayed and has no option.text property.\n        }\n        p.highlight(0)\n\n        if (distanceToTop > distanceToBottom * 3) { // Heuristic (only when the distance to the to top is 4 times more than distance to the bottom\n          elem.style.maxHeight = distanceToTop + 'px' // we display the dropDown on the top of the input text\n          elem.style.top = ''\n          elem.style.bottom = '100%'\n        } else {\n          elem.style.top = '100%'\n          elem.style.bottom = ''\n          elem.style.maxHeight = distanceToBottom + 'px'\n        }\n        elem.style.visibility = 'visible'\n      },\n      highlight: function (index) {\n        if (oldIndex !== -1 && rows[oldIndex]) {\n          rows[oldIndex].className = 'item'\n        }\n        rows[index].className = 'item hover'\n        oldIndex = index\n      },\n      move: function (step) { // moves the selection either up or down (unless it's not possible) step is either +1 or -1.\n        if (elem.style.visibility === 'hidden') return '' // nothing to move if there is no dropDown. (this happens if the user hits escape and then down or up)\n        if (ix + step === -1 || ix + step === rows.length) return rows[ix].__hint // NO CIRCULAR SCROLLING.\n        ix += step\n        p.highlight(ix)\n        return rows[ix].__hint// txtShadow.value = uRows[uIndex].__hint ;\n      },\n      onmouseselection: function () { } // it will be overwritten.\n    }\n    return p\n  }\n\n  function setEndOfContenteditable (contentEditableElement) {\n    let range, selection\n    if (document.createRange) {\n      // Firefox, Chrome, Opera, Safari, IE 9+\n      range = document.createRange()// Create a range (a range is a like the selection but invisible)\n      range.selectNodeContents(contentEditableElement)// Select the entire contents of the element with the range\n      range.collapse(false)// collapse the range to the end point. false means collapse to end rather than the start\n      selection = window.getSelection()// get the selection object (allows you to change selection)\n      selection.removeAllRanges()// remove any selections already made\n      selection.addRange(range)// make the range you have just created the visible selection\n    } else if (document.selection) {\n      // IE 8 and lower\n      range = document.body.createTextRange()// Create a range (a range is a like the selection but invisible)\n      range.moveToElementText(contentEditableElement)// Select the entire contents of the element with the range\n      range.collapse(false)// collapse the range to the end point. false means collapse to end rather than the start\n      range.select()// Select the range (make it the visible selection\n    }\n  }\n\n  function calculateWidthForText (text) {\n    if (spacer === undefined) { // on first call only.\n      spacer = document.createElement('span')\n      spacer.style.visibility = 'hidden'\n      spacer.style.position = 'fixed'\n      spacer.style.outline = '0'\n      spacer.style.margin = '0'\n      spacer.style.padding = '0'\n      spacer.style.border = '0'\n      spacer.style.left = '0'\n      spacer.style.whiteSpace = 'pre'\n      spacer.style.fontSize = fontSize\n      spacer.style.fontFamily = fontFamily\n      spacer.style.fontWeight = 'normal'\n      document.body.appendChild(spacer)\n    }\n\n    spacer.textContent = text\n    return spacer.getBoundingClientRect().right\n  }\n\n  const rs = {\n    onArrowDown: function () { }, // defaults to no action.\n    onArrowUp: function () { }, // defaults to no action.\n    onEnter: function () { }, // defaults to no action.\n    onTab: function () { }, // defaults to no action.\n    startFrom: 0,\n    options: [],\n    element: null,\n    elementHint: null,\n    elementStyle: null,\n    wrapper, // Only to allow  easy access to the HTML elements to the final user (possibly for minor customizations)\n    show: function (element, startPos, options) {\n      this.startFrom = startPos\n      this.wrapper.remove()\n      if (this.elementHint) {\n        this.elementHint.remove()\n        this.elementHint = null\n      }\n\n      if (fontSize === '') {\n        fontSize = window.getComputedStyle(element).getPropertyValue('font-size')\n      }\n      if (fontFamily === '') {\n        fontFamily = window.getComputedStyle(element).getPropertyValue('font-family')\n      }\n\n      dropDown.style.marginLeft = '0'\n      dropDown.style.marginTop = element.getBoundingClientRect().height + 'px'\n      this.options = options.map(ensureStringOption)\n\n      if (this.element !== element) {\n        this.element = element\n        this.elementStyle = {\n          zIndex: this.element.style.zIndex,\n          position: this.element.style.position,\n          backgroundColor: this.element.style.backgroundColor,\n          borderColor: this.element.style.borderColor\n        }\n      }\n\n      this.element.style.zIndex = 3\n      this.element.style.position = 'relative'\n      this.element.style.backgroundColor = 'transparent'\n      this.element.style.borderColor = 'transparent'\n\n      this.elementHint = element.cloneNode()\n      this.elementHint.className = 'autocomplete hint'\n      this.elementHint.style.zIndex = 2\n      this.elementHint.style.position = 'absolute'\n      this.elementHint.onfocus = () => { this.element.focus() }\n\n      if (this.element.addEventListener) {\n        this.element.removeEventListener('keydown', keyDownHandler)\n        this.element.addEventListener('keydown', keyDownHandler, false)\n        this.element.removeEventListener('blur', onBlurHandler)\n        this.element.addEventListener('blur', onBlurHandler, false)\n      }\n\n      wrapper.appendChild(this.elementHint)\n      wrapper.appendChild(dropDown)\n      element.parentElement.appendChild(wrapper)\n\n      this.repaint(element)\n    },\n    setText: function (text) {\n      this.element.innerText = text\n    },\n    getText: function () {\n      return this.element.innerText\n    },\n    hideDropDown: function () {\n      this.wrapper.remove()\n      if (this.elementHint) {\n        this.elementHint.remove()\n        this.elementHint = null\n        dropDownController.hide()\n        this.element.style.zIndex = this.elementStyle.zIndex\n        this.element.style.position = this.elementStyle.position\n        this.element.style.backgroundColor = this.elementStyle.backgroundColor\n        this.element.style.borderColor = this.elementStyle.borderColor\n      }\n    },\n    repaint: function (element) {\n      let text = element.innerText\n      text = text.replace('\\n', '')\n\n      const optionsLength = this.options.length\n\n      // breaking text in leftSide and token.\n\n      const token = text.substring(this.startFrom)\n      leftSide = text.substring(0, this.startFrom)\n\n      // Use the same filter logic as the dropdown for consistency\n      const filterFn = typeof config.filter === 'function' ? config.filter : defaultFilterFunction[config.filter]\n\n      for (let i = 0; i < optionsLength; i++) {\n        const opt = this.options[i]\n\n        if (filterFn && filterFn(token, opt, config)) {\n          const optText = getOptionText(opt)\n          const optValue = getOptionValue(opt)\n\n          // For hints, prioritize matches that start with the token for better UX\n          let hintText = ''\n          const normalizedToken = normalizeCase(token, config)\n          const normalizedOptText = normalizeCase(optText, config)\n          const normalizedOptValue = normalizeCase(optValue, config)\n\n          if (normalizedOptText.indexOf(normalizedToken) === 0) {\n            // Text starts with token - show completion\n            hintText = leftSide + token + optText.substring(token.length)\n          } else if (normalizedOptValue.indexOf(normalizedToken) === 0) {\n            // Value starts with token - show completion\n            hintText = leftSide + token + optValue.substring(token.length)\n          } else {\n            // Contains match but doesn't start with token - just show the token\n            hintText = leftSide + token\n          }\n\n          this.elementHint.innerText = hintText\n          this.elementHint.realInnerText = leftSide + optValue\n          break\n        }\n      }\n      // moving the dropDown and refreshing it.\n      dropDown.style.left = calculateWidthForText(leftSide) + 'px'\n      dropDownController.refresh(token, this.options)\n      this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + 10 + 'px'\n      const wasDropDownHidden = (dropDown.style.visibility === 'hidden')\n      if (!wasDropDownHidden) { this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + dropDown.clientWidth + 'px' }\n    }\n  }\n\n  const dropDownController = createDropDownController(dropDown, rs)\n\n  const keyDownHandler = function (e) {\n    // console.log(\"Keydown:\" + e.keyCode);\n    e = e || window.event\n    const keyCode = e.keyCode\n\n    if (this.elementHint == null) return\n\n    if (keyCode === 33) { return } // page up (do nothing)\n    if (keyCode === 34) { return } // page down (do nothing);\n\n    if (keyCode === 27) { // escape\n      rs.hideDropDown()\n      rs.element.focus()\n      e.preventDefault()\n      e.stopPropagation()\n      return\n    }\n\n    let text = this.element.innerText\n    text = text.replace('\\n', '')\n\n    if (config.confirmKeys.indexOf(keyCode) >= 0) { //  (autocomplete triggered)\n      if (keyCode === 9) {\n        if (this.elementHint.innerText.length === 0) {\n          rs.onTab()\n        }\n      }\n      if (this.elementHint.innerText.length > 0) { // if there is a hint\n        if (this.element.innerText !== this.elementHint.realInnerText) {\n          this.element.innerText = this.elementHint.realInnerText\n          rs.hideDropDown()\n          setEndOfContenteditable(this.element)\n          if (keyCode === 9) {\n            rs.element.focus()\n            e.preventDefault()\n            e.stopPropagation()\n          }\n        }\n      }\n      return\n    }\n\n    if (keyCode === 13) { // enter  (autocomplete triggered)\n      if (this.elementHint.innerText.length === 0) { // if there is a hint\n        rs.onEnter()\n      } else {\n        const wasDropDownHidden = (dropDown.style.visibility === 'hidden')\n        dropDownController.hide()\n\n        if (wasDropDownHidden) {\n          rs.hideDropDown()\n          rs.element.focus()\n          rs.onEnter()\n          return\n        }\n\n        this.element.innerText = this.elementHint.realInnerText\n        rs.hideDropDown()\n        setEndOfContenteditable(this.element)\n        e.preventDefault()\n        e.stopPropagation()\n      }\n      return\n    }\n\n    if (keyCode === 40) { // down\n      const token = text.substring(this.startFrom)\n      const m = dropDownController.move(+1)\n      if (m === '') { rs.onArrowDown() }\n      this.elementHint.innerText = leftSide + token + getOptionText(m).substring(token.length)\n      this.elementHint.realInnerText = leftSide + getOptionValue(m)\n      e.preventDefault()\n      e.stopPropagation()\n      return\n    }\n\n    if (keyCode === 38) { // up\n      const token = text.substring(this.startFrom)\n      const m = dropDownController.move(-1)\n      if (m === '') { rs.onArrowUp() }\n      this.elementHint.innerText = leftSide + token + getOptionText(m).substring(token.length)\n      this.elementHint.realInnerText = leftSide + getOptionValue(m)\n      e.preventDefault()\n      e.stopPropagation()\n    }\n  }.bind(rs)\n\n  const onBlurHandler = e => {\n    rs.hideDropDown()\n    // console.log(\"Lost focus.\");\n  }\n\n  dropDownController.onmouseselection = (option, rs) => {\n    const optionValue = getOptionValue(option)\n    rs.element.innerText = rs.elementHint.innerText = leftSide + optionValue\n    rs.hideDropDown()\n    window.setTimeout(() => {\n      rs.element.focus()\n      setEndOfContenteditable(rs.element)\n    }, 1)\n  }\n\n  return rs\n}\n"
  },
  {
    "path": "src/js/constants.js",
    "content": "export const DEFAULT_MODAL_ANCHOR = document.body\nexport const SIZE_LARGE = 10 * 1024 * 1024 // 10 MB\nexport const MAX_PREVIEW_CHARACTERS = 20000\nexport const PREVIEW_HISTORY_LIMIT = 2 * 1024 * 1024 * 1024 // 2 GB\n"
  },
  {
    "path": "src/js/createAbsoluteAnchor.js",
    "content": "import { isChildOf, removeEventListener, addEventListener } from './util'\n\n/**\n * Create an anchor element absolutely positioned in the `parent`\n * element.\n * @param {HTMLElement} anchor\n * @param {HTMLElement} parent\n * @param {function(HTMLElement)} [onDestroy]  Callback when the anchor is destroyed\n * @param {boolean} [destroyOnMouseOut=false] If true, anchor will be removed on mouse out\n * @returns {HTMLElement}\n */\nexport function createAbsoluteAnchor (anchor, parent, onDestroy, destroyOnMouseOut = false) {\n  const root = getRootNode(anchor)\n  const eventListeners = {}\n\n  const anchorRect = anchor.getBoundingClientRect()\n  const parentRect = parent.getBoundingClientRect()\n\n  const absoluteAnchor = document.createElement('div')\n  absoluteAnchor.className = 'jsoneditor-anchor'\n  absoluteAnchor.style.position = 'absolute'\n  absoluteAnchor.style.left = (anchorRect.left - parentRect.left) + 'px'\n  absoluteAnchor.style.top = (anchorRect.top - parentRect.top) + 'px'\n  absoluteAnchor.style.width = (anchorRect.width - 2) + 'px'\n  absoluteAnchor.style.height = (anchorRect.height - 2) + 'px'\n  absoluteAnchor.style.boxSizing = 'border-box'\n  parent.appendChild(absoluteAnchor)\n\n  function destroy () {\n    // remove temporary absolutely positioned anchor\n    if (absoluteAnchor && absoluteAnchor.parentNode) {\n      absoluteAnchor.parentNode.removeChild(absoluteAnchor)\n\n      // remove all event listeners\n      // all event listeners are supposed to be attached to document.\n      for (const name in eventListeners) {\n        if (hasOwnProperty(eventListeners, name)) {\n          const fn = eventListeners[name]\n          if (fn) {\n            removeEventListener(root, name, fn)\n          }\n          delete eventListeners[name]\n        }\n      }\n\n      if (typeof onDestroy === 'function') {\n        onDestroy(anchor)\n      }\n    }\n  }\n\n  function isOutside (target) {\n    return (target !== absoluteAnchor) && !isChildOf(target, absoluteAnchor)\n  }\n\n  // create and attach event listeners\n  function destroyIfOutside (event) {\n    if (isOutside(event.target)) {\n      destroy()\n    }\n  }\n\n  eventListeners.mousedown = addEventListener(root, 'mousedown', destroyIfOutside)\n  eventListeners.mousewheel = addEventListener(root, 'mousewheel', destroyIfOutside)\n\n  if (destroyOnMouseOut) {\n    let destroyTimer = null\n\n    absoluteAnchor.onmouseover = () => {\n      clearTimeout(destroyTimer)\n      destroyTimer = null\n    }\n\n    absoluteAnchor.onmouseout = () => {\n      if (!destroyTimer) {\n        destroyTimer = setTimeout(destroy, 200)\n      }\n    }\n  }\n\n  absoluteAnchor.destroy = destroy\n\n  return absoluteAnchor\n}\n\n/**\n * Node.getRootNode shim\n * @param  {HTMLElement} node node to check\n * @return {HTMLElement}      node's rootNode or `window` if there is ShadowDOM is not supported.\n */\nfunction getRootNode (node) {\n  return (typeof node.getRootNode === 'function')\n    ? node.getRootNode()\n    : window\n}\n\nfunction hasOwnProperty (object, key) {\n  return Object.prototype.hasOwnProperty.call(object, key)\n}\n"
  },
  {
    "path": "src/js/header.js",
    "content": "/*!\n * jsoneditor.js\n *\n * @brief\n * JSONEditor is a web-based tool to view, edit, format, and validate JSON.\n * It has various modes such as a tree editor, a code editor, and a plain text\n * editor.\n *\n * Supported browsers: Chrome, Firefox, Safari, Edge\n *\n * @license\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy\n * of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n *\n * Copyright (c) 2011-2026 Jos de Jong, http://jsoneditoronline.org\n *\n * @author  Jos de Jong, <wjosdejong@gmail.com>\n * @version @@version\n * @date    @@date\n */\n"
  },
  {
    "path": "src/js/i18n.js",
    "content": "'use strict'\n\n/* eslint-disable no-template-curly-in-string */\n\nimport './polyfills'\n\nconst _defs = {\n  en: {\n    array: 'Array',\n    auto: 'Auto',\n    appendText: 'Append',\n    appendTitle: 'Append a new field with type \\'auto\\' after this field (Ctrl+Shift+Ins)',\n    appendSubmenuTitle: 'Select the type of the field to be appended',\n    appendTitleAuto: 'Append a new field with type \\'auto\\' (Ctrl+Shift+Ins)',\n    ascending: 'Ascending',\n    ascendingTitle: 'Sort the childs of this ${type} in ascending order',\n    actionsMenu: 'Click to open the actions menu (Ctrl+M)',\n    cannotParseFieldError: 'Cannot parse field into JSON',\n    cannotParseValueError: 'Cannot parse value into JSON',\n    collapseAll: 'Collapse all fields',\n    compactTitle: 'Compact JSON data, remove all whitespaces (Ctrl+Shift+I)',\n    descending: 'Descending',\n    descendingTitle: 'Sort the childs of this ${type} in descending order',\n    drag: 'Drag to move this field (Alt+Shift+Arrows)',\n    duplicateKey: 'duplicate key',\n    duplicateText: 'Duplicate',\n    duplicateTitle: 'Duplicate selected fields (Ctrl+D)',\n    duplicateField: 'Duplicate this field (Ctrl+D)',\n    duplicateFieldError: 'Duplicate field name',\n    empty: 'empty',\n    expandAll: 'Expand all fields',\n    expandTitle: 'Click to expand/collapse this field (Ctrl+E). \\n' +\n      'Ctrl+Click to expand/collapse including all childs.',\n    formatTitle: 'Format JSON data, with proper indentation and line feeds (Ctrl+I)',\n    insert: 'Insert',\n    insertTitle: 'Insert a new field with type \\'auto\\' before this field (Ctrl+Ins)',\n    insertSub: 'Select the type of the field to be inserted',\n    object: 'Object',\n    ok: 'Ok',\n    redo: 'Redo (Ctrl+Shift+Z)',\n    removeText: 'Remove',\n    removeTitle: 'Remove selected fields (Ctrl+Del)',\n    removeField: 'Remove this field (Ctrl+Del)',\n    repairTitle: 'Repair JSON: fix quotes and escape characters, remove comments and JSONP notation, turn JavaScript objects into JSON.',\n    searchTitle: 'Search fields and values',\n    searchNextResultTitle: 'Next result (Enter)',\n    searchPreviousResultTitle: 'Previous result (Shift + Enter)',\n    selectNode: 'Select a node...',\n    showAll: 'show all',\n    showMore: 'show more',\n    showMoreStatus: 'displaying ${visibleChilds} of ${totalChilds} items.',\n    sort: 'Sort',\n    sortTitle: 'Sort the childs of this ${type}',\n    sortTitleShort: 'Sort contents',\n    sortFieldLabel: 'Field:',\n    sortDirectionLabel: 'Direction:',\n    sortFieldTitle: 'Select the nested field by which to sort the array or object',\n    sortAscending: 'Ascending',\n    sortAscendingTitle: 'Sort the selected field in ascending order',\n    sortDescending: 'Descending',\n    sortDescendingTitle: 'Sort the selected field in descending order',\n    string: 'String',\n    transform: 'Transform',\n    transformTitle: 'Filter, sort, or transform the childs of this ${type}',\n    transformTitleShort: 'Filter, sort, or transform contents',\n    extract: 'Extract',\n    extractTitle: 'Extract this ${type}',\n    transformQueryTitle: 'Enter a JMESPath query',\n    transformWizardLabel: 'Wizard',\n    transformWizardFilter: 'Filter',\n    transformWizardSortBy: 'Sort by',\n    transformWizardSelectFields: 'Select fields',\n    transformQueryLabel: 'Query',\n    transformPreviewLabel: 'Preview',\n    type: 'Type',\n    typeTitle: 'Change the type of this field',\n    openUrl: 'Ctrl+Click or Ctrl+Enter to open url in new window',\n    undo: 'Undo last action (Ctrl+Z)',\n    validationCannotMove: 'Cannot move a field into a child of itself',\n    autoType: 'Field type \"auto\". ' +\n      'The field type is automatically determined from the value ' +\n      'and can be a string, number, boolean, or null.',\n    objectType: 'Field type \"object\". ' +\n      'An object contains an unordered set of key/value pairs.',\n    arrayType: 'Field type \"array\". ' +\n      'An array contains an ordered collection of values.',\n    stringType: 'Field type \"string\". ' +\n      'Field type is not determined from the value, ' +\n      'but always returned as string.',\n    modeEditorTitle: 'Switch Editor Mode',\n    modeCodeText: 'Code',\n    modeCodeTitle: 'Switch to code highlighter',\n    modeFormText: 'Form',\n    modeFormTitle: 'Switch to form editor',\n    modeTextText: 'Text',\n    modeTextTitle: 'Switch to plain text editor',\n    modeTreeText: 'Tree',\n    modeTreeTitle: 'Switch to tree editor',\n    modeViewText: 'View',\n    modeViewTitle: 'Switch to tree view',\n    modePreviewText: 'Preview',\n    modePreviewTitle: 'Switch to preview mode',\n    examples: 'Examples',\n    default: 'Default',\n    containsInvalidProperties: 'Contains invalid properties',\n    containsInvalidItems: 'Contains invalid items'\n  },\n  es: {\n    array: 'Matriz',\n    auto: 'Auto',\n    appendText: 'Agregar',\n    appendTitle: 'Agregue un nuevo campo con el tipo \\'auto\\' después de este campo (Ctrl + Shift + Ins)',\n    appendSubmenuTitle: 'Seleccione el tipo de campo que se agregará',\n    appendTitleAuto: 'Agregue un nuevo campo con el tipo \\'auto\\' (Ctrl + Shift + Ins)',\n    ascending: 'Ascendente',\n    ascendingTitle: 'Ordene los elementos secundarios de este ${type} en orden ascendente',\n    actionsMenu: 'Haga clic para abrir el menú de acciones (Ctrl + M)',\n    cannotParseFieldError: 'No se puede parsear el campo en JSON',\n    cannotParseValueError: 'No se puede parsear el valor en JSON',\n    collapseAll: 'Contraer todos los campos',\n    compactTitle: 'Compactar datos JSON, eliminar todos los espacios en blanco (Ctrl + Shift + I)',\n    descending: 'Descendente',\n    descendingTitle: 'Ordene los hijos de este ${type} en orden descendente',\n    drag: 'Arrastre para mover este campo (Alt + Mayús + Flechas)',\n    duplicateKey: 'llave duplicada',\n    duplicateText: 'Duplicar',\n    duplicateTitle: 'Duplicar campos seleccionados (Ctrl + D)',\n    duplicateField: 'Duplicar este campo (Ctrl + D)',\n    duplicateFieldError: 'Nombre de campo duplicado',\n    empty: 'vacio',\n    expandAll: 'Expandir todos los campos',\n    expandTitle: 'Haga clic para expandir/contraer este campo (Ctrl + E). \\n ' + ' Ctrl+Clic para expandir/contraer incluyendo todos los niños.',\n    formatTitle: 'Formatee los datos JSON, con la sangría y los avances de línea adecuados (Ctrl + I)',\n    insert: 'Insertar',\n    insertTitle: 'Inserte un nuevo campo con el tipo \\'auto\\' antes de este campo (Ctrl + Ins)',\n    insertSub: 'Seleccione el tipo de campo a insertar',\n    object: 'Objeto',\n    ok: 'Ok',\n    redo: 'Rehacer (Ctrl+Mayús+Z)',\n    removeText: 'Eliminar',\n    removeTitle: 'Eliminar campos seleccionados (Ctrl+Supr)',\n    removeField: 'Eliminar este campo (Ctrl+Supr)',\n    repairTitle: 'Reparar JSON: corrija comillas y caracteres de escape, elimine comentarios y notación JSONP, convierta objetos JavaScript en JSON.',\n    searchTitle: 'Campos de búsqueda y valores',\n    searchNextResultTitle: 'Siguiente resultado (Entrar)',\n    searchPreviousResultTitle: 'Resultado anterior (Shift + Enter)',\n    selectNode: 'Seleccione un nodo...',\n    showAll: 'mostrar todo',\n    showMore: 'mostrar más',\n    showMoreStatus: 'mostrando ${visibleChilds} de ${totalChilds} elementos.',\n    sort: 'Ordenar',\n    sortTitle: 'Ordene los hijos de este ${type}',\n    sortTitleShort: 'Ordenar contenidos',\n    sortFieldLabel: 'Campo:',\n    sortDirectionLabel: 'Dirección:',\n    sortFieldTitle: 'Seleccione el campo anidado por el cual ordenar la matriz u objeto',\n    sortAscending: 'Ascendente',\n    sortAscendingTitle: 'Ordenar el campo seleccionado en orden ascendente',\n    sortDescending: 'Descendente',\n    sortDescendingTitle: 'Ordenar por el campo seleccionado, en orden descendente',\n    string: 'Texto',\n    transform: 'Transformar',\n    transformTitle: 'Filtrar, ordenar o transformar los hijos de este ${type}',\n    transformTitleShort: 'Filtrar, ordenar o transformar contenidos',\n    extract: 'Extraer',\n    extractTitle: 'Extrae este ${type}',\n    transformQueryTitle: 'Ingrese una consulta JMESPath',\n    transformWizardLabel: 'Wizard',\n    transformWizardFilter: 'Filtro',\n    transformWizardSortBy: 'Ordenar por',\n    transformWizardSelectFields: 'Seleccione un campo',\n    transformQueryLabel: 'Consulta',\n    transformPreviewLabel: 'Vista Previa',\n    type: 'Tipo',\n    typeTitle: 'Cambiar el tipo de campo',\n    openUrl: 'Ctrl+Click o Ctrl+Enter para abrir la URL en una nueva ventana',\n    undo: 'Deshacer la última acción (Ctrl+Z)',\n    validationCannotMove: 'No se puede mover un campo a un hijo de sí mismo.',\n    autoType: 'Tipo de campo \"auto\". ' + 'El tipo de campo se determina automáticamente a partir del valor ' + 'y puede ser una cadena, un número, un booleano o un valor nulo.',\n    objectType: 'Tipo de campo \"objeto\". ' + ' Un objeto contiene un conjunto desordenado de pares clave/valor.',\n    arrayType: 'Tipo de campo \"matriz\". ' + ' Una matriz contiene una colección ordenada de valores.',\n    stringType: 'Tipo de campo \"cadena\". ' + ' El tipo de campo no se determina a partir del valor, ' + ' pero siempre se devuelve como una cadena.',\n    modeEditorTitle: 'Cambiar modo de editor',\n    modeCodeText: 'Código',\n    modeCodeTitle: 'Cambiar al resaltador de código',\n    modeFormText: 'Formulario',\n    modeFormTitle: 'Cambiar al editor de formularios',\n    modeTextText: 'Texto',\n    modeTextTitle: 'Cambiar al editor de texto sin formato',\n    modeTreeText: 'Árbol',\n    modeTreeTitle: 'Cambiar al editor de árbol',\n    modeViewText: 'Vista',\n    modeViewTitle: 'Cambiar a la vista de árbol',\n    modePreviewText: 'Vista Previa',\n    modePreviewTitle: 'Cambiar al modo de vista previa',\n    examples: 'Ejemplos',\n    default: 'Predeterminado',\n    containsInvalidProperties: 'Contiene propiedades no válidas',\n    containsInvalidItems: 'Contiene ítems no válidos'\n  },\n  'zh-CN': {\n    array: '数组',\n    auto: '自动',\n    appendText: '追加',\n    appendTitle: '在此字段后追加一个类型为“auto”的新字段 (Ctrl+Shift+Ins)',\n    appendSubmenuTitle: '选择要追加的字段类型',\n    appendTitleAuto: '追加类型为“auto”的新字段 (Ctrl+Shift+Ins)',\n    ascending: '升序',\n    ascendingTitle: '升序排列${type}的子节点',\n    actionsMenu: '点击打开动作菜单(Ctrl+M)',\n    cannotParseFieldError: '无法将字段解析为JSON',\n    cannotParseValueError: '无法将值解析为JSON',\n    collapseAll: '缩进所有字段',\n    compactTitle: '压缩JSON数据，删除所有空格 (Ctrl+Shift+I)',\n    descending: '降序',\n    descendingTitle: '降序排列${type}的子节点',\n    drag: '拖拽移动该节点(Alt+Shift+Arrows)',\n    duplicateKey: '重复键',\n    duplicateText: '复制',\n    duplicateTitle: '复制选中字段(Ctrl+D)',\n    duplicateField: '复制该字段(Ctrl+D)',\n    duplicateFieldError: '重复的字段名称',\n    empty: '清空',\n    expandAll: '展开所有字段',\n    expandTitle: '点击 展开/收缩 该字段(Ctrl+E). \\n' +\n      'Ctrl+Click 展开/收缩 包含所有子节点.',\n    formatTitle: '使用适当的缩进和换行符格式化JSON数据 (Ctrl+I)',\n    insert: '插入',\n    insertTitle: '在此字段前插入类型为“auto”的新字段 (Ctrl+Ins)',\n    insertSub: '选择要插入的字段类型',\n    object: '对象',\n    ok: 'Ok',\n    redo: '重做 (Ctrl+Shift+Z)',\n    removeText: '移除',\n    removeTitle: '移除选中字段 (Ctrl+Del)',\n    removeField: '移除该字段 (Ctrl+Del)',\n    repairTitle: '修复JSON：修复引号和转义符，删除注释和JSONP表示法，将JavaScript对象转换为JSON。',\n    selectNode: '选择一个节点...',\n    showAll: '展示全部',\n    showMore: '展示更多',\n    showMoreStatus: '显示${totalChilds}的${visibleChilds}项目.',\n    sort: '排序',\n    sortTitle: '排序${type}的子节点',\n    sortTitleShort: '内容排序',\n    sortFieldLabel: '字段：',\n    sortDirectionLabel: '方向：',\n    sortFieldTitle: '选择用于对数组或对象排序的嵌套字段',\n    sortAscending: '升序排序',\n    sortAscendingTitle: '按照该字段升序排序',\n    sortDescending: '降序排序',\n    sortDescendingTitle: '按照该字段降序排序',\n    string: '字符串',\n    transform: '变换',\n    transformTitle: '筛选，排序，或者转换${type}的子节点',\n    transformTitleShort: '筛选，排序，或者转换内容',\n    extract: '提取',\n    extractTitle: '提取这个 ${type}',\n    transformQueryTitle: '输入JMESPath查询',\n    transformWizardLabel: '向导',\n    transformWizardFilter: '筛选',\n    transformWizardSortBy: '排序',\n    transformWizardSelectFields: '选择字段',\n    transformQueryLabel: '查询',\n    transformPreviewLabel: '预览',\n    type: '类型',\n    typeTitle: '更改字段类型',\n    openUrl: 'Ctrl+Click 或者 Ctrl+Enter 在新窗口打开链接',\n    undo: '撤销上次动作 (Ctrl+Z)',\n    validationCannotMove: '无法将字段移入其子节点',\n    autoType: '字段类型 \"auto\". ' +\n      '字段类型由值自动确定 ' +\n      '可以为 string，number，boolean，或者 null.',\n    objectType: '字段类型 \"object\". ' +\n      '对象包含一组无序的键/值对.',\n    arrayType: '字段类型 \"array\". ' +\n      '数组包含值的有序集合.',\n    stringType: '字段类型 \"string\". ' +\n      '字段类型由值自动确定，' +\n      '但始终作为字符串返回.',\n    modeCodeText: '代码',\n    modeCodeTitle: '切换至代码高亮',\n    modeFormText: '表单',\n    modeFormTitle: '切换至表单编辑',\n    modeTextText: '文本',\n    modeTextTitle: '切换至文本编辑',\n    modeTreeText: '树',\n    modeTreeTitle: '切换至树编辑',\n    modeViewText: '视图',\n    modeViewTitle: '切换至树视图',\n    modePreviewText: '预览',\n    modePreviewTitle: '切换至预览模式',\n    examples: '例子',\n    default: '缺省',\n    containsInvalidProperties: '包含无效的属性',\n    containsInvalidItems: '包含无效项目'\n  },\n  'pt-BR': {\n    array: 'Lista',\n    auto: 'Automatico',\n    appendText: 'Adicionar',\n    appendTitle: 'Adicionar novo campo com tipo \\'auto\\' depois deste campo (Ctrl+Shift+Ins)',\n    appendSubmenuTitle: 'Selecione o tipo do campo a ser adicionado',\n    appendTitleAuto: 'Adicionar novo campo com tipo \\'auto\\' (Ctrl+Shift+Ins)',\n    ascending: 'Ascendente',\n    ascendingTitle: 'Organizar filhor do tipo ${type} em crescente',\n    actionsMenu: 'Clique para abrir o menu de ações (Ctrl+M)',\n    cannotParseFieldError: 'Não é possível analisar o campo no JSON',\n    cannotParseValueError: 'Não é possível analisar o valor em JSON',\n    collapseAll: 'Fechar todos campos',\n    compactTitle: 'Dados JSON compactos, remova todos os espaços em branco (Ctrl+Shift+I)',\n    descending: 'Descendente',\n    descendingTitle: 'Organizar o filhos do tipo ${type} em decrescente',\n    duplicateKey: 'chave duplicada',\n    drag: 'Arraste para mover este campo (Alt+Shift+Arrows)',\n    duplicateText: 'Duplicar',\n    duplicateTitle: 'Duplicar campos selecionados (Ctrl+D)',\n    duplicateField: 'Duplicar este campo (Ctrl+D)',\n    duplicateFieldError: 'Nome do campo duplicado',\n    empty: 'vazio',\n    expandAll: 'Expandir todos campos',\n    expandTitle: 'Clique para expandir/encolher este campo (Ctrl+E). \\n' +\n      'Ctrl+Click para expandir/encolher incluindo todos os filhos.',\n    formatTitle: 'Formate dados JSON, com recuo e feeds de linha adequados (Ctrl+I)',\n    insert: 'Inserir',\n    insertTitle: 'Inserir um novo campo do tipo \\'auto\\' antes deste campo (Ctrl+Ins)',\n    insertSub: 'Selecionar o tipo de campo a ser inserido',\n    object: 'Objeto',\n    ok: 'Ok',\n    redo: 'Refazer (Ctrl+Shift+Z)',\n    removeText: 'Remover',\n    removeTitle: 'Remover campos selecionados (Ctrl+Del)',\n    removeField: 'Remover este campo (Ctrl+Del)',\n    repairTitle: 'Repare JSON: corrija aspas e caracteres de escape, remova comentários e notação JSONP, transforme objetos JavaScript em JSON.',\n    selectNode: 'Selecione um nódulo...',\n    showAll: 'mostrar todos',\n    showMore: 'mostrar mais',\n    showMoreStatus: 'exibindo ${visibleChilds} de ${totalChilds} itens.',\n    sort: 'Organizar',\n    sortTitle: 'Organizar os filhos deste ${type}',\n    sortTitleShort: 'Organizar os filhos',\n    sortFieldLabel: 'Campo:',\n    sortDirectionLabel: 'Direção:',\n    sortFieldTitle: 'Selecione um campo filho pelo qual ordenar o array ou objeto',\n    sortAscending: 'Ascendente',\n    sortAscendingTitle: 'Ordenar o campo selecionado por ordem ascendente',\n    sortDescending: 'Descendente',\n    sortDescendingTitle: 'Ordenar o campo selecionado por ordem descendente',\n    string: 'Texto',\n    transform: 'Transformar',\n    transformTitle: 'Filtrar, ordenar ou transformar os filhos deste ${type}',\n    transformTitleShort: 'Filtrar, ordenar ou transformar conteúdos',\n    transformQueryTitle: 'Insira uma expressão JMESPath',\n    transformWizardLabel: 'Assistente',\n    transformWizardFilter: 'Filtro',\n    transformWizardSortBy: 'Ordenar por',\n    transformWizardSelectFields: 'Selecionar campos',\n    transformQueryLabel: 'Expressão',\n    transformPreviewLabel: 'Visualizar',\n    type: 'Tipo',\n    typeTitle: 'Mudar o tipo deste campo',\n    openUrl: 'Ctrl+Click ou Ctrl+Enter para abrir link em nova janela',\n    undo: 'Desfazer último ação (Ctrl+Z)',\n    validationCannotMove: 'Não pode mover um campo como filho dele mesmo',\n    autoType: 'Campo do tipo \"auto\". ' +\n      'O tipo do campo é determinao automaticamente a partir do seu valor ' +\n      'e pode ser texto, número, verdade/falso ou nulo.',\n    objectType: 'Campo do tipo \"objeto\". ' +\n      'Um objeto contém uma lista de pares com chave e valor.',\n    arrayType: 'Campo do tipo \"lista\". ' +\n      'Uma lista contem uma coleção de valores ordenados.',\n    stringType: 'Campo do tipo \"string\". ' +\n      'Campo do tipo nao é determinado através do seu valor, ' +\n      'mas sempre retornara um texto.',\n    examples: 'Exemplos',\n    default: 'Revelia',\n    containsInvalidProperties: 'Contém propriedades inválidas',\n    containsInvalidItems: 'Contém itens inválidos'\n  },\n  tr: {\n    array: 'Dizin',\n    auto: 'Otomatik',\n    appendText: 'Ekle',\n    appendTitle: 'Bu alanın altına \\'otomatik\\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)',\n    appendSubmenuTitle: 'Eklenecek alanın tipini seç',\n    appendTitleAuto: '\\'Otomatik\\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)',\n    ascending: 'Artan',\n    ascendingTitle: '${type}\\'ın alt tiplerini artan düzende sırala',\n    actionsMenu: 'Aksiyon menüsünü açmak için tıklayın (Ctrl+M)',\n    collapseAll: 'Tüm alanları kapat',\n    descending: 'Azalan',\n    descendingTitle: '${type}\\'ın alt tiplerini azalan düzende sırala',\n    drag: 'Bu alanı taşımak için sürükleyin (Alt+Shift+Arrows)',\n    duplicateKey: 'Var olan anahtar',\n    duplicateText: 'Aşağıya kopyala',\n    duplicateTitle: 'Seçili alanlardan bir daha oluştur (Ctrl+D)',\n    duplicateField: 'Bu alandan bir daha oluştur (Ctrl+D)',\n    duplicateFieldError: 'Duplicate field name',\n    cannotParseFieldError: 'Alan JSON\\'a ayrıştırılamıyor',\n    cannotParseValueError: 'JSON\\'a değer ayrıştırılamıyor',\n    empty: 'boş',\n    expandAll: 'Tüm alanları aç',\n    expandTitle: 'Bu alanı açmak/kapatmak için tıkla (Ctrl+E). \\n' +\n      'Alt alanlarda dahil tüm alanları açmak için Ctrl+Click ',\n    insert: 'Ekle',\n    insertTitle: 'Bu alanın üstüne \\'otomatik\\' tipinde yeni bir alan ekle (Ctrl+Ins)',\n    insertSub: 'Araya eklenecek alanın tipini seç',\n    object: 'Nesne',\n    ok: 'Tamam',\n    redo: 'Yeniden yap (Ctrl+Shift+Z)',\n    removeText: 'Kaldır',\n    removeTitle: 'Seçilen alanları kaldır (Ctrl+Del)',\n    removeField: 'Bu alanı kaldır (Ctrl+Del)',\n    selectNode: 'Bir nesne seç...',\n    showAll: 'tümünü göster',\n    showMore: 'daha fazla göster',\n    showMoreStatus: '${totalChilds} alanın ${visibleChilds} alt alanları gösteriliyor',\n    sort: 'Sırala',\n    sortTitle: '${type}\\'ın alt alanlarını sırala',\n    sortTitleShort: 'İçerikleri sırala',\n    sortFieldLabel: 'Alan:',\n    sortDirectionLabel: 'Yön:',\n    sortFieldTitle: 'Diziyi veya nesneyi sıralamak için iç içe geçmiş alanı seçin',\n    sortAscending: 'Artan',\n    sortAscendingTitle: 'Seçili alanı artan düzende sırala',\n    sortDescending: 'Azalan',\n    sortDescendingTitle: 'Seçili alanı azalan düzende sırala',\n    string: 'Karakter Dizisi',\n    transform: 'Dönüştür',\n    transformTitle: '${type}\\'ın alt alanlarını filtrele, sırala veya dönüştür',\n    transformTitleShort: 'İçerikleri filterele, sırala veya dönüştür',\n    transformQueryTitle: 'JMESPath sorgusu gir',\n    transformWizardLabel: 'Sihirbaz',\n    transformWizardFilter: 'Filtre',\n    transformWizardSortBy: 'Sırala',\n    transformWizardSelectFields: 'Alanları seç',\n    transformQueryLabel: 'Sorgu',\n    transformPreviewLabel: 'Önizleme',\n    type: 'Tip',\n    typeTitle: 'Bu alanın tipini değiştir',\n    openUrl: 'URL\\'i yeni bir pencerede açmak için Ctrl+Click veya Ctrl+Enter',\n    undo: 'Son değişikliği geri al (Ctrl+Z)',\n    validationCannotMove: 'Alt alan olarak taşınamıyor',\n    autoType: 'Alan tipi \"otomatik\". ' +\n      'Alan türü otomatik olarak değerden belirlenir' +\n      've bir dize, sayı, boolean veya null olabilir.',\n    objectType: 'Alan tipi \"nesne\". ' +\n      'Bir nesne, sıralanmamış bir anahtar / değer çifti kümesi içerir.',\n    arrayType: 'Alan tipi \"dizi\". ' +\n      'Bir dizi, düzenli değerler koleksiyonu içerir.',\n    stringType: 'Alan tipi \"karakter dizisi\". ' +\n      'Alan türü değerden belirlenmez,' +\n      'ancak her zaman karakter dizisi olarak döndürülür.',\n    modeCodeText: 'Kod',\n    modeCodeTitle: 'Kod vurgulayıcıya geç',\n    modeFormText: 'Form',\n    modeFormTitle: 'Form düzenleyiciye geç',\n    modeTextText: 'Metin',\n    modeTextTitle: 'Düz metin düzenleyiciye geç',\n    modeTreeText: 'Ağaç',\n    modeTreeTitle: 'Ağaç düzenleyiciye geç',\n    modeViewText: 'Görünüm',\n    modeViewTitle: 'Ağaç görünümüne geç',\n    examples: 'Örnekler',\n    default: 'Varsayılan',\n    containsInvalidProperties: 'Geçersiz özellikler içeriyor',\n    containsInvalidItems: 'Geçersiz öğeler içeriyor'\n  },\n  ja: {\n    array: '配列',\n    auto: 'オート',\n    appendText: '追加',\n    appendTitle: '次のフィールドに\"オート\"のフィールドを追加 (Ctrl+Shift+Ins)',\n    appendSubmenuTitle: '追加するフィールドの型を選択してください',\n    appendTitleAuto: '\"オート\"のフィールドを追加 (Ctrl+Shift+Ins)',\n    ascending: '昇順',\n    ascendingTitle: '${type}の子要素を昇順に並べ替え',\n    actionsMenu: 'クリックしてアクションメニューを開く (Ctrl+M)',\n    collapseAll: 'すべてを折りたたむ',\n    descending: '降順',\n    descendingTitle: '${type}の子要素を降順に並べ替え',\n    drag: 'ドラッグして選択中のフィールドを移動 (Alt+Shift+Arrows)',\n    duplicateKey: '複製キー',\n    duplicateText: '複製',\n    duplicateTitle: '選択中のフィールドを複製 (Ctrl+D)',\n    duplicateField: '選択中のフィールドを複製 (Ctrl+D)',\n    duplicateFieldError: 'フィールド名が重複しています',\n    cannotParseFieldError: 'JSONのフィールドを解析できません',\n    cannotParseValueError: 'JSONの値を解析できません',\n    empty: '空',\n    expandAll: 'すべてを展開',\n    expandTitle: 'クリックしてフィールドを展開/折りたたむ (Ctrl+E). \\n' +\n      'Ctrl+Click ですべての子要素を展開/折りたたむ',\n    insert: '挿入',\n    insertTitle: '選択中のフィールドの前に新しいフィールドを挿入 (Ctrl+Ins)',\n    insertSub: '挿入するフィールドの型を選択',\n    object: 'オブジェクト',\n    ok: '実行',\n    redo: 'やり直す (Ctrl+Shift+Z)',\n    removeText: '削除',\n    removeTitle: '選択中のフィールドを削除 (Ctrl+Del)',\n    removeField: '選択中のフィールドを削除 (Ctrl+Del)',\n    selectNode: 'ノードを選択...',\n    showAll: 'すべてを表示',\n    showMore: 'もっと見る',\n    showMoreStatus: '${totalChilds}個のアイテムのうち ${visibleChilds}個を表示しています。',\n    sort: '並べ替え',\n    sortTitle: '${type}の子要素を並べ替え',\n    sortTitleShort: '並べ替え',\n    sortFieldLabel: 'フィールド:',\n    sortDirectionLabel: '順序:',\n    sortFieldTitle: '配列またはオブジェクトを並び替えるためのフィールドを選択',\n    sortAscending: '昇順',\n    sortAscendingTitle: '選択中のフィールドを昇順に並び替え',\n    sortDescending: '降順',\n    sortDescendingTitle: '選択中のフィールドを降順に並び替え',\n    string: '文字列',\n    transform: '変換',\n    transformTitle: '${type}の子要素をフィルター・並び替え・変換する',\n    transformTitleShort: '内容をフィルター・並び替え・変換する',\n    extract: '抽出',\n    extractTitle: '${type}を抽出',\n    transformQueryTitle: 'JMESPathクエリを入力',\n    transformWizardLabel: 'ウィザード',\n    transformWizardFilter: 'フィルター',\n    transformWizardSortBy: '並び替え',\n    transformWizardSelectFields: 'フィールドを選択',\n    transformQueryLabel: 'クエリ',\n    transformPreviewLabel: 'プレビュー',\n    type: '型',\n    typeTitle: '選択中のフィールドの型を変更',\n    openUrl: 'Ctrl+Click または Ctrl+Enter で 新規ウィンドウでURLを開く',\n    undo: '元に戻す (Ctrl+Z)',\n    validationCannotMove: '子要素に移動できません ',\n    autoType: 'オート： ' +\n      'フィールドの型は値から自動的に決定されます。 ' +\n      '(文字列・数値・ブール・null)',\n    objectType: 'オブジェクト： ' +\n      'オブジェクトは順序が決まっていないキーと値のペア組み合わせです。',\n    arrayType: '配列： ' +\n      '配列は順序が決まっている値の集合体です。',\n    stringType: '文字列： ' +\n      'フィールド型は値から決定されませんが、' +\n      '常に文字列として返されます。',\n    modeCodeText: 'コードモード',\n    modeCodeTitle: 'ハイライトモードに切り替え',\n    modeFormText: 'フォームモード',\n    modeFormTitle: 'フォームモードに切り替え',\n    modeTextText: 'テキストモード',\n    modeTextTitle: 'テキストモードに切り替え',\n    modeTreeText: 'ツリーモード',\n    modeTreeTitle: 'ツリーモードに切り替え',\n    modeViewText: 'ビューモード',\n    modeViewTitle: 'ビューモードに切り替え',\n    modePreviewText: 'プレビュー',\n    modePreviewTitle: 'プレビューに切り替え',\n    examples: '例',\n    default: 'デフォルト',\n    containsInvalidProperties: '無効なプロパティが含まれています',\n    containsInvalidItems: '無効なアイテムが含まれています'\n  },\n  'fr-FR': {\n    array: 'Liste',\n    auto: 'Auto',\n    appendText: 'Ajouter',\n    appendTitle: 'Ajouter un champ de type \\'auto\\' après ce champ (Ctrl+Shift+Ins)',\n    appendSubmenuTitle: 'Sélectionner le type du champ à ajouter',\n    appendTitleAuto: 'Ajouter un champ de type \\'auto\\' (Ctrl+Shift+Ins)',\n    ascending: 'Ascendant',\n    ascendingTitle: 'Trier les enfants de ce ${type} par ordre ascendant',\n    actionsMenu: 'Ouvrir le menu des actions (Ctrl+M)',\n    collapseAll: 'Regrouper',\n    descending: 'Descendant',\n    descendingTitle: 'Trier les enfants de ce ${type} par ordre descendant',\n    drag: 'Déplacer (Alt+Shift+Arrows)',\n    duplicateKey: 'Dupliquer la clé',\n    duplicateText: 'Dupliquer',\n    duplicateTitle: 'Dupliquer les champs sélectionnés (Ctrl+D)',\n    duplicateField: 'Dupliquer ce champ (Ctrl+D)',\n    duplicateFieldError: 'Dupliquer le nom de champ',\n    cannotParseFieldError: 'Champ impossible à parser en JSON',\n    cannotParseValueError: 'Valeur impossible à parser en JSON',\n    empty: 'vide',\n    expandAll: 'Étendre',\n    expandTitle: 'Étendre/regrouper ce champ (Ctrl+E). \\n' +\n      'Ctrl+Click pour étendre/regrouper avec tous les champs.',\n    insert: 'Insérer',\n    insertTitle: 'Insérer un champ de type \\'auto\\' avant ce champ (Ctrl+Ins)',\n    insertSub: 'Sélectionner le type de champ à insérer',\n    object: 'Objet',\n    ok: 'Ok',\n    redo: 'Rejouer (Ctrl+Shift+Z)',\n    removeText: 'Supprimer',\n    removeTitle: 'Supprimer les champs sélectionnés (Ctrl+Del)',\n    removeField: 'Supprimer ce champ (Ctrl+Del)',\n    searchTitle: 'Rechercher champs et valeurs',\n    searchNextResultTitle: 'Résultat suivant (Enter)',\n    searchPreviousResultTitle: 'Résultat précédent (Shift + Enter)',\n    selectNode: 'Sélectionner un nœud...',\n    showAll: 'voir tout',\n    showMore: 'voir plus',\n    showMoreStatus: '${visibleChilds} éléments affichés de ${totalChilds}.',\n    sort: 'Trier',\n    sortTitle: 'Trier les champs de ce ${type}',\n    sortTitleShort: 'Trier',\n    sortFieldLabel: 'Champ:',\n    sortDirectionLabel: 'Direction:',\n    sortFieldTitle: 'Sélectionner les champs permettant de trier les listes et objet',\n    sortAscending: 'Ascendant',\n    sortAscendingTitle: 'Trier les champs sélectionnés par ordre ascendant',\n    sortDescending: 'Descendant',\n    sortDescendingTitle: 'Trier les champs sélectionnés par ordre descendant',\n    string: 'Chaîne',\n    transform: 'Transformer',\n    transformTitle: 'Filtrer, trier, or transformer les enfants de ce ${type}',\n    transformTitleShort: 'Filtrer, trier ou transformer le contenu',\n    extract: 'Extraire',\n    extractTitle: 'Extraire ce ${type}',\n    transformQueryTitle: 'Saisir une requête JMESPath',\n    transformWizardLabel: 'Assistant',\n    transformWizardFilter: 'Filtrer',\n    transformWizardSortBy: 'Trier par',\n    transformWizardSelectFields: 'Sélectionner les champs',\n    transformQueryLabel: 'Requête',\n    transformPreviewLabel: 'Prévisualisation',\n    type: 'Type',\n    typeTitle: 'Changer le type de ce champ',\n    openUrl: 'Ctrl+Click ou Ctrl+Enter pour ouvrir l\\'url dans une autre fenêtre',\n    undo: 'Annuler la dernière action (Ctrl+Z)',\n    validationCannotMove: 'Cannot move a field into a child of itself',\n    autoType: 'Champe de type \"auto\". ' +\n      'Ce type de champ est automatiquement déterminé en fonction de la valeur ' +\n      'et peut être de type \"chaîne\", \"nombre\", \"booléen\" ou null.',\n    objectType: 'Champ de type \"objet\". ' +\n      'Un objet contient un ensemble non ordonné de paires clé/valeur.',\n    arrayType: 'Champ de type \"liste\". ' +\n      'Une liste contient une collection ordonnée de valeurs.',\n    stringType: 'Champ de type \"chaîne\". ' +\n      'Ce type de champ n\\'est pas déterminé en fonction de la valeur, ' +\n      'mais retourne systématiquement une chaîne de caractères.',\n    modeEditorTitle: 'Changer mode d\\'édition',\n    modeCodeText: 'Code',\n    modeCodeTitle: 'Activer surlignage code',\n    modeFormText: 'Formulaire',\n    modeFormTitle: 'Activer formulaire',\n    modeTextText: 'Texte',\n    modeTextTitle: 'Activer éditeur texte',\n    modeTreeText: 'Arbre',\n    modeTreeTitle: 'Activer éditeur arbre',\n    modeViewText: 'Lecture seule',\n    modeViewTitle: 'Activer vue arbre',\n    modePreviewText: 'Prévisualisation',\n    modePreviewTitle: 'Activer mode prévisualiser',\n    examples: 'Exemples',\n    default: 'Défaut',\n    containsInvalidProperties: 'Contient des propriétés non valides',\n    containsInvalidItems: 'Contient des éléments invalides'\n  },\n  de: {\n    array: 'Auflistung',\n    auto: 'Auto',\n    appendText: 'anhängen',\n    appendTitle: 'Fügen Sie nach diesem Feld ein neues Feld mit dem Typ \\'auto\\' ein (Strg+Umschalt+Ein)',\n    appendSubmenuTitle: 'Wählen Sie den Typ des neuen Feldes',\n    appendTitleAuto: 'Ein neues Feld vom Typ \\'auto\\' hinzufügen (Strg+Umschalt+Ein)',\n    ascending: 'Aufsteigend',\n    ascendingTitle: 'Sortieren Sie die Elemente dieses ${type} in aufsteigender Reihenfolge',\n    actionsMenu: 'Klicken Sie zum Öffnen des Aktionsmenüs (Strg+M)',\n    cannotParseFieldError: 'Feld kann nicht in JSON geparst werden',\n    cannotParseValueError: 'Wert kann nicht in JSON geparst werden',\n    collapseAll: 'Alle Felder zuklappen',\n    compactTitle: 'JSON-Daten verdichten, alle Leerzeichen entfernen (Strg+Umschalt+\\\\)',\n    descending: 'Absteigend',\n    descendingTitle: 'Sortieren Sie die Elemente dieses ${type} in absteigender Reihenfolge',\n    drag: 'Ziehen, um dieses Feld zu verschieben (Alt+Umschalt+Pfeile)',\n    duplicateKey: 'Doppelter Schlüssel',\n    duplicateText: 'Duplikat',\n    duplicateTitle: 'Ausgewählte Felder duplizieren (Strg+D)',\n    duplicateField: 'Dieses Feld duplizieren (Strg+D)',\n    duplicateFieldError: 'Doppelter Feldname',\n    empty: 'leer',\n    expandAll: 'Alle Felder anzeigen',\n    expandTitle: 'Klicken Sie, um dieses Feld zu erweitern/zu kollabieren (Strg+E). \\nStrg+Klicken Sie, um dieses Feld einschließlich aller Elemente zu erweitern/zu kollabieren.',\n    formatTitle: 'JSON-Daten mit korrekter Einrückung und Zeilenvorschüben formatieren (Strg+\\\\)',\n    insert: 'einfügen',\n    insertTitle: 'Fügen Sie vor diesem Feld ein neues Feld mit dem Typ \\'auto\\' ein (Strg+Einfg)',\n    insertSub: 'Wählen Sie den Typ des neuen Feldes',\n    object: 'Objekt',\n    ok: 'Ok',\n    redo: 'Wiederholen (Strg+Umschalt+Z)',\n    removeText: 'entfernen',\n    removeTitle: 'Ausgewählte Felder entfernen (Strg+Entf)',\n    removeField: 'Dieses Feld entfernen (Strg+Entf)',\n    repairTitle: 'JSON reparieren: Anführungszeichen und Escape-Zeichen korrigieren, Kommentare und JSONP-Notation entfernen, JavaScript-Objekte in JSON umwandeln.',\n    searchTitle: 'Suchfelder und Werte',\n    searchNextResultTitle: 'Nächstes Ergebnis (Enter)',\n    searchPreviousResultTitle: 'Vorheriges Ergebnis (Umschalt + Eingabe)',\n    selectNode: 'Wählen Sie einen Knoten aus...',\n    showAll: 'alle anzeigen',\n    showMore: 'mehr anzeigen',\n    showMoreStatus: 'Anzeige von ${visibleChilds} von ${totalChilds}-Elementen.',\n    sort: 'Sortieren',\n    sortTitle: 'Sortieren Sie die Elemente dieses ${type}',\n    sortTitleShort: 'Inhalt sortieren',\n    sortFieldLabel: 'Feld:',\n    sortDirectionLabel: 'Richtung:',\n    sortFieldTitle: 'Wählen Sie das verschachtelte Feld, nach dem das Array oder Objekt sortiert werden soll.',\n    sortAscending: 'Aufsteigend',\n    sortAscendingTitle: 'Sortieren Sie das ausgewählte Feld in aufsteigender Reihenfolge',\n    sortDescending: 'Absteigend',\n    sortDescendingTitle: 'Sortieren Sie das ausgewählte Feld in absteigender Reihenfolge',\n    string: 'Zeichenfolge',\n    transform: 'Verwandeln',\n    transformTitle: 'Die Elemente dieses ${type} filtern, sortieren oder transformieren',\n    transformTitleShort: 'Inhalte filtern, sortieren oder transformieren',\n    extract: 'Auszug',\n    extractTitle: 'Extrahieren Sie diesen ${type}',\n    transformQueryTitle: 'Eine JMESPath-Abfrage eingeben',\n    transformWizardLabel: 'Zauberer',\n    transformWizardFilter: 'Filter',\n    transformWizardSortBy: 'Sortieren nach',\n    transformWizardSelectFields: 'Felder auswählen',\n    transformQueryLabel: 'Anfrage',\n    transformPreviewLabel: 'Vorschau',\n    type: 'Geben Sie  ein.',\n    typeTitle: 'Ändern Sie den Typ dieses Feldes',\n    openUrl: 'Strg+Klicken oder Strg+Eingabe, um die URL in einem neuen Fenster zu öffnen',\n    undo: 'Letzte Aktion rückgängig machen (Strg+Z)',\n    validationCannotMove: 'Kann ein Feld nicht in ein Kind seiner selbst verschieben',\n    autoType: 'Feldtyp \"auto\". Der Feldtyp wird automatisch aus dem Wert bestimmt und kann ein String, eine Zahl, boolesch oder null sein.',\n    objectType: 'Feldtyp \"Objekt\". Ein Objekt enthält eine ungeordnete Menge von Schlüssel/Wert-Paaren.',\n    arrayType: 'Feldtyp \"Array\". Ein Array enthält eine geordnete Sammlung von Werten.',\n    stringType: 'Feldtyp \"Zeichenfolge\". Der Feldtyp wird nicht aus dem Wert bestimmt, sondern immer als Zeichenfolge zurückgegeben.',\n    modeEditorTitle: 'Editor-Modus umschalten',\n    modeCodeText: 'Code',\n    modeCodeTitle: 'Umschalten auf Code-Highlighter',\n    modeFormText: 'Formular',\n    modeFormTitle: 'Zum Formular-Editor wechseln',\n    modeTextText: 'Text',\n    modeTextTitle: 'Zum Editor für einfachen Text wechseln',\n    modeTreeText: 'Baum',\n    modeTreeTitle: 'Zum Baum-Editor wechseln',\n    modeViewText: 'Siehe',\n    modeViewTitle: 'Zur Baumansicht wechseln',\n    modePreviewText: 'Vorschau',\n    modePreviewTitle: 'In den Vorschau-Modus wechseln',\n    examples: 'Beispiele',\n    default: 'Standardmäßig',\n    containsInvalidProperties: 'Enthält ungültige Eigenschaften',\n    containsInvalidItems: 'Enthält ungültige Elemente'\n  },\n  ru: {\n    array: 'Массив',\n    auto: 'Авто',\n    appendText: 'Добавить',\n    appendTitle: 'Добавить новое поле с типом \\'авто\\' после этого поля (Ctrl+Shift+Ins)',\n    appendSubmenuTitle: 'Выбрать тип поля для добавления',\n    appendTitleAuto: 'Добавить новое поле с типом \\'авто\\' (Ctrl+Shift+Ins)',\n    ascending: 'По возрастанию',\n    ascendingTitle: 'Сортировать ${type} по возрастанию',\n    actionsMenu: 'Нажмите для открытия меню действий (Ctrl+M)',\n    cannotParseFieldError: 'Невозможно преобразовать поле в JSON',\n    cannotParseValueError: 'Невозможно преобразовать значение в JSON',\n    collapseAll: 'Свернуть все',\n    compactTitle: 'Минификация JSON (Ctrl+Shift+I)',\n    descending: 'По убыванию',\n    descendingTitle: 'Сортировать ${type} по убыванию',\n    drag: 'Потяните для перемещения этого поля (Alt+Shift+Arrows)',\n    duplicateKey: 'повторяющийся ключ',\n    duplicateText: 'Дублировать',\n    duplicateTitle: 'Дублирование полей (Ctrl+D)',\n    duplicateField: 'Дублировать поле (Ctrl+D)',\n    duplicateFieldError: 'Дублирование названия поля',\n    empty: 'пустой',\n    expandAll: 'Развернуть все',\n    expandTitle: 'Нажмите для раскрытия/скрытия поля (Ctrl+E)\\n' +\n        'или Ctrl+Click для раскрытия/скрытия всех потомков.',\n    formatTitle: 'Форматирование JSON (Ctrl+I)',\n    insert: 'Вставить',\n    insertTitle: 'Вставить новое поле с типом \\'авто\\' перед этим полем (Ctrl+Ins)',\n    insertSub: 'Выбрать тип поля для вставки',\n    object: 'Объект',\n    ok: 'ОК',\n    redo: 'Повторить (Ctrl+Shift+Z)',\n    removeText: 'Удалить',\n    removeTitle: 'Удалить выбранные поля (Ctrl+Del)',\n    removeField: 'Удалить поле (Ctrl+Del)',\n    repairTitle: 'Восстановите JSON: исправьте кавычки и escape-символы, удалите комментарии и нотацию JSONP, модифицируйте объекты JavaScript в JSON.',\n    searchTitle: 'Поиск',\n    searchNextResultTitle: 'Следующий результат (Enter)',\n    searchPreviousResultTitle: 'Предыдущий результат (Shift + Enter)',\n    selectNode: 'Выбор узла...',\n    showAll: 'показать все',\n    showMore: 'больше',\n    showMoreStatus: '${visibleChilds} из ${totalChilds}',\n    sort: 'Сортировка',\n    sortTitle: 'Сортировка потомков типа ${type}',\n    sortTitleShort: 'Сортировка содержимого',\n    sortFieldLabel: 'Поле:',\n    sortDirectionLabel: 'Направление:',\n    sortFieldTitle: 'Выберите поле для сортировки массива или объекта',\n    sortAscending: 'По возрастанию',\n    sortAscendingTitle: 'Сортировка выбранного поря по возрастанию',\n    sortDescending: 'По убыванию',\n    sortDescendingTitle: 'Сортировка выбранного поря по убыванию',\n    string: 'Строка',\n    transform: 'Модификация',\n    transformTitle: 'Фильтрация, сортировка или модификация данных типа ${type}',\n    transformTitleShort: 'Фильтрация, сортировка или модификация данных',\n    extract: 'Извлечение',\n    extractTitle: 'Извлечь тип ${type}',\n    transformQueryTitle: 'Введите JMESpath запрос',\n    transformWizardLabel: 'Мастер',\n    transformWizardFilter: 'Фильтр',\n    transformWizardSortBy: 'Сортировка',\n    transformWizardSelectFields: 'Поля',\n    transformQueryLabel: 'Запрос',\n    transformPreviewLabel: 'Просмотр',\n    type: 'Тип',\n    typeTitle: 'Изменить тип этого поля',\n    openUrl: 'Ctrl+Click или Ctrl+Enter для открытия url в новом окне',\n    undo: 'Отменить (Ctrl+Z)',\n    validationCannotMove: 'Поле не может быть перемещено в потомка',\n    autoType: 'Тип поля автоматически определяется по значению ' +\n        'и может быть строкой, числом, логическим значением или null.',\n    objectType: 'Объект содержит неупорядоченный набор пар ключ/значение.',\n    arrayType: 'Массив содержит упорядоченный набор значений.',\n    stringType: 'Тип поля не определяется из значения, ' +\n        'но всегда возвращается как строка.',\n    modeEditorTitle: 'Переключение режима редактора',\n    modeCodeText: 'Код',\n    modeCodeTitle: 'Переключить в режим редактора кода',\n    modeFormText: 'Форма',\n    modeFormTitle: 'Переключить в режим формы',\n    modeTextText: 'Текст',\n    modeTextTitle: 'Переключить в режим редактора текста',\n    modeTreeText: 'Дерево',\n    modeTreeTitle: 'Переключить в режим редактора дерева',\n    modeViewText: 'Просмотр дерева',\n    modeViewTitle: 'Переключить в режим просмотра дерева',\n    modePreviewText: 'Просмотр',\n    modePreviewTitle: 'Переключить в режим просмотра',\n    examples: 'Примеры',\n    default: 'По умолчанию',\n    containsInvalidProperties: 'Содержит недопустимые свойства',\n    containsInvalidItems: 'Содержит недопустимые элементы'\n  },\n  ko: {\n    array: '배열',\n    auto: '자동',\n    appendText: '추가',\n    appendTitle: '선택한 요소 아래에 \"자동\" 요소를 추가합니다. (Ctrl + Shift + Ins)',\n    appendSubmenuTitle: '추가할 요소의 유형을 선택해주세요.',\n    appendTitleAuto: '\"자동\" 요소를 추가합니다. (Ctrl + Shift + Ins)',\n    ascending: '오름차순',\n    ascendingTitle: '선택한 ${type}의 하위 요소를 오름차순 정렬합니다.',\n    actionsMenu: '메뉴 열기 (Ctrl + M)',\n    cannotParseFieldError: 'JSON의 요소를 해석할 수 없습니다.',\n    cannotParseValueError: 'JSON의 값을 해석할 수 없습니다.',\n    collapseAll: '모두 접기',\n    compactTitle: '모든 공백을 제거하여 JSON 데이터를 작게 만듭니다. (Ctrl + Shift + I)',\n    descending: '내림차순',\n    descendingTitle: '선택한 ${type}의 하위 요소를 내림차순으로 정렬',\n    drag: '드래그하여 요소를 이동합니다. (Alt + Shift + Arrows)',\n    duplicateKey: '복제키',\n    duplicateText: '복제',\n    duplicateTitle: '선택한 요소를 복제합니다. (Ctrl + D)',\n    duplicateField: '선택한 요소를 복제합니다. (Ctrl + D)',\n    duplicateFieldError: '요소 이름이 중복되었습니다.',\n    empty: '비어있음',\n    expandAll: '모두 열기',\n    expandTitle: '클릭하여 요소를 열거나 닫습니다. (Ctrl + E) \\nCtrl + Click으로 모든 하위 요소를 열거나 닫습니다.',\n    formatTitle: '적절한 들여쓰기 및 줄바꿈으로 JSON 데이터를 정형화합니다. (Ctrl + I)',\n    insert: '삽입',\n    insertTitle: '선택한 요소 위에 새요소를 삽입합니다. (Ctrl + Ins)',\n    insertSub: '삽입할 요소의 유형을 선택해주세요.',\n    object: '객체',\n    ok: '확인',\n    redo: '다시 실행 (Ctrl + Shift + Z)',\n    removeText: '삭제',\n    removeTitle: '선택한 요소를 삭제합니다. (Ctrl + Del)',\n    removeField: '선택한 요소를 삭제합니다. (Ctrl + Del)',\n    repairTitle: 'JSON 교정: JSON 내의 주석과 JSONP 표기법을 지우고 따옴표와 이스케이프 문자를 수정합니다.',\n    searchTitle: '요소 또는 값 찾기',\n    searchNextResultTitle: '다음으로 찾기 (Enter)',\n    searchPreviousResultTitle: '이전으로 찾기 (Shift + Enter)',\n    selectNode: '요소를 선택해주세요...',\n    showAll: '모두보기',\n    showMore: '더보기',\n    showMoreStatus: '${totalChilds} 개의 항목 중 ${visibleChilds} 개를 표시합니다.',\n    sort: '정렬',\n    sortTitle: '선택한 ${type}의 하위 요소를 정렬합니다.',\n    sortTitleShort: '정렬',\n    sortFieldLabel: '요소:',\n    sortDirectionLabel: '순서:',\n    sortFieldTitle: '배열이나 객체를 정렬하는 요소를 선택해주세요.',\n    sortAscending: '오름차순',\n    sortAscendingTitle: '선택한 요소를 오름차순으로 정렬합니다.',\n    sortDescending: '내림차순',\n    sortDescendingTitle: '선택한 요소를 내림차순으로 정렬합니다.',\n    string: '문자',\n    transform: '변환',\n    transformTitle: '선택한 ${type}의 하위 요소를 필터하거나 정렬 또는 변환합니다.',\n    transformTitleShort: '내용을 필터하거나 정렬 또는 변환합니다.',\n    extract: '추출',\n    extractTitle: '선택한 ${type}의 값을 최상위에 위치시킵니다.',\n    transformQueryTitle: 'JMESPath 쿼리를 입력해주세요.',\n    transformWizardLabel: '마법사',\n    transformWizardFilter: '필터',\n    transformWizardSortBy: '정렬',\n    transformWizardSelectFields: '요소를 선택해주세요.',\n    transformQueryLabel: '쿼리',\n    transformPreviewLabel: '미리보기',\n    type: '유형',\n    typeTitle: '선택한 요소의 유형을 변경합니다.',\n    openUrl: 'Ctrl + Click 또는 Ctrl + Enter로 새 창에서 URL 열기',\n    undo: '실행 취소 (Ctrl + Z)',\n    validationCannotMove: '하위 요소로 이동할 수 없습니다.',\n    autoType: '자동: 요소의 형식이 값의 유형으로 결정됩니다. 문자, 숫자, 부울, 또는 null만 허용됩니다.',\n    objectType: '객체: 순서대로 나열되지 않은 이름/값 쌍으로 이루어진 집합입니다.',\n    arrayType: '배열: 순서대로 나열된 값의 집합입니다.',\n    stringType: '문자: 요소의 유형이 값에서 결정되지 않지만 항상 문자로 반환됩니다.',\n    modeEditorTitle: '편집기 유형 변경',\n    modeCodeText: '코드',\n    modeCodeTitle: '형식 교정을 도와주는 기능이 포함된 문자 편집기',\n    modeFormText: '입력 양식',\n    modeFormTitle: '정해진 요소에 값을 입력하는 편집기',\n    modeTextText: '문자',\n    modeTextTitle: '단순 문자 편집기',\n    modeTreeText: '트리',\n    modeTreeTitle: '트리 구조로 표시되는 편집기',\n    modeViewText: '보기',\n    modeViewTitle: '읽기전용 트리 구조로 JSON을 표시',\n    modePreviewText: '미리보기',\n    modePreviewTitle: '읽기전용 문자로 JSON을 표시',\n    examples: '예제',\n    default: '기본값',\n    containsInvalidProperties: '잘못된 속성이 포함되어 있습니다.',\n    containsInvalidItems: '잘못된 항목이 포함되어 있습니다'\n  }\n}\nconst _locales = Object.keys(_defs)\n\nconst _defaultLang = 'en'\nconst userLang = typeof navigator !== 'undefined'\n  ? navigator.language || navigator.userLanguage\n  : undefined\nlet _lang = _locales.find(l => l === userLang) || _defaultLang\n\nexport function setLanguage (lang) {\n  if (!lang) {\n    return\n  }\n  const langFound = _locales.find(l => l === lang)\n  if (langFound) {\n    _lang = langFound\n  } else {\n    console.error('Language not found')\n  }\n}\n\nexport function setLanguages (languages) {\n  if (!languages) {\n    return\n  }\n  for (const language in languages) {\n    const langFound = _locales.find(l => l === language)\n    if (!langFound) {\n      _locales.push(language)\n    }\n    _defs[language] = Object.assign({}, _defs[_defaultLang], _defs[language], languages[language])\n  }\n}\n\nexport function translate (key, data, lang) {\n  if (!lang) {\n    lang = _lang\n  }\n  let text = _defs[lang][key] || _defs[_defaultLang][key] || key\n  if (data) {\n    for (const dataKey in data) {\n      text = text.replace('${' + dataKey + '}', data[dataKey])\n    }\n  }\n  return text\n}\n"
  },
  {
    "path": "src/js/jmespathQuery.js",
    "content": "import jmespath from 'jmespath'\nimport { get, parsePath, parseString } from './util'\n\n/**\n * Build a JMESPath query based on query options coming from the wizard\n * @param {JSON} json   The JSON document for which to build the query.\n *                      Used for context information like determining\n *                      the type of values (string or number)\n * @param {QueryOptions} queryOptions\n * @return {string} Returns a query (as string)\n */\nexport function createQuery (json, queryOptions) {\n  const { sort, filter, projection } = queryOptions\n  let query = ''\n\n  if (filter) {\n    const examplePath = filter.field !== '@'\n      ? ['0'].concat(parsePath('.' + filter.field))\n      : ['0']\n    const exampleValue = get(json, examplePath)\n    const value1 = typeof exampleValue === 'string'\n      ? filter.value\n      : parseString(filter.value)\n\n    query += '[? ' +\n      filter.field + ' ' +\n      filter.relation + ' ' +\n      '`' + JSON.stringify(value1) + '`' +\n      ']'\n  } else {\n    query += Array.isArray(json)\n      ? '[*]'\n      : '@'\n  }\n\n  if (sort) {\n    if (sort.direction === 'desc') {\n      query += ' | reverse(sort_by(@, &' + sort.field + '))'\n    } else {\n      query += ' | sort_by(@, &' + sort.field + ')'\n    }\n  }\n\n  if (projection) {\n    if (query[query.length - 1] !== ']') {\n      query += ' | [*]'\n    }\n\n    if (projection.fields.length === 1) {\n      query += '.' + projection.fields[0]\n    } else if (projection.fields.length > 1) {\n      query += '.{' +\n        projection.fields.map(value => {\n          const parts = value.split('.')\n          const last = parts[parts.length - 1]\n          return last + ': ' + value\n        }).join(', ') +\n        '}'\n    } else { // values.length === 0\n      // ignore\n    }\n  }\n\n  return query\n}\n\n/**\n * Execute a JMESPath query\n * @param {JSON} json\n * @param {string} query\n * @return {JSON} Returns the transformed JSON\n */\nexport function executeQuery (json, query) {\n  return jmespath.search(json, query)\n}\n"
  },
  {
    "path": "src/js/jsonUtils.js",
    "content": "'use strict'\n\n/**\n * Convert part of a JSON object to a JSON string.\n * Use case is to stringify a small part of a large JSON object so you can see\n * a preview.\n *\n * @param {*} value\n * The value to convert to a JSON string.\n *\n * @param {number | string | null} [space]\n * A String or Number object that's used to insert white space into the output\n * JSON string for readability purposes. If this is a Number, it indicates the\n * number of space characters to use as white space; this number is capped at 10\n * if it's larger than that. Values less than 1 indicate that no space should be\n * used. If this is a String, the string (or the first 10 characters of the string,\n * if it's longer than that) is used as white space. If this parameter is not\n * provided (or is null), no white space is used.\n *\n * @param {number} [limit] Maximum size of the string output.\n *\n * @returns {string | undefined} Returns the string representation of the JSON object.\n */\nexport function stringifyPartial (value, space, limit) {\n  let _space // undefined by default\n  if (typeof space === 'number') {\n    if (space > 10) {\n      _space = repeat(' ', 10)\n    } else if (space >= 1) {\n      _space = repeat(' ', space)\n    }\n    // else ignore\n  } else if (typeof space === 'string' && space !== '') {\n    _space = space\n  }\n\n  const output = stringifyValue(value, _space, '', limit)\n\n  return output.length > limit\n    ? (slice(output, limit) + '...')\n    : output\n}\n\n/**\n * Stringify a value\n * @param {*} value\n * @param {string} space\n * @param {string} indent\n * @param {number} limit\n * @return {string | undefined}\n */\nfunction stringifyValue (value, space, indent, limit) {\n  // boolean, null, number, string, or date\n  if (typeof value === 'boolean' || value instanceof Boolean ||\n      value === null ||\n      typeof value === 'number' || value instanceof Number ||\n      typeof value === 'string' || value instanceof String ||\n      value instanceof Date) {\n    return JSON.stringify(value)\n  }\n\n  // array\n  if (Array.isArray(value)) {\n    return stringifyArray(value, space, indent, limit)\n  }\n\n  // object (test lastly!)\n  if (value && typeof value === 'object') {\n    return stringifyObject(value, space, indent, limit)\n  }\n\n  return undefined\n}\n\n/**\n * Stringify an array\n * @param {Array} array\n * @param {string} space\n * @param {string} indent\n * @param {number} limit\n * @return {string}\n */\nfunction stringifyArray (array, space, indent, limit) {\n  const childIndent = space ? (indent + space) : undefined\n  let str = space ? '[\\n' : '['\n\n  for (let i = 0; i < array.length; i++) {\n    const item = array[i]\n\n    if (space) {\n      str += childIndent\n    }\n\n    if (typeof item !== 'undefined' && typeof item !== 'function') {\n      str += stringifyValue(item, space, childIndent, limit)\n    } else {\n      str += 'null'\n    }\n\n    if (i < array.length - 1) {\n      str += space ? ',\\n' : ','\n    }\n\n    // stop as soon as we're exceeding the limit\n    if (str.length > limit) {\n      return str + '...'\n    }\n  }\n\n  str += space ? ('\\n' + indent + ']') : ']'\n  return str\n}\n\n/**\n * Stringify an object\n * @param {Object} object\n * @param {string} space\n * @param {string} indent\n * @param {number} limit\n * @return {string}\n */\nfunction stringifyObject (object, space, indent, limit) {\n  const childIndent = space ? (indent + space) : undefined\n  let first = true\n  let str = space ? '{\\n' : '{'\n\n  if (typeof object.toJSON === 'function') {\n    return stringifyValue(object.toJSON(), space, indent, limit)\n  }\n\n  for (const key in object) {\n    if (hasOwnProperty(object, key)) {\n      const value = object[key]\n\n      if (first) {\n        first = false\n      } else {\n        str += space ? ',\\n' : ','\n      }\n\n      str += space\n        ? (childIndent + '\"' + key + '\": ')\n        : ('\"' + key + '\":')\n\n      str += stringifyValue(value, space, childIndent, limit)\n\n      // stop as soon as we're exceeding the limit\n      if (str.length > limit) {\n        return str + '...'\n      }\n    }\n  }\n\n  str += space ? ('\\n' + indent + '}') : '}'\n  return str\n}\n\n/**\n * Repeat a string a number of times.\n * Simple linear solution, we only need up to 10 iterations in practice\n * @param {string} text\n * @param {number} times\n * @return {string}\n */\nfunction repeat (text, times) {\n  let res = ''\n  while (times-- > 0) {\n    res += text\n  }\n  return res\n}\n\n/**\n * Limit the length of text\n * @param {string} text\n * @param {number} [limit]\n * @return {string}\n */\nfunction slice (text, limit) {\n  return typeof limit === 'number'\n    ? text.slice(0, limit)\n    : text\n}\n\n/**\n * Test whether some text contains a JSON array, i.e. the first\n * non-white space character is a [\n * @param {string} jsonText\n * @return {boolean}\n */\nexport function containsArray (jsonText) {\n  return /^\\s*\\[/.test(jsonText)\n}\n\nfunction hasOwnProperty (object, key) {\n  return Object.prototype.hasOwnProperty.call(object, key)\n}\n"
  },
  {
    "path": "src/js/polyfills.js",
    "content": "if (typeof Element !== 'undefined') {\n  // Polyfill for array remove\n  (() => {\n    function polyfill (item) {\n      if (typeof item !== 'undefined') {\n        if ('remove' in item) {\n          return\n        }\n        Object.defineProperty(item, 'remove', {\n          configurable: true,\n          enumerable: true,\n          writable: true,\n          value: function remove () {\n            if (this.parentNode !== undefined) { this.parentNode.removeChild(this) }\n          }\n        })\n      }\n    }\n\n    if (typeof window.Element !== 'undefined') { polyfill(window.Element.prototype) }\n    if (typeof window.CharacterData !== 'undefined') { polyfill(window.CharacterData.prototype) }\n    if (typeof window.DocumentType !== 'undefined') { polyfill(window.DocumentType.prototype) }\n  })()\n}\n\n// simple polyfill for Array.findIndex\nif (!Array.prototype.findIndex) {\n  // eslint-disable-next-line no-extend-native\n  Object.defineProperty(Array.prototype, 'findIndex', {\n    value: function (predicate) {\n      for (let i = 0; i < this.length; i++) {\n        const element = this[i]\n        if (predicate.call(this, element, i, this)) {\n          return i\n        }\n      }\n      return -1\n    },\n    configurable: true,\n    writable: true\n  })\n}\n\n// Polyfill for Array.find\nif (!Array.prototype.find) {\n  // eslint-disable-next-line no-extend-native\n  Object.defineProperty(Array.prototype, 'find', {\n    value: function (predicate) {\n      const i = this.findIndex(predicate)\n      return this[i]\n    },\n    configurable: true,\n    writable: true\n  })\n}\n\n// Polyfill for String.trim\nif (!String.prototype.trim) {\n  // eslint-disable-next-line no-extend-native\n  String.prototype.trim = function () {\n    return this.replace(/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g, '')\n  }\n}\n"
  },
  {
    "path": "src/js/previewmode.js",
    "content": "'use strict'\n\nimport { jsonrepair } from 'jsonrepair'\nimport {\n  DEFAULT_MODAL_ANCHOR,\n  MAX_PREVIEW_CHARACTERS,\n  PREVIEW_HISTORY_LIMIT,\n  SIZE_LARGE\n} from './constants'\nimport { ErrorTable } from './ErrorTable'\nimport { FocusTracker } from './FocusTracker'\nimport { History } from './History'\nimport { setLanguage, setLanguages, translate } from './i18n'\nimport { createQuery, executeQuery } from './jmespathQuery'\nimport { ModeSwitcher } from './ModeSwitcher'\nimport { showSortModal } from './showSortModal'\nimport { showTransformModal } from './showTransformModal'\nimport { textModeMixins } from './textmode'\nimport {\n  addClassName,\n  debounce,\n  escapeUnicodeChars,\n  formatSize,\n  isObject,\n  limitCharacters,\n  parse,\n  removeClassName,\n  sort,\n  sortObjectKeys\n} from './util'\n\nconst textmode = textModeMixins[0].mixin\n\n// create a mixin with the functions for text mode\nconst previewmode = {}\n\n/**\n * Create a JSON document preview, suitable for processing of large documents\n * @param {Element} container\n * @param {Object} [options]   Object with options. See docs for details.\n * @private\n */\npreviewmode.create = function (container, options = {}) {\n  if (typeof options.statusBar === 'undefined') {\n    options.statusBar = true\n  }\n\n  // setting default for previewmode\n  options.mainMenuBar = options.mainMenuBar !== false\n  options.enableSort = options.enableSort !== false\n  options.enableTransform = options.enableTransform !== false\n  options.createQuery = options.createQuery || createQuery\n  options.executeQuery = options.executeQuery || executeQuery\n\n  this.options = options\n\n  // indentation\n  if (typeof options.indentation === 'number') {\n    this.indentation = Number(options.indentation)\n  } else {\n    this.indentation = 2 // number of spaces\n  }\n\n  // language\n  setLanguages(this.options.languages)\n  setLanguage(this.options.language)\n\n  // determine mode\n  this.mode = 'preview'\n\n  const me = this\n  this.container = container\n  this.dom = {}\n\n  this.json = undefined\n  this.text = ''\n\n  // TODO: JSON Schema support\n\n  // create a debounced validate function\n  this._debouncedValidate = debounce(this.validate.bind(this), this.DEBOUNCE_INTERVAL)\n\n  this.width = container.clientWidth\n  this.height = container.clientHeight\n\n  this.frame = document.createElement('div')\n  this.frame.className = 'jsoneditor jsoneditor-mode-preview'\n  this.frame.onclick = event => {\n    // prevent default submit action when the editor is located inside a form\n    event.preventDefault()\n  }\n\n  // setting the FocusTracker on 'this.frame' to track the editor's focus event\n  const focusTrackerConfig = {\n    target: this.frame,\n    onFocus: this.options.onFocus || null,\n    onBlur: this.options.onBlur || null\n  }\n\n  this.frameFocusTracker = new FocusTracker(focusTrackerConfig)\n\n  this.content = document.createElement('div')\n  this.content.className = 'jsoneditor-outer'\n\n  this.dom.busy = document.createElement('div')\n  this.dom.busy.className = 'jsoneditor-busy'\n  this.dom.busyContent = document.createElement('span')\n  this.dom.busyContent.textContent = 'busy...'\n  this.dom.busy.appendChild(this.dom.busyContent)\n  this.content.appendChild(this.dom.busy)\n\n  this.dom.previewContent = document.createElement('pre')\n  this.dom.previewContent.className = 'jsoneditor-preview'\n  this.dom.previewText = document.createTextNode('')\n  this.dom.previewContent.appendChild(this.dom.previewText)\n  this.content.appendChild(this.dom.previewContent)\n\n  if (this.options.mainMenuBar) {\n    addClassName(this.content, 'has-main-menu-bar')\n\n    // create menu\n    this.menu = document.createElement('div')\n    this.menu.className = 'jsoneditor-menu'\n    this.frame.appendChild(this.menu)\n\n    // create format button\n    const buttonFormat = document.createElement('button')\n    buttonFormat.type = 'button'\n    buttonFormat.className = 'jsoneditor-format'\n    buttonFormat.title = translate('formatTitle')\n    this.menu.appendChild(buttonFormat)\n    buttonFormat.onclick = function handleFormat () {\n      me.executeWithBusyMessage(() => {\n        try {\n          me.format()\n        } catch (err) {\n          me._onError(err)\n        }\n      }, 'formatting...')\n    }\n\n    // create compact button\n    const buttonCompact = document.createElement('button')\n    buttonCompact.type = 'button'\n    buttonCompact.className = 'jsoneditor-compact'\n    buttonCompact.title = translate('compactTitle')\n    this.menu.appendChild(buttonCompact)\n    buttonCompact.onclick = function handleCompact () {\n      me.executeWithBusyMessage(() => {\n        try {\n          me.compact()\n        } catch (err) {\n          me._onError(err)\n        }\n      }, 'compacting...')\n    }\n\n    // create sort button\n    if (this.options.enableSort) {\n      const sort = document.createElement('button')\n      sort.type = 'button'\n      sort.className = 'jsoneditor-sort'\n      sort.title = translate('sortTitleShort')\n      sort.onclick = () => {\n        me._showSortModal()\n      }\n      this.menu.appendChild(sort)\n    }\n\n    // create transform button\n    if (this.options.enableTransform) {\n      const transform = document.createElement('button')\n      transform.type = 'button'\n      transform.title = translate('transformTitleShort')\n      transform.className = 'jsoneditor-transform'\n      transform.onclick = () => {\n        me._showTransformModal()\n      }\n      this.dom.transform = transform\n      this.menu.appendChild(transform)\n    }\n\n    // create repair button\n    const buttonRepair = document.createElement('button')\n    buttonRepair.type = 'button'\n    buttonRepair.className = 'jsoneditor-repair'\n    buttonRepair.title = translate('repairTitle')\n    this.menu.appendChild(buttonRepair)\n    buttonRepair.onclick = () => {\n      if (me.json === undefined) { // only repair if we don't have valid JSON\n        me.executeWithBusyMessage(() => {\n          try {\n            me.repair()\n          } catch (err) {\n            me._onError(err)\n          }\n        }, 'repairing...')\n      }\n    }\n\n    // create history and undo/redo buttons\n    if (this.options.history !== false) { // default option value is true\n      const onHistoryChange = () => {\n        me.dom.undo.disabled = !me.history.canUndo()\n        me.dom.redo.disabled = !me.history.canRedo()\n      }\n\n      const calculateItemSize = item => // times two to account for the json object\n        item.text.length * 2\n\n      this.history = new History(onHistoryChange, calculateItemSize, PREVIEW_HISTORY_LIMIT)\n\n      // create undo button\n      const undo = document.createElement('button')\n      undo.type = 'button'\n      undo.className = 'jsoneditor-undo jsoneditor-separator'\n      undo.title = translate('undo')\n      undo.onclick = () => {\n        const action = me.history.undo()\n        if (action) {\n          me._applyHistory(action)\n        }\n      }\n      this.menu.appendChild(undo)\n      this.dom.undo = undo\n\n      // create redo button\n      const redo = document.createElement('button')\n      redo.type = 'button'\n      redo.className = 'jsoneditor-redo'\n      redo.title = translate('redo')\n      redo.onclick = () => {\n        const action = me.history.redo()\n        if (action) {\n          me._applyHistory(action)\n        }\n      }\n      this.menu.appendChild(redo)\n      this.dom.redo = redo\n\n      // force enabling/disabling the undo/redo button\n      this.history.onChange()\n    }\n\n    // create mode box\n    if (this.options && this.options.modes && this.options.modes.length) {\n      this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch (mode) {\n        // switch mode and restore focus\n        try {\n          me.setMode(mode)\n          me.modeSwitcher.focus()\n        } catch (err) {\n          me._onError(err)\n        }\n      })\n    }\n  }\n\n  const errorTableVisible = Array.isArray(this.options.showErrorTable)\n    ? this.options.showErrorTable.includes(this.mode)\n    : this.options.showErrorTable === true\n\n  this.errorTable = new ErrorTable({\n    errorTableVisible,\n    onToggleVisibility: function () {\n      me.validate()\n    },\n    onFocusLine: null,\n    onChangeHeight: function (height) {\n      // TODO: change CSS to using flex box, remove setting height using JavaScript\n      const statusBarHeight = me.dom.statusBar ? me.dom.statusBar.clientHeight : 0\n      const totalHeight = height + statusBarHeight + 1\n      me.content.style.marginBottom = (-totalHeight) + 'px'\n      me.content.style.paddingBottom = totalHeight + 'px'\n    }\n  })\n\n  this.frame.appendChild(this.content)\n  this.frame.appendChild(this.errorTable.getErrorTable())\n  this.container.appendChild(this.frame)\n\n  if (options.statusBar) {\n    addClassName(this.content, 'has-status-bar')\n\n    const statusBar = document.createElement('div')\n    this.dom.statusBar = statusBar\n    statusBar.className = 'jsoneditor-statusbar'\n    this.frame.appendChild(statusBar)\n\n    this.dom.fileSizeInfo = document.createElement('span')\n    this.dom.fileSizeInfo.className = 'jsoneditor-size-info'\n    this.dom.fileSizeInfo.innerText = ''\n    statusBar.appendChild(this.dom.fileSizeInfo)\n\n    this.dom.arrayInfo = document.createElement('span')\n    this.dom.arrayInfo.className = 'jsoneditor-size-info'\n    this.dom.arrayInfo.innerText = ''\n    statusBar.appendChild(this.dom.arrayInfo)\n\n    statusBar.appendChild(this.errorTable.getErrorCounter())\n    statusBar.appendChild(this.errorTable.getWarningIcon())\n    statusBar.appendChild(this.errorTable.getErrorIcon())\n  }\n\n  this._renderPreview()\n\n  this.setSchema(this.options.schema, this.options.schemaRefs)\n}\n\npreviewmode._renderPreview = function () {\n  const text = this.getText()\n\n  this.dom.previewText.nodeValue = limitCharacters(text, MAX_PREVIEW_CHARACTERS)\n\n  if (this.dom.fileSizeInfo) {\n    this.dom.fileSizeInfo.innerText = 'Size: ' + formatSize(text.length)\n  }\n\n  if (this.dom.arrayInfo) {\n    if (Array.isArray(this.json)) {\n      this.dom.arrayInfo.innerText = ('Array: ' + this.json.length + ' items')\n    } else {\n      this.dom.arrayInfo.innerText = ''\n    }\n  }\n}\n\n/**\n * Handle a change:\n * - Validate JSON schema\n * - Send a callback to the onChange listener if provided\n * @private\n */\npreviewmode._onChange = function () {\n  // validate JSON schema (if configured)\n  this._debouncedValidate()\n\n  // trigger the onChange callback\n  if (this.options.onChange) {\n    try {\n      this.options.onChange()\n    } catch (err) {\n      console.error('Error in onChange callback: ', err)\n    }\n  }\n\n  // trigger the onChangeJSON callback\n  if (this.options.onChangeJSON) {\n    try {\n      this.options.onChangeJSON(this.get())\n    } catch (err) {\n      console.error('Error in onChangeJSON callback: ', err)\n    }\n  }\n\n  // trigger the onChangeText callback\n  if (this.options.onChangeText) {\n    try {\n      this.options.onChangeText(this.getText())\n    } catch (err) {\n      console.error('Error in onChangeText callback: ', err)\n    }\n  }\n}\n\n/**\n * Open a sort modal\n * @private\n */\npreviewmode._showSortModal = function () {\n  const me = this\n\n  function onSort (json, sortedBy) {\n    if (Array.isArray(json)) {\n      const sortedArray = sort(json, sortedBy.path, sortedBy.direction)\n\n      me.sortedBy = sortedBy\n      me._setAndFireOnChange(sortedArray)\n    }\n\n    if (isObject(json)) {\n      const sortedObject = sortObjectKeys(json, sortedBy.direction)\n\n      me.sortedBy = sortedBy\n      me._setAndFireOnChange(sortedObject)\n    }\n  }\n\n  this.executeWithBusyMessage(() => {\n    const container = me.options.modalAnchor || DEFAULT_MODAL_ANCHOR\n    const json = me.get()\n    me._renderPreview() // update array count\n\n    showSortModal(container, json, sortedBy => {\n      me.executeWithBusyMessage(() => {\n        onSort(json, sortedBy)\n      }, 'sorting...')\n    }, me.sortedBy)\n  }, 'parsing...')\n}\n\n/**\n * Open a transform modal\n * @private\n */\npreviewmode._showTransformModal = function () {\n  this.executeWithBusyMessage(() => {\n    const { createQuery, executeQuery, modalAnchor, queryDescription } = this.options\n    const json = this.get()\n\n    this._renderPreview() // update array count\n\n    showTransformModal({\n      container: modalAnchor || DEFAULT_MODAL_ANCHOR,\n      json,\n      queryDescription, // can be undefined\n      createQuery,\n      executeQuery,\n      onTransform: query => {\n        this.executeWithBusyMessage(() => {\n          const updatedJson = executeQuery(json, query)\n          this._setAndFireOnChange(updatedJson)\n        }, 'transforming...')\n      }\n    })\n  }, 'parsing...')\n}\n\n/**\n * Destroy the editor. Clean up DOM, event listeners, and web workers.\n */\npreviewmode.destroy = function () {\n  if (this.frame && this.container && this.frame.parentNode === this.container) {\n    this.container.removeChild(this.frame)\n  }\n\n  if (this.modeSwitcher) {\n    this.modeSwitcher.destroy()\n    this.modeSwitcher = null\n  }\n\n  this._debouncedValidate = null\n\n  if (this.history) {\n    this.history.clear()\n    this.history = null\n  }\n\n  // Removing the FocusTracker set to track the editor's focus event\n  this.frameFocusTracker.destroy()\n}\n\n/**\n * Compact the code in the text editor\n */\npreviewmode.compact = function () {\n  const json = this.get()\n  const text = JSON.stringify(json)\n\n  // we know that in this case the json is still the same, so we pass json too\n  this._setTextAndFireOnChange(text, json)\n}\n\n/**\n * Format the code in the text editor\n */\npreviewmode.format = function () {\n  const json = this.get()\n  const text = JSON.stringify(json, null, this.indentation)\n\n  // we know that in this case the json is still the same, so we pass json too\n  this._setTextAndFireOnChange(text, json)\n}\n\n/**\n * Repair the code in the text editor\n */\npreviewmode.repair = function () {\n  const text = this.getText()\n  try {\n    const repairedText = jsonrepair(text)\n\n    this._setTextAndFireOnChange(repairedText)\n  } catch (err) {\n    // repair was not successful, do nothing\n  }\n}\n\n/**\n * Set focus to the editor\n */\npreviewmode.focus = function () {\n  // we don't really have a place to focus,\n  // let's focus on the transform button\n  this.dom.transform.focus()\n}\n\n/**\n * Set json data in the editor\n * @param {*} json\n */\npreviewmode.set = function (json) {\n  if (this.history) {\n    this.history.clear()\n  }\n\n  this._set(json)\n}\n\n/**\n * Update data. Same as calling `set` in text/code mode.\n * @param {*} json\n */\npreviewmode.update = function (json) {\n  this._set(json)\n}\n\n/**\n * Set json data\n * @param {*} json\n */\npreviewmode._set = function (json) {\n  this.text = undefined\n  this.json = json\n\n  this._renderPreview()\n\n  this._pushHistory()\n\n  // validate JSON schema\n  this._debouncedValidate()\n}\n\npreviewmode._setAndFireOnChange = function (json) {\n  this._set(json)\n  this._onChange()\n}\n\n/**\n * Get json data\n * @return {*} json\n */\npreviewmode.get = function () {\n  if (this.json === undefined) {\n    const text = this.getText()\n\n    this.json = parse(text) // this can throw an error\n  }\n\n  return this.json\n}\n\n/**\n * Get the text contents of the editor\n * @return {String} jsonText\n */\npreviewmode.getText = function () {\n  if (this.text === undefined) {\n    this.text = JSON.stringify(this.json, null, this.indentation)\n\n    if (this.options.escapeUnicode === true) {\n      this.text = escapeUnicodeChars(this.text)\n    }\n  }\n\n  return this.text\n}\n\n/**\n * Set the text contents of the editor\n * @param {String} jsonText\n */\npreviewmode.setText = function (jsonText) {\n  if (this.history) {\n    this.history.clear()\n  }\n\n  this._setText(jsonText)\n}\n\n/**\n * Update the text contents\n * @param {string} jsonText\n */\npreviewmode.updateText = function (jsonText) {\n  // don't update if there are no changes\n  if (this.getText() === jsonText) {\n    return\n  }\n\n  this._setText(jsonText)\n}\n\n/**\n * Set the text contents of the editor\n * @param {string} jsonText\n * @param {*} [json] Optional JSON instance of the text\n * @private\n */\npreviewmode._setText = function (jsonText, json) {\n  if (this.options.escapeUnicode === true) {\n    this.text = escapeUnicodeChars(jsonText)\n  } else {\n    this.text = jsonText\n  }\n  this.json = json\n\n  this._renderPreview()\n\n  if (this.json === undefined) {\n    const me = this\n    this.executeWithBusyMessage(() => {\n      try {\n        // force parsing the json now, else it will be done in validate without feedback\n        me.json = me.get()\n        me._renderPreview()\n        me._pushHistory()\n      } catch (err) {\n        // no need to throw an error, validation will show an error\n      }\n    }, 'parsing...')\n  } else {\n    this._pushHistory()\n  }\n\n  this._debouncedValidate()\n}\n\n/**\n * Set text and fire onChange callback\n * @param {string} jsonText\n * @param {*} [json] Optional JSON instance of the text\n * @private\n */\npreviewmode._setTextAndFireOnChange = function (jsonText, json) {\n  this._setText(jsonText, json)\n  this._onChange()\n}\n\n/**\n * Apply history to the current state\n * @param {{json?: JSON, text?: string}} action\n * @private\n */\npreviewmode._applyHistory = function (action) {\n  this.json = action.json\n  this.text = action.text\n\n  this._renderPreview()\n\n  this._debouncedValidate()\n}\n\n/**\n * Push the current state to history\n * @private\n */\npreviewmode._pushHistory = function () {\n  if (!this.history) {\n    return\n  }\n\n  const action = {\n    text: this.text,\n    json: this.json\n  }\n\n  this.history.add(action)\n}\n\n/**\n * Execute a heavy, blocking action.\n * Before starting the action, show a message on screen like \"parsing...\"\n * @param {function} fn\n * @param {string} message\n */\npreviewmode.executeWithBusyMessage = function (fn, message) {\n  const size = this.getText().length\n\n  if (size > SIZE_LARGE) {\n    const me = this\n    addClassName(me.frame, 'busy')\n    me.dom.busyContent.innerText = message\n\n    setTimeout(() => {\n      fn()\n      removeClassName(me.frame, 'busy')\n      me.dom.busyContent.innerText = ''\n    }, 100)\n  } else {\n    fn()\n  }\n}\n\n// TODO: refactor into composable functions instead of this shaky mixin-like structure\npreviewmode.validate = textmode.validate\npreviewmode._renderErrors = textmode._renderErrors\n\n// define modes\nexport const previewModeMixins = [\n  {\n    mode: 'preview',\n    mixin: previewmode,\n    data: 'json'\n  }\n]\n"
  },
  {
    "path": "src/js/showMoreNodeFactory.js",
    "content": "'use strict'\n\nimport { translate } from './i18n'\n\n/**\n * A factory function to create an ShowMoreNode, which depends on a Node\n * @param {function} Node\n */\nexport function showMoreNodeFactory (Node) {\n  /**\n   * @constructor ShowMoreNode\n   * @extends Node\n   * @param {TreeEditor} editor\n   * @param {Node} parent\n   * Create a new ShowMoreNode. This is a special node which is created\n   * for arrays or objects having more than 100 items\n   */\n  function ShowMoreNode (editor, parent) {\n    /** @type {TreeEditor} */\n    this.editor = editor\n    this.parent = parent\n    this.dom = {}\n  }\n\n  ShowMoreNode.prototype = new Node()\n\n  /**\n   * Return a table row with an append button.\n   * @return {Element} dom   TR element\n   */\n  ShowMoreNode.prototype.getDom = function () {\n    if (this.dom.tr) {\n      return this.dom.tr\n    }\n\n    this._updateEditability()\n\n    // display \"show more\"\n    if (!this.dom.tr) {\n      const me = this\n      const parent = this.parent\n      const showMoreButton = document.createElement('a')\n      showMoreButton.appendChild(document.createTextNode(translate('showMore')))\n      showMoreButton.href = '#'\n      showMoreButton.onclick = event => {\n        // TODO: use callback instead of accessing a method of the parent\n        parent.visibleChilds = Math.floor(parent.visibleChilds / parent.getMaxVisibleChilds() + 1) *\n            parent.getMaxVisibleChilds()\n        me.updateDom()\n        parent.showChilds()\n\n        event.preventDefault()\n        return false\n      }\n\n      const showAllButton = document.createElement('a')\n      showAllButton.appendChild(document.createTextNode(translate('showAll')))\n      showAllButton.href = '#'\n      showAllButton.onclick = event => {\n        // TODO: use callback instead of accessing a method of the parent\n        parent.visibleChilds = Infinity\n        me.updateDom()\n        parent.showChilds()\n\n        event.preventDefault()\n        return false\n      }\n\n      const moreContents = document.createElement('div')\n      const moreText = document.createTextNode(this._getShowMoreText())\n      moreContents.className = 'jsoneditor-show-more'\n      moreContents.appendChild(moreText)\n      moreContents.appendChild(showMoreButton)\n      moreContents.appendChild(document.createTextNode('. '))\n      moreContents.appendChild(showAllButton)\n      moreContents.appendChild(document.createTextNode('. '))\n\n      const tdContents = document.createElement('td')\n      tdContents.appendChild(moreContents)\n\n      const moreTr = document.createElement('tr')\n      if (this.editor.options.mode === 'tree') {\n        moreTr.appendChild(document.createElement('td'))\n        moreTr.appendChild(document.createElement('td'))\n      }\n      moreTr.appendChild(tdContents)\n      moreTr.className = 'jsoneditor-show-more'\n      this.dom.tr = moreTr\n      this.dom.moreContents = moreContents\n      this.dom.moreText = moreText\n    }\n\n    this.updateDom()\n\n    return this.dom.tr\n  }\n\n  /**\n   * Update the HTML dom of the Node\n   */\n  ShowMoreNode.prototype.updateDom = function (options) {\n    if (this.isVisible()) {\n      // attach to the right child node (the first non-visible child)\n      this.dom.tr.node = this.parent.childs[this.parent.visibleChilds]\n\n      if (!this.dom.tr.parentNode) {\n        const nextTr = this.parent._getNextTr()\n        if (nextTr) {\n          nextTr.parentNode.insertBefore(this.dom.tr, nextTr)\n        }\n      }\n\n      // update the counts in the text\n      this.dom.moreText.nodeValue = this._getShowMoreText()\n\n      // update left margin\n      this.dom.moreContents.style.marginLeft = (this.getLevel() + 1) * 24 + 'px'\n    } else {\n      if (this.dom.tr && this.dom.tr.parentNode) {\n        this.dom.tr.parentNode.removeChild(this.dom.tr)\n      }\n    }\n  }\n\n  ShowMoreNode.prototype._getShowMoreText = function () {\n    return translate('showMoreStatus', {\n      visibleChilds: this.parent.visibleChilds,\n      totalChilds: this.parent.childs.length\n    }) + ' '\n  }\n\n  /**\n   * Check whether the ShowMoreNode is currently visible.\n   * the ShowMoreNode is visible when it's parent has more childs than\n   * the current visibleChilds\n   * @return {boolean} isVisible\n   */\n  ShowMoreNode.prototype.isVisible = function () {\n    return this.parent.expanded && this.parent.childs.length > this.parent.visibleChilds\n  }\n\n  /**\n   * Handle an event. The event is caught centrally by the editor\n   * @param {Event} event\n   */\n  ShowMoreNode.prototype.onEvent = function (event) {\n    const type = event.type\n    if (type === 'keydown') {\n      this.onKeyDown(event)\n    }\n  }\n\n  return ShowMoreNode\n}\n"
  },
  {
    "path": "src/js/showSortModal.js",
    "content": "import picoModal from 'picomodal'\nimport { translate } from './i18n'\nimport { contains, getChildPaths } from './util'\n\n/**\n * Show advanced sorting modal\n * @param {HTMLElement} container   The container where to center\n *                                  the modal and create an overlay\n * @param {JSON} json               The JSON data to be sorted.\n * @param {function} onSort         Callback function, invoked with\n *                                  an object containing the selected\n *                                  path and direction\n * @param {Object} options\n *            Available options:\n *                - {string} path              The selected path\n *                - {'asc' | 'desc'} direction The selected direction\n */\nexport function showSortModal (container, json, onSort, options) {\n  const paths = Array.isArray(json)\n    ? getChildPaths(json)\n    : ['']\n  const selectedPath = options && options.path && contains(paths, options.path)\n    ? options.path\n    : paths[0]\n  const selectedDirection = (options && options.direction) || 'asc'\n\n  const content = '<div class=\"pico-modal-contents\">' +\n      '<div class=\"pico-modal-header\">' + translate('sort') + '</div>' +\n      '<form>' +\n      '<table>' +\n      '<tbody>' +\n      '<tr>' +\n      '  <td>' + translate('sortFieldLabel') + ' </td>' +\n      '  <td class=\"jsoneditor-modal-input\">' +\n      '  <div class=\"jsoneditor-select-wrapper\">' +\n      '    <select id=\"field\" title=\"' + translate('sortFieldTitle') + '\">' +\n      '    </select>' +\n      '  </div>' +\n      '  </td>' +\n      '</tr>' +\n      '<tr>' +\n      '  <td>' + translate('sortDirectionLabel') + ' </td>' +\n      '  <td class=\"jsoneditor-modal-input\">' +\n      '  <div id=\"direction\" class=\"jsoneditor-button-group\">' +\n      '<input type=\"button\" ' +\n      'value=\"' + translate('sortAscending') + '\" ' +\n      'title=\"' + translate('sortAscendingTitle') + '\" ' +\n      'data-value=\"asc\" ' +\n      'class=\"jsoneditor-button-first jsoneditor-button-asc\"/>' +\n      '<input type=\"button\" ' +\n      'value=\"' + translate('sortDescending') + '\" ' +\n      'title=\"' + translate('sortDescendingTitle') + '\" ' +\n      'data-value=\"desc\" ' +\n      'class=\"jsoneditor-button-last jsoneditor-button-desc\"/>' +\n      '  </div>' +\n      '  </td>' +\n      '</tr>' +\n      '<tr>' +\n      '<td colspan=\"2\" class=\"jsoneditor-modal-input jsoneditor-modal-actions\">' +\n      '  <input type=\"submit\" id=\"ok\" value=\"' + translate('ok') + '\" />' +\n      '</td>' +\n      '</tr>' +\n      '</tbody>' +\n      '</table>' +\n      '</form>' +\n      '</div>'\n\n  picoModal({\n    parent: container,\n    content,\n    overlayClass: 'jsoneditor-modal-overlay',\n    overlayStyles: {\n      backgroundColor: 'rgb(1,1,1)',\n      opacity: 0.3\n    },\n    modalClass: 'jsoneditor-modal jsoneditor-modal-sort'\n  })\n    .afterCreate(modal => {\n      const form = modal.modalElem().querySelector('form')\n      const ok = modal.modalElem().querySelector('#ok')\n      const field = modal.modalElem().querySelector('#field')\n      const direction = modal.modalElem().querySelector('#direction')\n\n      function preprocessPath (path) {\n        return (path === '')\n          ? '@'\n          : (path[0] === '.')\n              ? path.slice(1)\n              : path\n      }\n\n      paths.forEach(path => {\n        const option = document.createElement('option')\n        option.text = preprocessPath(path)\n        option.value = path\n        field.appendChild(option)\n      })\n\n      function setDirection (value) {\n        direction.value = value\n        direction.className = 'jsoneditor-button-group jsoneditor-button-group-value-' + direction.value\n      }\n\n      field.value = selectedPath || paths[0]\n      setDirection(selectedDirection || 'asc')\n\n      direction.onclick = event => {\n        setDirection(event.target.getAttribute('data-value'))\n      }\n\n      ok.onclick = event => {\n        event.preventDefault()\n        event.stopPropagation()\n\n        modal.close()\n\n        onSort({\n          path: field.value,\n          direction: direction.value\n        })\n      }\n\n      if (form) { // form is not available when JSONEditor is created inside a form\n        form.onsubmit = ok.onclick\n      }\n    })\n    .afterClose(modal => {\n      modal.destroy()\n    })\n    .show()\n}\n"
  },
  {
    "path": "src/js/showTransformModal.js",
    "content": "import picoModal from 'picomodal'\nimport Selectr from './assets/selectr/selectr'\nimport { translate } from './i18n'\nimport { stringifyPartial } from './jsonUtils'\nimport { getChildPaths, debounce } from './util'\nimport { MAX_PREVIEW_CHARACTERS } from './constants'\n\nconst DEFAULT_DESCRIPTION =\n  'Enter a <a href=\"http://jmespath.org\" target=\"_blank\">JMESPath</a> query to filter, sort, or transform the JSON data.<br/>' +\n  'To learn JMESPath, go to <a href=\"http://jmespath.org/tutorial.html\" target=\"_blank\">the interactive tutorial</a>.'\n\n/**\n * Show advanced filter and transform modal using JMESPath\n * @param {Object} params\n * @property {HTMLElement} container   The container where to center\n *                                     the modal and create an overlay\n * @property {JSON} json               The json data to be transformed\n * @property {string} [queryDescription] Optional custom description explaining\n *                                       the transform functionality\n * @property {function} createQuery    Function called to create a query\n *                                     from the wizard form\n * @property {function} executeQuery   Execute a query for the preview pane\n * @property {function} onTransform    Callback invoked with the created\n *                                     query as callback\n */\nexport function showTransformModal (\n  {\n    container,\n    json,\n    queryDescription = DEFAULT_DESCRIPTION,\n    createQuery,\n    executeQuery,\n    onTransform\n  }\n) {\n  const value = json\n\n  const content = '<div class=\"pico-modal-contents\">' +\n      '<div class=\"pico-modal-header\">' + translate('transform') + '</div>' +\n      '<p>' + queryDescription + '</p>' +\n      '<div class=\"jsoneditor-jmespath-label\">' + translate('transformWizardLabel') + ' </div>' +\n      '<div id=\"wizard\" class=\"jsoneditor-jmespath-block jsoneditor-jmespath-wizard\">' +\n      '  <table class=\"jsoneditor-jmespath-wizard-table\">' +\n      '    <tbody>' +\n      '      <tr>' +\n      '        <th>' + translate('transformWizardFilter') + '</th>' +\n      '        <td class=\"jsoneditor-jmespath-filter\">' +\n      '          <div class=\"jsoneditor-inline jsoneditor-jmespath-filter-field\" >' +\n      '            <select id=\"filterField\">' +\n      '            </select>' +\n      '          </div>' +\n      '          <div class=\"jsoneditor-inline jsoneditor-jmespath-filter-relation\" >' +\n      '            <select id=\"filterRelation\">' +\n      '              <option value=\"==\">==</option>' +\n      '              <option value=\"!=\">!=</option>' +\n      '              <option value=\"<\">&lt;</option>' +\n      '              <option value=\"<=\">&lt;=</option>' +\n      '              <option value=\">\">&gt;</option>' +\n      '              <option value=\">=\">&gt;=</option>' +\n      '            </select>' +\n      '          </div>' +\n      '          <div class=\"jsoneditor-inline jsoneditor-jmespath-filter-value\" >' +\n      '            <input type=\"text\" class=\"value\" placeholder=\"value...\" id=\"filterValue\" />' +\n      '          </div>' +\n      '        </td>' +\n      '      </tr>' +\n      '      <tr>' +\n      '        <th>' + translate('transformWizardSortBy') + '</th>' +\n      '        <td class=\"jsoneditor-jmespath-filter\">' +\n      '          <div class=\"jsoneditor-inline jsoneditor-jmespath-sort-field\">' +\n      '            <select id=\"sortField\">' +\n      '            </select>' +\n      '          </div>' +\n      '          <div class=\"jsoneditor-inline jsoneditor-jmespath-sort-order\" >' +\n      '            <select id=\"sortOrder\">' +\n      '              <option value=\"asc\">Ascending</option>' +\n      '              <option value=\"desc\">Descending</option>' +\n      '            </select>' +\n      '          </div>' +\n      '        </td>' +\n      '      </tr>' +\n      '      <tr id=\"selectFieldsPart\">' +\n      '        <th>' + translate('transformWizardSelectFields') + '</th>' +\n      '        <td class=\"jsoneditor-jmespath-filter\">' +\n      '          <select class=\"jsoneditor-jmespath-select-fields\" id=\"selectFields\" multiple></select>' +\n      '        </td>' +\n      '      </tr>' +\n      '    </tbody>' +\n      '  </table>' +\n      '</div>' +\n      '<div class=\"jsoneditor-jmespath-label\">' + translate('transformQueryLabel') + ' </div>' +\n      '<div class=\"jsoneditor-jmespath-block\">' +\n      '  <textarea id=\"query\" ' +\n      '            rows=\"4\" ' +\n      '            autocomplete=\"off\" ' +\n      '            autocorrect=\"off\" ' +\n      '            autocapitalize=\"off\" ' +\n      '            spellcheck=\"false\"' +\n      '            title=\"' + translate('transformQueryTitle') + '\">[*]</textarea>' +\n      '</div>' +\n      '<div class=\"jsoneditor-jmespath-label\">' + translate('transformPreviewLabel') + ' </div>' +\n      '<div class=\"jsoneditor-jmespath-block\">' +\n      '  <textarea id=\"preview\" ' +\n      '      class=\"jsoneditor-transform-preview\"' +\n      '      readonly> </textarea>' +\n      '</div>' +\n      '<div class=\"jsoneditor-jmespath-block jsoneditor-modal-actions\">' +\n      '  <input type=\"submit\" id=\"ok\" value=\"' + translate('ok') + '\" autofocus />' +\n      '</div>' +\n      '</div>'\n\n  picoModal({\n    parent: container,\n    content,\n    overlayClass: 'jsoneditor-modal-overlay',\n    overlayStyles: {\n      backgroundColor: 'rgb(1,1,1)',\n      opacity: 0.3\n    },\n    modalClass: 'jsoneditor-modal jsoneditor-modal-transform',\n    focus: false\n  })\n    .afterCreate(modal => {\n      const elem = modal.modalElem()\n\n      const wizard = elem.querySelector('#wizard')\n      const ok = elem.querySelector('#ok')\n      const filterField = elem.querySelector('#filterField')\n      const filterRelation = elem.querySelector('#filterRelation')\n      const filterValue = elem.querySelector('#filterValue')\n      const sortField = elem.querySelector('#sortField')\n      const sortOrder = elem.querySelector('#sortOrder')\n      const selectFields = elem.querySelector('#selectFields')\n      const query = elem.querySelector('#query')\n      const preview = elem.querySelector('#preview')\n\n      if (!Array.isArray(value)) {\n        wizard.style.fontStyle = 'italic'\n        wizard.textContent = '(wizard not available for objects, only for arrays)'\n      }\n\n      const sortablePaths = getChildPaths(json)\n\n      sortablePaths.forEach(path => {\n        const formattedPath = preprocessPath(path)\n        const filterOption = document.createElement('option')\n        filterOption.text = formattedPath\n        filterOption.value = formattedPath\n        filterField.appendChild(filterOption)\n\n        const sortOption = document.createElement('option')\n        sortOption.text = formattedPath\n        sortOption.value = formattedPath\n        sortField.appendChild(sortOption)\n      })\n\n      const selectablePaths = getChildPaths(json, true).filter(path => path !== '')\n      if (selectablePaths.length > 0) {\n        selectablePaths.forEach(path => {\n          const formattedPath = preprocessPath(path)\n          const option = document.createElement('option')\n          option.text = formattedPath\n          option.value = formattedPath\n          selectFields.appendChild(option)\n        })\n      } else {\n        const selectFieldsPart = elem.querySelector('#selectFieldsPart')\n        if (selectFieldsPart) {\n          selectFieldsPart.style.display = 'none'\n        }\n      }\n\n      const selectrFilterField = new Selectr(filterField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' })\n      const selectrFilterRelation = new Selectr(filterRelation, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'compare...' })\n      const selectrSortField = new Selectr(sortField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' })\n      const selectrSortOrder = new Selectr(sortOrder, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'order...' })\n      const selectrSelectFields = new Selectr(selectFields, { multiple: true, clearable: true, defaultSelected: false, placeholder: 'select fields...' })\n\n      selectrFilterField.on('selectr.change', generateQueryFromWizard)\n      selectrFilterRelation.on('selectr.change', generateQueryFromWizard)\n      filterValue.oninput = generateQueryFromWizard\n      selectrSortField.on('selectr.change', generateQueryFromWizard)\n      selectrSortOrder.on('selectr.change', generateQueryFromWizard)\n      selectrSelectFields.on('selectr.change', generateQueryFromWizard)\n\n      elem.querySelector('.pico-modal-contents').onclick = event => {\n        // prevent the first clear button (in any select box) from getting\n        // focus when clicking anywhere in the modal. Only allow clicking links.\n        if (event.target.nodeName !== 'A') {\n          event.preventDefault()\n        }\n      }\n\n      function preprocessPath (path) {\n        return (path === '')\n          ? '@'\n          : (path[0] === '.')\n              ? path.slice(1)\n              : path\n      }\n\n      function updatePreview () {\n        try {\n          const transformed = executeQuery(value, query.value)\n\n          preview.className = 'jsoneditor-transform-preview'\n          preview.value = stringifyPartial(transformed, 2, MAX_PREVIEW_CHARACTERS)\n\n          ok.disabled = false\n        } catch (err) {\n          preview.className = 'jsoneditor-transform-preview jsoneditor-error'\n          preview.value = err.toString()\n          ok.disabled = true\n        }\n      }\n\n      const debouncedUpdatePreview = debounce(updatePreview, 300)\n\n      function tryCreateQuery (json, queryOptions) {\n        try {\n          query.value = createQuery(json, queryOptions)\n          ok.disabled = false\n\n          debouncedUpdatePreview()\n        } catch (err) {\n          const message = 'Error: an error happened when executing \"createQuery\": ' + (err.message || err.toString())\n\n          query.value = ''\n          ok.disabled = true\n\n          preview.className = 'jsoneditor-transform-preview jsoneditor-error'\n          preview.value = message\n        }\n      }\n\n      function generateQueryFromWizard () {\n        const queryOptions = {}\n\n        if (filterField.value && filterRelation.value && filterValue.value) {\n          queryOptions.filter = {\n            field: filterField.value,\n            relation: filterRelation.value,\n            value: filterValue.value\n          }\n        }\n\n        if (sortField.value && sortOrder.value) {\n          queryOptions.sort = {\n            field: sortField.value,\n            direction: sortOrder.value\n          }\n        }\n\n        if (selectFields.value) {\n          const fields = []\n          for (let i = 0; i < selectFields.options.length; i++) {\n            if (selectFields.options[i].selected) {\n              const selectedField = selectFields.options[i].value\n              fields.push(selectedField)\n            }\n          }\n\n          queryOptions.projection = {\n            fields\n          }\n        }\n\n        tryCreateQuery(json, queryOptions)\n      }\n\n      query.oninput = debouncedUpdatePreview\n\n      ok.onclick = event => {\n        event.preventDefault()\n        event.stopPropagation()\n\n        modal.close()\n\n        onTransform(query.value)\n      }\n\n      // initialize with empty query\n      tryCreateQuery(json, {})\n\n      setTimeout(() => {\n        query.select()\n        query.focus()\n        query.selectionStart = 3\n        query.selectionEnd = 3\n      })\n    })\n    .afterClose(modal => {\n      modal.destroy()\n    })\n    .show()\n}\n"
  },
  {
    "path": "src/js/textmode.js",
    "content": "'use strict'\n\nimport { jsonrepair } from 'jsonrepair'\nimport ace from './ace'\nimport { DEFAULT_MODAL_ANCHOR } from './constants'\nimport { ErrorTable } from './ErrorTable'\nimport { FocusTracker } from './FocusTracker'\nimport { setLanguage, setLanguages, translate } from './i18n'\nimport { createQuery, executeQuery } from './jmespathQuery'\nimport { ModeSwitcher } from './ModeSwitcher'\nimport { showSortModal } from './showSortModal'\nimport { showTransformModal } from './showTransformModal'\nimport { tryRequireThemeJsonEditor } from './tryRequireThemeJsonEditor'\nimport { SchemaTextCompleter } from './SchemaTextCompleter'\nimport {\n  addClassName,\n  debounce,\n  escapeUnicodeChars,\n  getIndexForPosition,\n  getInputSelection,\n  getPositionForPath,\n  improveSchemaError,\n  isObject,\n  isValidationErrorChanged,\n  parse,\n  sort,\n  sortObjectKeys\n} from './util'\nimport { validateCustom } from './validationUtils'\n\n// create a mixin with the functions for text mode\nconst textmode = {}\n\nconst DEFAULT_THEME = 'ace/theme/jsoneditor'\n\n/**\n * Create a text editor\n * @param {Element} container\n * @param {Object} [options]   Object with options. See docs for details.\n * @private\n */\ntextmode.create = function (container, options = {}) {\n  if (typeof options.statusBar === 'undefined') {\n    options.statusBar = true\n  }\n\n  // setting default for textmode\n  options.mainMenuBar = options.mainMenuBar !== false\n  options.enableSort = options.enableSort !== false\n  options.enableTransform = options.enableTransform !== false\n  options.createQuery = options.createQuery || createQuery\n  options.executeQuery = options.executeQuery || executeQuery\n  options.showErrorTable = options.showErrorTable !== undefined\n    ? options.showErrorTable\n    : ['text', 'preview']\n\n  this.options = options\n\n  // indentation\n  if (typeof options.indentation === 'number') {\n    this.indentation = Number(options.indentation)\n  } else {\n    this.indentation = 2 // number of spaces\n  }\n\n  // language\n  setLanguages(this.options.languages)\n  setLanguage(this.options.language)\n\n  // grab ace from options if provided\n  const _ace = options.ace ? options.ace : ace\n  // TODO: make the option options.ace deprecated, it's not needed anymore (see #309)\n\n  // determine mode\n  this.mode = (options.mode === 'code') ? 'code' : 'text'\n  if (this.mode === 'code') {\n    // verify whether Ace editor is available and supported\n    if (typeof _ace === 'undefined') {\n      this.mode = 'text'\n      console.warn('Failed to load Ace editor, falling back to plain text mode. Please use a JSONEditor bundle including Ace, or pass Ace as via the configuration option `ace`.')\n    }\n  }\n\n  // determine theme\n  this.theme = options.theme || DEFAULT_THEME\n  if (this.theme === DEFAULT_THEME && _ace) {\n    tryRequireThemeJsonEditor()\n  }\n\n  if (options.onTextSelectionChange) {\n    this.onTextSelectionChange(options.onTextSelectionChange)\n  }\n\n  const me = this\n  this.container = container\n  this.dom = {}\n  this.aceEditor = undefined // ace code editor\n  this.textarea = undefined // plain text editor (fallback when Ace is not available)\n  this.validateSchema = null\n  this.annotations = []\n  this.lastSchemaErrors = undefined\n\n  // create a debounced validate function\n  this._debouncedValidate = debounce(this._validateAndCatch.bind(this), this.DEBOUNCE_INTERVAL)\n\n  this.width = container.clientWidth\n  this.height = container.clientHeight\n\n  this.frame = document.createElement('div')\n  this.frame.className = 'jsoneditor jsoneditor-mode-' + this.options.mode\n  this.frame.onclick = event => {\n    // prevent default submit action when the editor is located inside a form\n    event.preventDefault()\n  }\n  this.frame.onkeydown = event => {\n    me._onKeyDown(event)\n  }\n\n  // setting the FocusTracker on 'this.frame' to track the editor's focus event\n  const focusTrackerConfig = {\n    target: this.frame,\n    onFocus: this.options.onFocus || null,\n    onBlur: this.options.onBlur || null\n  }\n  this.frameFocusTracker = new FocusTracker(focusTrackerConfig)\n\n  this.content = document.createElement('div')\n  this.content.className = 'jsoneditor-outer'\n\n  if (this.options.mainMenuBar) {\n    addClassName(this.content, 'has-main-menu-bar')\n\n    // create menu\n    this.menu = document.createElement('div')\n    this.menu.className = 'jsoneditor-menu'\n    this.frame.appendChild(this.menu)\n\n    // create format button\n    const buttonFormat = document.createElement('button')\n    buttonFormat.type = 'button'\n    buttonFormat.className = 'jsoneditor-format'\n    buttonFormat.title = translate('formatTitle')\n    this.menu.appendChild(buttonFormat)\n    buttonFormat.onclick = () => {\n      try {\n        me.format()\n        me._onChange()\n      } catch (err) {\n        me._onError(err)\n      }\n    }\n\n    // create compact button\n    const buttonCompact = document.createElement('button')\n    buttonCompact.type = 'button'\n    buttonCompact.className = 'jsoneditor-compact'\n    buttonCompact.title = translate('compactTitle')\n    this.menu.appendChild(buttonCompact)\n    buttonCompact.onclick = () => {\n      try {\n        me.compact()\n        me._onChange()\n      } catch (err) {\n        me._onError(err)\n      }\n    }\n\n    // create sort button\n    if (this.options.enableSort) {\n      const sort = document.createElement('button')\n      sort.type = 'button'\n      sort.className = 'jsoneditor-sort'\n      sort.title = translate('sortTitleShort')\n      sort.onclick = () => {\n        me._showSortModal()\n      }\n      this.menu.appendChild(sort)\n    }\n\n    // create transform button\n    if (this.options.enableTransform) {\n      const transform = document.createElement('button')\n      transform.type = 'button'\n      transform.title = translate('transformTitleShort')\n      transform.className = 'jsoneditor-transform'\n      transform.onclick = () => {\n        me._showTransformModal()\n      }\n      this.menu.appendChild(transform)\n    }\n\n    // create repair button\n    const buttonRepair = document.createElement('button')\n    buttonRepair.type = 'button'\n    buttonRepair.className = 'jsoneditor-repair'\n    buttonRepair.title = translate('repairTitle')\n    this.menu.appendChild(buttonRepair)\n    buttonRepair.onclick = () => {\n      try {\n        me.repair()\n        me._onChange()\n      } catch (err) {\n        me._onError(err)\n      }\n    }\n\n    // create undo/redo buttons\n    if (this.mode === 'code') {\n      // create undo button\n      const undo = document.createElement('button')\n      undo.type = 'button'\n      undo.className = 'jsoneditor-undo jsoneditor-separator'\n      undo.title = translate('undo')\n      undo.onclick = () => {\n        this.aceEditor.getSession().getUndoManager().undo()\n      }\n      this.menu.appendChild(undo)\n      this.dom.undo = undo\n\n      // create redo button\n      const redo = document.createElement('button')\n      redo.type = 'button'\n      redo.className = 'jsoneditor-redo'\n      redo.title = translate('redo')\n      redo.onclick = () => {\n        this.aceEditor.getSession().getUndoManager().redo()\n      }\n      this.menu.appendChild(redo)\n      this.dom.redo = redo\n    }\n\n    // create mode box\n    if (this.options && this.options.modes && this.options.modes.length) {\n      this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch (mode) {\n        // switch mode and restore focus\n        try {\n          me.setMode(mode)\n          me.modeSwitcher.focus()\n        } catch (err) {\n          me._onError(err)\n        }\n      })\n    }\n\n    if (this.mode === 'code') {\n      const poweredBy = document.createElement('a')\n      poweredBy.appendChild(document.createTextNode('powered by ace'))\n      poweredBy.href = 'https://ace.c9.io/'\n      poweredBy.target = '_blank'\n      poweredBy.className = 'jsoneditor-poweredBy'\n      poweredBy.onclick = () => {\n        // TODO: this anchor falls below the margin of the content,\n        // therefore the normal a.href does not work. We use a click event\n        // for now, but this should be fixed.\n        window.open(poweredBy.href, poweredBy.target, 'noreferrer')\n      }\n      this.menu.appendChild(poweredBy)\n    }\n  }\n\n  const emptyNode = {}\n  const isReadOnly = (this.options.onEditable &&\n  typeof (this.options.onEditable === 'function') &&\n  !this.options.onEditable(emptyNode))\n\n  this.frame.appendChild(this.content)\n  this.container.appendChild(this.frame)\n\n  if (this.mode === 'code') {\n    this.editorDom = document.createElement('div')\n    this.editorDom.style.height = '100%' // TODO: move to css\n    this.editorDom.style.width = '100%' // TODO: move to css\n    this.content.appendChild(this.editorDom)\n\n    const aceEditor = _ace.edit(this.editorDom)\n    const aceSession = aceEditor.getSession()\n    aceEditor.$blockScrolling = Infinity\n    aceEditor.setTheme(this.theme)\n    aceEditor.setOptions({ readOnly: isReadOnly })\n    aceEditor.setShowPrintMargin(false)\n    aceEditor.setFontSize('14px')\n    aceSession.setMode('ace/mode/json')\n    aceSession.setTabSize(this.indentation)\n    aceSession.setUseSoftTabs(true)\n    aceSession.setUseWrapMode(true)\n\n    // replace ace setAnnotations with custom function that also covers jsoneditor annotations\n    const originalSetAnnotations = aceSession.setAnnotations\n    aceSession.setAnnotations = function (annotations) {\n      originalSetAnnotations.call(this, annotations && annotations.length ? annotations : me.annotations)\n    }\n\n    // disable Ctrl+L quickkey of Ace (is used by the browser to select the address bar)\n    aceEditor.commands.bindKey('Ctrl-L', null)\n    aceEditor.commands.bindKey('Command-L', null)\n\n    // disable the quickkeys we want to use for Format and Compact\n    aceEditor.commands.bindKey('Ctrl-\\\\', null)\n    aceEditor.commands.bindKey('Command-\\\\', null)\n    aceEditor.commands.bindKey('Ctrl-Shift-\\\\', null)\n    aceEditor.commands.bindKey('Command-Shift-\\\\', null)\n\n    this.aceEditor = aceEditor\n\n    // register onchange event\n    aceEditor.on('change', this._onChange.bind(this))\n    aceEditor.on('changeSelection', this._onSelect.bind(this))\n  } else {\n    // load a plain text textarea\n    const textarea = document.createElement('textarea')\n    textarea.className = 'jsoneditor-text'\n    textarea.spellcheck = false\n    this.content.appendChild(textarea)\n    this.textarea = textarea\n    this.textarea.readOnly = isReadOnly\n\n    // register onchange event\n    if (this.textarea.oninput === null) {\n      this.textarea.oninput = this._onChange.bind(this)\n    } else {\n      // oninput is undefined. For IE8-\n      this.textarea.onchange = this._onChange.bind(this)\n    }\n\n    textarea.onselect = this._onSelect.bind(this)\n    textarea.onmousedown = this._onMouseDown.bind(this)\n    textarea.onblur = this._onBlur.bind(this)\n  }\n\n  this._updateHistoryButtons()\n\n  const errorTableVisible = Array.isArray(this.options.showErrorTable)\n    ? this.options.showErrorTable.includes(this.mode)\n    : this.options.showErrorTable === true\n\n  this.errorTable = new ErrorTable({\n    errorTableVisible,\n    onToggleVisibility: function () {\n      me._validateAndCatch()\n    },\n    onFocusLine: function (line) {\n      me.isFocused = true\n      if (!isNaN(line)) {\n        me.setTextSelection({ row: line, column: 1 }, { row: line, column: 1000 })\n      }\n    },\n    onChangeHeight: function (height) {\n      // TODO: change CSS to using flex box, remove setting height using JavaScript\n      const statusBarHeight = me.dom.statusBar ? me.dom.statusBar.clientHeight : 0\n      const totalHeight = height + statusBarHeight + 1\n      me.content.style.marginBottom = (-totalHeight) + 'px'\n      me.content.style.paddingBottom = totalHeight + 'px'\n    }\n  })\n  this.frame.appendChild(this.errorTable.getErrorTable())\n\n  if (options.statusBar) {\n    addClassName(this.content, 'has-status-bar')\n\n    this.curserInfoElements = {}\n    const statusBar = document.createElement('div')\n    this.dom.statusBar = statusBar\n    statusBar.className = 'jsoneditor-statusbar'\n    this.frame.appendChild(statusBar)\n\n    const lnLabel = document.createElement('span')\n    lnLabel.className = 'jsoneditor-curserinfo-label'\n    lnLabel.innerText = 'Ln:'\n\n    const lnVal = document.createElement('span')\n    lnVal.className = 'jsoneditor-curserinfo-val'\n    lnVal.innerText = '1'\n\n    statusBar.appendChild(lnLabel)\n    statusBar.appendChild(lnVal)\n\n    const colLabel = document.createElement('span')\n    colLabel.className = 'jsoneditor-curserinfo-label'\n    colLabel.innerText = 'Col:'\n\n    const colVal = document.createElement('span')\n    colVal.className = 'jsoneditor-curserinfo-val'\n    colVal.innerText = '1'\n\n    statusBar.appendChild(colLabel)\n    statusBar.appendChild(colVal)\n\n    this.curserInfoElements.colVal = colVal\n    this.curserInfoElements.lnVal = lnVal\n\n    const countLabel = document.createElement('span')\n    countLabel.className = 'jsoneditor-curserinfo-label'\n    countLabel.innerText = 'characters selected'\n    countLabel.style.display = 'none'\n\n    const countVal = document.createElement('span')\n    countVal.className = 'jsoneditor-curserinfo-count'\n    countVal.innerText = '0'\n    countVal.style.display = 'none'\n\n    this.curserInfoElements.countLabel = countLabel\n    this.curserInfoElements.countVal = countVal\n\n    statusBar.appendChild(countVal)\n    statusBar.appendChild(countLabel)\n\n    statusBar.appendChild(this.errorTable.getErrorCounter())\n    statusBar.appendChild(this.errorTable.getWarningIcon())\n    statusBar.appendChild(this.errorTable.getErrorIcon())\n  }\n\n  this.setSchema(this.options.schema, this.options.schemaRefs)\n}\n\ntextmode._onSchemaChange = function (schema, schemaRefs) {\n  if (!this.aceEditor) {\n    return\n  }\n\n  if (this.options.allowSchemaSuggestions && schema) {\n    this.aceEditor.setOption('enableBasicAutocompletion', [new SchemaTextCompleter(schema, schemaRefs)])\n    this.aceEditor.setOption('enableLiveAutocompletion', true)\n  } else {\n    this.aceEditor.setOption('enableBasicAutocompletion', undefined)\n    this.aceEditor.setOption('enableLiveAutocompletion', false)\n  }\n}\n\n/**\n * Handle a change:\n * - Validate JSON schema\n * - Send a callback to the onChange listener if provided\n * @private\n */\ntextmode._onChange = function () {\n  if (this.onChangeDisabled) {\n    return\n  }\n\n  // enable/disable undo/redo buttons\n  setTimeout(() => {\n    if (this._updateHistoryButtons) {\n      this._updateHistoryButtons()\n    }\n  })\n\n  // validate JSON schema (if configured)\n  this._debouncedValidate()\n\n  // trigger the onChange callback\n  if (this.options.onChange) {\n    try {\n      this.options.onChange()\n    } catch (err) {\n      console.error('Error in onChange callback: ', err)\n    }\n  }\n\n  // trigger the onChangeText callback\n  if (this.options.onChangeText) {\n    try {\n      this.options.onChangeText(this.getText())\n    } catch (err) {\n      console.error('Error in onChangeText callback: ', err)\n    }\n  }\n}\n\ntextmode._updateHistoryButtons = function () {\n  if (this.aceEditor && this.dom.undo && this.dom.redo) {\n    const undoManager = this.aceEditor.getSession().getUndoManager()\n\n    if (undoManager && undoManager.hasUndo && undoManager.hasRedo) {\n      this.dom.undo.disabled = !undoManager.hasUndo()\n      this.dom.redo.disabled = !undoManager.hasRedo()\n    }\n  }\n}\n\n/**\n * Open a sort modal\n * @private\n */\ntextmode._showSortModal = function () {\n  try {\n    const me = this\n    const container = this.options.modalAnchor || DEFAULT_MODAL_ANCHOR\n    const json = this.get()\n\n    function onSort (sortedBy) {\n      if (Array.isArray(json)) {\n        const sortedJson = sort(json, sortedBy.path, sortedBy.direction)\n\n        me.sortedBy = sortedBy\n        me.update(sortedJson)\n      }\n\n      if (isObject(json)) {\n        const sortedJson = sortObjectKeys(json, sortedBy.direction)\n\n        me.sortedBy = sortedBy\n        me.update(sortedJson)\n      }\n    }\n\n    showSortModal(container, json, onSort, me.sortedBy)\n  } catch (err) {\n    this._onError(err)\n  }\n}\n\n/**\n * Open a transform modal\n * @private\n */\ntextmode._showTransformModal = function () {\n  try {\n    const { modalAnchor, createQuery, executeQuery, queryDescription } = this.options\n\n    const json = this.get()\n\n    showTransformModal({\n      container: modalAnchor || DEFAULT_MODAL_ANCHOR,\n      json,\n      queryDescription, // can be undefined\n      createQuery,\n      executeQuery,\n      onTransform: query => {\n        const updatedJson = executeQuery(json, query)\n        this.update(updatedJson)\n      }\n    })\n  } catch (err) {\n    this._onError(err)\n  }\n}\n\n/**\n * Handle text selection\n * Calculates the cursor position and selection range and updates menu\n * @private\n */\ntextmode._onSelect = function () {\n  this._updateCursorInfo()\n  this._emitSelectionChange()\n}\n\n/**\n * Event handler for keydown. Handles shortcut keys\n * @param {Event} event\n * @private\n */\ntextmode._onKeyDown = function (event) {\n  const keynum = event.which || event.keyCode\n  let handled = false\n\n  if (keynum === 73 && event.ctrlKey) {\n    if (event.shiftKey) { // Ctrl+Shift+I\n      this.compact()\n      this._onChange()\n    } else { // Ctrl+I\n      this.format()\n      this._onChange()\n    }\n    handled = true\n  }\n\n  if (handled) {\n    event.preventDefault()\n    event.stopPropagation()\n  }\n\n  this._updateCursorInfo()\n  this._emitSelectionChange()\n}\n\n/**\n * Event handler for mousedown.\n * @private\n */\ntextmode._onMouseDown = function () {\n  this._updateCursorInfo()\n  this._emitSelectionChange()\n}\n\n/**\n * Event handler for blur.\n * @private\n */\ntextmode._onBlur = function () {\n  const me = this\n  // this allows to avoid blur when clicking inner elements (like the errors panel)\n  // just make sure to set the isFocused to true on the inner element onclick callback\n  setTimeout(() => {\n    if (!me.isFocused) {\n      me._updateCursorInfo()\n      me._emitSelectionChange()\n    }\n    me.isFocused = false\n  })\n}\n\n/**\n * Update the cursor info and the status bar, if presented\n */\ntextmode._updateCursorInfo = function () {\n  const me = this\n  let line, col, count\n\n  if (this.textarea) {\n    setTimeout(() => { // this to verify we get the most updated textarea cursor selection\n      const selectionRange = getInputSelection(me.textarea)\n\n      if (selectionRange.startIndex !== selectionRange.endIndex) {\n        count = selectionRange.endIndex - selectionRange.startIndex\n      }\n\n      if (count && me.cursorInfo && me.cursorInfo.line === selectionRange.end.row && me.cursorInfo.column === selectionRange.end.column) {\n        line = selectionRange.start.row\n        col = selectionRange.start.column\n      } else {\n        line = selectionRange.end.row\n        col = selectionRange.end.column\n      }\n\n      me.cursorInfo = {\n        line,\n        column: col,\n        count\n      }\n\n      if (me.options.statusBar) {\n        updateDisplay()\n      }\n    }, 0)\n  } else if (this.aceEditor && this.curserInfoElements) {\n    const curserPos = this.aceEditor.getCursorPosition()\n    const selectedText = this.aceEditor.getSelectedText()\n\n    line = curserPos.row + 1\n    col = curserPos.column + 1\n    count = selectedText.length\n\n    me.cursorInfo = {\n      line,\n      column: col,\n      count\n    }\n\n    if (this.options.statusBar) {\n      updateDisplay()\n    }\n  }\n\n  function updateDisplay () {\n    if (me.curserInfoElements.countVal.innerText !== count) {\n      me.curserInfoElements.countVal.innerText = count\n      me.curserInfoElements.countVal.style.display = count ? 'inline' : 'none'\n      me.curserInfoElements.countLabel.style.display = count ? 'inline' : 'none'\n    }\n    me.curserInfoElements.lnVal.innerText = line\n    me.curserInfoElements.colVal.innerText = col\n  }\n}\n\n/**\n * emits selection change callback, if given\n * @private\n */\ntextmode._emitSelectionChange = function () {\n  if (this._selectionChangedHandler) {\n    const currentSelection = this.getTextSelection()\n    this._selectionChangedHandler(currentSelection.start, currentSelection.end, currentSelection.text)\n  }\n}\n\n/**\n * refresh ERROR annotations state\n * error annotations are handled by the ace json mode (ace/mode/json)\n * validation annotations are handled by this mode\n * therefore in order to refresh we send only the annotations of error type in order to maintain its state\n * @private\n */\ntextmode._refreshAnnotations = function () {\n  const session = this.aceEditor && this.aceEditor.getSession()\n  if (session) {\n    const errEnnotations = session.getAnnotations().filter(annotation => annotation.type === 'error')\n    session.setAnnotations(errEnnotations)\n  }\n}\n\n/**\n * Destroy the editor. Clean up DOM, event listeners, and web workers.\n */\ntextmode.destroy = function () {\n  // remove old ace editor\n  if (this.aceEditor) {\n    this.aceEditor.destroy()\n    this.aceEditor = null\n  }\n\n  if (this.frame && this.container && this.frame.parentNode === this.container) {\n    this.container.removeChild(this.frame)\n  }\n\n  if (this.modeSwitcher) {\n    this.modeSwitcher.destroy()\n    this.modeSwitcher = null\n  }\n\n  this.textarea = null\n\n  this._debouncedValidate = null\n\n  // Removing the FocusTracker set to track the editor's focus event\n  this.frameFocusTracker.destroy()\n}\n\n/**\n * Compact the code in the text editor\n */\ntextmode.compact = function () {\n  const json = this.get()\n  const text = JSON.stringify(json)\n  this.updateText(text)\n}\n\n/**\n * Format the code in the text editor\n */\ntextmode.format = function () {\n  const json = this.get()\n  const text = JSON.stringify(json, null, this.indentation)\n  this.updateText(text)\n}\n\n/**\n * Repair the code in the text editor\n */\ntextmode.repair = function () {\n  const text = this.getText()\n  try {\n    const repairedText = jsonrepair(text)\n    this.updateText(repairedText)\n  } catch (err) {\n    // repair was not successful, do nothing\n  }\n}\n\n/**\n * Set focus to the formatter\n */\ntextmode.focus = function () {\n  if (this.textarea) {\n    this.textarea.focus()\n  }\n  if (this.aceEditor) {\n    this.aceEditor.focus()\n  }\n}\n\n/**\n * Resize the formatter\n */\ntextmode.resize = function () {\n  if (this.aceEditor) {\n    const force = false\n    this.aceEditor.resize(force)\n  }\n}\n\n/**\n * Set json data in the formatter\n * @param {*} json\n */\ntextmode.set = function (json) {\n  this.setText(JSON.stringify(json, null, this.indentation))\n}\n\n/**\n * Update data. Same as calling `set` in text/code mode.\n * @param {*} json\n */\ntextmode.update = function (json) {\n  this.updateText(JSON.stringify(json, null, this.indentation))\n}\n\n/**\n * Get json data from the formatter\n * @return {*} json\n */\ntextmode.get = function () {\n  const text = this.getText()\n\n  return parse(text) // this can throw an error\n}\n\n/**\n * Get the text contents of the editor\n * @return {String} jsonText\n */\ntextmode.getText = function () {\n  if (this.textarea) {\n    return this.textarea.value\n  }\n  if (this.aceEditor) {\n    return this.aceEditor.getValue()\n  }\n  return ''\n}\n\n/**\n * Set the text contents of the editor and optionally clear the history\n * @param {String} jsonText\n * @param {boolean} clearHistory   Only applicable for mode 'code'\n * @private\n */\ntextmode._setText = function (jsonText, clearHistory) {\n  const text = (this.options.escapeUnicode === true)\n    ? escapeUnicodeChars(jsonText)\n    : jsonText\n\n  if (this.textarea) {\n    this.textarea.value = text\n  }\n  if (this.aceEditor) {\n    // prevent emitting onChange events while setting new text\n    this.onChangeDisabled = true\n    this.aceEditor.setValue(text, -1)\n    this.onChangeDisabled = false\n\n    if (clearHistory) {\n      // prevent initial undo action clearing the initial contents\n      const me = this\n      setTimeout(() => {\n        if (me.aceEditor) {\n          me.aceEditor.session.getUndoManager().reset()\n        }\n      })\n    }\n\n    setTimeout(() => {\n      if (this._updateHistoryButtons) {\n        this._updateHistoryButtons()\n      }\n    })\n  }\n\n  // validate JSON schema\n  this._debouncedValidate()\n}\n\n/**\n * Set the text contents of the editor\n * @param {String} jsonText\n */\ntextmode.setText = function (jsonText) {\n  this._setText(jsonText, true)\n}\n\n/**\n * Update the text contents\n * @param {string} jsonText\n */\ntextmode.updateText = function (jsonText) {\n  // don't update if there are no changes\n  if (this.getText() === jsonText) {\n    return\n  }\n\n  this._setText(jsonText, false)\n}\n\n/**\n * Validate current JSON object against the configured JSON schema\n * Throws an exception when no JSON schema is configured\n */\ntextmode.validate = function () {\n  let schemaErrors = []\n  let parseErrors = []\n  let json\n  try {\n    json = this.get() // this can fail when there is no valid json\n\n    // execute JSON schema validation (ajv)\n    if (this.validateSchema) {\n      const valid = this.validateSchema(json)\n      if (!valid) {\n        schemaErrors = this.validateSchema.errors.map(error => {\n          error.type = 'validation'\n          return improveSchemaError(error)\n        })\n      }\n    }\n\n    // execute custom validation and after than merge and render all errors\n    // TODO: implement a better mechanism for only using the last validation action\n    this.validationSequence = (this.validationSequence || 0) + 1\n    const me = this\n    const seq = this.validationSequence\n    return validateCustom(json, this.options.onValidate)\n      .then(customValidationErrors => {\n        // only apply when there was no other validation started whilst resolving async results\n        if (seq === me.validationSequence) {\n          const errors = schemaErrors.concat(parseErrors).concat(customValidationErrors)\n          me._renderErrors(errors)\n          if (\n            typeof this.options.onValidationError === 'function' &&\n            isValidationErrorChanged(errors, this.lastSchemaErrors)\n          ) {\n            this.options.onValidationError.call(this, errors)\n          }\n          this.lastSchemaErrors = errors\n        }\n\n        return this.lastSchemaErrors\n      })\n  } catch (err) {\n    if (this.getText()) {\n      // try to extract the line number from the jsonlint error message\n      const match = /\\w*line\\s*(\\d+)\\w*/g.exec(err.message)\n      let line\n      if (match) {\n        line = +match[1]\n      }\n      parseErrors = [{\n        type: 'error',\n        message: err.message.replace(/\\n/g, '<br>'),\n        line\n      }]\n    }\n\n    this._renderErrors(parseErrors)\n\n    if (\n      typeof this.options.onValidationError === 'function' &&\n      isValidationErrorChanged(parseErrors, this.lastSchemaErrors)\n    ) {\n      this.options.onValidationError.call(this, parseErrors)\n    }\n    this.lastSchemaErrors = parseErrors\n\n    return Promise.resolve(this.lastSchemaErrors)\n  }\n}\n\ntextmode._validateAndCatch = function () {\n  this.validate().catch(err => {\n    console.error('Error running validation:', err)\n  })\n}\n\ntextmode._renderErrors = function (errors) {\n  const jsonText = this.getText()\n  const errorPaths = []\n  errors.reduce((acc, curr) => {\n    if (typeof curr.dataPath === 'string' && acc.indexOf(curr.dataPath) === -1) {\n      acc.push(curr.dataPath)\n    }\n    return acc\n  }, errorPaths)\n  const errorLocations = getPositionForPath(jsonText, errorPaths)\n\n  // render annotations in Ace Editor (if any)\n  if (this.aceEditor) {\n    this.annotations = errorLocations.map(errLoc => {\n      const validationErrors = errors.filter(err => err.dataPath === errLoc.path)\n      const message = validationErrors.map(err => err.message).join('\\n')\n      if (message) {\n        return {\n          row: errLoc.line,\n          column: errLoc.column,\n          text: 'Schema validation error' + (validationErrors.length !== 1 ? 's' : '') + ': \\n' + message,\n          type: 'warning',\n          source: 'jsoneditor'\n        }\n      }\n\n      return {}\n    })\n    this._refreshAnnotations()\n  }\n\n  // render errors in the errors table (if any)\n  this.errorTable.setErrors(errors, errorLocations)\n\n  // update the height of the ace editor\n  if (this.aceEditor) {\n    const force = false\n    this.aceEditor.resize(force)\n  }\n}\n\n/**\n * Get the selection details\n * @returns {{start:{row:Number, column:Number},end:{row:Number, column:Number},text:String}}\n */\ntextmode.getTextSelection = function () {\n  let selection = {}\n  if (this.textarea) {\n    const selectionRange = getInputSelection(this.textarea)\n\n    if (this.cursorInfo && this.cursorInfo.line === selectionRange.end.row && this.cursorInfo.column === selectionRange.end.column) {\n      // selection direction is bottom => up\n      selection.start = selectionRange.end\n      selection.end = selectionRange.start\n    } else {\n      selection = selectionRange\n    }\n\n    return {\n      start: selection.start,\n      end: selection.end,\n      text: this.textarea.value.substring(selectionRange.startIndex, selectionRange.endIndex)\n    }\n  }\n\n  if (this.aceEditor) {\n    const aceSelection = this.aceEditor.getSelection()\n    const selectedText = this.aceEditor.getSelectedText()\n    const range = aceSelection.getRange()\n    const lead = aceSelection.getSelectionLead()\n\n    if (lead.row === range.end.row && lead.column === range.end.column) {\n      selection = range\n    } else {\n      // selection direction is bottom => up\n      selection.start = range.end\n      selection.end = range.start\n    }\n\n    return {\n      start: {\n        row: selection.start.row + 1,\n        column: selection.start.column + 1\n      },\n      end: {\n        row: selection.end.row + 1,\n        column: selection.end.column + 1\n      },\n      text: selectedText\n    }\n  }\n}\n\n/**\n * Callback registration for selection change\n * @param {selectionCallback} callback\n *\n * @callback selectionCallback\n */\ntextmode.onTextSelectionChange = function (callback) {\n  if (typeof callback === 'function') {\n    this._selectionChangedHandler = debounce(callback, this.DEBOUNCE_INTERVAL)\n  }\n}\n\n/**\n * Set selection on editor's text\n * @param {{row:Number, column:Number}} startPos selection start position\n * @param {{row:Number, column:Number}} endPos selected end position\n */\ntextmode.setTextSelection = function (startPos, endPos) {\n  if (!startPos || !endPos) return\n\n  if (this.textarea) {\n    const startIndex = getIndexForPosition(this.textarea, startPos.row, startPos.column)\n    const endIndex = getIndexForPosition(this.textarea, endPos.row, endPos.column)\n    if (startIndex > -1 && endIndex > -1) {\n      if (this.textarea.setSelectionRange) {\n        this.textarea.focus()\n        this.textarea.setSelectionRange(startIndex, endIndex)\n      } else if (this.textarea.createTextRange) { // IE < 9\n        const range = this.textarea.createTextRange()\n        range.collapse(true)\n        range.moveEnd('character', endIndex)\n        range.moveStart('character', startIndex)\n        range.select()\n      }\n      const rows = (this.textarea.value.match(/\\n/g) || []).length + 1\n      const lineHeight = this.textarea.scrollHeight / rows\n      const selectionScrollPos = (startPos.row * lineHeight)\n      this.textarea.scrollTop = selectionScrollPos > this.textarea.clientHeight ? (selectionScrollPos - (this.textarea.clientHeight / 2)) : 0\n    }\n  } else if (this.aceEditor) {\n    const range = {\n      start: {\n        row: startPos.row - 1,\n        column: startPos.column - 1\n      },\n      end: {\n        row: endPos.row - 1,\n        column: endPos.column - 1\n      }\n    }\n    this.aceEditor.selection.setRange(range)\n    this.aceEditor.scrollToLine(startPos.row - 1, true)\n  }\n}\n\nfunction load () {\n  try {\n    this.format()\n  } catch (err) {\n    // in case of an error, just move on, failing formatting is not a big deal\n  }\n}\n\n// define modes\nexport const textModeMixins = [\n  {\n    mode: 'text',\n    mixin: textmode,\n    data: 'text',\n    load\n  },\n  {\n    mode: 'code',\n    mixin: textmode,\n    data: 'text',\n    load\n  }\n]\n"
  },
  {
    "path": "src/js/treemode.js",
    "content": "'use strict'\n\nimport { autocomplete } from './autocomplete'\nimport { ContextMenu } from './ContextMenu'\nimport { FocusTracker } from './FocusTracker'\nimport { Highlighter } from './Highlighter'\nimport { setLanguage, setLanguages, translate } from './i18n'\nimport { createQuery, executeQuery } from './jmespathQuery'\nimport { ModeSwitcher } from './ModeSwitcher'\nimport { Node } from './Node'\nimport { NodeHistory } from './NodeHistory'\nimport { SearchBox } from './SearchBox'\nimport { TreePath } from './TreePath'\nimport {\n  addClassName,\n  addEventListener,\n  debounce,\n  getAbsoluteTop,\n  getSelectionOffset,\n  getWindow,\n  hasParentNode,\n  improveSchemaError,\n  isPromise,\n  isValidationErrorChanged,\n  isValidValidationError,\n  parse,\n  removeClassName,\n  removeEventListener,\n  selectContentEditable,\n  setSelectionOffset,\n  tryJsonRepair\n} from './util'\nimport VanillaPicker from './vanilla-picker'\n\n// create a mixin with the functions for tree mode\nconst treemode = {}\n\n/**\n * Create a tree editor\n * @param {Element} container    Container element\n * @param {Object} [options]   Object with options. See docs for details.\n * @private\n */\ntreemode.create = function (container, options) {\n  if (!container) {\n    throw new Error('No container element provided.')\n  }\n  this.container = container\n  this.dom = {}\n  this.highlighter = new Highlighter()\n  this.selection = undefined // will hold the last input selection\n  this.multiselection = {\n    nodes: []\n  }\n  this.validateSchema = null // will be set in .setSchema(schema)\n  this.validationSequence = 0\n  this.errorNodes = []\n  this.lastSchemaErrors = undefined\n\n  this.node = null\n  this.focusTarget = null\n\n  this._setOptions(options)\n\n  if (options.autocomplete) { this.autocomplete = autocomplete(options.autocomplete) }\n\n  if (this.options.history && this.options.mode !== 'view') {\n    this.history = new NodeHistory(this)\n  }\n\n  this._createFrame()\n  this._createTable()\n}\n\n/**\n * Destroy the editor. Clean up DOM, event listeners, and web workers.\n */\ntreemode.destroy = function () {\n  if (this.frame && this.container && this.frame.parentNode === this.container) {\n    this.container.removeChild(this.frame)\n    this.frame = null\n  }\n  this.container = null\n\n  this.dom = null\n\n  this.clear()\n  this.node = null\n  this.focusTarget = null\n  this.selection = null\n  this.multiselection = null\n  this.errorNodes = null\n  this.validateSchema = null\n  this._debouncedValidate = null\n\n  if (this.history) {\n    this.history.destroy()\n    this.history = null\n  }\n\n  if (this.searchBox) {\n    this.searchBox.destroy()\n    this.searchBox = null\n  }\n\n  if (this.modeSwitcher) {\n    this.modeSwitcher.destroy()\n    this.modeSwitcher = null\n  }\n\n  // Removing the FocusTracker set to track the editor's focus event\n  this.frameFocusTracker.destroy()\n}\n\n/**\n * Initialize and set default options\n * @param {Object}  [options]    See description in constructor\n * @private\n */\ntreemode._setOptions = function (options) {\n  this.options = {\n    search: true,\n    history: true,\n    mode: 'tree',\n    name: undefined, // field name of root node\n    schema: null,\n    schemaRefs: null,\n    autocomplete: null,\n    navigationBar: true,\n    mainMenuBar: true,\n    limitDragging: false,\n    onSelectionChange: null,\n    colorPicker: true,\n    onColorPicker: function (parent, color, onChange) {\n      if (VanillaPicker) {\n        // we'll render the color picker on top\n        // when there is not enough space below, and there is enough space above\n        const pickerHeight = 300 // estimated height of the color picker\n        const top = parent.getBoundingClientRect().top\n        const windowHeight = getWindow(parent).innerHeight\n        const showOnTop = ((windowHeight - top) < pickerHeight && top > pickerHeight)\n\n        new VanillaPicker({\n          parent,\n          color,\n          popup: showOnTop ? 'top' : 'bottom',\n          onDone: function (color) {\n            const alpha = color.rgba[3]\n            const hex = (alpha === 1)\n              ? color.hex.substr(0, 7) // return #RRGGBB\n              : color.hex // return #RRGGBBAA\n            onChange(hex)\n          }\n        }).show()\n      } else {\n        console.warn('Cannot open color picker: the `vanilla-picker` library is not included in the bundle. ' +\n            'Either use the full bundle or implement your own color picker using `onColorPicker`.')\n      }\n    },\n    timestampTag: true,\n    timestampFormat: null,\n    createQuery,\n    executeQuery,\n    onEvent: null,\n    enableSort: true,\n    enableTransform: true\n  }\n\n  // copy all options\n  if (options) {\n    Object.keys(options).forEach(prop => {\n      this.options[prop] = options[prop]\n    })\n\n    // default limitDragging to true when a JSON schema is defined\n    if (options.limitDragging == null && options.schema != null) {\n      this.options.limitDragging = true\n    }\n  }\n\n  // compile a JSON schema validator if a JSON schema is provided\n  this.setSchema(this.options.schema, this.options.schemaRefs)\n\n  // create a debounced validate function\n  this._debouncedValidate = debounce(this._validateAndCatch.bind(this), this.DEBOUNCE_INTERVAL)\n\n  if (options.onSelectionChange) {\n    this.onSelectionChange(options.onSelectionChange)\n  }\n\n  setLanguages(this.options.languages)\n  setLanguage(this.options.language)\n}\n\n/**\n * Set new JSON object in editor.\n * Resets the state of the editor (expanded nodes, search, selection).\n *\n * @param {*} json\n */\ntreemode.set = function (json) {\n  // verify if json is valid JSON, ignore when a function\n  if (json instanceof Function || (json === undefined)) {\n    this.clear()\n  } else {\n    this.content.removeChild(this.table) // Take the table offline\n\n    // replace the root node\n    const params = {\n      field: this.options.name,\n      value: json\n    }\n    const node = new Node(this, params)\n    this._setRoot(node)\n\n    // validate JSON schema (if configured)\n    this._validateAndCatch()\n\n    // expand\n    const recurse = false\n    this.node.expand(recurse)\n\n    this.content.appendChild(this.table) // Put the table online again\n  }\n\n  // TODO: maintain history, store last state and previous document\n  if (this.history) {\n    this.history.clear()\n  }\n\n  // clear search\n  if (this.searchBox) {\n    this.searchBox.clear()\n  }\n}\n\n/**\n * Update JSON object in editor.\n * Maintains the state of the editor (expanded nodes, search, selection).\n *\n * @param {*} json\n */\ntreemode.update = function (json) {\n  // don't update if there are no changes\n  if (this.node.deepEqual(json)) {\n    return\n  }\n\n  const selection = this.getSelection()\n\n  // apply the changed json\n  this.onChangeDisabled = true // don't fire an onChange event\n  this.node.update(json)\n  this.onChangeDisabled = false\n\n  // validate JSON schema\n  this._validateAndCatch()\n\n  // update search result if any\n  if (this.searchBox && !this.searchBox.isEmpty()) {\n    this.searchBox.forceSearch()\n  }\n\n  // update selection if any\n  if (selection && selection.start && selection.end) {\n    // only keep/update the selection if both start and end node still exists,\n    // else we clear the selection\n    const startNode = this.node.findNodeByPath(selection.start.path)\n    const endNode = this.node.findNodeByPath(selection.end.path)\n    if (startNode && endNode) {\n      this.setSelection(selection.start, selection.end)\n    } else {\n      this.setSelection({}, {}) // clear selection\n    }\n  } else {\n    this.setSelection({}, {}) // clear selection\n  }\n}\n\n/**\n * Get JSON object from editor\n * @return {Object | undefined} json\n */\ntreemode.get = function () {\n  // TODO: resolve pending debounced input changes if any, but do not resolve invalid inputs\n\n  if (this.node) {\n    return this.node.getValue()\n  } else {\n    return undefined\n  }\n}\n\n/**\n * Get the text contents of the editor\n * @return {String} jsonText\n */\ntreemode.getText = function () {\n  return JSON.stringify(this.get())\n}\n\n/**\n * Set the text contents of the editor.\n * Resets the state of the editor (expanded nodes, search, selection).\n * @param {String} jsonText\n */\ntreemode.setText = function (jsonText) {\n  try {\n    this.set(parse(jsonText)) // this can throw an error\n  } catch (err) {\n    // try to repair json, replace JavaScript notation with JSON notation\n    const repairedJsonText = tryJsonRepair(jsonText)\n\n    // try to parse again\n    this.set(parse(repairedJsonText)) // this can throw an error\n  }\n}\n\n/**\n * Update the text contents of the editor.\n * Maintains the state of the editor (expanded nodes, search, selection).\n * @param {String} jsonText\n */\ntreemode.updateText = function (jsonText) {\n  try {\n    this.update(parse(jsonText)) // this can throw an error\n  } catch (err) {\n    // try to repair json, replace JavaScript notation with JSON notation\n    const repairJsonText = tryJsonRepair(jsonText)\n\n    // try to parse again\n    this.update(parse(repairJsonText)) // this can throw an error\n  }\n}\n\n/**\n * Set a field name for the root node.\n * @param {String | undefined} name\n */\ntreemode.setName = function (name) {\n  this.options.name = name\n  if (this.node) {\n    this.node.updateField(this.options.name)\n  }\n}\n\n/**\n * Get the field name for the root node.\n * @return {String | undefined} name\n */\ntreemode.getName = function () {\n  return this.options.name\n}\n\n/**\n * Set focus to the editor. Focus will be set to:\n * - the first editable field or value, or else\n * - to the expand button of the root node, or else\n * - to the context menu button of the root node, or else\n * - to the first button in the top menu\n */\ntreemode.focus = function () {\n  let input = this.scrollableContent.querySelector('[contenteditable=true]')\n  if (input) {\n    input.focus()\n  } else if (this.node.dom.expand) {\n    this.node.dom.expand.focus()\n  } else if (this.node.dom.menu) {\n    this.node.dom.menu.focus()\n  } else {\n    // focus to the first button in the menu\n    input = this.frame.querySelector('button')\n    if (input) {\n      input.focus()\n    }\n  }\n}\n\n/**\n * Remove the root node from the editor\n */\ntreemode.clear = function () {\n  if (this.node) {\n    this.node.hide()\n    delete this.node\n  }\n\n  if (this.treePath) {\n    this.treePath.reset()\n  }\n}\n\n/**\n * Set the root node for the json editor\n * @param {Node} node\n * @private\n */\ntreemode._setRoot = function (node) {\n  this.clear()\n\n  this.node = node\n  node.setParent(null)\n  node.setField(this.getName(), false)\n  delete node.index\n\n  // append to the dom\n  this.tbody.appendChild(node.getDom())\n}\n\n/**\n * Search text in all nodes\n * The nodes will be expanded when the text is found one of its childs,\n * else it will be collapsed. Searches are case insensitive.\n * @param {String} text\n * @return {Object[]} results  Array with nodes containing the search results\n *                             The result objects contains fields:\n *                             - {Node} node,\n *                             - {String} elem  the dom element name where\n *                                              the result is found ('field' or\n *                                              'value')\n */\ntreemode.search = function (text) {\n  let results\n  if (this.node) {\n    this.content.removeChild(this.table) // Take the table offline\n    results = this.node.search(text)\n    this.content.appendChild(this.table) // Put the table online again\n  } else {\n    results = []\n  }\n\n  return results\n}\n\n/**\n * Expand all nodes\n */\ntreemode.expandAll = function () {\n  if (this.node) {\n    this.content.removeChild(this.table) // Take the table offline\n    this.node.expand()\n    this.content.appendChild(this.table) // Put the table online again\n  }\n}\n\n/**\n * Collapse all nodes\n */\ntreemode.collapseAll = function () {\n  if (this.node) {\n    this.content.removeChild(this.table) // Take the table offline\n    this.node.collapse()\n    this.content.appendChild(this.table) // Put the table online again\n  }\n}\n\n/**\n * Expand/collapse a given JSON node.\n * @param {Object} [options] Available parameters:\n *                         {Array<String>} [path] Path for the node to expand/collapse.\n *                         {Boolean} [isExpand]  When true, expand the node. Else collapse it.\n *                         {Boolean} [recursive] When true, expand/collapse child nodes recursively.\n *                         {Boolean} [withPath]  When true, expand/collapse all nodes of `path` itself.\n */\ntreemode.expand = function (options) {\n  if (!options || !this.node) return\n\n  const node = this.node.findNodeByPath(options.path)\n  if (!node) return\n\n  if (options.withPath) {\n    for (let i = 0; i < options.path.length; i++) {\n      const parentNode = this.node.findNodeByPath(options.path.slice(0, i))\n      if (parentNode) {\n        if (options.isExpand) {\n          parentNode.expand(false)\n        } else {\n          parentNode.collapse(false)\n        }\n      }\n    }\n  }\n\n  if (options.isExpand) {\n    node.expand(options.recursive)\n  } else {\n    node.collapse(options.recursive)\n  }\n}\n\n/**\n * The method onChange is called whenever a field or value is changed, created,\n * deleted, duplicated, etc.\n * @param {String} action  Change action. Available values: \"editField\",\n *                         \"editValue\", \"changeType\", \"appendNode\",\n *                         \"removeNode\", \"duplicateNode\", \"moveNode\", \"expand\",\n *                         \"collapse\".\n * @param {Object} params  Object containing parameters describing the change.\n *                         The parameters in params depend on the action (for\n *                         example for \"editValue\" the Node, old value, and new\n *                         value are provided). params contains all information\n *                         needed to undo or redo the action.\n * @private\n */\ntreemode._onAction = function (action, params) {\n  // add an action to the history\n  if (this.history) {\n    this.history.add(action, params)\n  }\n\n  this._onChange()\n}\n\n/**\n * Handle a change:\n * - Validate JSON schema\n * - Send a callback to the onChange listener if provided\n * @private\n */\ntreemode._onChange = function () {\n  if (this.onChangeDisabled) {\n    return\n  }\n\n  // selection can be changed after undo/redo\n  this.selection = this.getDomSelection()\n\n  // validate JSON schema (if configured)\n  this._debouncedValidate()\n\n  if (this.treePath) {\n    const selectedNode = (this.node && this.selection)\n      ? this.node.findNodeByInternalPath(this.selection.path)\n      : this.multiselection\n        ? this.multiselection.nodes[0]\n        : undefined\n\n    if (selectedNode) {\n      this._updateTreePath(selectedNode.getNodePath())\n    } else {\n      this.treePath.reset()\n    }\n  }\n\n  // trigger the onChange callback\n  if (this.options.onChange) {\n    try {\n      this.options.onChange()\n    } catch (err) {\n      console.error('Error in onChange callback: ', err)\n    }\n  }\n\n  // trigger the onChangeJSON callback\n  if (this.options.onChangeJSON) {\n    try {\n      this.options.onChangeJSON(this.get())\n    } catch (err) {\n      console.error('Error in onChangeJSON callback: ', err)\n    }\n  }\n\n  // trigger the onChangeText callback\n  if (this.options.onChangeText) {\n    try {\n      this.options.onChangeText(this.getText())\n    } catch (err) {\n      console.error('Error in onChangeText callback: ', err)\n    }\n  }\n\n  // trigger the onClassName callback\n  if (this.options.onClassName) {\n    this.node.recursivelyUpdateCssClassesOnNodes()\n  }\n\n  // trigger the onNodeName callback\n  if (this.options.onNodeName && this.node.childs) {\n    try {\n      this.node.recursivelyUpdateNodeName()\n    } catch (err) {\n      console.error('Error in onNodeName callback: ', err)\n    }\n  }\n}\n\n/**\n * Validate current JSON object against the configured JSON schema\n * Throws an exception when no JSON schema is configured\n */\ntreemode.validate = function () {\n  const root = this.node\n  if (!root) { // TODO: this should be redundant but is needed on mode switch\n    return Promise.resolve([])\n  }\n\n  const json = root.getValue()\n\n  // execute JSON schema validation\n  let schemaErrors = []\n  if (this.validateSchema) {\n    const valid = this.validateSchema(json)\n    if (!valid) {\n      // apply all new errors\n      schemaErrors = this.validateSchema.errors\n        .map(error => improveSchemaError(error))\n        .map(function findNode (error) {\n          return {\n            node: root.findNode(error.dataPath),\n            error,\n            type: 'validation'\n          }\n        })\n        .filter(function hasNode (entry) {\n          return entry.node != null\n        })\n    }\n  }\n\n  // execute custom validation and after than merge and render all errors\n  try {\n    this.validationSequence++\n    const me = this\n    const seq = this.validationSequence\n    return this._validateCustom(json)\n      .then(customValidationErrors => {\n        // only apply when there was no other validation started whilst resolving async results\n        if (seq === me.validationSequence) {\n          const errorNodes = [].concat(schemaErrors, customValidationErrors || [])\n          me._renderValidationErrors(errorNodes)\n          if (\n            typeof this.options.onValidationError === 'function' &&\n            isValidationErrorChanged(errorNodes, this.lastSchemaErrors)\n          ) {\n            this.options.onValidationError.call(this, errorNodes)\n          }\n\n          this.lastSchemaErrors = errorNodes\n        }\n\n        return this.lastSchemaErrors\n      })\n  } catch (err) {\n    return Promise.reject(err)\n  }\n}\n\ntreemode._validateAndCatch = function () {\n  this.validate().catch(err => {\n    console.error('Error running validation:', err)\n  })\n}\n\ntreemode._renderValidationErrors = function (errorNodes) {\n  // clear all current errors\n  if (this.errorNodes) {\n    this.errorNodes.forEach(node => {\n      node.setError(null)\n    })\n  }\n\n  // render the new errors\n  const parentPairs = errorNodes\n    .reduce((all, entry) => entry.node\n      .findParents()\n      .filter(parent => !all.some(pair => pair[0] === parent))\n      .map(parent => [parent, entry.node])\n      .concat(all), [])\n\n  this.errorNodes = parentPairs\n    .map(pair => ({\n      node: pair[0],\n      child: pair[1],\n\n      error: {\n        message: pair[0].type === 'object'\n          ? translate('containsInvalidProperties') // object\n          : translate('containsInvalidItems') // array\n      }\n    }))\n    .concat(errorNodes)\n    .map(function setError (entry) {\n      entry.node.setError(entry.error, entry.child)\n      return entry.node\n    })\n}\n\n/**\n * Execute custom validation if configured.\n *\n * Returns a promise resolving with the custom errors (or nothing).\n */\ntreemode._validateCustom = function (json) {\n  try {\n    if (this.options.onValidate) {\n      const root = this.node\n      const customValidateResults = this.options.onValidate(json)\n\n      const resultPromise = isPromise(customValidateResults)\n        ? customValidateResults\n        : Promise.resolve(customValidateResults)\n\n      return resultPromise.then(customValidationPathErrors => {\n        if (Array.isArray(customValidationPathErrors)) {\n          return customValidationPathErrors\n            .filter(error => {\n              const valid = isValidValidationError(error)\n\n              if (!valid) {\n                console.warn('Ignoring a custom validation error with invalid structure. ' +\n                      'Expected structure: {path: [...], message: \"...\"}. ' +\n                      'Actual error:', error)\n              }\n\n              return valid\n            })\n            .map(error => {\n              let node\n              try {\n                node = (error && error.path) ? root.findNodeByPath(error.path) : null\n              } catch (err) {\n                // stay silent here, we throw a generic warning if no node is found\n              }\n              if (!node) {\n                console.warn('Ignoring validation error: node not found. Path:', error.path, 'Error:', error)\n              }\n\n              return {\n                node,\n                error,\n                type: 'customValidation'\n              }\n            })\n            .filter(entry => entry && entry.node && entry.error && entry.error.message)\n        } else {\n          return null\n        }\n      })\n    }\n  } catch (err) {\n    return Promise.reject(err)\n  }\n\n  return Promise.resolve(null)\n}\n\n/**\n * Refresh the rendered contents\n */\ntreemode.refresh = function () {\n  if (this.node) {\n    this.node.updateDom({ recurse: true })\n  }\n}\n\n/**\n * Start autoscrolling when given mouse position is above the top of the\n * editor contents, or below the bottom.\n * @param {Number} mouseY  Absolute mouse position in pixels\n */\ntreemode.startAutoScroll = function (mouseY) {\n  const me = this\n  const content = this.scrollableContent\n  const top = getAbsoluteTop(content)\n  const height = content.clientHeight\n  const bottom = top + height\n  const margin = 24\n  const interval = 50 // ms\n\n  if ((mouseY < top + margin) && content.scrollTop > 0) {\n    this.autoScrollStep = ((top + margin) - mouseY) / 3\n  } else if (mouseY > bottom - margin &&\n      height + content.scrollTop < content.scrollHeight) {\n    this.autoScrollStep = ((bottom - margin) - mouseY) / 3\n  } else {\n    this.autoScrollStep = undefined\n  }\n\n  if (this.autoScrollStep) {\n    if (!this.autoScrollTimer) {\n      this.autoScrollTimer = setInterval(() => {\n        if (me.autoScrollStep) {\n          content.scrollTop -= me.autoScrollStep\n        } else {\n          me.stopAutoScroll()\n        }\n      }, interval)\n    }\n  } else {\n    this.stopAutoScroll()\n  }\n}\n\n/**\n * Stop auto scrolling. Only applicable when scrolling\n */\ntreemode.stopAutoScroll = function () {\n  if (this.autoScrollTimer) {\n    clearTimeout(this.autoScrollTimer)\n    delete this.autoScrollTimer\n  }\n  if (this.autoScrollStep) {\n    delete this.autoScrollStep\n  }\n}\n\n/**\n * Set the focus to an element in the editor, set text selection, and\n * set scroll position.\n * @param {Object} selection  An object containing fields:\n *                            {Element | undefined} dom     The dom element\n *                                                          which has focus\n *                            {Range | TextRange} range     A text selection\n *                            {Node[]} nodes                Nodes in case of multi selection\n *                            {Number} scrollTop            Scroll position\n */\ntreemode.setDomSelection = function (selection) {\n  if (!selection) {\n    return\n  }\n\n  if ('scrollTop' in selection && this.scrollableContent) {\n    // TODO: animated scroll\n    this.scrollableContent.scrollTop = selection.scrollTop\n  }\n  if (selection.paths) {\n    // multi-select\n    const me = this\n    const nodes = selection.paths.map(path => me.node.findNodeByInternalPath(path))\n\n    this.select(nodes)\n  } else {\n    // find the actual DOM element where to apply the focus\n    const node = selection.path\n      ? this.node.findNodeByInternalPath(selection.path)\n      : null\n    const container = (node && selection.domName)\n      ? node.dom[selection.domName]\n      : null\n    if (selection.range && container) {\n      const range = Object.assign({}, selection.range, { container })\n      setSelectionOffset(range)\n    } else if (node) { // just a fallback\n      node.focus()\n    }\n  }\n}\n\n/**\n * Get the current focus\n * @return {Object} selection An object containing fields:\n *                            {Element | undefined} dom     The dom element\n *                                                          which has focus\n *                            {Range | TextRange} range     A text selection\n *                            {Node[]} nodes                Nodes in case of multi selection\n *                            {Number} scrollTop            Scroll position\n */\ntreemode.getDomSelection = function () {\n  // find the node and field name of the current target,\n  // so we can store the current selection in a serializable\n  // way (internal node path and domName)\n  const node = Node.getNodeFromTarget(this.focusTarget)\n  const focusTarget = this.focusTarget\n  const domName = node\n    ? Object.keys(node.dom).find(domName => node.dom[domName] === focusTarget)\n    : null\n\n  let range = getSelectionOffset()\n  if (range && range.container.nodeName !== 'DIV') { // filter on (editable) divs)\n    range = null\n  }\n  if (range && range.container !== focusTarget) {\n    range = null\n  }\n  if (range) {\n    // we cannot rely on the current instance of the container,\n    // we need to store the internal node path and field and\n    // find the actual DOM field when applying the selection\n    delete range.container\n  }\n\n  return {\n    path: node ? node.getInternalPath() : null,\n    domName,\n    range,\n    paths: this.multiselection.length > 0\n      ? this.multiselection.nodes.map(node => node.getInternalPath())\n      : null,\n    scrollTop: this.scrollableContent ? this.scrollableContent.scrollTop : 0\n  }\n}\n\n/**\n * Adjust the scroll position such that given top position is shown at 1/4\n * of the window height.\n * @param {Number} top\n * @param {function(boolean)} [animateCallback] Callback, executed when animation is\n *                                              finished. The callback returns true\n *                                              when animation is finished, or false\n *                                              when not.\n */\ntreemode.scrollTo = function (top, animateCallback) {\n  const content = this.scrollableContent\n  if (content) {\n    const editor = this\n    // cancel any running animation\n    if (editor.animateTimeout) {\n      clearTimeout(editor.animateTimeout)\n      delete editor.animateTimeout\n    }\n    if (editor.animateCallback) {\n      editor.animateCallback(false)\n      delete editor.animateCallback\n    }\n\n    // calculate final scroll position\n    const height = content.clientHeight\n    const bottom = content.scrollHeight - height\n    const finalScrollTop = Math.min(Math.max(top - height / 4, 0), bottom)\n\n    // animate towards the new scroll position\n    const animate = () => {\n      const scrollTop = content.scrollTop\n      const diff = (finalScrollTop - scrollTop)\n      if (Math.abs(diff) > 3) {\n        content.scrollTop += diff / 3\n        editor.animateCallback = animateCallback\n        editor.animateTimeout = setTimeout(animate, 50)\n      } else {\n        // finished\n        if (animateCallback) {\n          animateCallback(true)\n        }\n        content.scrollTop = finalScrollTop\n        delete editor.animateTimeout\n        delete editor.animateCallback\n      }\n    }\n    animate()\n  } else {\n    if (animateCallback) {\n      animateCallback(false)\n    }\n  }\n}\n\n/**\n * Create main frame\n * @private\n */\ntreemode._createFrame = function () {\n  // create the frame\n  this.frame = document.createElement('div')\n  this.frame.className = 'jsoneditor jsoneditor-mode-' + this.options.mode\n  // this.frame.setAttribute(\"tabindex\",\"0\");\n\n  this.container.appendChild(this.frame)\n\n  this.contentOuter = document.createElement('div')\n  this.contentOuter.className = 'jsoneditor-outer'\n\n  // create one global event listener to handle all events from all nodes\n  const editor = this\n  function onEvent (event) {\n    // when switching to mode \"code\" or \"text\" via the menu, some events\n    // are still fired whilst the _onEvent methods is already removed.\n    if (editor._onEvent) {\n      editor._onEvent(event)\n    }\n  }\n\n  // setting the FocusTracker on 'this.frame' to track the editor's focus event\n  const focusTrackerConfig = {\n    target: this.frame,\n    onFocus: this.options.onFocus || null,\n    onBlur: this.options.onBlur || null\n  }\n\n  this.frameFocusTracker = new FocusTracker(focusTrackerConfig)\n\n  this.frame.onclick = event => {\n    const target = event.target// || event.srcElement;\n\n    onEvent(event)\n\n    // prevent default submit action of buttons when editor is located\n    // inside a form\n    if (target.nodeName === 'BUTTON') {\n      event.preventDefault()\n    }\n  }\n  this.frame.oninput = onEvent\n  this.frame.onchange = onEvent\n  this.frame.onkeydown = onEvent\n  this.frame.onkeyup = onEvent\n  this.frame.oncut = onEvent\n  this.frame.onpaste = onEvent\n  this.frame.onmousedown = onEvent\n  this.frame.onmouseup = onEvent\n  this.frame.onmouseover = onEvent\n  this.frame.onmouseout = onEvent\n  // Note: focus and blur events do not propagate, therefore they defined\n  // using an eventListener with useCapture=true\n  // see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html\n  addEventListener(this.frame, 'focus', onEvent, true)\n  addEventListener(this.frame, 'blur', onEvent, true)\n  this.frame.onfocusin = onEvent // for IE\n  this.frame.onfocusout = onEvent // for IE\n\n  if (this.options.mainMenuBar) {\n    addClassName(this.contentOuter, 'has-main-menu-bar')\n\n    // create menu\n    this.menu = document.createElement('div')\n    this.menu.className = 'jsoneditor-menu'\n    this.frame.appendChild(this.menu)\n\n    // create expand all button\n    const expandAll = document.createElement('button')\n    expandAll.type = 'button'\n    expandAll.className = 'jsoneditor-expand-all'\n    expandAll.title = translate('expandAll')\n    expandAll.onclick = () => {\n      editor.expandAll()\n      if (typeof this.options.onExpand === 'function') {\n        this.options.onExpand({\n          path: [],\n          isExpand: true,\n          recursive: true\n        })\n      }\n    }\n    this.menu.appendChild(expandAll)\n\n    // create collapse all button\n    const collapseAll = document.createElement('button')\n    collapseAll.type = 'button'\n    collapseAll.title = translate('collapseAll')\n    collapseAll.className = 'jsoneditor-collapse-all'\n    collapseAll.onclick = () => {\n      editor.collapseAll()\n      if (typeof this.options.onExpand === 'function') {\n        this.options.onExpand({\n          path: [],\n          isExpand: false,\n          recursive: true\n        })\n      }\n    }\n    this.menu.appendChild(collapseAll)\n\n    // create sort button\n    if (this.options.enableSort) {\n      const sort = document.createElement('button')\n      sort.type = 'button'\n      sort.className = 'jsoneditor-sort'\n      sort.title = translate('sortTitleShort')\n      sort.onclick = () => {\n        editor.node.showSortModal()\n      }\n      this.menu.appendChild(sort)\n    }\n\n    // create transform button\n    if (this.options.enableTransform) {\n      const transform = document.createElement('button')\n      transform.type = 'button'\n      transform.title = translate('transformTitleShort')\n      transform.className = 'jsoneditor-transform'\n      transform.onclick = () => {\n        editor.node.showTransformModal()\n      }\n      this.menu.appendChild(transform)\n    }\n\n    // create undo/redo buttons\n    if (this.history) {\n      // create undo button\n      const undo = document.createElement('button')\n      undo.type = 'button'\n      undo.className = 'jsoneditor-undo jsoneditor-separator'\n      undo.title = translate('undo')\n      undo.onclick = () => {\n        editor._onUndo()\n      }\n      this.menu.appendChild(undo)\n      this.dom.undo = undo\n\n      // create redo button\n      const redo = document.createElement('button')\n      redo.type = 'button'\n      redo.className = 'jsoneditor-redo'\n      redo.title = translate('redo')\n      redo.onclick = () => {\n        editor._onRedo()\n      }\n      this.menu.appendChild(redo)\n      this.dom.redo = redo\n\n      // register handler for onchange of history\n      this.history.onChange = () => {\n        undo.disabled = !editor.history.canUndo()\n        redo.disabled = !editor.history.canRedo()\n      }\n      this.history.onChange()\n    }\n\n    // create mode box\n    if (this.options && this.options.modes && this.options.modes.length) {\n      const me = this\n      this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch (mode) {\n        // switch mode and restore focus\n        try {\n          me.setMode(mode)\n          me.modeSwitcher.focus()\n        } catch (err) {\n          me._onError(err)\n        }\n      })\n    }\n\n    // create search box\n    if (this.options.search) {\n      this.searchBox = new SearchBox(this, this.menu)\n    }\n  }\n\n  if (this.options.navigationBar) {\n    // create second menu row for treepath\n    this.navBar = document.createElement('div')\n    this.navBar.className = 'jsoneditor-navigation-bar nav-bar-empty'\n    this.frame.appendChild(this.navBar)\n\n    this.treePath = new TreePath(this.navBar, this.getPopupAnchor())\n    this.treePath.onSectionSelected(this._onTreePathSectionSelected.bind(this))\n    this.treePath.onContextMenuItemSelected(this._onTreePathMenuItemSelected.bind(this))\n  }\n}\n\n/**\n * Perform an undo action\n * @private\n */\ntreemode._onUndo = function () {\n  if (this.history) {\n    // undo last action\n    this.history.undo()\n\n    // fire change event\n    this._onChange()\n  }\n}\n\n/**\n * Perform a redo action\n * @private\n */\ntreemode._onRedo = function () {\n  if (this.history) {\n    // redo last action\n    this.history.redo()\n\n    // fire change event\n    this._onChange()\n  }\n}\n\n/**\n * Event handler\n * @param event\n * @private\n */\ntreemode._onEvent = function (event) {\n  // don't process events when coming from the color picker\n  if (Node.targetIsColorPicker(event.target)) {\n    return\n  }\n\n  const node = Node.getNodeFromTarget(event.target)\n\n  if (event.type === 'keydown') {\n    this._onKeyDown(event)\n  }\n\n  if (node && event.type === 'focus') {\n    this.focusTarget = event.target\n    if (this.options.autocomplete && this.options.autocomplete.trigger === 'focus') {\n      this._showAutoComplete(event.target)\n    }\n  }\n\n  if (event.type === 'mousedown') {\n    this._startDragDistance(event)\n  }\n  if (event.type === 'mousemove' || event.type === 'mouseup' || event.type === 'click') {\n    this._updateDragDistance(event)\n  }\n\n  if (node && this.options && this.options.navigationBar && node && (event.type === 'keydown' || event.type === 'mousedown')) {\n    // apply on next tick, right after the new key press is applied\n    const me = this\n    setTimeout(() => {\n      me._updateTreePath(node.getNodePath())\n    })\n  }\n\n  if (node && node.selected) {\n    if (event.type === 'click') {\n      if (event.target === node.dom.menu) {\n        this.showContextMenu(event.target)\n\n        // stop propagation (else we will open the context menu of a single node)\n        return\n      }\n\n      // deselect a multi selection\n      if (!event.hasMoved) {\n        this.deselect()\n      }\n    }\n\n    if (event.type === 'mousedown') {\n      // drag multiple nodes\n      Node.onDragStart(this.multiselection.nodes, event)\n    }\n  } else {\n    // filter mouse events in the contents part of the editor (not the main menu)\n    if (event.type === 'mousedown' && hasParentNode(event.target, this.content)) {\n      this.deselect()\n\n      if (node && event.target === node.dom.drag) {\n        // drag a singe node\n        Node.onDragStart(node, event)\n      } else if (!node || (event.target !== node.dom.field && event.target !== node.dom.value && event.target !== node.dom.select)) {\n        // select multiple nodes\n        this._onMultiSelectStart(event)\n      }\n    }\n  }\n\n  if (node) {\n    node.onEvent(event)\n  }\n}\n\n/**\n * Update TreePath components\n * @param {Array<Node>} pathNodes list of nodes in path from root to selection\n * @private\n */\ntreemode._updateTreePath = function (pathNodes) {\n  if (pathNodes && pathNodes.length) {\n    removeClassName(this.navBar, 'nav-bar-empty')\n\n    const pathObjs = []\n    pathNodes.forEach(node => {\n      const pathObj = {\n        name: getName(node),\n        node,\n        children: []\n      }\n      if (node.childs && node.childs.length) {\n        node.childs.forEach(childNode => {\n          pathObj.children.push({\n            name: getName(childNode),\n            node: childNode\n          })\n        })\n      }\n      pathObjs.push(pathObj)\n    })\n    this.treePath.setPath(pathObjs)\n  } else {\n    addClassName(this.navBar, 'nav-bar-empty')\n  }\n\n  function getName (node) {\n    return node.parent\n      ? ((node.parent.type === 'array') ? node.index : node.field)\n      : (node.field || node.type)\n  }\n}\n\n/**\n * Callback for tree path section selection - focus the selected node in the tree\n * @param {Object} pathObj path object that was represents the selected section node\n * @private\n */\ntreemode._onTreePathSectionSelected = pathObj => {\n  if (pathObj && pathObj.node) {\n    pathObj.node.expandTo()\n    pathObj.node.focus()\n  }\n}\n\n/**\n * Callback for tree path menu item selection - rebuild the path accrding to the new selection and focus the selected node in the tree\n * @param {Object} pathObj path object that was represents the parent section node\n * @param {String} selection selected section child\n * @private\n */\ntreemode._onTreePathMenuItemSelected = function (pathObj, selection) {\n  if (pathObj && pathObj.children.length) {\n    const selectionObj = pathObj.children.find(obj => obj.name === selection)\n    if (selectionObj && selectionObj.node) {\n      this._updateTreePath(selectionObj.node.getNodePath())\n      selectionObj.node.expandTo()\n      selectionObj.node.focus()\n    }\n  }\n}\n\ntreemode._startDragDistance = function (event) {\n  this.dragDistanceEvent = {\n    initialTarget: event.target,\n    initialPageX: event.pageX,\n    initialPageY: event.pageY,\n    dragDistance: 0,\n    hasMoved: false\n  }\n}\n\ntreemode._updateDragDistance = function (event) {\n  if (!this.dragDistanceEvent) {\n    this._startDragDistance(event)\n  }\n\n  const diffX = event.pageX - this.dragDistanceEvent.initialPageX\n  const diffY = event.pageY - this.dragDistanceEvent.initialPageY\n\n  this.dragDistanceEvent.dragDistance = Math.sqrt(diffX * diffX + diffY * diffY)\n  this.dragDistanceEvent.hasMoved =\n      this.dragDistanceEvent.hasMoved || this.dragDistanceEvent.dragDistance > 10\n\n  event.dragDistance = this.dragDistanceEvent.dragDistance\n  event.hasMoved = this.dragDistanceEvent.hasMoved\n\n  return event.dragDistance\n}\n\n/**\n * Start multi selection of nodes by dragging the mouse\n * @param {MouseEvent} event\n * @private\n */\ntreemode._onMultiSelectStart = function (event) {\n  const node = Node.getNodeFromTarget(event.target)\n\n  if (this.options.mode !== 'tree' || this.options.onEditable !== undefined) {\n    // dragging not allowed in modes 'view' and 'form'\n    // TODO: allow multiselection of items when option onEditable is specified\n    return\n  }\n\n  this.multiselection = {\n    start: node || null,\n    end: null,\n    nodes: []\n  }\n\n  this._startDragDistance(event)\n\n  const editor = this\n  if (!this.mousemove) {\n    this.mousemove = addEventListener(event.view, 'mousemove', event => {\n      editor._onMultiSelect(event)\n    })\n  }\n  if (!this.mouseup) {\n    this.mouseup = addEventListener(event.view, 'mouseup', event => {\n      editor._onMultiSelectEnd(event)\n    })\n  }\n\n  event.preventDefault()\n}\n\n/**\n * Multiselect nodes by dragging\n * @param {MouseEvent} event\n * @private\n */\ntreemode._onMultiSelect = function (event) {\n  event.preventDefault()\n\n  this._updateDragDistance(event)\n  if (!event.hasMoved) {\n    return\n  }\n\n  const node = Node.getNodeFromTarget(event.target)\n\n  if (node) {\n    if (this.multiselection.start == null) {\n      this.multiselection.start = node\n    }\n    this.multiselection.end = node\n  }\n\n  // deselect previous selection\n  this.deselect()\n\n  // find the selected nodes in the range from first to last\n  const start = this.multiselection.start\n  const end = this.multiselection.end || this.multiselection.start\n  if (start && end) {\n    // find the top level childs, all having the same parent\n    this.multiselection.nodes = this._findTopLevelNodes(start, end)\n    if (this.multiselection.nodes && this.multiselection.nodes.length) {\n      const firstNode = this.multiselection.nodes[0]\n      if (this.multiselection.start === firstNode || this.multiselection.start.isDescendantOf(firstNode)) {\n        this.multiselection.direction = 'down'\n      } else {\n        this.multiselection.direction = 'up'\n      }\n    }\n    this.select(this.multiselection.nodes)\n  }\n}\n\n/**\n * End of multiselect nodes by dragging\n * @param {MouseEvent} event\n * @private\n */\ntreemode._onMultiSelectEnd = function (event) {\n  // set focus to the context menu button of the first node\n  const firstNode = this.multiselection.nodes[0]\n  if (firstNode && firstNode.dom.menu) {\n    firstNode.dom.menu.focus()\n  }\n\n  this.multiselection.start = null\n  this.multiselection.end = null\n\n  // cleanup global event listeners\n  if (this.mousemove) {\n    removeEventListener(event.view, 'mousemove', this.mousemove)\n    delete this.mousemove\n  }\n  if (this.mouseup) {\n    removeEventListener(event.view, 'mouseup', this.mouseup)\n    delete this.mouseup\n  }\n}\n\n/**\n * deselect currently selected nodes\n * @param {boolean} [clearStartAndEnd=false]  If true, the `start` and `end`\n *                                            state is cleared too.\n */\ntreemode.deselect = function (clearStartAndEnd) {\n  const selectionChanged = !!this.multiselection.nodes.length\n  this.multiselection.nodes.forEach(node => {\n    node.setSelected(false)\n  })\n  this.multiselection.nodes = []\n\n  if (clearStartAndEnd) {\n    this.multiselection.start = null\n    this.multiselection.end = null\n  }\n\n  if (selectionChanged) {\n    if (this._selectionChangedHandler) {\n      this._selectionChangedHandler()\n    }\n  }\n}\n\n/**\n * select nodes\n * @param {Node[] | Node} nodes\n */\ntreemode.select = function (nodes) {\n  if (!Array.isArray(nodes)) {\n    return this.select([nodes])\n  }\n\n  if (nodes) {\n    this.deselect()\n\n    this.multiselection.nodes = nodes.slice(0)\n\n    const first = nodes[0]\n    nodes.forEach(node => {\n      node.expandPathToNode()\n      node.setSelected(true, node === first)\n    })\n\n    if (this._selectionChangedHandler) {\n      const selection = this.getSelection()\n      this._selectionChangedHandler(selection.start, selection.end)\n    }\n  }\n}\n\n/**\n * From two arbitrary selected nodes, find their shared parent node.\n * From that parent node, select the two child nodes in the brances going to\n * nodes `start` and `end`, and select all childs in between.\n * @param {Node} start\n * @param {Node} end\n * @return {Array.<Node>} Returns an ordered list with child nodes\n * @private\n */\ntreemode._findTopLevelNodes = (start, end) => {\n  const startPath = start.getNodePath()\n  const endPath = end.getNodePath()\n  let i = 0\n  while (i < startPath.length && startPath[i] === endPath[i]) {\n    i++\n  }\n  let root = startPath[i - 1]\n  let startChild = startPath[i]\n  let endChild = endPath[i]\n\n  if (!startChild || !endChild) {\n    if (root.parent) {\n      // startChild is a parent of endChild or vice versa\n      startChild = root\n      endChild = root\n      root = root.parent\n    } else {\n      // we have selected the root node (which doesn't have a parent)\n      startChild = root.childs[0]\n      endChild = root.childs[root.childs.length - 1]\n    }\n  }\n\n  if (root && startChild && endChild) {\n    const startIndex = root.childs.indexOf(startChild)\n    const endIndex = root.childs.indexOf(endChild)\n    const firstIndex = Math.min(startIndex, endIndex)\n    const lastIndex = Math.max(startIndex, endIndex)\n\n    return root.childs.slice(firstIndex, lastIndex + 1)\n  } else {\n    return []\n  }\n}\n\n/**\n * Show autocomplete menu\n * @param {HTMLElement} element\n * @private\n */\ntreemode._showAutoComplete = function (element) {\n  const node = Node.getNodeFromTarget(element)\n\n  let jsonElementType = ''\n  if (element.className.indexOf('jsoneditor-value') >= 0) jsonElementType = 'value'\n  if (element.className.indexOf('jsoneditor-field') >= 0) jsonElementType = 'field'\n\n  if (jsonElementType === '') {\n    // Unknown element field. Could be a button or something else\n    return\n  }\n\n  const self = this\n\n  setTimeout(() => {\n    if (node && (self.options.autocomplete.trigger === 'focus' || element.innerText.length > 0)) {\n      const result = self.options.autocomplete.getOptions(element.innerText, node.getPath(), jsonElementType, node.editor)\n      if (result === null) {\n        self.autocomplete.hideDropDown()\n      } else if (typeof result.then === 'function') {\n        // probably a promise\n        result\n          .then(obj => {\n            if (obj === null) {\n              self.autocomplete.hideDropDown()\n            } else if (obj.options) {\n              self.autocomplete.show(element, obj.startFrom, obj.options)\n            } else {\n              self.autocomplete.show(element, 0, obj)\n            }\n          })\n          .catch(err => {\n            console.error(err)\n          })\n      } else {\n        // definitely not a promise\n        if (result.options) {\n          self.autocomplete.show(element, result.startFrom, result.options)\n        } else {\n          self.autocomplete.show(element, 0, result)\n        }\n      }\n    } else { self.autocomplete.hideDropDown() }\n  }, 50)\n}\n\n/**\n * Event handler for keydown. Handles shortcut keys\n * @param {Event} event\n * @private\n */\ntreemode._onKeyDown = function (event) {\n  const keynum = event.which || event.keyCode\n  const altKey = event.altKey\n  const ctrlKey = event.ctrlKey\n  const metaKey = event.metaKey\n  const shiftKey = event.shiftKey\n  let handled = false\n  const currentTarget = this.focusTarget\n\n  if (keynum === 9) { // Tab or Shift+Tab\n    const me = this\n    setTimeout(() => {\n      /*\n          - Checking for change in focusTarget\n          - Without the check,\n            pressing tab after reaching the final DOM element in the editor will\n            set the focus back to it than passing focus outside the editor\n      */\n      if (me.focusTarget !== currentTarget) {\n        // select all text when moving focus to an editable div\n        selectContentEditable(me.focusTarget)\n      }\n    }, 0)\n  }\n\n  if (this.searchBox) {\n    if (ctrlKey && keynum === 70) { // Ctrl+F\n      this.searchBox.dom.search.focus()\n      this.searchBox.dom.search.select()\n      handled = true\n    } else if (keynum === 114 || (ctrlKey && keynum === 71)) { // F3 or Ctrl+G\n      const focus = true\n      if (!shiftKey) {\n        // select next search result (F3 or Ctrl+G)\n        this.searchBox.next(focus)\n      } else {\n        // select previous search result (Shift+F3 or Ctrl+Shift+G)\n        this.searchBox.previous(focus)\n      }\n\n      handled = true\n    }\n  }\n\n  if (this.history) {\n    if (ctrlKey && !shiftKey && keynum === 90) { // Ctrl+Z\n      // undo\n      this._onUndo()\n      handled = true\n    } else if (ctrlKey && shiftKey && keynum === 90) { // Ctrl+Shift+Z\n      // redo\n      this._onRedo()\n      handled = true\n    }\n  }\n\n  if ((this.options.autocomplete) && (!handled)) {\n    if (!ctrlKey && !altKey && !metaKey && (event.key.length === 1 || keynum === 8 || keynum === 46)) {\n      handled = false\n      // Activate autocomplete\n      this._showAutoComplete(event.target)\n    }\n  }\n\n  if (handled) {\n    event.preventDefault()\n    event.stopPropagation()\n  }\n}\n\n/**\n * Create main table\n * @private\n */\ntreemode._createTable = function () {\n  if (this.options.navigationBar) {\n    addClassName(this.contentOuter, 'has-nav-bar')\n  }\n\n  this.scrollableContent = document.createElement('div')\n  this.scrollableContent.className = 'jsoneditor-tree'\n  this.contentOuter.appendChild(this.scrollableContent)\n\n  // the jsoneditor-tree-inner div with bottom padding is here to\n  // keep space for the action menu dropdown. It's created as a\n  // separate div instead of using scrollableContent to work around\n  // and issue in the Chrome browser showing scrollable contents outside of the div\n  // see https://github.com/josdejong/jsoneditor/issues/557\n  this.content = document.createElement('div')\n  this.content.className = 'jsoneditor-tree-inner'\n  this.scrollableContent.appendChild(this.content)\n\n  this.table = document.createElement('table')\n  this.table.className = 'jsoneditor-tree'\n  this.content.appendChild(this.table)\n\n  // create colgroup where the first two columns don't have a fixed\n  // width, and the edit columns do have a fixed width\n  let col\n  this.colgroupContent = document.createElement('colgroup')\n  if (this.options.mode === 'tree') {\n    col = document.createElement('col')\n    col.width = '24px'\n    this.colgroupContent.appendChild(col)\n  }\n  col = document.createElement('col')\n  col.width = '24px'\n  this.colgroupContent.appendChild(col)\n  col = document.createElement('col')\n  this.colgroupContent.appendChild(col)\n  this.table.appendChild(this.colgroupContent)\n\n  this.tbody = document.createElement('tbody')\n  this.table.appendChild(this.tbody)\n\n  this.frame.appendChild(this.contentOuter)\n}\n\n/**\n * Show a contextmenu for this node.\n * Used for multiselection\n * @param {HTMLElement} anchor   Anchor element to attach the context menu to.\n * @param {function} [onClose]   Callback method called when the context menu\n *                               is being closed.\n */\ntreemode.showContextMenu = function (anchor, onClose) {\n  let items = []\n  const selectedNodes = this.multiselection.nodes.slice()\n\n  // create duplicate button\n  items.push({\n    text: translate('duplicateText'),\n    title: translate('duplicateTitle'),\n    className: 'jsoneditor-duplicate',\n    click: function () {\n      Node.onDuplicate(selectedNodes)\n    }\n  })\n\n  // create remove button\n  items.push({\n    text: translate('remove'),\n    title: translate('removeTitle'),\n    className: 'jsoneditor-remove',\n    click: function () {\n      Node.onRemove(selectedNodes)\n    }\n  })\n\n  if (this.options.onCreateMenu) {\n    const paths = selectedNodes.map(node => node.getPath())\n\n    items = this.options.onCreateMenu(items, {\n      type: 'multiple',\n      path: paths[0],\n      paths\n    })\n  }\n\n  const menu = new ContextMenu(items, { close: onClose })\n  menu.show(anchor, this.getPopupAnchor())\n}\n\ntreemode.getPopupAnchor = function () {\n  return this.options.popupAnchor || this.frame\n}\n\n/**\n * Get current selected nodes\n * @return {{start:SerializableNode, end: SerializableNode}}\n */\ntreemode.getSelection = function () {\n  const selection = {\n    start: null,\n    end: null\n  }\n  if (this.multiselection.nodes && this.multiselection.nodes.length) {\n    if (this.multiselection.nodes.length) {\n      const selection1 = this.multiselection.nodes[0]\n      const selection2 = this.multiselection.nodes[this.multiselection.nodes.length - 1]\n      if (this.multiselection.direction === 'down') {\n        selection.start = selection1.serialize()\n        selection.end = selection2.serialize()\n      } else {\n        selection.start = selection2.serialize()\n        selection.end = selection1.serialize()\n      }\n    }\n  }\n  return selection\n}\n\n/**\n * Callback registration for selection change\n * @param {selectionCallback} callback\n *\n * @callback selectionCallback\n */\ntreemode.onSelectionChange = function (callback) {\n  if (typeof callback === 'function') {\n    this._selectionChangedHandler = debounce(callback, this.DEBOUNCE_INTERVAL)\n  }\n}\n\n/**\n * Select range of nodes.\n * For selecting single node send only the start parameter\n * For clear the selection do not send any parameter\n * If the nodes are not from the same level the first common parent will be selected\n * @param {{path: Array.<String>}} start object contains the path for selection start\n * @param {{path: Array.<String>}} end object contains the path for selection end\n */\ntreemode.setSelection = function (start, end) {\n  // check for old usage\n  if (start && start.dom && start.range) {\n    console.warn('setSelection/getSelection usage for text selection is deprecated and should not be used, see documentation for supported selection options')\n    this.setDomSelection(start)\n  }\n\n  const nodes = this._getNodeInstancesByRange(start, end)\n\n  nodes.forEach(node => {\n    node.expandTo()\n  })\n  this.select(nodes)\n}\n\n/**\n * Returns a set of Nodes according to a range of selection\n * @param {{path: Array.<String>}} start object contains the path for range start\n * @param {{path: Array.<String>}=} end object contains the path for range end\n * @return {Array.<Node>} Node instances on the given range\n * @private\n */\ntreemode._getNodeInstancesByRange = function (start, end) {\n  let startNode, endNode\n\n  if (start && start.path) {\n    startNode = this.node.findNodeByPath(start.path)\n    if (end && end.path) {\n      endNode = this.node.findNodeByPath(end.path)\n    }\n  }\n\n  let nodes = []\n  if (startNode instanceof Node) {\n    if (endNode instanceof Node && endNode !== startNode) {\n      if (startNode.parent === endNode.parent) {\n        if (startNode.getIndex() < endNode.getIndex()) {\n          start = startNode\n          end = endNode\n        } else {\n          start = endNode\n          end = startNode\n        }\n        let current = start\n        nodes.push(current)\n        do {\n          current = current.nextSibling()\n          nodes.push(current)\n        } while (current && current !== end)\n      } else {\n        nodes = this._findTopLevelNodes(startNode, endNode)\n      }\n    } else {\n      nodes.push(startNode)\n    }\n  }\n\n  return nodes\n}\n\ntreemode.getNodesByRange = function (start, end) {\n  const nodes = this._getNodeInstancesByRange(start, end)\n  const serializableNodes = []\n\n  nodes.forEach(node => {\n    serializableNodes.push(node.serialize())\n  })\n\n  return serializableNodes\n}\n\n// define modes\nexport const treeModeMixins = [\n  {\n    mode: 'tree',\n    mixin: treemode,\n    data: 'json'\n  },\n  {\n    mode: 'view',\n    mixin: treemode,\n    data: 'json'\n  },\n  {\n    mode: 'form',\n    mixin: treemode,\n    data: 'json'\n  }\n]\n"
  },
  {
    "path": "src/js/tryRequireAjv.js",
    "content": "exports.tryRequireAjv = function () {\n  try {\n    return require('ajv')\n  } catch (err) {\n    // no problem... when we need Ajv we will throw a neat exception\n  }\n}\n"
  },
  {
    "path": "src/js/tryRequireThemeJsonEditor.js",
    "content": "exports.tryRequireThemeJsonEditor = function () {\n  try {\n    require('./ace/theme-jsoneditor')\n  } catch (err) {\n    console.error(err)\n  }\n}\n"
  },
  {
    "path": "src/js/types.js",
    "content": "/**\n * @typedef {object} QueryOptions\n * @property {FilterOptions} [filter]\n * @property {SortOptions} [sort]\n * @property {ProjectionOptions} [projection]\n */\n\n/**\n * @typedef {object} FilterOptions\n * @property {string} field\n * @property {string} relation  Can be '==', '<', etc\n * @property {string} value\n */\n\n/**\n * @typedef {object} SortOptions\n * @property {string} field\n * @property {string} direction   Can be 'asc' or 'desc'\n */\n\n/**\n * @typedef {object} ProjectionOptions\n * @property {string[]} fields\n */\n"
  },
  {
    "path": "src/js/util.js",
    "content": "'use strict'\n\nimport './polyfills'\nimport naturalSort from 'javascript-natural-sort'\nimport { jsonrepair } from 'jsonrepair'\nimport jsonlint from './assets/jsonlint/jsonlint'\nimport jsonMap from 'json-source-map'\nimport { translate } from './i18n'\n\nconst MAX_ITEMS_FIELDS_COLLECTION = 10000\nconst YEAR_2000 = 946684800000\n\n/**\n * Parse JSON using the parser built-in in the browser.\n * On exception, the jsonString is validated and a detailed error is thrown.\n * @param {String} jsonString\n * @return {JSON} json\n */\nexport function parse (jsonString) {\n  try {\n    return JSON.parse(jsonString)\n  } catch (err) {\n    // try to throw a more detailed error message using validate\n    validate(jsonString)\n\n    // rethrow the original error\n    throw err\n  }\n}\n\n/**\n * Try to fix the JSON string. If not successful, return the original string\n * @param {string} jsonString\n */\nexport function tryJsonRepair (jsonString) {\n  try {\n    return jsonrepair(jsonString)\n  } catch (err) {\n    // repair was not successful, return original text\n    return jsonString\n  }\n}\n\n/**\n * Escape unicode characters.\n * For example input '\\u2661' (length 1) will output '\\\\u2661' (length 5).\n * @param {string} text\n * @return {string}\n */\nexport function escapeUnicodeChars (\n  // see https://www.wikiwand.com/en/UTF-16\n  text\n) {\n  return (\n    // note: we leave surrogate pairs as two individual chars,\n    // as JSON doesn't interpret them as a single unicode char.\n    text.replace(\n      /[\\u007F-\\uFFFF]/g,\n      c => '\\\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4)\n    )\n  )\n}\n\n/**\n * Validate a string containing a JSON object\n * This method uses JSONLint to validate the String. If JSONLint is not\n * available, the built-in JSON parser of the browser is used.\n * @param {String} jsonString   String with an (invalid) JSON object\n * @throws Error\n */\nexport function validate (jsonString) {\n  if (typeof (jsonlint) !== 'undefined') {\n    jsonlint.parse(jsonString)\n  } else {\n    JSON.parse(jsonString)\n  }\n}\n\n/**\n * Extend object a with the properties of object b\n * @param {Object} a\n * @param {Object} b\n * @return {Object} a\n */\nexport function extend (a, b) {\n  for (const prop in b) {\n    if (hasOwnProperty(b, prop)) {\n      a[prop] = b[prop]\n    }\n  }\n  return a\n}\n\n/**\n * Remove all properties from object a\n * @param {Object} a\n * @return {Object} a\n */\nexport function clear (a) {\n  for (const prop in a) {\n    if (hasOwnProperty(a, prop)) {\n      delete a[prop]\n    }\n  }\n  return a\n}\n\n/**\n * Get the type of an object\n * @param {*} object\n * @return {String} type\n */\nexport function getType (object) {\n  if (object === null) {\n    return 'null'\n  }\n  if (object === undefined) {\n    return 'undefined'\n  }\n  if ((object instanceof Number) || (typeof object === 'number')) {\n    return 'number'\n  }\n  if ((object instanceof String) || (typeof object === 'string')) {\n    return 'string'\n  }\n  if ((object instanceof Boolean) || (typeof object === 'boolean')) {\n    return 'boolean'\n  }\n  if (object instanceof RegExp) {\n    return 'regexp'\n  }\n  if (isArray(object)) {\n    return 'array'\n  }\n\n  return 'object'\n}\n\n/**\n * Test whether a text contains a url (matches when a string starts\n * with 'http://*' or 'https://*' and has no whitespace characters)\n * @param {String} text\n */\nconst isUrlRegex = /^https?:\\/\\/\\S+$/\n\nexport function isUrl (text) {\n  return (typeof text === 'string' || text instanceof String) &&\n      isUrlRegex.test(text)\n}\n\n/**\n * Tes whether given object is an Array\n * @param {*} obj\n * @returns {boolean} returns true when obj is an array\n */\nexport function isArray (obj) {\n  return Object.prototype.toString.call(obj) === '[object Array]'\n}\n\n/**\n * Gets a DOM element's Window.  This is normally just the global `window`\n * variable, but if we opened a child window, it may be different.\n * @param {HTMLElement} element\n * @return {Window}\n */\nexport function getWindow (element) {\n  return element.ownerDocument.defaultView\n}\n\n/**\n * Retrieve the absolute left value of a DOM element\n * @param {Element} elem    A dom element, for example a div\n * @return {Number} left    The absolute left position of this element\n *                          in the browser page.\n */\nexport function getAbsoluteLeft (elem) {\n  const rect = elem.getBoundingClientRect()\n  return rect.left + window.pageXOffset || document.scrollLeft || 0\n}\n\n/**\n * Retrieve the absolute top value of a DOM element\n * @param {Element} elem    A dom element, for example a div\n * @return {Number} top     The absolute top position of this element\n *                          in the browser page.\n */\nexport function getAbsoluteTop (elem) {\n  const rect = elem.getBoundingClientRect()\n  return rect.top + window.pageYOffset || document.scrollTop || 0\n}\n\n/**\n * add a className to the given elements style\n * @param {Element} elem\n * @param {String} className\n */\nexport function addClassName (elem, className) {\n  const classes = elem.className.split(' ')\n  if (classes.indexOf(className) === -1) {\n    classes.push(className) // add the class to the array\n    elem.className = classes.join(' ')\n  }\n}\n\n/**\n * remove all classes from the given elements style\n * @param {Element} elem\n */\nexport function removeAllClassNames (elem) {\n  elem.className = ''\n}\n\n/**\n * add a className to the given elements style\n * @param {Element} elem\n * @param {String} className\n */\nexport function removeClassName (elem, className) {\n  const classes = elem.className.split(' ')\n  const index = classes.indexOf(className)\n  if (index !== -1) {\n    classes.splice(index, 1) // remove the class from the array\n    elem.className = classes.join(' ')\n  }\n}\n\n/**\n * Strip the formatting from the contents of a div\n * the formatting from the div itself is not stripped, only from its childs.\n * @param {Element} divElement\n */\nexport function stripFormatting (divElement) {\n  const childs = divElement.childNodes\n  for (let i = 0, iMax = childs.length; i < iMax; i++) {\n    const child = childs[i]\n\n    // remove the style\n    if (child.style) {\n      // TODO: test if child.attributes does contain style\n      child.removeAttribute('style')\n    }\n\n    // remove all attributes\n    const attributes = child.attributes\n    if (attributes) {\n      for (let j = attributes.length - 1; j >= 0; j--) {\n        const attribute = attributes[j]\n        if (attribute.specified === true) {\n          child.removeAttribute(attribute.name)\n        }\n      }\n    }\n\n    // recursively strip childs\n    stripFormatting(child)\n  }\n}\n\n/**\n * Set focus to the end of an editable div\n * code from Nico Burns\n * http://stackoverflow.com/users/140293/nico-burns\n * http://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity\n * @param {Element} contentEditableElement   A content editable div\n */\nexport function setEndOfContentEditable (contentEditableElement) {\n  let range, selection\n  if (document.createRange) {\n    range = document.createRange()// Create a range (a range is a like the selection but invisible)\n    range.selectNodeContents(contentEditableElement)// Select the entire contents of the element with the range\n    range.collapse(false)// collapse the range to the end point. false means collapse to end rather than the start\n    selection = window.getSelection()// get the selection object (allows you to change selection)\n    selection.removeAllRanges()// remove any selections already made\n    selection.addRange(range)// make the range you have just created the visible selection\n  }\n}\n\n/**\n * Select all text of a content editable div.\n * http://stackoverflow.com/a/3806004/1262753\n * @param {Element} contentEditableElement   A content editable div\n */\nexport function selectContentEditable (contentEditableElement) {\n  if (!contentEditableElement || contentEditableElement.nodeName !== 'DIV') {\n    return\n  }\n\n  let sel, range\n  if (window.getSelection && document.createRange) {\n    range = document.createRange()\n    range.selectNodeContents(contentEditableElement)\n    sel = window.getSelection()\n    sel.removeAllRanges()\n    sel.addRange(range)\n  }\n}\n\n/**\n * Get text selection\n * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore\n * @return {Range | TextRange | null} range\n */\nexport function getSelection () {\n  if (window.getSelection) {\n    const sel = window.getSelection()\n    if (sel.getRangeAt && sel.rangeCount) {\n      return sel.getRangeAt(0)\n    }\n  }\n  return null\n}\n\n/**\n * Set text selection\n * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore\n * @param {Range | TextRange | null} range\n */\nexport function setSelection (range) {\n  if (range) {\n    if (window.getSelection) {\n      const sel = window.getSelection()\n      sel.removeAllRanges()\n      sel.addRange(range)\n    }\n  }\n}\n\n/**\n * Get selected text range\n * @return {Object} params  object containing parameters:\n *                              {Number}  startOffset\n *                              {Number}  endOffset\n *                              {Element} container  HTML element holding the\n *                                                   selected text element\n *                          Returns null if no text selection is found\n */\nexport function getSelectionOffset () {\n  const range = getSelection()\n\n  if (range && 'startOffset' in range && 'endOffset' in range &&\n      range.startContainer && (range.startContainer === range.endContainer)) {\n    return {\n      startOffset: range.startOffset,\n      endOffset: range.endOffset,\n      container: range.startContainer.parentNode\n    }\n  }\n\n  return null\n}\n\n/**\n * Set selected text range in given element\n * @param {Object} params   An object containing:\n *                              {Element} container\n *                              {Number} startOffset\n *                              {Number} endOffset\n */\nexport function setSelectionOffset (params) {\n  if (document.createRange && window.getSelection) {\n    const selection = window.getSelection()\n    if (selection) {\n      const range = document.createRange()\n\n      if (!params.container.firstChild) {\n        params.container.appendChild(document.createTextNode(''))\n      }\n\n      // TODO: do not suppose that the first child of the container is a textnode,\n      //       but recursively find the textnodes\n      range.setStart(params.container.firstChild, params.startOffset)\n      range.setEnd(params.container.firstChild, params.endOffset)\n\n      setSelection(range)\n    }\n  }\n}\n/**\n * Get the inner text of an HTML element (for example a div element)\n * @param {Element} element\n * @param {Object} [buffer]\n * @return {String} innerText\n */\nexport function getInnerText (element, buffer) {\n  const first = (buffer === undefined)\n  if (first) {\n    buffer = {\n      _text: '',\n      flush: function () {\n        const text = this._text\n        this._text = ''\n        return text\n      },\n      set: function (text) {\n        this._text = text\n      }\n    }\n  }\n\n  // text node\n  if (element.nodeValue) {\n    // remove return characters and the whitespaces surrounding those return characters\n    const trimmedValue = removeReturnsAndSurroundingWhitespace(element.nodeValue)\n    if (trimmedValue !== '') {\n      return buffer.flush() + trimmedValue\n    } else {\n      // ignore empty text\n      return ''\n    }\n  }\n\n  // divs or other HTML elements\n  if (element.hasChildNodes()) {\n    const childNodes = element.childNodes\n    let innerText = ''\n\n    for (let i = 0, iMax = childNodes.length; i < iMax; i++) {\n      const child = childNodes[i]\n\n      if (child.nodeName === 'DIV' || child.nodeName === 'P') {\n        const prevChild = childNodes[i - 1]\n        const prevName = prevChild ? prevChild.nodeName : undefined\n        if (prevName && prevName !== 'DIV' && prevName !== 'P' && prevName !== 'BR') {\n          if (innerText !== '') {\n            innerText += '\\n'\n          }\n          buffer.flush()\n        }\n        innerText += getInnerText(child, buffer)\n        buffer.set('\\n')\n      } else if (child.nodeName === 'BR') {\n        innerText += buffer.flush()\n        buffer.set('\\n')\n      } else {\n        innerText += getInnerText(child, buffer)\n      }\n    }\n\n    return innerText\n  }\n\n  // br or unknown\n  return ''\n}\n\n// regular expression matching one or multiple return characters with all their\n// enclosing white spaces\nexport function removeReturnsAndSurroundingWhitespace (text) {\n  return text.replace(/(\\b|^)\\s*(\\b|$)/g, (match) => {\n    return /\\n/.exec(match) ? '' : match\n  })\n}\n\n/**\n * Test whether an element has the provided parent node somewhere up the node tree.\n * @param {Element} elem\n * @param {Element} parent\n * @return {boolean}\n */\nexport function hasParentNode (elem, parent) {\n  let e = elem ? elem.parentNode : undefined\n\n  while (e) {\n    if (e === parent) {\n      return true\n    }\n    e = e.parentNode\n  }\n\n  return false\n}\n\n/**\n * Returns the version of Internet Explorer or a -1\n * (indicating the use of another browser).\n * Source: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx\n * @return {Number} Internet Explorer version, or -1 in case of an other browser\n */\nexport function getInternetExplorerVersion () {\n  if (_ieVersion === -1) {\n    let rv = -1 // Return value assumes failure.\n    if (typeof navigator !== 'undefined' && navigator.appName === 'Microsoft Internet Explorer') {\n      const ua = navigator.userAgent\n      const re = /MSIE ([0-9]+[.0-9]+)/\n      if (re.exec(ua) != null) {\n        rv = parseFloat(RegExp.$1)\n      }\n    }\n\n    _ieVersion = rv\n  }\n\n  return _ieVersion\n}\n\n/**\n * cached internet explorer version\n * @type {Number}\n * @private\n */\nlet _ieVersion = -1\n\n/**\n * Test whether the current browser is Firefox\n * @returns {boolean} isFirefox\n */\nexport function isFirefox () {\n  return (typeof navigator !== 'undefined' && navigator.userAgent.indexOf('Firefox') !== -1)\n}\n\n/**\n * Add an event listener. Works for all browsers\n * @param {Element}     element    An html element\n * @param {string}      action     The action, for example \"click\",\n *                                 without the prefix \"on\"\n * @param {function}    listener   The callback function to be executed\n * @param {boolean}     [useCapture] false by default\n * @return {function}   the created event listener\n */\nexport function addEventListener (element, action, listener, useCapture) {\n  if (element.addEventListener) {\n    if (useCapture === undefined) { useCapture = false }\n\n    if (action === 'mousewheel' && isFirefox()) {\n      action = 'DOMMouseScroll' // For Firefox\n    }\n\n    element.addEventListener(action, listener, useCapture)\n    return listener\n  } else if (element.attachEvent) {\n    // Old IE browsers\n    const f = () => listener.call(element, window.event)\n    element.attachEvent('on' + action, f)\n    return f\n  }\n}\n\n/**\n * Remove an event listener from an element\n * @param {Element}  element   An html dom element\n * @param {string}   action    The name of the event, for example \"mousedown\"\n * @param {function} listener  The listener function\n * @param {boolean}  [useCapture]   false by default\n */\nexport function removeEventListener (element, action, listener, useCapture) {\n  if (element.removeEventListener) {\n    if (useCapture === undefined) { useCapture = false }\n\n    if (action === 'mousewheel' && isFirefox()) {\n      action = 'DOMMouseScroll' // For Firefox\n    }\n\n    element.removeEventListener(action, listener, useCapture)\n  } else if (element.detachEvent) {\n    // Old IE browsers\n    element.detachEvent('on' + action, listener)\n  }\n}\n\n/**\n * Test if an element is a child of a parent element.\n * @param {Element} elem\n * @param {Element} parent\n * @return {boolean} returns true if elem is a child of the parent\n */\nexport function isChildOf (elem, parent) {\n  let e = elem.parentNode\n  while (e) {\n    if (e === parent) {\n      return true\n    }\n    e = e.parentNode\n  }\n\n  return false\n}\n\n/**\n * Parse a JSON path like '.items[3].name' into an array\n * @param {string} jsonPath\n * @return {Array}\n */\nexport function parsePath (jsonPath) {\n  const path = []\n  let i = 0\n\n  function parseProperty () {\n    let prop = ''\n    while (jsonPath[i] !== undefined && /[\\w$]/.test(jsonPath[i])) {\n      prop += jsonPath[i]\n      i++\n    }\n\n    if (prop === '') {\n      throw new Error('Invalid JSON path: property name expected at index ' + i)\n    }\n\n    return prop\n  }\n\n  function parseIndex (end) {\n    let name = ''\n    while (jsonPath[i] !== undefined && jsonPath[i] !== end) {\n      name += jsonPath[i]\n      i++\n    }\n\n    if (jsonPath[i] !== end) {\n      throw new Error('Invalid JSON path: unexpected end, character ' + end + ' expected')\n    }\n\n    return name\n  }\n\n  while (jsonPath[i] !== undefined) {\n    if (jsonPath[i] === '.') {\n      i++\n      path.push(parseProperty())\n    } else if (jsonPath[i] === '[') {\n      i++\n\n      if (jsonPath[i] === '\\'' || jsonPath[i] === '\"') {\n        const end = jsonPath[i]\n        i++\n\n        path.push(parseIndex(end))\n\n        if (jsonPath[i] !== end) {\n          throw new Error('Invalid JSON path: closing quote \\' expected at index ' + i)\n        }\n        i++\n      } else {\n        let index = parseIndex(']').trim()\n        if (index.length === 0) {\n          throw new Error('Invalid JSON path: array value expected at index ' + i)\n        }\n        // Coerce numeric indices to numbers, but ignore star\n        index = index === '*' ? index : JSON.parse(index)\n        path.push(index)\n      }\n\n      if (jsonPath[i] !== ']') {\n        throw new Error('Invalid JSON path: closing bracket ] expected at index ' + i)\n      }\n      i++\n    } else {\n      throw new Error('Invalid JSON path: unexpected character \"' + jsonPath[i] + '\" at index ' + i)\n    }\n  }\n\n  return path\n}\n\n/**\n * Stringify an array with a path in a JSON path like '.items[3].name'\n * @param {Array.<string | number>} path\n * @returns {string}\n */\nexport function stringifyPath (path) {\n  return path\n    .map(p => {\n      if (typeof p === 'number') {\n        return ('[' + p + ']')\n      } else if (typeof p === 'string' && p.match(/^[A-Za-z0-9_$]+$/)) {\n        return '.' + p\n      } else {\n        return '[\"' + p + '\"]'\n      }\n    })\n    .join('')\n}\n\n/**\n * Improve the error message of a JSON schema error\n * @param {Object} error\n * @return {Object} The error\n */\nexport function improveSchemaError (error) {\n  if (error.keyword === 'enum' && Array.isArray(error.schema)) {\n    let enums = error.schema\n    if (enums) {\n      enums = enums.map(value => JSON.stringify(value))\n\n      if (enums.length > 5) {\n        const more = ['(' + (enums.length - 5) + ' more...)']\n        enums = enums.slice(0, 5)\n        enums.push(more)\n      }\n      error.message = 'should be equal to one of: ' + enums.join(', ')\n    }\n  }\n\n  if (error.keyword === 'additionalProperties') {\n    error.message = 'should NOT have additional property: ' + error.params.additionalProperty\n  }\n\n  return error\n}\n\n/**\n * Test whether something is a Promise\n * @param {*} object\n * @returns {boolean} Returns true when object is a promise, false otherwise\n */\nexport function isPromise (object) {\n  return object && typeof object.then === 'function' && typeof object.catch === 'function'\n}\n\n/**\n * Test whether a custom validation error has the correct structure\n * @param {*} validationError The error to be checked.\n * @returns {boolean} Returns true if the structure is ok, false otherwise\n */\nexport function isValidValidationError (validationError) {\n  return typeof validationError === 'object' &&\n      Array.isArray(validationError.path) &&\n      typeof validationError.message === 'string'\n}\n\n/**\n * Test whether the child rect fits completely inside the parent rect.\n * @param {ClientRect} parent\n * @param {ClientRect} child\n * @param {number} margin\n */\nexport function insideRect (parent, child, margin) {\n  const _margin = margin !== undefined ? margin : 0\n  return child.left - _margin >= parent.left &&\n      child.right + _margin <= parent.right &&\n      child.top - _margin >= parent.top &&\n      child.bottom + _margin <= parent.bottom\n}\n\n/**\n * Returns a function, that, as long as it continues to be invoked, will not\n * be triggered. The function will be called after it stops being called for\n * N milliseconds.\n *\n * Source: https://davidwalsh.name/javascript-debounce-function\n *\n * @param {function} func\n * @param {number} wait                 Number in milliseconds\n * @param {boolean} [immediate=false]   If `immediate` is passed, trigger the\n *                                      function on the leading edge, instead\n *                                      of the trailing.\n * @return {function} Return the debounced function\n */\nexport function debounce (func, wait, immediate) {\n  let timeout\n  return function () {\n    const context = this; const args = arguments\n    const later = () => {\n      timeout = null\n      if (!immediate) func.apply(context, args)\n    }\n    const callNow = immediate && !timeout\n    clearTimeout(timeout)\n    timeout = setTimeout(later, wait)\n    if (callNow) func.apply(context, args)\n  }\n}\n\n/**\n * Determines the difference between two texts.\n * Can only detect one removed or inserted block of characters.\n * @param {string} oldText\n * @param {string} newText\n * @return {{start: number, end: number}} Returns the start and end\n *                                        of the changed part in newText.\n */\nexport function textDiff (oldText, newText) {\n  const len = newText.length\n  let start = 0\n  let oldEnd = oldText.length\n  let newEnd = newText.length\n\n  while (newText.charAt(start) === oldText.charAt(start) &&\n  start < len) {\n    start++\n  }\n\n  while (newText.charAt(newEnd - 1) === oldText.charAt(oldEnd - 1) &&\n  newEnd > start && oldEnd > 0) {\n    newEnd--\n    oldEnd--\n  }\n\n  return { start, end: newEnd }\n}\n\n/**\n * Return an object with the selection range or cursor position (if both have the same value)\n * Support also old browsers (IE8-)\n * Source: http://ourcodeworld.com/articles/read/282/how-to-get-the-current-cursor-position-and-selection-within-a-text-input-or-textarea-in-javascript\n * @param {DOMElement} el A dom element of a textarea or input text.\n * @return {Object} reference Object with 2 properties (start and end) with the identifier of the location of the cursor and selected text.\n **/\nexport function getInputSelection (el) {\n  let startIndex = 0; let endIndex = 0; let normalizedValue; let range; let textInputRange; let len; let endRange\n\n  if (typeof el.selectionStart === 'number' && typeof el.selectionEnd === 'number') {\n    startIndex = el.selectionStart\n    endIndex = el.selectionEnd\n  } else {\n    range = document.selection.createRange()\n\n    if (range && range.parentElement() === el) {\n      len = el.value.length\n      normalizedValue = el.value.replace(/\\r\\n/g, '\\n')\n\n      // Create a working TextRange that lives only in the input\n      textInputRange = el.createTextRange()\n      textInputRange.moveToBookmark(range.getBookmark())\n\n      // Check if the startIndex and endIndex of the selection are at the very end\n      // of the input, since moveStart/moveEnd doesn't return what we want\n      // in those cases\n      endRange = el.createTextRange()\n      endRange.collapse(false)\n\n      if (textInputRange.compareEndPoints('StartToEnd', endRange) > -1) {\n        startIndex = endIndex = len\n      } else {\n        startIndex = -textInputRange.moveStart('character', -len)\n        startIndex += normalizedValue.slice(0, startIndex).split('\\n').length - 1\n\n        if (textInputRange.compareEndPoints('EndToEnd', endRange) > -1) {\n          endIndex = len\n        } else {\n          endIndex = -textInputRange.moveEnd('character', -len)\n          endIndex += normalizedValue.slice(0, endIndex).split('\\n').length - 1\n        }\n      }\n    }\n  }\n\n  return {\n    startIndex,\n    endIndex,\n    start: _positionForIndex(startIndex),\n    end: _positionForIndex(endIndex)\n  }\n\n  /**\n   * Returns textarea row and column position for certain index\n   * @param {Number} index text index\n   * @returns {{row: Number, column: Number}}\n   */\n  function _positionForIndex (index) {\n    const textTillIndex = el.value.substring(0, index)\n    const row = (textTillIndex.match(/\\n/g) || []).length + 1\n    const col = textTillIndex.length - textTillIndex.lastIndexOf('\\n')\n\n    return {\n      row,\n      column: col\n    }\n  }\n}\n\n/**\n * Returns the index for certain position in text element\n * @param {DOMElement} el A dom element of a textarea or input text.\n * @param {Number} row row value, > 0, if exceeds rows number - last row will be returned\n * @param {Number} column column value, > 0, if exceeds column length - end of column will be returned\n * @returns {Number} index of position in text, -1 if not found\n */\nexport function getIndexForPosition (el, row, column) {\n  const text = el.value || ''\n  if (row > 0 && column > 0) {\n    const rows = text.split('\\n', row)\n    row = Math.min(rows.length, row)\n    column = Math.min(rows[row - 1].length, column - 1)\n    const columnCount = (row === 1 ? column : column + 1) // count new line on multiple rows\n    return rows.slice(0, row - 1).join('\\n').length + columnCount\n  }\n  return -1\n}\n\n/**\n * Returns location of json paths in certain json string\n * @param {String} text json string\n * @param {Array<String>} paths array of json paths\n * @returns {Array<{path: String, line: Number, row: Number}>}\n */\nexport function getPositionForPath (text, paths) {\n  const result = []\n  let jsmap\n  if (!paths || !paths.length) {\n    return result\n  }\n\n  try {\n    jsmap = jsonMap.parse(text)\n  } catch (err) {\n    return result\n  }\n\n  paths.forEach(path => {\n    const pathArr = parsePath(path)\n    const pointerName = compileJSONPointer(pathArr)\n    const pointer = jsmap.pointers[pointerName]\n    if (pointer) {\n      result.push({\n        path,\n        line: pointer.key ? pointer.key.line : (pointer.value ? pointer.value.line : 0),\n        column: pointer.key ? pointer.key.column : (pointer.value ? pointer.value.column : 0)\n      })\n    }\n  })\n\n  return result\n}\n\n/**\n * Compile a JSON Pointer\n * WARNING: this is an incomplete implementation\n * @param {Array.<string | number>} path\n * @return {string}\n */\nexport function compileJSONPointer (path) {\n  return path\n    .map(p => ('/' + String(p)\n      .replace(/~/g, '~0')\n      .replace(/\\//g, '~1')\n    ))\n    .join('')\n}\n\n/**\n * Get the applied color given a color name or code\n * Source: https://stackoverflow.com/questions/6386090/validating-css-color-names/33184805\n * @param {string} color\n * @returns {string | null} returns the color if the input is a valid\n *                   color, and returns null otherwise. Example output:\n *                   'rgba(255,0,0,0.7)' or 'rgb(255,0,0)'\n */\nexport function getColorCSS (color) {\n  const ele = document.createElement('div')\n  ele.style.color = color\n  return ele.style.color.split(/\\s+/).join('').toLowerCase() || null\n}\n\n/**\n * Test if a string contains a valid color name or code.\n * @param {string} color\n * @returns {boolean} returns true if a valid color, false otherwise\n */\nexport function isValidColor (color) {\n  return !!getColorCSS(color)\n}\n\n/**\n * Make a tooltip for a field based on the field's schema.\n * @param {object} schema JSON schema\n * @param {string} [locale] Locale code (for example, zh-CN)\n * @returns {string} Field tooltip, may be empty string if all relevant schema properties are missing\n */\nexport function makeFieldTooltip (schema, locale) {\n  if (!schema) {\n    return ''\n  }\n\n  let tooltip = ''\n  if (schema.title) {\n    tooltip += schema.title\n  }\n\n  if (schema.description) {\n    if (tooltip.length > 0) {\n      tooltip += '\\n'\n    }\n    tooltip += schema.description\n  }\n\n  if (schema.default) {\n    if (tooltip.length > 0) {\n      tooltip += '\\n\\n'\n    }\n    tooltip += translate('default', undefined, locale) + '\\n'\n    tooltip += JSON.stringify(schema.default, null, 2)\n  }\n\n  if (Array.isArray(schema.examples) && schema.examples.length > 0) {\n    if (tooltip.length > 0) {\n      tooltip += '\\n\\n'\n    }\n    tooltip += translate('examples', undefined, locale) + '\\n'\n    schema.examples.forEach((example, index) => {\n      tooltip += JSON.stringify(example, null, 2)\n      if (index !== schema.examples.length - 1) {\n        tooltip += '\\n'\n      }\n    })\n  }\n\n  return tooltip\n}\n\n/**\n * Get a nested property from an object.\n * Returns undefined when the property does not exist.\n * @param {Object} object\n * @param {string[]} path\n * @return {*}\n */\nexport function get (object, path) {\n  let value = object\n\n  for (let i = 0; i < path.length && value !== undefined && value !== null; i++) {\n    value = value[path[i]]\n  }\n\n  return value\n}\n\n/**\n * Find a unique name. Suffix the name with ' (copy)', '(copy 2)', etc\n * until a unique name is found\n * @param {string} name\n * @param {Array} existingPropNames    Array with existing prop names\n */\nexport function findUniqueName (name, existingPropNames) {\n  if (existingPropNames.indexOf(name) === -1) {\n    return name\n  }\n\n  const strippedName = name.replace(/ \\(copy( \\d+)?\\)$/, '')\n  let validName = strippedName\n  let i = 1\n\n  while (existingPropNames.indexOf(validName) !== -1) {\n    const copy = 'copy' + (i > 1 ? (' ' + i) : '')\n    validName = strippedName + ' (' + copy + ')'\n    i++\n  }\n\n  return validName\n}\n\n/**\n * Get the child paths of an array\n * @param {JSON} json\n * @param {boolean} [includeObjects=false] If true, object and array paths are returned as well\n * @return {string[]}\n */\nexport function getChildPaths (json, includeObjects) {\n  const pathsMap = {}\n\n  function getObjectChildPaths (json, pathsMap, rootPath, includeObjects) {\n    const isValue = !Array.isArray(json) && !isObject(json)\n\n    if (isValue || includeObjects) {\n      pathsMap[rootPath || ''] = true\n    }\n\n    if (isObject(json)) {\n      Object.keys(json).forEach(field => {\n        getObjectChildPaths(json[field], pathsMap, rootPath + '.' + field, includeObjects)\n      })\n    }\n  }\n\n  if (Array.isArray(json)) {\n    const max = Math.min(json.length, MAX_ITEMS_FIELDS_COLLECTION)\n    for (let i = 0; i < max; i++) {\n      const item = json[i]\n      getObjectChildPaths(item, pathsMap, '', includeObjects)\n    }\n  } else {\n    pathsMap[''] = true\n  }\n\n  return Object.keys(pathsMap).sort()\n}\n\n/**\n * Sort object keys using natural sort\n * @param {Array} array\n * @param {String} [path] JSON pointer\n * @param {'asc' | 'desc'} [direction]\n */\nexport function sort (array, path, direction) {\n  const parsedPath = path && path !== '.' ? parsePath(path) : []\n  const sign = direction === 'desc' ? -1 : 1\n\n  const sortedArray = array.slice()\n  sortedArray.sort((a, b) => {\n    const aValue = get(a, parsedPath)\n    const bValue = get(b, parsedPath)\n\n    return sign * (aValue > bValue ? 1 : aValue < bValue ? -1 : 0)\n  })\n\n  return sortedArray\n}\n\n/**\n * Sort object keys using natural sort\n * @param {Object} object\n * @param {'asc' | 'desc'} [direction]\n */\nexport function sortObjectKeys (object, direction) {\n  const sign = (direction === 'desc') ? -1 : 1\n  const sortedFields = Object.keys(object).sort((a, b) => sign * naturalSort(a, b))\n\n  const sortedObject = {}\n  sortedFields.forEach(field => {\n    sortedObject[field] = object[field]\n  })\n\n  return sortedObject\n}\n\n/**\n * Cast contents of a string to the correct type.\n * This can be a string, a number, a boolean, etc\n * @param {String} str\n * @return {*} castedStr\n * @private\n */\nexport function parseString (str) {\n  if (str === '') {\n    return ''\n  }\n\n  const lower = str.toLowerCase()\n  if (lower === 'null') {\n    return null\n  }\n  if (lower === 'true') {\n    return true\n  }\n  if (lower === 'false') {\n    return false\n  }\n\n  const containsLeadingZero = /^0\\d+$/\n  const startsWithZeroPrefix = /^0[xbo]/i // hex, binary, octal numbers\n  if (containsLeadingZero.test(str) || startsWithZeroPrefix.test(str)) {\n    // treat '001', '0x1A', '0b1101', and '0o3700' as a string\n    return str\n  }\n\n  const num = Number(str) // will nicely fail with '123ab'\n  const numFloat = parseFloat(str) // will nicely fail with '  '\n  const isFiniteNumber = !isNaN(num) && !isNaN(numFloat) && isFinite(num)\n  const isInSafeRange = num <= Number.MAX_SAFE_INTEGER && num >= Number.MIN_SAFE_INTEGER\n  const isInteger = /^\\d+$/.test(str)\n  if (isFiniteNumber && (isInSafeRange || !isInteger)) {\n    return num\n  }\n\n  return str\n}\n\n/**\n * Test whether some field contains a timestamp in milliseconds after the year 2000.\n * @param {string} field\n * @param {number} value\n * @return {boolean}\n */\nexport function isTimestamp (field, value) {\n  return typeof value === 'number' &&\n    value > YEAR_2000 &&\n    isFinite(value) &&\n    Math.floor(value) === value &&\n    !isNaN(new Date(value).valueOf())\n}\n\n/**\n * Return a human readable document size\n * For example formatSize(7570718) outputs '7.6 MB'\n * @param {number} size\n * @return {string} Returns a human readable size\n */\nexport function formatSize (size) {\n  if (size < 900) {\n    return size.toFixed() + ' B'\n  }\n\n  const KB = size / 1000\n  if (KB < 900) {\n    return KB.toFixed(1) + ' KB'\n  }\n\n  const MB = KB / 1000\n  if (MB < 900) {\n    return MB.toFixed(1) + ' MB'\n  }\n\n  const GB = MB / 1000\n  if (GB < 900) {\n    return GB.toFixed(1) + ' GB'\n  }\n\n  const TB = GB / 1000\n  return TB.toFixed(1) + ' TB'\n}\n\n/**\n * Limit text to a maximum number of characters\n * @param {string} text\n * @param {number} maxCharacterCount\n * @return {string} Returns the limited text,\n *                  ending with '...' if the max was exceeded\n */\nexport function limitCharacters (text, maxCharacterCount) {\n  if (text.length <= maxCharacterCount) {\n    return text\n  }\n\n  return text.slice(0, maxCharacterCount) + '...'\n}\n\n/**\n * Test whether a value is an Object\n * @param {*} value\n * @return {boolean}\n */\nexport function isObject (value) {\n  return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Helper function to test whether an array contains an item\n * @param {Array} array\n * @param {*} item\n * @return {boolean} Returns true if `item` is in `array`, returns false otherwise.\n */\nexport function contains (array, item) {\n  return array.indexOf(item) !== -1\n}\n\n/**\n * Checks if validation has changed from the previous execution\n * @param {Array} currErr current validation errors\n * @param {Array} prevErr previous validation errors\n */\nexport function isValidationErrorChanged (currErr, prevErr) {\n  if (!currErr && !prevErr) {\n    return false\n  }\n  if (!Array.isArray(currErr) || !Array.isArray(prevErr) || prevErr.length !== currErr.length) {\n    return true\n  }\n\n  for (let i = 0; i < currErr.length; i++) {\n    const currItem = currErr[i]\n    const prevItem = prevErr[i]\n\n    if (\n      currItem.type !== prevItem.type ||\n      JSON.stringify(currItem.error) !== JSON.stringify(prevItem.error)\n    ) {\n      return true\n    }\n  }\n\n  return false\n}\n\n/**\n * Uniquely merge array of elements\n * @param {Array<string|number>} inputArray1\n * @param {Array<string|number>} inputArray2\n * @returns {Array<string|number>} an array with unique merged elements\n */\nexport function uniqueMergeArrays (inputArray1, inputArray2) {\n  const arr1 = inputArray1?.length ? inputArray1 : []\n  const arr2 = inputArray2?.length ? inputArray2 : []\n  return [...new Set(arr1.concat(arr2))]\n}\n\nexport function asyncExec (callback) {\n  setTimeout(callback)\n}\n\nfunction hasOwnProperty (object, key) {\n  return Object.prototype.hasOwnProperty.call(object, key)\n}\n"
  },
  {
    "path": "src/js/validationUtils.js",
    "content": "import { isPromise, isValidValidationError, stringifyPath } from './util'\n\n/**\n * Execute custom validation if configured.\n *\n * Returns a promise resolving with the custom errors (or an empty array).\n */\nexport function validateCustom (json, onValidate) {\n  if (!onValidate) {\n    return Promise.resolve([])\n  }\n\n  try {\n    const customValidateResults = onValidate(json)\n\n    const resultPromise = isPromise(customValidateResults)\n      ? customValidateResults\n      : Promise.resolve(customValidateResults)\n\n    return resultPromise.then(customValidationPathErrors => {\n      if (Array.isArray(customValidationPathErrors)) {\n        return customValidationPathErrors\n          .filter(error => {\n            const valid = isValidValidationError(error)\n\n            if (!valid) {\n              console.warn('Ignoring a custom validation error with invalid structure. ' +\n                    'Expected structure: {path: [...], message: \"...\"}. ' +\n                    'Actual error:', error)\n            }\n\n            return valid\n          })\n          .map(error => // change data structure into the structure matching the JSON schema errors\n            ({\n              dataPath: stringifyPath(error.path),\n              message: error.message,\n              type: 'customValidation'\n            }))\n      } else {\n        return []\n      }\n    })\n  } catch (err) {\n    return Promise.reject(err)\n  }\n}\n"
  },
  {
    "path": "src/js/vanilla-picker/index.js",
    "content": "let VanillaPicker\n\nif (window.Picker) {\n  // use the already loaded instance of VanillaPicker\n  VanillaPicker = window.Picker\n} else {\n  try {\n    // load color picker\n    VanillaPicker = require('vanilla-picker')\n  } catch (err) {\n    // probably running the minimalist bundle\n  }\n}\n\nmodule.exports = VanillaPicker\n"
  },
  {
    "path": "src/scss/img/description.txt",
    "content": "JSON Editor Icons\n\nsize:             outer: 24x24 px\n                  inner: 16x16 px \n\nblue background:  RGBA 97b0f8ff\ngray background:  RGBA 4d4d4dff\ngrey background:  RGBA d3d3d3ff\n\nred foreground:   RGBA ff3300ff\ngreen foreground: RGBA 13ae00ff\n\ncharacters are based on the Arial font\n\n"
  },
  {
    "path": "src/scss/jsoneditor/_autocomplete.scss",
    "content": "@use \"variables\";\n\n.jsoneditor {\r\n  .autocomplete {\r\n    &.dropdown {\r\n      position: absolute;\r\n      background: variables.$jse-white;\r\n      box-shadow: variables.$jse-box-shadow;\r\n      border: 1px solid variables.$jse-bar-border;\r\n      overflow-x: hidden;\r\n      overflow-y: auto;\r\n      cursor: default;\r\n      margin: 0;\r\n      padding: 5px;\r\n      text-align: left;\r\n      outline: 0;\r\n      font-family: variables.$jse-font-mono;\r\n      font-size: variables.$jse-font-size;\r\n      .item {\r\n        color: variables.$jse-content-color;\r\n        &.hover {\r\n          background-color: variables.$jse-light-bg;\r\n        }\r\n      }\r\n    }\r\n    &.hint {\r\n      color: variables.$jse-date;\r\n      top: 4px;\r\n      left: 4px;\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "src/scss/jsoneditor/_contextmenu.scss",
    "content": "@use \"variables\";\n\n.jsoneditor-contextmenu-root {\n  position: relative;\n  width: 0;\n  height: 0;\n}\n.jsoneditor-contextmenu {\n  position: absolute;\n  box-sizing: content-box;\n  z-index: 2;\n  .jsoneditor-menu {\n    position: relative;\n    left: 0;\n    top: 0;\n    width: 128px;\n    height: auto;\n    background: variables.$jse-white;\n    border: 1px solid variables.$jse-bar-border;\n    box-shadow: variables.$jse-box-shadow;\n    list-style: none;\n    margin: 0;\n    padding: 0;\n    button {\n      position: relative;\n      padding: 0 8px 0 0;\n      margin: 0;\n      width: 128px;\n      height: auto;\n      border: none;\n      cursor: pointer;\n      color: variables.$jse-contextmenu-color;\n      background: transparent;\n      font-size: variables.$jse-font-size;\n      font-family: variables.$jse-font;\n      box-sizing: border-box;\n      text-align: left;\n      &::-moz-focus-inner {\n        padding: 0;\n        border: 0;\n      }\n      &.jsoneditor-default {\n        width: 96px;\n      }\n      &.jsoneditor-expand {\n        float: right;\n        width: 32px;\n        height: 24px;\n        border-left: 1px solid variables.$jse-separator;\n      }\n    }\n    li {\n      overflow: hidden;\n      ul {\n        display: none;\n        position: relative;\n        left: -10px;\n        top: 0;\n        border: none;\n        box-shadow: variables.$jse-box-shadow-inner;\n        padding: 0 10px;\n        -webkit-transition: all 0.3s ease-out;\n        -moz-transition: all 0.3s ease-out;\n        -o-transition: all 0.3s ease-out;\n        transition: all 0.3s ease-out;\n        .jsoneditor-icon {\n          margin-left: 24px;\n        }\n        li {\n          button {\n            padding-left: 24px;\n            animation: all ease-in-out 1s;\n          }\n        }\n      }\n      button {\n        .jsoneditor-expand {\n          position: absolute;\n          top: 0;\n          right: 0;\n          width: 24px;\n          height: 24px;\n          padding: 0;\n          margin: 0 4px 0 0;\n          background-image: variables.$jse-icons-url;\n          background-position: 0 -72px;\n        }\n      }\n    }\n  }\n  .jsoneditor-icon {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 24px;\n    height: 24px;\n    border: none;\n    padding: 0;\n    margin: 0;\n    background-image: variables.$jse-icons-url;\n  }\n  .jsoneditor-text {\n    padding: 4px 0 4px 24px;\n    word-wrap: break-word;\n    &.jsoneditor-right-margin {\n      padding-right: 24px;\n    }\n  }\n  .jsoneditor-separator {\n    height: 0;\n    border-top: 1px solid variables.$jse-separator;\n    padding-top: 5px;\n    margin-top: 5px;\n  }\n  button {\n    &.jsoneditor-remove {\n      .jsoneditor-icon {\n        background-position: -24px 0;\n      }\n    }\n    &.jsoneditor-append {\n      .jsoneditor-icon {\n        background-position: 0 0;\n      }\n    }\n    &.jsoneditor-insert {\n      .jsoneditor-icon {\n        background-position: 0 0;\n      }\n    }\n    &.jsoneditor-duplicate {\n      .jsoneditor-icon {\n        background-position: -48px 0;\n      }\n    }\n    &.jsoneditor-sort-asc {\n      .jsoneditor-icon {\n        background-position: -168px 0;\n      }\n    }\n    &.jsoneditor-sort-desc {\n      .jsoneditor-icon {\n        background-position: -192px 0;\n      }\n    }\n    &.jsoneditor-transform {\n      .jsoneditor-icon {\n        background-position: -216px 0;\n      }\n    }\n    &.jsoneditor-extract {\n      .jsoneditor-icon {\n        background-position: 0 -24px;\n      }\n    }\n    &.jsoneditor-type-string {\n      .jsoneditor-icon {\n        background-position: -144px 0;\n      }\n    }\n    &.jsoneditor-type-auto {\n      .jsoneditor-icon {\n        background-position: -120px 0;\n      }\n    }\n    &.jsoneditor-type-object {\n      .jsoneditor-icon {\n        background-position: -72px 0;\n      }\n    }\n    &.jsoneditor-type-array {\n      .jsoneditor-icon {\n        background-position: -96px 0;\n      }\n    }\n    &.jsoneditor-type-modes {\n      .jsoneditor-icon {\n        background-image: none;\n        width: 6px;\n      }\n    }\n  }\n}\n.jsoneditor-contextmenu ul,\n.jsoneditor-contextmenu li {\n  box-sizing: content-box;\n  position: relative;\n}\n.jsoneditor-contextmenu .jsoneditor-menu button:hover,\n.jsoneditor-contextmenu .jsoneditor-menu button:focus {\n  color: variables.$jse-content-color;\n  background-color: variables.$jse-preview;\n  outline: none;\n}\n.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected,\n.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:hover,\n.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:focus {\n  color: variables.$jse-white;\n  background-color: variables.$jse-number;\n}\n.jsoneditor-contextmenu .jsoneditor-menu li ul li button:hover,\n.jsoneditor-contextmenu .jsoneditor-menu li ul li button:focus {\n  background-color: variables.$jse-preview;\n}\n\n.jsoneditor-modal {\n  max-width: 95%;\n  border-radius: 2px !important;\n  padding: 45px 15px 15px 15px !important;\n  box-shadow: variables.$jse-box-shadow;\n  color: variables.$jse-contextmenu-color;\n  line-height: 1.3em;\n  &.jsoneditor-modal-transform {\n    width: 600px !important;\n  }\n  .pico-modal-header {\n    position: absolute;\n    box-sizing: border-box;\n    top: 0;\n    left: 0;\n    width: 100%;\n    padding: 0 10px;\n    height: 30px;\n    line-height: 30px;\n    font-family: variables.$jse-font;\n    font-size: 11pt;\n    background: variables.$jse-blue;\n    color: variables.$jse-white;\n  }\n  table {\n    width: 100%;\n    td {\n      padding: 3px 0;\n      &.jsoneditor-modal-input {\n        text-align: right;\n        padding-right: 0;\n        white-space: nowrap;\n      }\n      &.jsoneditor-modal-actions {\n        padding-top: 15px;\n      }\n    }\n    th {\n      vertical-align: middle;\n    }\n  }\n  p {\n    &:first-child {\n      margin-top: 0;\n    }\n  }\n  a {\n    color: variables.$jse-blue;\n  }\n  .jsoneditor-jmespath-block {\n    margin-bottom: 10px;\n  }\n  .pico-close {\n    background: none !important;\n    font-size: 24px !important;\n    top: 7px !important;\n    right: 7px !important;\n    color: variables.$jse-white;\n  }\n  input {\n    padding: 4px;\n  }\n  input[type=\"text\"] {\n    cursor: inherit;\n  }\n  input[disabled] {\n    background: variables.$jse-empty;\n    color: variables.$jse-readonly;\n  }\n  .jsoneditor-select-wrapper {\n    position: relative;\n    display: inline-block;\n    &:after {\n      content: \"\";\n      width: 0;\n      height: 0;\n      border-left: 5px solid transparent;\n      border-right: 5px solid transparent;\n      border-top: 6px solid #666;\n      position: absolute;\n      right: 8px;\n      top: 14px;\n      pointer-events: none;\n    }\n  }\n  select {\n    padding: 3px 24px 3px 10px;\n    min-width: 180px;\n    max-width: 350px;\n    -webkit-appearance: none;\n    -moz-appearance: none;\n    appearance: none;\n    text-indent: 0;\n    text-overflow: \"\";\n    font-size: variables.$jse-font-size;\n    line-height: 1.5em;\n    &::-ms-expand {\n      display: none;\n    }\n  }\n  .jsoneditor-button-group {\n    input {\n      padding: 4px 10px;\n      margin: 0;\n      border-radius: 0;\n      border-left-style: none;\n      &.jsoneditor-button-first {\n        border-top-left-radius: 3px;\n        border-bottom-left-radius: 3px;\n        border-left-style: solid;\n      }\n      &.jsoneditor-button-last {\n        border-top-right-radius: 3px;\n        border-bottom-right-radius: 3px;\n      }\n    }\n  }\n  .jsoneditor-transform-preview {\n    background: variables.$jse-preview;\n    height: 200px;\n    &.jsoneditor-error {\n      color: variables.$jse-number;\n    }\n  }\n  .jsoneditor-jmespath-wizard {\n    line-height: 1.2em;\n    width: 100%;\n    padding: 0;\n    border-radius: 3px;\n  }\n  .jsoneditor-jmespath-label {\n    font-weight: bold;\n    color: dodgerblue;\n    margin-top: 20px;\n    margin-bottom: 5px;\n  }\n  .jsoneditor-jmespath-wizard-table {\n    width: 100%;\n    border-collapse: collapse;\n  }\n  .jsoneditor-jmespath-wizard-label {\n    font-style: italic;\n    margin: 4px 0 2px 0;\n  }\n  .jsoneditor-inline {\n    position: relative;\n    display: inline-block;\n    width: 100%;\n    padding-top: 2px;\n    padding-bottom: 2px;\n    &:not(:last-child) {\n      padding-right: 2px;\n    }\n  }\n  .jsoneditor-jmespath-filter {\n    display: flex;\n    flex-wrap: wrap;\n  }\n  .jsoneditor-jmespath-filter-field {\n    width: 180px;\n  }\n  .jsoneditor-jmespath-filter-relation {\n    width: 100px;\n  }\n  .jsoneditor-jmespath-filter-value {\n    min-width: 180px;\n    flex: 1;\n  }\n  .jsoneditor-jmespath-sort-field {\n    width: 170px;\n  }\n  .jsoneditor-jmespath-sort-order {\n    width: 150px;\n  }\n  .jsoneditor-jmespath-select-fields {\n    width: 100%;\n  }\n  .selectr-selected {\n    border-color: variables.$jse-bar-border;\n    padding: 4px 28px 4px 8px;\n    .selectr-tag {\n      background-color: variables.$jse-blue;\n      border-radius: 5px;\n    }\n  }\n}\n.jsoneditor-modal table th,\n.jsoneditor-modal table td {\n  text-align: left;\n  vertical-align: middle;\n  font-weight: normal;\n  color: variables.$jse-contextmenu-color;\n  border-spacing: 0;\n  border-collapse: collapse;\n}\n.jsoneditor-modal select,\n.jsoneditor-modal textarea,\n.jsoneditor-modal input,\n.jsoneditor-modal input[type=\"text\"],\n.jsoneditor-modal input[type=\"text\"]:focus,\n.jsoneditor-modal #query {\n  background: #ffffff;\n  border: 1px solid variables.$jse-bar-border;\n  color: variables.$jse-contextmenu-color;\n  border-radius: 3px;\n  padding: 4px;\n}\n.jsoneditor-modal textarea,\n.jsoneditor-modal #query {\n  // workaround for a bug on Chrome resulting in blurry text,\n  // see https://github.com/josdejong/jsoneditor/issues/1419\n  border-radius: unset;\n}\n\n.jsoneditor-modal,\n.jsoneditor-modal table td,\n.jsoneditor-modal table th,\n.jsoneditor-modal select,\n.jsoneditor-modal option,\n.jsoneditor-modal textarea,\n.jsoneditor-modal input,\n.jsoneditor-modal input[type=\"text\"],\n.jsoneditor-modal #query {\n  font-size: 10.5pt;\n  font-family: variables.$jse-font;\n}\n.jsoneditor-modal #query,\n.jsoneditor-modal .jsoneditor-transform-preview {\n  font-family: variables.$jse-font-mono;\n  font-size: variables.$jse-font-size;\n  width: 100%;\n  box-sizing: border-box;\n}\n.jsoneditor-modal input[type=\"button\"],\n.jsoneditor-modal input[type=\"submit\"] {\n  background: variables.$jse-preview;\n  padding: 4px 20px;\n}\n.jsoneditor-modal select,\n.jsoneditor-modal input {\n  cursor: pointer;\n}\n.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-asc input.jsoneditor-button-asc,\n.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-desc input.jsoneditor-button-desc {\n  background: variables.$jse-blue;\n  border-color: variables.$jse-blue;\n  color: variables.$jse-white;\n}\n"
  },
  {
    "path": "src/scss/jsoneditor/_editor.scss",
    "content": "@use \"variables\";\n\n.jsoneditor {\n  color: variables.$jse-content-color;\n  border: thin solid variables.$jse-blue;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n  width: 100%;\n  height: 100%;\n  position: relative;\n  padding: 0;\n  line-height: 100%;\n}\ndiv.jsoneditor-field,\ndiv.jsoneditor-value,\na.jsoneditor-value,\ndiv.jsoneditor-readonly,\ndiv.jsoneditor-default {\n  border: 1px solid transparent;\n  min-height: 16px;\n  min-width: 32px;\n  line-height: 16px;\n  padding: 2px;\n  margin: 1px;\n  word-wrap: break-word;\n  word-break: break-word;\n  overflow-wrap: break-word;\n  float: left;\n}\ndiv.jsoneditor-field p,\ndiv.jsoneditor-value p {\n  margin: 0;\n}\ndiv {\n  &.jsoneditor-value {\n    &.jsoneditor-empty {\n      &::after {\n        content: \"value\";\n      }\n    }\n    &.jsoneditor-string {\n      color: variables.$jse-string;\n    }\n    &.jsoneditor-number {\n      color: variables.$jse-number;\n    }\n    &.jsoneditor-boolean {\n      color: variables.$jse-boolean;\n    }\n    &.jsoneditor-null {\n      color: variables.$jse-null;\n    }\n    &.jsoneditor-color-value {\n      color: variables.$jse-color-value;\n    }\n    &.jsoneditor-invalid {\n      color: variables.$jse-invalid;\n    }\n  }\n  &.jsoneditor-readonly {\n    min-width: 16px;\n    color: variables.$jse-readonly;\n  }\n  &.jsoneditor-empty {\n    border-color: variables.$jse-bar-border;\n    border-style: dashed;\n    border-radius: 2px;\n  }\n  &.jsoneditor-field {\n    &.jsoneditor-empty {\n      &::after {\n        content: \"field\";\n      }\n    }\n  }\n  &.jsoneditor {\n    td {\n      vertical-align: top;\n      &.jsoneditor-separator {\n        padding: 3px 0;\n        vertical-align: top;\n        color: variables.$jse-readonly;\n      }\n      &.jsoneditor-tree {\n        vertical-align: top;\n      }\n    }\n    &.busy {\n      pre {\n        &.jsoneditor-preview {\n          background: variables.$jse-preview;\n          color: variables.$jse-readonly;\n        }\n      }\n      div {\n        &.jsoneditor-busy {\n          display: inherit;\n        }\n      }\n    }\n    code {\n      &.jsoneditor-preview {\n        background: none;\n      }\n    }\n    &.jsoneditor-mode-preview {\n      pre {\n        &.jsoneditor-preview {\n          width: 100%;\n          height: 100%;\n          box-sizing: border-box;\n          overflow: auto;\n          padding: 2px;\n          margin: 0;\n          white-space: pre-wrap;\n          word-break: break-all;\n        }\n      }\n    }\n  }\n  &.jsoneditor-default {\n    color: variables.$jse-readonly;\n    padding-left: 10px;\n  }\n  &.jsoneditor-tree {\n    width: 100%;\n    height: 100%;\n    position: relative;\n    overflow: auto;\n    background: variables.$jse-white;\n\n    button {\n      &.jsoneditor-button {\n        width: 24px;\n        height: 24px;\n        padding: 0;\n        margin: 0;\n        border: none;\n        cursor: pointer;\n        background-color: transparent;\n        background-image: variables.$jse-icons-url;\n        &:focus {\n          background-color: variables.$jse-preview;\n          outline: #e5e5e5 solid 1px;\n        }\n      }\n      &.jsoneditor-collapsed {\n        background-position: 0 -48px;\n      }\n      &.jsoneditor-expanded {\n        background-position: 0 -72px;\n      }\n      &.jsoneditor-contextmenu-button {\n        background-position: -48px -72px;\n      }\n      &.jsoneditor-invisible {\n        visibility: hidden;\n        background: none;\n      }\n      &.jsoneditor-dragarea {\n        background-image: variables.$jse-icons-url;\n        background-position: -72px -72px;\n        cursor: move;\n      }\n    }\n    *:focus {\n      outline: none;\n    }\n    div {\n      &.jsoneditor-show-more {\n        display: inline-block;\n        padding: 3px 4px;\n        margin: 2px 0;\n        background-color: variables.$jse-separator;\n        border-radius: 3px;\n        color: variables.$jse-readonly;\n        font-family: variables.$jse-font;\n        font-size: variables.$jse-font-size;\n        a {\n          display: inline-block;\n          color: variables.$jse-readonly;\n        }\n      }\n      &.jsoneditor-color {\n        display: inline-block;\n        width: 12px;\n        height: 12px;\n        margin: 4px;\n        border: 1px solid variables.$jse-readonly;\n        cursor: pointer;\n\n        &.jsoneditor-color-readonly {\n          cursor: inherit;\n        }\n      }\n      &.jsoneditor-date {\n        background: variables.$jse-date;\n        color: variables.$jse-white;\n        font-family: variables.$jse-font;\n        border-radius: 3px;\n        display: inline-block;\n        padding: 3px;\n        margin: 0 3px;\n      }\n    }\n    table {\n      &.jsoneditor-tree {\n        border-collapse: collapse;\n        border-spacing: 0;\n        width: 100%;\n      }\n    }\n    .jsoneditor-button {\n      display: block;\n\n      &.jsoneditor-schema-error {\n        width: 24px;\n        height: 24px;\n        padding: 0;\n        margin: 0 4px 0 0;\n        background-image: variables.$jse-icons-url;\n        background-position: -168px -48px;\n        background-color: transparent;\n      }\n    }\n  }\n  &.jsoneditor-outer {\n    position: static;\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    -moz-box-sizing: border-box;\n    -webkit-box-sizing: border-box;\n    box-sizing: border-box;\n    &.has-nav-bar {\n      margin-top: -26px;\n      padding-top: 26px;\n      &.has-main-menu-bar {\n        margin-top: -61px;\n        padding-top: 61px;\n      }\n    }\n    &.has-status-bar {\n      margin-bottom: -26px;\n      padding-bottom: 26px;\n    }\n    &.has-main-menu-bar {\n      margin-top: -35px;\n      padding-top: 35px;\n    }\n  }\n  &.jsoneditor-busy {\n    position: absolute;\n    top: 15%;\n    left: 0;\n    box-sizing: border-box;\n    width: 100%;\n    text-align: center;\n    display: none;\n    span {\n      background-color: variables.$jse-busy;\n      border: 1px solid variables.$jse-busy-border-color;\n      border-radius: 3px;\n      padding: 5px 15px;\n      box-shadow: variables.$jse-box-shadow-sm;\n    }\n  }\n}\ndiv.jsoneditor-field.jsoneditor-empty::after,\ndiv.jsoneditor-value.jsoneditor-empty::after {\n  pointer-events: none;\n  color: variables.$jse-empty;\n  font-size: 8pt;\n}\ndiv.jsoneditor-value.jsoneditor-url,\na.jsoneditor-value.jsoneditor-url {\n  color: variables.$jse-string;\n  text-decoration: underline;\n}\na {\n  &.jsoneditor-value {\n    &.jsoneditor-url {\n      display: inline-block;\n      padding: 2px;\n      margin: 2px;\n    }\n  }\n}\na.jsoneditor-value.jsoneditor-url:hover,\na.jsoneditor-value.jsoneditor-url:focus {\n  color: variables.$jse-number;\n}\ndiv.jsoneditor-field[contenteditable=\"true\"]:focus,\ndiv.jsoneditor-field[contenteditable=\"true\"]:hover,\ndiv.jsoneditor-value[contenteditable=\"true\"]:focus,\ndiv.jsoneditor-value[contenteditable=\"true\"]:hover,\ndiv.jsoneditor-field.jsoneditor-highlight,\ndiv.jsoneditor-value.jsoneditor-highlight {\n  background-color: variables.$jse-busy;\n  border: 1px solid variables.$jse-busy-border-color;\n  border-radius: 2px;\n}\ndiv.jsoneditor-field.jsoneditor-highlight-active,\ndiv.jsoneditor-field.jsoneditor-highlight-active:focus,\ndiv.jsoneditor-field.jsoneditor-highlight-active:hover,\ndiv.jsoneditor-value.jsoneditor-highlight-active,\ndiv.jsoneditor-value.jsoneditor-highlight-active:focus,\ndiv.jsoneditor-value.jsoneditor-highlight-active:hover {\n  background-color: variables.$jse-highlight-bg;\n  border: 1px solid variables.$jse-highlight-border-color;\n  border-radius: 2px;\n}\ndiv.jsoneditor-value.jsoneditor-object,\ndiv.jsoneditor-value.jsoneditor-array {\n  min-width: 16px;\n}\ndiv.jsoneditor-tree button.jsoneditor-contextmenu-button:hover,\ndiv.jsoneditor-tree button.jsoneditor-contextmenu-button:focus,\ndiv.jsoneditor-tree button.jsoneditor-contextmenu-button.jsoneditor-selected,\ntr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu-button {\n  background-position: -48px -48px;\n}\ndiv.jsoneditor-tree div.jsoneditor-show-more a:hover,\ndiv.jsoneditor-tree div.jsoneditor-show-more a:focus {\n  color: variables.$jse-number;\n}\ntextarea.jsoneditor-text,\n.ace-jsoneditor {\n  min-height: 150px;\n\n  &.ace_editor {\n    font-family: variables.$jse-font-mono;\n  }\n}\ntextarea {\n  &.jsoneditor-text {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    -moz-box-sizing: border-box;\n    -webkit-box-sizing: border-box;\n    box-sizing: border-box;\n    outline-width: 0;\n    border: none;\n    background-color: variables.$jse-white;\n    resize: none;\n  }\n}\ntr.jsoneditor-highlight,\ntr.jsoneditor-selected {\n  background-color: variables.$jse-empty;\n}\ntr.jsoneditor-selected button.jsoneditor-dragarea,\ntr.jsoneditor-selected button.jsoneditor-contextmenu-button {\n  visibility: hidden;\n}\ntr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea,\ntr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu-button {\n  visibility: visible;\n}\ndiv.jsoneditor-tree button.jsoneditor-dragarea:hover,\ndiv.jsoneditor-tree button.jsoneditor-dragarea:focus,\ntr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea {\n  background-position: -72px -48px;\n}\ndiv.jsoneditor tr,\ndiv.jsoneditor th,\ndiv.jsoneditor td {\n  padding: 0;\n  margin: 0;\n}\ndiv.jsoneditor-field,\ndiv.jsoneditor-value,\ndiv.jsoneditor td,\ndiv.jsoneditor th,\ndiv.jsoneditor textarea,\npre.jsoneditor-preview,\n.jsoneditor-schema-error,\n.jsoneditor-popover {\n  font-family: variables.$jse-font-mono;\n  font-size: variables.$jse-font-size;\n  color: variables.$jse-content-color;\n}\n\n.jsoneditor-schema-error {\n  cursor: default;\n  display: inline-block;\n  height: 24px;\n  line-height: 24px;\n  position: relative;\n  text-align: center;\n  width: 24px;\n}\n\n.jsoneditor-popover {\n  background-color: variables.$jse-popover-bg;\n  border-radius: 3px;\n  box-shadow: variables.$jse-box-shadow-sm;\n  color: variables.$jse-white;\n  padding: 7px 10px;\n  position: absolute;\n  cursor: auto;\n  width: 200px;\n  &.jsoneditor-above {\n    bottom: 32px;\n    left: -98px;\n    &:before {\n      border-top: 7px solid variables.$jse-popover-bg;\n      bottom: -7px;\n    }\n  }\n  &.jsoneditor-below {\n    top: 32px;\n    left: -98px;\n    &:before {\n      border-bottom: 7px solid variables.$jse-popover-bg;\n      top: -7px;\n    }\n  }\n  &.jsoneditor-left {\n    top: -7px;\n    right: 32px;\n    &:before {\n      border-left: 7px solid variables.$jse-popover-bg;\n      border-top: 7px solid transparent;\n      border-bottom: 7px solid transparent;\n      content: \"\";\n      top: 19px;\n      right: -14px;\n      left: inherit;\n      margin-left: inherit;\n      margin-top: -7px;\n      position: absolute;\n    }\n  }\n  &.jsoneditor-right {\n    top: -7px;\n    left: 32px;\n    &:before {\n      border-right: 7px solid variables.$jse-popover-bg;\n      border-top: 7px solid transparent;\n      border-bottom: 7px solid transparent;\n      content: \"\";\n      top: 19px;\n      left: -14px;\n      margin-left: inherit;\n      margin-top: -7px;\n      position: absolute;\n    }\n  }\n  &:before {\n    border-right: 7px solid transparent;\n    border-left: 7px solid transparent;\n    content: \"\";\n    display: block;\n    left: 50%;\n    margin-left: -7px;\n    position: absolute;\n  }\n}\n\n.jsoneditor-text-errors {\n  tr {\n    &.jump-to-line {\n      &:hover {\n        text-decoration: underline;\n        cursor: pointer;\n      }\n    }\n  }\n}\n.jsoneditor-schema-error:hover .jsoneditor-popover,\n.jsoneditor-schema-error:focus .jsoneditor-popover {\n  display: block;\n  animation: fade-in 0.3s linear 1, move-up 0.3s linear 1;\n}\n\n@keyframes fade-in {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n\n/* JSON schema errors displayed at the bottom of the editor in mode text and code */\n\n.jsoneditor {\n  .jsoneditor-validation-errors-container {\n    max-height: 130px;\n    overflow-y: auto;\n  }\n  .jsoneditor-validation-errors {\n    width: 100%;\n    overflow: hidden;\n  }\n  .jsoneditor-additional-errors {\n    position: absolute;\n    margin: auto;\n    bottom: 31px;\n    left: calc(50% - 92px);\n    color: variables.$jse-readonly;\n    background-color: variables.$jse-light-bg;\n    padding: 7px 15px;\n    border-radius: 8px;\n    &.visible {\n      visibility: visible;\n      opacity: 1;\n      transition: opacity 2s linear;\n    }\n    &.hidden {\n      visibility: hidden;\n      opacity: 0;\n      transition: visibility 0s 2s, opacity 2s linear;\n    }\n  }\n  .jsoneditor-text-errors {\n    width: 100%;\n    border-collapse: collapse;\n    border-top: 1px solid variables.$jse-highlight-border-color;\n    td {\n      padding: 3px 6px;\n      vertical-align: middle;\n      pre {\n        margin: 0;\n        white-space: pre-wrap;\n      }\n    }\n    tr {\n      background-color: variables.$jse-busy;\n      &.parse-error {\n        background-color: variables.$jse-error;\n      }\n    }\n  }\n}\n.jsoneditor-text-errors {\n  .jsoneditor-schema-error {\n    border: none;\n    width: 24px;\n    height: 24px;\n    padding: 0;\n    margin: 0 4px 0 0;\n    cursor: pointer;\n  }\n  tr {\n    .jsoneditor-schema-error {\n      background-image: variables.$jse-icons-url;\n      background-position: -168px -48px;\n      background-color: transparent;\n    }\n    &.parse-error {\n      .jsoneditor-schema-error {\n        background-image: variables.$jse-icons-url;\n        background-position: -25px 0px;\n        background-color: transparent;\n      }\n    }\n  }\n}\n\n.jsoneditor-anchor {\n  cursor: pointer;\n\n  .picker_wrapper {\n    &.popup {\n      &.popup_bottom {\n        top: 28px;\n        left: -10px;\n      }\n    }\n  }\n}\n\n.fadein {\n  -webkit-animation: fadein 0.3s;\n  animation: fadein 0.3s;\n  -moz-animation: fadein 0.3s;\n  -o-animation: fadein 0.3s;\n}\n\n@keyframes fadein {\n  0% {\n    opacity: 0;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n\n// override some styles which where cleared in reset.scss\n.jsoneditor-modal {\n  input[type=\"search\"].selectr-input {\n    border: 1px solid #d3d3d3;\n    width: calc(100% - 4px);\n    margin: 2px;\n    padding: 4px;\n    box-sizing: border-box;\n  }\n\n  button.selectr-input-clear {\n    right: 8px;\n  }\n}\n"
  },
  {
    "path": "src/scss/jsoneditor/_menu.scss",
    "content": "@use \"variables\";\n\n.jsoneditor-menu {\n  width: 100%;\n  height: 35px;\n  padding: 2px;\n  margin: 0;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n\n  color: variables.$jse-white;\n  background-color: variables.$jse-blue;\n  border-bottom: 1px solid variables.$jse-blue;\n}\n\n.jsoneditor-menu > button,\n.jsoneditor-menu > .jsoneditor-modes > button {\n  width: 26px;\n  height: 26px;\n  margin: 2px;\n  padding: 0;\n  border-radius: 2px;\n  border: 1px solid transparent;\n  background-color: transparent;\n  background-image: variables.$jse-icons-url;\n  color: variables.$jse-white;\n  opacity: 0.8;\n\n  font-family: variables.$jse-font;\n  font-size: variables.$jse-font-size;\n\n  float: left;\n}\n\n.jsoneditor-menu > button:hover,\n.jsoneditor-menu > .jsoneditor-modes > button:hover {\n  background-color: rgba(255, 255, 255, 0.2);\n  border: 1px solid rgba(255, 255, 255, 0.4);\n}\n.jsoneditor-menu > button:focus,\n.jsoneditor-menu > button:active,\n.jsoneditor-menu > .jsoneditor-modes > button:focus,\n.jsoneditor-menu > .jsoneditor-modes > button:active {\n  background-color: rgba(255, 255, 255, 0.3);\n}\n.jsoneditor-menu > button:disabled,\n.jsoneditor-menu > .jsoneditor-modes > button:disabled {\n  opacity: 0.5;\n  background-color: transparent;\n  border: none;\n}\n\n.jsoneditor-menu > button.jsoneditor-collapse-all {\n  background-position: 0 -96px;\n}\n.jsoneditor-menu > button.jsoneditor-expand-all {\n  background-position: 0 -120px;\n}\n.jsoneditor-menu > button.jsoneditor-sort {\n  background-position: -120px -96px;\n}\n.jsoneditor-menu > button.jsoneditor-transform {\n  background-position: -144px -96px;\n}\n.jsoneditor.jsoneditor-mode-view > .jsoneditor-menu > button.jsoneditor-sort,\n.jsoneditor.jsoneditor-mode-form > .jsoneditor-menu > button.jsoneditor-sort,\n.jsoneditor.jsoneditor-mode-view > .jsoneditor-menu > button.jsoneditor-transform,\n.jsoneditor.jsoneditor-mode-form > .jsoneditor-menu > button.jsoneditor-transform {\n  display: none;\n}\n.jsoneditor-menu > button.jsoneditor-undo {\n  background-position: -24px -96px;\n}\n.jsoneditor-menu > button.jsoneditor-undo:disabled {\n  background-position: -24px -120px;\n}\n.jsoneditor-menu > button.jsoneditor-redo {\n  background-position: -48px -96px;\n}\n.jsoneditor-menu > button.jsoneditor-redo:disabled {\n  background-position: -48px -120px;\n}\n.jsoneditor-menu > button.jsoneditor-compact {\n  background-position: -72px -96px;\n}\n.jsoneditor-menu > button.jsoneditor-format {\n  background-position: -72px -120px;\n}\n.jsoneditor-menu > button.jsoneditor-repair {\n  background-position: -96px -96px;\n}\n\n.jsoneditor-menu > .jsoneditor-modes {\n  display: inline-block;\n  float: left;\n}\n\n.jsoneditor-menu > .jsoneditor-modes > button {\n  background-image: none;\n  width: auto;\n  padding-left: 6px;\n  padding-right: 6px;\n}\n\n.jsoneditor-menu > button.jsoneditor-separator,\n.jsoneditor-menu > .jsoneditor-modes > button.jsoneditor-separator {\n  margin-left: 10px;\n}\n\n.jsoneditor-menu a {\n  font-family: variables.$jse-font;\n  font-size: variables.$jse-font-size;\n  color: variables.$jse-white;\n  opacity: 0.8;\n  vertical-align: middle;\n}\n\n.jsoneditor-menu a:hover {\n  opacity: 1;\n}\n\n.jsoneditor-menu a.jsoneditor-poweredBy {\n  font-size: 8pt;\n  position: absolute;\n  right: 0;\n  top: 0;\n  padding: 10px;\n}\n"
  },
  {
    "path": "src/scss/jsoneditor/_navigationbar.scss",
    "content": "@use \"variables\";\n\n.jsoneditor-navigation-bar {\r\n  width: 100%;\r\n  height: 26px;\r\n  line-height: 26px;\r\n  padding: 0;\r\n  margin: 0;\r\n  border-bottom: 1px solid variables.$jse-bar-border;\r\n  -moz-box-sizing: border-box;\r\n  -webkit-box-sizing: border-box;\r\n  box-sizing: border-box;\r\n  color: variables.$jse-readonly;\r\n  background-color: variables.$jse-light-bg;\r\n  overflow: hidden;\r\n\r\n  font-family: variables.$jse-font;\r\n  font-size: variables.$jse-font-size;\r\n}\r\n"
  },
  {
    "path": "src/scss/jsoneditor/_reset.scss",
    "content": ".jsoneditor,\n.jsoneditor-modal {\n  -webkit-text-size-adjust: none;\n  text-size-adjust: none;\n\n  input,\n  input:not([type]),\n  input[type=\"text\"],\n  input[type=\"search\"], {\n    height: auto;\n    border: inherit;\n    box-shadow: none;\n    font-size: inherit;\n    box-sizing: inherit;\n    padding: inherit;\n    font-family: inherit;\n    transition: none;\n    line-height: inherit;\n\n    &:focus {\n      border: inherit;\n      box-shadow: inherit;\n    }\n  }\n\n  textarea {\n    height: inherit;\n  }\n\n  select {\n    display: inherit;\n    height: inherit;\n  }\n\n  label {\n    font-size: inherit;\n    font-weight: inherit;\n    color: inherit;\n  }\n\n  table {\n    border-collapse: collapse;\n    width: auto;\n  }\n\n  td,\n  th {\n    padding: 0;\n    display: table-cell;\n    text-align: left;\n    vertical-align: inherit;\n    border-radius: inherit;\n  }\n}\n"
  },
  {
    "path": "src/scss/jsoneditor/_searchbox.scss",
    "content": "@use \"variables\";\n\n.jsoneditor {\n  &-search {\n    font-family: variables.$jse-font;\n    position: absolute;\n    right: 4px;\n    top: 4px;\n    border-collapse: collapse;\n    border-spacing: 0;\n    display: flex;\n\n    input {\n      color: variables.$jse-content-color;\n      width: 120px;\n      border: none;\n      outline: none;\n      margin: 1px;\n      line-height: 20px;\n      font-family: variables.$jse-font;\n    }\n\n    button {\n      width: 16px;\n      height: 24px;\n      padding: 0;\n      margin: 0;\n      border: none;\n      background: variables.$jse-icons-url;\n      vertical-align: top;\n\n      &:hover {\n        background-color: transparent;\n      }\n\n      &.jsoneditor-refresh {\n        width: 18px;\n        background-position: -99px -73px;\n      }\n\n      &.jsoneditor-next {\n        cursor: pointer;\n        background-position: -124px -73px;\n\n        &:hover {\n          background-position: -124px -49px;\n        }\n      }\n      &.jsoneditor-previous {\n        cursor: pointer;\n        background-position: -148px -73px;\n        margin-right: 2px;\n\n        &:hover {\n          background-position: -148px -49px;\n        }\n      }\n    }\n  }\n\n  &-results {\n    font-family: variables.$jse-font;\n    color: variables.$jse-white;\n    padding-right: 5px;\n    line-height: 26px;\n  }\n\n  &-frame {\n    border: 1px solid transparent;\n    background-color: variables.$jse-white;\n    padding: 0 2px;\n    margin: 0;\n  }\n}\n"
  },
  {
    "path": "src/scss/jsoneditor/_statusbar.scss",
    "content": "@use \"variables\";\n\n.jsoneditor-statusbar {\n  line-height: 26px;\n  height: 26px;\n  color: variables.$jse-readonly;\n  background-color: variables.$jse-bar-bg;\n  border-top: 1px solid variables.$jse-bar-border;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n  font-size: variables.$jse-font-size;\n  & > .jsoneditor-curserinfo-val {\n    margin-right: 12px;\n  }\n  & > .jsoneditor-curserinfo-count {\n    margin-left: 4px;\n  }\n  & > .jsoneditor-validation-error-icon {\n    float: right;\n    width: 24px;\n    height: 24px;\n    padding: 0;\n    margin-top: 1px;\n    background-image: variables.$jse-icons-url;\n    background-position: -168px -48px;\n    cursor: pointer;\n  }\n  & > .jsoneditor-validation-error-count {\n    float: right;\n    margin: 0 4px 0 0;\n    cursor: pointer;\n  }\n  & > .jsoneditor-parse-error-icon {\n    float: right;\n    width: 24px;\n    height: 24px;\n    padding: 0;\n    margin: 1px;\n    background-image: variables.$jse-icons-url;\n    background-position: -25px 0px;\n  }\n  .jsoneditor-array-info {\n    a {\n      color: inherit;\n    }\n  }\n}\ndiv.jsoneditor-statusbar > .jsoneditor-curserinfo-label,\ndiv.jsoneditor-statusbar > .jsoneditor-size-info {\n  margin: 0 4px;\n}\n"
  },
  {
    "path": "src/scss/jsoneditor/_treepath.scss",
    "content": "@use \"variables\";\n\n.jsoneditor-treepath {\n  padding: 0 5px;\n  overflow: hidden;\n  white-space: nowrap;\n  outline: none;\n  &.show-all {\n    word-wrap: break-word;\n    white-space: normal;\n    position: absolute;\n    background-color: variables.$jse-light-bg;\n    z-index: 1;\n    box-shadow: variables.$jse-box-shadow;\n    span {\n      &.jsoneditor-treepath-show-all-btn {\n        display: none;\n      }\n    }\n  }\n  div {\n    &.jsoneditor-contextmenu-root {\n      position: absolute;\n      left: 0;\n    }\n  }\n  .jsoneditor-treepath-show-all-btn {\n    position: absolute;\n    background-color: variables.$jse-light-bg;\n    left: 0;\n    height: 20px;\n    padding: 0 3px;\n    cursor: pointer;\n  }\n  .jsoneditor-treepath-element {\n    margin: 1px;\n    font-family: variables.$jse-font;\n    font-size:  variables.$jse-font-size;\n  }\n  .jsoneditor-treepath-seperator {\n    margin: 2px;\n    font-size: 9pt;\n    font-family: variables.$jse-font;\n  }\n}\n.jsoneditor-treepath span.jsoneditor-treepath-element:hover,\n.jsoneditor-treepath span.jsoneditor-treepath-seperator:hover {\n  cursor: pointer;\n  text-decoration: underline;\n}\n"
  },
  {
    "path": "src/scss/jsoneditor/_variables.scss",
    "content": "$jse-white: #ffffff !default;\n$jse-grey: #999999 !default;\n$jse-light-bg: #ebebeb !default;\n$jse-blue: #3883fa !default;\n$jse-content-color: #1a1a1a !default;\n\n$jse-string: #006000 !default;\n$jse-number: #ee422e !default;\n$jse-boolean: #ff8c00 !default;\n$jse-null: #004ed0 !default;\n$jse-color-value: $jse-content-color !default;\n$jse-invalid: $jse-content-color !default;\n$jse-readonly: #808080 !default;\n$jse-empty: #d3d3d3 !default;\n$jse-preview: #f5f5f5 !default;\n$jse-busy: #ffffab !default;\n$jse-busy-border-color: #ffee00 !default;\n\n$jse-error: #ee2e2e70 !default;\n$jse-separator: #e5e5e5 !default;\n$jse-highlight-bg: #ffee00 !default;\n$jse-highlight-border-color: #ffc700 !default;\n\n$jse-popover-bg: #4c4c4c !default;\n$jse-bar-bg: $jse-light-bg !default;\n$jse-bar-border: $jse-empty !default;\n\n$jse-menu-color: $jse-empty !default;\n\n$jse-contextmenu-color: #4d4d4d !default;\n\n$jse-box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3) !default;\n$jse-box-shadow-sm: 0 0 5px rgba(0, 0, 0, 0.4) !default;\n$jse-box-shadow-inner: inset 0 0 10px rgba(128, 128, 128, 0.5) !default;\n\n$jse-date: #a1a1a1 !default;\n\n$jse-font: arial, sans-serif !default;\n// \"consolas\" for Windows, \"menlo\" for Mac with fallback to \"monaco\", 'Ubuntu Mono' for Ubuntu\n// (at Mac this font looks too large at 14px, but 13px is too small for the font on Windows)\n$jse-font-mono: consolas, menlo, monaco, 'Ubuntu Mono', 'source-code-pro', monospace !default;\n$jse-font-size: 14px !default;\n\n$jse-icons: \"./img/jsoneditor-icons.svg\" !default;\n$jse-icons-url: url($jse-icons) !default;\n"
  },
  {
    "path": "src/scss/jsoneditor.scss",
    "content": "@use \"jsoneditor/reset\";\n@use \"jsoneditor/variables\";\n@use \"jsoneditor/autocomplete\";\n@use \"jsoneditor/contextmenu\";\n@use \"jsoneditor/editor\";\n@use \"jsoneditor/menu\";\n@use \"jsoneditor/navigationbar\";\n@use \"jsoneditor/searchbox\";\n@use \"jsoneditor/statusbar\";\n@use \"jsoneditor/treepath\";\n@use \"../js/assets/selectr/selectr\";\n"
  },
  {
    "path": "test/Node.test.js",
    "content": "import assert from 'assert'\nimport './setup'\nimport { Node } from '../src/js/Node'\n\ndescribe('Node', () => {\n  describe('_findSchema', () => {\n    it('should find schema', () => {\n      const schema = {\n        type: 'object',\n        properties: {\n          child: {\n            type: 'string'\n          }\n        }\n      }\n      const path = ['child']\n      assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.child)\n    })\n\n    it('should find schema inside an array item', () => {\n      const schema = {\n        properties: {\n          job: {\n            type: 'array',\n            items: {\n              type: 'object',\n              properties: {\n                company: {\n                  enum: ['test1', 'test2']\n                }\n              }\n            }\n          }\n        }\n      }\n\n      assert.strictEqual(Node._findSchema(schema, {}, []), schema)\n\n      assert.strictEqual(Node._findSchema(schema, {}, ['job']), schema.properties.job)\n\n      assert.strictEqual(Node._findSchema(schema, {}, ['job', 0]),\n        schema.properties.job.items)\n\n      assert.strictEqual(Node._findSchema(schema, {}, ['job', 0, 'company']),\n        schema.properties.job.items.properties.company)\n    })\n\n    it('should find schema within multi-level object properties', () => {\n      const schema = {\n        type: 'object',\n        properties: {\n          levelTwo: {\n            type: 'object',\n            properties: {\n              levelThree: {\n                type: 'object',\n                properties: {\n                  bool: {\n                    type: 'boolean'\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n      let path = []\n      assert.strictEqual(Node._findSchema(schema, {}, path), schema)\n      path = ['levelTwo']\n      assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo)\n      path = ['levelTwo', 'levelThree']\n      assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo.properties.levelThree)\n      path = ['levelTwo', 'levelThree', 'bool']\n      assert.strictEqual(\n        Node._findSchema(schema, {}, path),\n        schema.properties.levelTwo.properties.levelThree.properties.bool\n      )\n    })\n\n    it('should find referenced schema within multi-level object properties', () => {\n      const schema = {\n        type: 'object',\n        properties: {\n          aProperty: {\n            $ref: 'second_schema#/definitions/some_def'\n          }\n        }\n      }\n      const schemaRefs = {\n        second_schema: {\n          definitions: {\n            some_def: {\n              type: 'object',\n              properties: {\n                enumProp: {\n                  enum: [1, 2, 3]\n                }\n              }\n            }\n          }\n        }\n      }\n      const path = ['aProperty', 'enumProp']\n      const expectedSchema = {\n        enum: [1, 2, 3]\n      }\n      assert.deepStrictEqual(Node._findSchema(schema, schemaRefs, path), expectedSchema)\n    })\n\n    it('should find array referenced schema within multi-level object properties', () => {\n      const schema = {\n        type: 'object',\n        properties: {\n          aProperty: {\n            type: 'array',\n            items: {\n              $ref: 'second_schema#/definitions/some_def'\n            }\n          }\n        }\n      }\n      const schemaRefs = {\n        second_schema: {\n          definitions: {\n            some_def: {\n              type: 'object',\n              properties: {\n                enumProp: {\n                  enum: [1, 2, 3]\n                }\n              }\n            }\n          }\n        }\n      }\n      const path = ['aProperty', 0, 'enumProp']\n      const expectedSchema = {\n        enum: [1, 2, 3]\n      }\n      assert.deepStrictEqual(Node._findSchema(schema, schemaRefs, path), expectedSchema)\n    })\n\n    it('should return null for path that has no schema', () => {\n      const schema = {\n        type: 'object',\n        properties: {\n          foo: {\n            type: 'object',\n            properties: {\n              baz: {\n                type: 'number'\n              }\n            }\n          }\n        }\n      }\n      let path = ['bar']\n      assert.strictEqual(Node._findSchema(schema, {}, path), null)\n      path = ['foo', 'bar']\n      assert.strictEqual(Node._findSchema(schema, {}, path), null)\n    })\n\n    it('should find one of required properties', () => {\n      const schema = {\n        properties: {\n          company: {\n            type: 'string',\n            enum: ['1', '2']\n          },\n          worker: {\n            type: 'string',\n            enum: ['a', 'b']\n          },\n          manager: {\n            type: 'string',\n            enum: ['c', 'd']\n          }\n        },\n        additionalProperties: false,\n        oneOf: [\n          {\n            required: ['worker']\n          },\n          {\n            required: ['manager']\n          }\n        ]\n      }\n      let path = ['company']\n      assert.deepStrictEqual(Node._findSchema(schema, {}, path), {\n        type: 'string',\n        enum: ['1', '2']\n      })\n      path = ['worker']\n      assert.deepStrictEqual(Node._findSchema(schema, {}, path), {\n        type: 'string',\n        enum: ['a', 'b']\n      })\n    })\n\n    // https://json-schema.org/understanding-json-schema/reference/object#extending\n    it('works with extending schemas', () => {\n      const schema = {\n        properties: {\n          name: true,\n          manager: {\n            type: 'string',\n            enum: ['c', 'd']\n          }\n        },\n        additionalProperties: false,\n        allOf: [\n          {\n            properties: {\n              name: {\n                type: 'string',\n                enum: ['a', 'b']\n              }\n            }\n          }\n        ]\n      }\n      let path = ['name']\n      assert.deepStrictEqual(Node._findSchema(schema, {}, path), {\n        type: 'string',\n        enum: ['a', 'b']\n      })\n      path = ['manager']\n      assert.deepStrictEqual(Node._findSchema(schema, {}, path), {\n        type: 'string',\n        enum: ['c', 'd']\n      })\n    })\n\n    describe('with $ref', () => {\n      it('should find a referenced schema', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            foo: {\n              $ref: 'foo'\n            }\n          }\n        }\n        const fooSchema = {\n          type: 'number',\n          title: 'Foo'\n        }\n        const path = ['foo']\n        assert.strictEqual(Node._findSchema(schema, { foo: fooSchema }, path), fooSchema)\n      })\n\n      it('should find a referenced schema property', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            foo: {\n              $ref: 'foo'\n            }\n          }\n        }\n        const fooSchema = {\n          type: 'object',\n          properties: {\n            levelTwo: {\n              type: 'string'\n            }\n          }\n        }\n        const path = ['foo', 'levelTwo']\n        assert.strictEqual(Node._findSchema(schema, { foo: fooSchema }, path), fooSchema.properties.levelTwo)\n      })\n\n      it('should find a referenced schema definition', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            foo: {\n              type: 'array',\n              items: {\n                $ref: 'foo#/definitions/some_def'\n              }\n            }\n          }\n        }\n        const fooSchema = {\n          definitions: {\n            some_def: {\n              type: 'object',\n              properties: {\n                propA: {\n                  type: 'string'\n                },\n                propB: {\n                  type: 'string'\n                }\n              }\n            }\n          }\n        }\n        const path = ['foo', 0]\n        assert.strictEqual(Node._findSchema(schema, { foo: fooSchema }, path), fooSchema.definitions.some_def)\n      })\n\n      it('should find a referenced schema definition 2', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            foo: {\n              type: 'array',\n              items: {\n                $ref: 'foo#/definitions/some_def'\n              }\n            }\n          }\n        }\n        const fooSchema = {\n          definitions: {\n            some_def: {\n              type: 'object',\n              properties: {\n                propA: {\n                  type: 'string'\n                },\n                propB: {\n                  type: 'string'\n                }\n              }\n            }\n          }\n        }\n        const path = ['foo', 0, 'propA']\n        assert.strictEqual(Node._findSchema(schema, { foo: fooSchema }, path), fooSchema.definitions.some_def.properties.propA)\n      })\n\n      it('should find a referenced schema definition 3', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            foo: {\n              type: 'array',\n              items: {\n                $ref: 'foo#/definitions/some_def'\n              }\n            }\n          }\n        }\n        const fooSchema = {\n          definitions: {\n            some_def: {\n              type: 'object',\n              properties: {\n                propA: {\n                  type: 'object',\n                  properties: {\n                    propA1: { type: 'boolean' }\n                  }\n                },\n                propB: { type: 'string' }\n              }\n            }\n          }\n        }\n        const path = ['foo', 0, 'propA', 'propA1']\n        assert.strictEqual(Node._findSchema(schema, { foo: fooSchema }, path), fooSchema.definitions.some_def.properties.propA.properties.propA1)\n      })\n    })\n\n    describe('with $ref to internal definition', () => {\n      it('should find a referenced schema', () => {\n        const schema = {\n          $schema: 'http://json-schema.org/draft-07/schema#',\n          type: 'object',\n          patternProperties: {\n            '^/[a-z0-9]*$': {\n              $ref: '#/definitions/component'\n            }\n          },\n          definitions: {\n            component: {\n              type: 'object',\n              properties: {\n                type: {\n                  type: 'string',\n                  minLength: 1\n                },\n                config: {\n                  type: 'object'\n                },\n                children: {\n                  type: 'object',\n                  patternProperties: {\n                    '^/[a-z0-9]+$': {\n                      $ref: '#/definitions/component'\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n        const path = ['/status', 'children', '/bus', 'config']\n        const foundSchema = {\n          type: 'object'\n        }\n        assert.notStrictEqual(Node._findSchema(schema, {}, path), foundSchema)\n      })\n    })\n\n    describe('with $ref to external definition', () => {\n      it('should find a referenced schema', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            address: {\n              $ref: 'definitions.json#/address'\n            }\n          }\n        }\n        const definitions = {\n          address: {\n            type: 'object',\n            properties: {\n              country: {\n                type: 'string'\n              },\n              city: {\n                type: 'string'\n              }\n            }\n          }\n        }\n        const path = ['address', 'city']\n        const foundSchema = { type: 'string' }\n        assert.notStrictEqual(Node._findSchema(schema, { 'definitions.json': definitions }, path), foundSchema)\n      })\n    })\n\n    describe('with pattern properties', () => {\n      it('should find schema', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            str: {\n              title: 'str',\n              type: 'boolean'\n            }\n          },\n          patternProperties: {\n            '^foo[0-9]': {\n              title: 'foo[0-] pattern property',\n              type: 'string'\n            }\n          }\n        }\n        let path = []\n        assert.strictEqual(Node._findSchema(schema, {}, path), schema, 'top level')\n        path = ['str']\n        assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.str, 'normal property')\n      })\n\n      it('should find schema within multi-level object properties', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            levelTwo: {\n              type: 'object',\n              properties: {\n                levelThree: {\n                  type: 'object',\n                  properties: {\n                    bool: {\n                      title: 'bool',\n                      type: 'boolean'\n                    }\n                  }\n                }\n              }\n            }\n          },\n          patternProperties: {\n            '^foo[0-9]': {\n              title: 'foo[0-9] pattern property',\n              type: 'string'\n            }\n          }\n        }\n        let path = []\n        assert.strictEqual(Node._findSchema(schema, {}, path), schema, 'top level')\n        path = ['levelTwo']\n        assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo, 'level two')\n        path = ['levelTwo', 'levelThree']\n        assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo.properties.levelThree, 'level three')\n        path = ['levelTwo', 'levelThree', 'bool']\n        assert.strictEqual(\n          Node._findSchema(schema, {}, path),\n          schema.properties.levelTwo.properties.levelThree.properties.bool,\n          'normal property'\n        )\n      })\n\n      it('should find schema for pattern properties', () => {\n        const schema = {\n          type: 'object',\n          patternProperties: {\n            '^foo[0-9]': {\n              title: 'foo[0-9] pattern property',\n              type: 'string'\n            },\n            '^bar[0-9]': {\n              title: 'bar[0-9] pattern property',\n              type: 'string'\n            }\n          }\n        }\n        let path = ['foo1']\n        assert.strictEqual(\n          Node._findSchema(schema, {}, path),\n          schema.patternProperties['^foo[0-9]'],\n          'first pattern property'\n        )\n        path = ['bar5']\n        assert.strictEqual(\n          Node._findSchema(schema, {}, path),\n          schema.patternProperties['^bar[0-9]'],\n          'second pattern property'\n        )\n      })\n\n      it('should find schema for multi-level pattern properties', () => {\n        const schema = {\n          type: 'object',\n          patternProperties: {\n            '^foo[0-9]': {\n              title: 'foo[0-9] pattern property',\n              type: 'object',\n              properties: {\n                fooChild: {\n                  type: 'object',\n                  properties: {\n                    fooChild2: {\n                      type: 'string'\n                    }\n                  }\n                }\n              }\n            },\n            '^bar[0-9]': {\n              title: 'bar[0-9] pattern property',\n              type: 'object',\n              properties: {\n                barChild: {\n                  type: 'string'\n                }\n\n              }\n            }\n          }\n        }\n        let path = ['foo1', 'fooChild', 'fooChild2']\n        assert.strictEqual(\n          Node._findSchema(schema, {}, path),\n          schema.patternProperties['^foo[0-9]'].properties.fooChild.properties.fooChild2,\n          'first pattern property child of child'\n        )\n        path = ['bar5', 'barChild']\n        assert.strictEqual(\n          Node._findSchema(schema, {}, path),\n          schema.patternProperties['^bar[0-9]'].properties.barChild,\n          'second pattern property child'\n        )\n      })\n\n      it('should return null for path that has no schema', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            levelTwo: {\n              type: 'object',\n              properties: {\n                levelThree: {\n                  type: 'number'\n                }\n              }\n            }\n          },\n          patternProperties: {\n            '^foo[0-9]': {\n              title: 'foo[0-9] pattern property',\n              type: 'string'\n            },\n            '^bar[0-9]': {\n              title: 'bar[0-9] pattern property',\n              type: 'string'\n            }\n          }\n        }\n        let path = ['not-in-schema']\n        assert.strictEqual(Node._findSchema(schema, {}, path), null)\n        path = ['levelOne', 'not-in-schema']\n        assert.strictEqual(Node._findSchema(schema, {}, path), null)\n      })\n\n      it('should return additionalProperties schema', () => {\n        const schema = {\n          type: 'object',\n          properties: {\n            company: {\n              type: 'string',\n              enum: ['1', '2']\n            },\n            nested: {\n              type: 'object',\n              additionalProperties: {\n                type: 'number'\n              }\n            }\n          },\n          additionalProperties: {\n            type: 'string',\n            enum: ['1', '2']\n          }\n        }\n        let path = ['company2']\n        assert.strictEqual(\n          Node._findSchema(schema, {}, path),\n          schema.additionalProperties,\n          'additionalProperties schema'\n        )\n        path = ['nested', 'virtual']\n        assert.strictEqual(\n          Node._findSchema(schema, {}, path),\n          schema.properties.nested.additionalProperties,\n          'additionalProperties schema'\n        )\n      })\n    })\n  })\n  describe('_findEnum', () => {\n    it('should find enum', () => {\n      const schema = {\n        type: 'object',\n        enum: [1, 2, 3]\n      }\n      assert.strictEqual(Node._findEnum(schema), schema.enum)\n    })\n  })\n})\n"
  },
  {
    "path": "test/SchemaTextCompleter.test.js",
    "content": "import assert from 'assert'\nimport { schema, schemaRefs } from './data/schemas'\nimport { autocompleteJsonStr } from './data/jsons'\nimport { SchemaTextCompleter } from '../src/js/SchemaTextCompleter'\n\nconst sessionMock = {\n  getValue: () => autocompleteJsonStr\n}\n\ndescribe('SchemaTextCompleter tests', () => {\n  let schemaTextCompleter\n  before(() => {\n    schemaTextCompleter = new SchemaTextCompleter(schema, schemaRefs)\n  })\n\n  it('should initiate and expose getCompletions function', () => {\n    assert.strictEqual(typeof schemaTextCompleter.getCompletions, 'function')\n  })\n\n  it('should validate completions of single schema ref', (done) => {\n    schemaTextCompleter.getCompletions(\n      undefined,\n      sessionMock,\n      { row: 2, column: 18 },\n      '',\n      (a, completions) => {\n        assert.strictEqual(completions.length, 1)\n        assert.strictEqual(completions[0].caption, 'John')\n        assert.strictEqual(completions[0].meta, 'schema [examples]')\n        assert.strictEqual(completions[0].score, 0)\n        assert.strictEqual(completions[0].value, 'John')\n        done()\n      }\n    )\n  })\n\n  it('should validate completions of triple schema refs', (done) => {\n    schemaTextCompleter.getCompletions(\n      undefined,\n      sessionMock,\n      { row: 15, column: 14 },\n      '',\n      (a, completions) => {\n        assert.strictEqual(completions.length, 3)\n\n        assert.strictEqual(completions[0].caption, 'junior')\n        assert.strictEqual(completions[0].meta, 'schema [enum]')\n        assert.strictEqual(completions[0].score, 0)\n        assert.strictEqual(completions[0].value, 'junior')\n\n        assert.strictEqual(completions[1].caption, 'experienced')\n        assert.strictEqual(completions[1].meta, 'schema [enum]')\n        assert.strictEqual(completions[1].score, 1)\n        assert.strictEqual(completions[1].value, 'experienced')\n\n        assert.strictEqual(completions[2].caption, 'senior')\n        assert.strictEqual(completions[2].meta, 'schema [enum]')\n        assert.strictEqual(completions[2].score, 2)\n        assert.strictEqual(completions[2].value, 'senior')\n\n        done()\n      }\n    )\n  })\n\n  it('should validate completions of recursive schema refs', (done) => {\n    schemaTextCompleter.getCompletions(\n      undefined,\n      sessionMock,\n      { row: 22, column: 21 },\n      '',\n      (a, completions) => {\n        assert.strictEqual(completions.length, 1)\n\n        assert.strictEqual(completions[0].caption, 'Smith')\n        assert.strictEqual(completions[0].meta, 'schema [examples]')\n        assert.strictEqual(completions[0].score, 0)\n        assert.strictEqual(completions[0].value, 'Smith')\n\n        done()\n      }\n    )\n  })\n})\n"
  },
  {
    "path": "test/couchdbeditor.html",
    "content": "<!DOCTYPE HTML>\n<html>\n<head>\n  <title>CouchDB Document Editor</title>\n  \n  <meta name=\"description\" content=\"CouchDB Document Editor\">\n  <meta name=\"keywords\" content=\"json, editor, couchdb, online, javascript, javascript object notation, treeview, open source, free\">\n  <meta name=\"author\" content=\"Jos de Jong\">\n\n  <link rel=\"shortcut icon\" href=\"../app/web/favicon.ico\">\n  \n  <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js\"></script>\n  <script src=\"../jsoneditor/js/jsoneditor.js\"></script>\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"../jsoneditor/css/jsoneditor.css\">\n\n  <style type=\"text/css\">\n    body, html {\n      font-family: arial, verdana;\n      font-size: 11pt;\n      width: 100%;\n      height: 100%;\n      margin: 0px;\n      padding: 0px;\n    }\n\n    h1 {\n      color: gray;\n    }\n    \n    input[type=text] {\n      border: 1px solid gray;\n    }\n  </style>\n\n  <script>\n    var editor = null;\n\n    function init() {\n      var container = document.getElementById('jsoneditor');\n      editor = new JSONEditor(container);\n     \n      document.getElementById('url').focus();\n    }\n    \n    function load() {\n      var url = document.getElementById(\"url\").value;\n      $.ajax({\n        'type': 'GET',\n        'url': url,\n        'dataType': 'json',\n        'success': function (doc) {\n          editor.set(doc);\n        }\n      });\n    }\n    \n    function save() {\n      var doc = editor.get();\n      var url = document.getElementById(\"url\").value;\n      $.ajax({\n        'type': 'PUT',\n        'url': url,\n        'data': JSON.stringify(doc),\n        'success': function (response) {\n          load();\n        }\n      });      \n    }\n  </script>\n</head>\n\n<body onload=\"init();\">\n\n<table align=\"center\" width=\"790px\" height=\"100%\">\n  <tr>\n    <td style=\"height: 50px;\"><h1>CouchDB Document Editor</h1></td>\n  </tr>\n  <tr>\n    <td style=\"height: 30px;\">\n      <table width=\"100%\">\n        <col width=\"100px\"></col>\n        <col ></col>\n        <col width=\"50px\"></col>\n        <col width=\"50px\"></col>\n        <tr>\n          <td>Document Url:</td>\n          <td><input type=\"text\" id=\"url\" style=\"width: 100%;\" value=\"http://localhost:5984/test/jos\"></td>\n          <td><input type=\"button\" value=\"Load\" onclick=\"load();\" /></td>\n          <td><input type=\"button\" value=\"Save\" onclick=\"save();\" /></td>\n        </tr>\n      </table>\n    </td>\n  </tr>\n  <tr>\n    <td id=\"jsoneditor\"></td>\n  </tr>\n</table>\n\n</body>\n</html>\n"
  },
  {
    "path": "test/data/jsons.js",
    "content": "export const autocompleteJsonStr = `{\n  \"personalDetails\": {\n    \"firstName\": \"John\",\n    \"lastName\": \"Doe\",\n    \"gender\": \"male\",\n    \"age\": 32\n  },\n  \"availableToHire\": true,\n  \"job\": {\n    \"company\": \"freelance\",\n    \"role\": \"Human Resources Coordinator\",\n    \"salary\": 140,\n    \"address\": \"Jerusalem\"\n  },\n  \"profession\": {\n    \"level\": \"senior\",\n    \"experience\": 10\n  },\n  \"reporters\": [\n    {\n      \"personalDetails\": {\n        \"firstName\": \"John\",\n        \"lastName\": \"Doe\",\n        \"gender\": \"male\",\n        \"age\": 28\n      },\n      \"job\": {\n        \"company\": \"freelance\",\n        \"role\": \"developer\",\n        \"salary\": 120,\n        \"address\": \"New York\"\n      },\n      \"profession\": {\n        \"level\": \"junior\",\n        \"experience\": 2\n      }\n    }\n  ],\n  \"publications\": [\n    {\n      \"type\": \"academic\",\n      \"journal\": \"MIT today\"\n    },\n    {\n      \"type\": \"professional\",\n      \"journal\": \"stack overflow\"\n    }\n  ]\n}`\n"
  },
  {
    "path": "test/data/schemas.js",
    "content": "const employeeSchema = {\n  title: 'Employee',\n  description: 'Object containing employee details',\n  type: 'object',\n  additonalProperties: false,\n  properties: {\n    personalDetails: {\n      $ref: 'personal'\n    },\n    availableToHire: {\n      type: 'boolean',\n      default: false\n    },\n    job: {\n      $ref: 'job'\n    },\n    profession: {\n      oneOf: [\n        {\n          $ref: 'junior'\n        },\n        {\n          $ref: 'experienced'\n        },\n        {\n          $ref: 'senior'\n        }\n      ]\n    },\n    reporters: {\n      type: 'array',\n      items: {\n        $ref: 'employeeSchema'\n      }\n    },\n    publications: {\n      type: 'array',\n      items: {\n        type: 'object',\n        properties: {\n          type: {\n            type: 'string',\n            enum: ['academic', 'professional']\n          },\n          journal: {\n            type: 'string'\n          }\n        }\n      }\n    }\n  },\n  required: ['personalDetails']\n}\n\nconst personal = {\n  title: 'Personal Details',\n  type: 'object',\n  required: ['firstName', 'lastName'],\n  properties: {\n    firstName: {\n      title: 'First Name',\n      description: 'The given name.',\n      examples: [\n        'John'\n      ],\n      type: 'string'\n    },\n    lastName: {\n      title: 'Last Name',\n      description: 'The family name.',\n      examples: [\n        'Smith'\n      ],\n      type: 'string'\n    },\n    gender: {\n      title: 'Gender',\n      type: 'string',\n      enum: ['male', 'female'],\n      examples: ['male', 'female']\n    },\n    age: {\n      description: 'Age in years',\n      type: 'integer',\n      minimum: 0,\n      examples: [28, 32]\n    }\n  }\n}\n\nconst job = {\n  title: 'Job description',\n  type: 'object',\n  required: ['address'],\n  properties: {\n    company: {\n      type: 'string',\n      examples: [\n        'ACME',\n        'Dexter Industries'\n      ]\n    },\n    role: {\n      description: 'Job title.',\n      type: 'string',\n      examples: [\n        'Human Resources Coordinator',\n        'Software Developer'\n      ],\n      default: 'Software Developer'\n    },\n    address: {\n      type: 'string'\n    },\n    salary: {\n      type: 'number',\n      minimum: 120,\n      examples: [100, 110, 120]\n    }\n  }\n}\n\nconst junior = {\n  type: 'object',\n  properties: {\n    level: {\n      type: 'string',\n      enum: ['junior']\n    },\n    experience: {\n      description: 'years of experience',\n      type: 'number',\n      minimum: 0,\n      maximum: 3,\n      examples: [0, 1, 2, 3]\n    }\n  }\n}\n\nconst experienced = {\n  type: 'object',\n  properties: {\n    level: {\n      type: 'string',\n      enum: ['experienced']\n    },\n    experience: {\n      description: 'years of experience',\n      type: 'number',\n      minimum: 3,\n      maximum: 7,\n      examples: [3, 4, 5, 6, 7]\n    }\n  }\n}\n\nconst senior = {\n  type: 'object',\n  properties: {\n    level: {\n      type: 'string',\n      enum: ['senior']\n    },\n    experience: {\n      description: 'years of experience',\n      type: 'number',\n      minimum: 7,\n      examples: [7, 8, 9, 10, 11]\n    }\n  }\n}\n\nexport const schema = {\n  oneOf: [\n    {\n      $ref: 'employeeSchema'\n    }\n  ]\n}\n\nexport const schemaRefs = { employeeSchema, personal, job, junior, experienced, senior }\n"
  },
  {
    "path": "test/jsonUtils.test.js",
    "content": "import assert from 'assert'\nimport { stringifyPartial, containsArray } from '../src/js/jsonUtils'\n\ndescribe('jsonUtils', () => {\n  it('should stringify a small object', () => {\n    const json = {\n      a: 2,\n      b: 'foo',\n      c: null,\n      d: false,\n      e: [1, 2, 3],\n      f: { g: 'h' }\n    }\n\n    assert.strictEqual(stringifyPartial(json), '{\"a\":2,\"b\":\"foo\",\"c\":null,\"d\":false,\"e\":[1,2,3],\"f\":{\"g\":\"h\"}}')\n  })\n\n  it('should stringify a small object with formatting', () => {\n    const json = {\n      a: 2,\n      b: 'foo',\n      c: null,\n      d: false,\n      e: [1, 2, 3],\n      f: { g: 'h' }\n    }\n\n    assert.strictEqual(stringifyPartial(json, 2),\n      '{\\n' +\n        '  \"a\": 2,\\n' +\n        '  \"b\": \"foo\",\\n' +\n        '  \"c\": null,\\n' +\n        '  \"d\": false,\\n' +\n        '  \"e\": [\\n' +\n        '    1,\\n' +\n        '    2,\\n' +\n        '    3\\n' +\n        '  ],\\n' +\n        '  \"f\": {\\n' +\n        '    \"g\": \"h\"\\n' +\n        '  }\\n' +\n        '}')\n\n    assert.strictEqual(stringifyPartial(json, '    '), '{\\n' +\n        '    \"a\": 2,\\n' +\n        '    \"b\": \"foo\",\\n' +\n        '    \"c\": null,\\n' +\n        '    \"d\": false,\\n' +\n        '    \"e\": [\\n' +\n        '        1,\\n' +\n        '        2,\\n' +\n        '        3\\n' +\n        '    ],\\n' +\n        '    \"f\": {\\n' +\n        '        \"g\": \"h\"\\n' +\n        '    }\\n' +\n        '}')\n  })\n\n  it('should limit stringified output', () => {\n    const json = {\n      a: 2,\n      b: 'foo',\n      c: null,\n      d: false,\n      e: [1, 2, 3],\n      f: { g: 'h' }\n    }\n\n    const all = '{\"a\":2,\"b\":\"foo\",\"c\":null,\"d\":false,\"e\":[1,2,3],\"f\":{\"g\":\"h\"}}'\n    const limit = 20\n\n    assert.strictEqual(stringifyPartial(json, undefined, limit),\n      all.slice(0, limit) + '...')\n\n    assert.strictEqual(stringifyPartial(json, undefined, all.length), all)\n\n    assert.strictEqual(stringifyPartial([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], undefined, 10),\n      '[1,2,3,4,5...')\n\n    assert.strictEqual(stringifyPartial(12345678, undefined, 4), '1234...')\n  })\n\n  it('should count array items', () => {\n    // assert.strictEqual(countArrayItems('[1,2,3]'), 3)\n    assert.strictEqual(containsArray('[]'), true)\n    assert.strictEqual(containsArray(' []'), true)\n    assert.strictEqual(containsArray(' \\t  []'), true)\n    assert.strictEqual(containsArray(' \\t\\n  []'), true)\n    assert.strictEqual(containsArray('\"[\"'), false)\n    assert.strictEqual(containsArray('2'), false)\n    assert.strictEqual(containsArray('null'), false)\n    assert.strictEqual(containsArray('{}'), false)\n  })\n})\n"
  },
  {
    "path": "test/setup.js",
    "content": "import { JSDOM } from 'jsdom'\n\n/**\n * Set up the test environment by simulating browser globals.\n * @param {string} [locale=en] A locale to set in navigator.language\n * @return {void}\n */\nfunction setUpTestEnvironment (locale) {\n  if (!locale) {\n    locale = 'en'\n  }\n\n  const dom = new JSDOM('...')\n  global.window = dom.window\n  global.document = dom.window.document\n  if (typeof global.navigator === 'undefined') {\n    global.navigator = dom.window.navigator\n  }\n\n  // JSDom has no setter defined for navigator.language, so defineProperty is necessary in order to override it\n  Object.defineProperty(navigator, 'language', { value: locale })\n};\n\nsetUpTestEnvironment()\n"
  },
  {
    "path": "test/test_bootstrap.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css\" rel=\"stylesheet\" type=\"text/css\">\n  <!--<link href=\"//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.min.css\" rel=\"stylesheet\" type=\"text/css\">-->\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n\n  </style>\n</head>\n<body>\n<p>\n  Test with bootstrap\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  var container = document.getElementById('jsoneditor');\n  var options = {\n    \"mode\": \"tree\",\n    \"modes\": [\n      \"tree\",\n      \"form\",\n      \"code\",\n      \"text\",\n      \"view\"\n    ],\n    \"history\": false,\n    onChange: function () {\n      if (editor) {\n        // if you comment out the next line of code, the problem is solved\n        // editor.get() throws an exception when the editor does not\n        // contain valid JSON.\n        console.log('change', editor.get())\n      }\n    }\n  };\n  var editor = new JSONEditor(container, options);\n\n  var text = \"[{\\\"test\\\":\\\"test\\\"}]\";\n  var json = JSON.parse(text);\n  editor.set(json);\n\n</script>\n</body>\n</html>"
  },
  {
    "path": "test/test_build.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n      padding-left: 40px;\n      background: #f5f5f5;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<form>\n  <div id=\"jsoneditor\"></div>\n</form>\n\n<p>\n  <button id=\"update\">Update</button>\n</p>\n\n<p>\n  <iframe></iframe>\n</p>\n\n<script>\n  var container, options, json, editor;\n\n  container = document.getElementById('jsoneditor');\n\n  options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onError: function (err) {\n      console.error(err);\n      alert(err.toString());\n    },\n    onChange: function () {\n      console.log('onChange');\n      editorTest.refresh()\n    },\n    onChangeJSON: function (json) {\n      console.log('onChangeJSON', json);\n\n      // test feedback loop which is typical in React -> should not change anything\n      editorTest.update(json)\n    },\n    onChangeText: function (text) {\n      console.log('onChangeText', text);\n    },\n    onFocus: function(event) {\n      console.log('Focus : ',event);\n      container.style.outline = '5px solid red';\n    },\n    onBlur: function(event) {\n      console.log('Blur : ',event);\n      container.style.outline = '';\n    },\n    indentation: 4,\n    // escapeUnicode: true,\n    limitDragging: true\n  };\n\n  json = {\n    \"array\": [1, 2, [3,4,5]],\n    \"boolean\": true,\n    \"color\": \"#82b92c\",\n    \"htmlcode\": '&quot;',\n    \"escaped_unicode\": '\\\\u20b9',\n    \"unicode\": '\\u{1F600},\\uD83D\\uDCA9',\n    \"return\": '\\n',\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\",\n    \"timestamp\": 1534952749890,\n    \"url\": \"http://jsoneditoronline.org\",\n    \"<button onclick=alert('oopsie!!!')>test xss</button>\": \"xss?\",\n    \"xss array\": [\n      {\n        \"<button onclick=alert('oopsie!!!')>test xss</button>\": \"xss?\"\n      }\n    ],\n    \"wrap\": {\n      \"a\" : \"123456789012345678901234567890123456789001234567890\",\n      \"b\" :\n        {\n          \"c\" : \"123456789012345678901234567890123456789012345678\",\n          \"d\" : \"1234567890123456789012345678901234567890123456789012345\",\n          \"url\": \"https://github.com/josdejong/jsoneditor/issues/1558\"\n        }\n    }\n  };\n\n  editorTest = new JSONEditor(container, options, json);\n\n  console.log('json', json);\n  console.log('string', JSON.stringify(json));\n\n\n  const update = document.getElementById('update')\n  update.onclick = function () {\n    const json2 = {\n      \"array\": [1, 2, [3,4,5]],\n      \"array2\": [1, 2, [3,4,5]],\n      \"url\": \"http://jsoneditoronline.org\",\n      \"boolean\": true,\n      \"color\": \"#82b92c\",\n      \"htmlcode\": '&quot;',\n      \"escaped_unicode\": '\\\\u20b9',\n      \"unicode\": '\\u20b9,\\uD83D\\uDCA9',\n      \"return\": '\\n',\n      \"null\": null,\n      \"number\": 123,\n      \"object\": {\"a\": \"b\", \"c\": \"d\"},\n      \"string\": \"Hello World\",\n      \"timestamp\": 1534952749890\n    };\n\n    editorTest.update(json2)\n  }\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_build_min.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.min.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.min.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  var container, options, json, editor;\n\n  container = document.getElementById('jsoneditor');\n\n  options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onError: function (err) {\n      alert(err.toString());\n    }\n  };\n\n  json = {\n    \"array\": [1, 2, 3],\n    \"boolean\": true,\n    \"color\": \"#82b92c\",\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\"\n  };\n\n  editor = new JSONEditor(container, options, json);\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_code_mode.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n      padding-left: 40px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<form>\n  <div id=\"jsoneditor\"></div>\n</form>\n\n<script>\n  var container, options, json, editor;\n\n  container = document.getElementById('jsoneditor');\n\n  options = {\n    mode: 'code',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onError: function (err) {\n      alert(err.toString());\n    },\n    onChange: function () {\n      console.log('change');\n    },\n    onChangeJSON: function (json) {\n      console.log('onChangeJSON', json);\n    },\n    onChangeText: function (text) {\n      console.log('onChangeText', text);\n    },\n    indentation: 4,\n    escapeUnicode: true\n  };\n\n  var json = [];\n  for (var i = 0; i < 10000; i++) {\n    var longitude = 4 + i / 10000;\n    var latitude = 51 + i / 10000;\n\n    json.push({\n      name: 'Item ' + i,\n      id: String(i),\n      index: i,\n      time: new Date().toISOString(),\n      location: {\n        latitude: longitude,\n        longitude: latitude,\n        coordinates: [longitude, latitude]\n      },\n      random: Math.random()\n    });\n  }\n\n  editorTest = new JSONEditor(container, options, json);\n\n  // console.log('json', json);\n  console.log('stringified size: ', Math.round(JSON.stringify(json).length / 1024 / 1024), 'MB');\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_color_picker.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n      padding-left: 40px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Test color picker firing onChange on every change instead of onDone.\n</p>\n\n<form>\n  <div id=\"jsoneditor\"></div>\n</form>\n\n<script>\n  var container, options, json, editor;\n\n  container = document.getElementById('jsoneditor');\n\n  options = {\n    onChangeJSON: function (json) {\n      console.log('onChangeJSON', json);\n    },\n    onColorPicker: function (parent, color, onChange) {\n      new JSONEditor.VanillaPicker({\n        parent: parent,\n        color: color,\n        popup: 'bottom',\n        onChange: function (color) {\n          console.log('onChange', color)\n          var alpha = color.rgba[3]\n          var hex = (alpha === 1)\n              ? color.hex.substr(0, 7)  // return #RRGGBB\n              : color.hex               // return #RRGGBBAA\n          onChange(hex)\n        },\n        onDone: function (color) {\n          console.log('onDone', color)\n        },\n        onClose: function (color) {\n          console.log('onClose', color)\n        }\n      }).show();\n    }\n  };\n\n  json = {\n    \"array\": [1, 2, [3,4,5]],\n    \"boolean\": true,\n    \"color\": \"#82b92c\",\n    \"htmlcode\": '&quot;',\n    \"escaped_unicode\": '\\\\u20b9',\n    \"unicode\": '\\u20b9,\\uD83D\\uDCA9',\n    \"return\": '\\n',\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\",\n    \"timestamp\": 1534952749890,\n    \"url\": \"http://jsoneditoronline.org\"\n  };\n\n  editor = new JSONEditor(container, options, json);\n\n  console.log('json', json);\n  console.log('string', JSON.stringify(json));\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_destroy.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n      padding-left: 40px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<form>\n  <div id=\"jsoneditor\"></div>\n</form>\n\n<p>\n  <button id=\"destroy\">Destroy</button>\n</p>\n\n<script>\n  var container, options, json, editor;\n\n  container = document.getElementById('jsoneditor');\n\n  options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onError: function (err) {\n      alert(err.toString());\n    },\n    onChange: function () {\n      console.log('change');\n    },\n    indentation: 4,\n    escapeUnicode: true\n  };\n\n  json = {\n    \"array\": [1, 2, [3,4,5]],\n    \"boolean\": true,\n    \"htmlcode\": '&quot;',\n    \"escaped_unicode\": '\\\\u20b9',\n    \"unicode\": '\\u20b9,\\uD83D\\uDCA9',\n    \"return\": '\\n',\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\",\n    \"url\": \"http://jsoneditoronline.org\"\n  };\n\n  editor = new JSONEditor(container, options, json);\n\n  console.log('json', json);\n  console.log('string', JSON.stringify(json));\n\n  document.querySelector('#destroy').onclick = function () {\n    editor.destroy();\n    editor = null;\n  }\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_enum.html",
    "content": "<!DOCTYPE HTML>\n<html>\n<head>\n    <title>JSONEditor | template + enums</title>\n\n    <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n    <script src=\"../dist/jsoneditor.js\"></script>\n\n    <style type=\"text/css\">\n        #jsoneditor {\n            width: 500px;\n            height: 700px;\n            background: white;\n        }\n\n        p {\n            width: 500px;\n            font-family: sans;\n        }\n    </style>\n</head>\n<body>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n    var schema = {\n        \"title\": \"Example Schema\",\n        \"type\": \"object\",\n        \"properties\": {\n            \"firstName\": {\n                \"type\": \"string\",\n                \"enum\": [\"a\", \"b\"]\n            },\n            \"testObj\": {\n                \"type\": \"object\",\n                \"additionalProperties\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"a\", \"b\"]\n                },\n            }\n        },\n        \"required\": [\"firstName\"]\n    };\n\n\n    var json =\n        {\n            firstName: 'a',\n            testObj: {},\n        };\n\n    var options = {\n        schema: schema\n    };\n\n    // create the editor\n    var container = document.getElementById('jsoneditor');\n    var editor = new JSONEditor(container, options, json);\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_enum_2.html",
    "content": "<!DOCTYPE HTML>\n<html>\n<head>\n    <title>JSONEditor | template + enums</title>\n\n    <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n    <script src=\"../dist/jsoneditor.js\"></script>\n\n    <style type=\"text/css\">\n        #jsoneditor {\n            width: 500px;\n            height: 700px;\n            background: white;\n        }\n\n        p {\n            width: 500px;\n            font-family: sans;\n        }\n    </style>\n</head>\n<body>\n<p>Demonstrates a template with JSON schema validation. To use: click the context menu of the first or second employee, click insert or append, click \"Employee\".</p>\n<p><a href=\"https://github.com/josdejong/jsoneditor/issues/473\">See github issue #473</a></p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n    var schema = {\n        \"title\": \"Example Schema\",\n        \"type\": \"array\",\n        \"items\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"firstName\": {\n                    \"type\": \"string\"\n                },\n                \"lastName\": {\n                    \"type\": \"string\"\n                },\n                \"gender\": {\n                    \"enum\": [\"male\", \"female\"]\n                },\n                \"age\": {\n                    \"description\": \"Age in years\",\n                    \"type\": \"integer\",\n                    \"minimum\": 0\n                },\n                \"job\": {\n                    \"$ref\": \"job\"\n                }\n            },\n            \"required\": [\"firstName\", \"lastName\"]\n        }\n    };\n\n    var job = {\n        \"title\": \"Job description\",\n        \"type\": \"object\",\n        \"properties\": {\n            \"company\": {\n                \"type\": \"string\"\n            },\n            \"role\": {\n                \"type\": \"string\"\n            }\n        }\n    };\n\n    var json = [\n        {\n            firstName: 'John',\n            lastName: 'Doe',\n            gender: 'male',\n            age: 28,\n            job: {\n                company: 'freelance',\n                role: 'developer'\n            }\n        },\n        {\n            firstName: 'Susan',\n            lastName: 'Smith',\n            gender: null,\n            age: 28,\n            job: {\n                company: 'freelance',\n                role: 'sales'\n            }\n        }\n    ];\n\n    var templates = [\n        {\n            text: 'Employee',\n            title: 'Insert a new employee',\n            className: 'jsoneditor-type-object',\n            field: 'employee',\n            value: {\n                firstName: '',\n                lastName: '',\n                gender: '',\n                age: '',\n                job: {\n                    company: '',\n                    role: ''\n                }\n            }\n        }\n    ]\n\n    var options = {\n        schema: schema,\n        schemaRefs: {\"job\": job},\n        templates: templates\n    };\n\n    // create the editor\n    var container = document.getElementById('jsoneditor');\n    var editor = new JSONEditor(container, options, json);\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_focus_tracker.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n      padding-left: 40px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n\n    .input {\n    \tdisplay: block;\n    \tmargin: 5px;\n    }\n\n    .input:focus {\n    \tborder: 5px solid red;\n    }\n    .input:blur {\n    \tborder: unset;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n<input type=\"text\" class=\"input\">\n<input type=\"text\" class=\"input\">\n<form>\n  <div id=\"jsoneditor\"></div>\n</form>\n<input type=\"text\" class=\"input\">\n<input type=\"text\" class=\"input\">\n\n<script>\n  var container, options, json, editor;\n\n  container = document.getElementById('jsoneditor');\n\n  options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onError: function (err) {\n      alert(err.toString());\n    },\n    onFocus: function(event) {\n      container.style.border = '5px solid red';\n      console.log(\"Focus : \",event);\n    },\n    onBlur: function(event) {\n      container.style.border = 'unset';\n      console.log(\"Blur : \",event);\n    },\n    indentation: 4,\n    escapeUnicode: true\n  };\n\n  json = {\n    \"array\": [1, 2, [3,4,5]],\n    \"boolean\": true,\n    \"color\": \"#82b92c\",\n    \"htmlcode\": '&quot;',\n    \"escaped_unicode\": '\\\\u20b9',\n    \"unicode\": '\\u20b9,\\uD83D\\uDCA9',\n    \"return\": '\\n',\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\",\n    \"timestamp\": 1534952749890,\n    \"url\": \"http://jsoneditoronline.org\"\n  };\n\n  editorTest = new JSONEditor(container, options, json);\n\n  console.log('json', json);\n  console.log('string', JSON.stringify(json));\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_get_inner_text.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor test getInnerHtml</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style>\n    html, body {\n      font-family: verdana;\n      background: #f5f5f5;\n      font-size: 11pt;\n    }\n\n    #editableDiv {\n      width: 600px;\n      height: 200px;\n      border: 1px solid red;\n      background: white;\n      font-family: monospace;\n    }\n\n    #editableDiv p {\n      margin: 0;\n    }\n\n    #innerText,\n    #getInnerText,\n    #innerTextStr,\n    #textContentStr {\n      width: 600px;\n    }\n\n    #innerText,\n    #getInnerText {\n      height: 200px;\n      border: 1px solid green;\n    }\n\n    #innerTextStr,\n    #textContentStr {\n      width: 600px;\n      border: 1px solid gray;\n      background: #f5f5f5;\n      font-family: monospace;\n    }\n  </style>\n</head>\n<body>\ncontenteditable div:\n<div id=\"editableDiv\" contenteditable=\"true\">\n  <p>Hello world</p>\n  <p>test paste from OpenOffice  </p>\n  a\n  <p>\n    <br>\n  </p>\n  <p>test</p>\n</div>\n\n<p>\n  innerText: <br/>\n  <textarea id=\"innerText\"></textarea>\n</p>\n<p>\n  getInnerText: <br/>\n  <textarea id=\"getInnerText\"></textarea>\n</p>\n<p>\n<p>\n  inner text (stringified): <br/>\n  <input id=\"innerTextStr\" readonly />\n</p>\n<p>\n  text content (stringified): <br/>\n  <input id=\"textContentStr\" readonly />\n</p>\n\n<script>\n  const editableDiv = document.getElementById('editableDiv')\n  const innerText = document.getElementById('innerText')\n  const getInnerTextDiv = document.getElementById('getInnerText')\n  const innerTextStr = document.getElementById('innerTextStr')\n  const textContentStr = document.getElementById('textContentStr')\n\n  function updateInnerTexts () {\n    innerText.value = editableDiv.innerText\n    getInnerTextDiv.value = JSONEditor.getInnerText(editableDiv)\n    innerTextStr.value = JSON.stringify(editableDiv.innerText)\n    textContentStr.value = JSON.stringify(editableDiv.textContent)\n  }\n\n  editableDiv.oninput = updateInnerTexts\n\n  updateInnerTexts()\n\n  innerText.oninput = function () {\n    editableDiv.innerText = innerText.value\n    getInnerTextDiv.value = getInnerText(editableDiv)\n  }\n\n  getInnerTextDiv.oninput = function () {\n    editableDiv.innerText = getInnerTextDiv.value\n    innerText.value = editableDiv.innerText\n  }\n\n</script>\n</body>\n</html>"
  },
  {
    "path": "test/test_large_array.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n      padding-left: 40px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  var container = document.getElementById('jsoneditor');\n\n  var options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onError: function (err) {\n      console.error(err);\n      alert(err.toString());\n    },\n    onChange: function () {\n      console.log('change');\n    },\n    indentation: 4,\n    escapeUnicode: true\n  };\n\n  var json = {\n    empty: [],\n    numbers: [],\n    abc: 123,\n    array: [],\n    object: { a: 2, b: 3}\n  };\n  for (var i = 0; i < 10000; i++) {\n    var longitude = 4 + i / 10000;\n    var latitude = 51 + i / 10000;\n\n    json.numbers.push(i);\n    json.array.push({\n      name: 'Item ' + i,\n      id: String(i),\n      index: i,\n      time: new Date().toISOString(),\n      location: {\n        latitude: longitude,\n        longitude: latitude,\n        coordinates: [longitude, latitude]\n      },\n      random: Math.random()\n    });\n  }\n\n  var editor = new JSONEditor(container, options, json);\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_materialize.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <!-- materialize css -->\n  <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/css/materialize.min.css\">\n  <script src=\"https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/js/materialize.min.js\"></script>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style>\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n      padding-left: 40px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<form>\n  <div id=\"jsoneditor\"></div>\n</form>\n\n<script>\n  var container, options, json, editor;\n\n  container = document.getElementById('jsoneditor');\n\n  options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onError: function (err) {\n      alert(err.toString());\n    },\n    onChange: function () {\n      console.log('change');\n    },\n    indentation: 4,\n    escapeUnicode: true\n  };\n\n  json = {\n    \"array\": [1, 2, [3,4,5]],\n    \"boolean\": true,\n    \"htmlcode\": '&quot;',\n    \"escaped_unicode\": '\\\\u20b9',\n    \"unicode\": '\\u20b9,\\uD83D\\uDCA9',\n    \"return\": '\\n',\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\",\n    \"url\": \"http://jsoneditoronline.org\"\n  };\n\n  editor = new JSONEditor(container, options, json);\n\n  console.log('json', json);\n  console.log('string', JSON.stringify(json));\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_minimalist_min.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.min.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor-minimalist.min.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 500px;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  var container, options, json, editor;\n\n  container = document.getElementById('jsoneditor');\n\n  options = {\n    mode: 'tree',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    onError: function (err) {\n      alert(err.toString());\n    }\n  };\n\n  json = {\n    \"array\": [1, 2, 3],\n    \"boolean\": true,\n    \"color\": \"#82b92c\",\n    \"null\": null,\n    \"number\": 123,\n    \"object\": {\"a\": \"b\", \"c\": \"d\"},\n    \"string\": \"Hello World\"\n  };\n\n  editor = new JSONEditor(container, options, json);\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_popup_anchor.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | JSON schema validation</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      width: 600px;\n      font: 11pt sans-serif;\n    }\n\n    #anchor {\n      position: relative;\n    }\n\n    #jsoneditor {\n      width: 320px;\n      height: 300px;\n      position: relative;\n      overflow: hidden;\n    }\n\n    /* custom bold styling for non-default JSON schema values */\n    .jsoneditor-is-not-default {\n      font-weight: bold;\n    }\n  </style>\n</head>\n<body>\n<h1>Test custom tooltip anchor</h1>\n<p>\n  The JSON Schema error tooltips and the color picker should have correct placing and overflow the editor, also in combination with scrolling.\n</p>\n\n<div id=\"anchor\">\n  <div id=\"jsoneditor\"></div>\n</div>\n<div style=\"height: 2000px\"></div>\n\n<script>\n  const schema = {\n    \"title\": \"Employee\",\n    \"description\": \"Object containing employee details\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"firstName\": {\n        \"title\": \"First Name\",\n        \"description\": \"The given name.\",\n        \"examples\": [\n          \"John\"\n        ],\n        \"type\": \"string\"\n      },\n      \"lastName\": {\n        \"title\": \"Last Name\",\n        \"description\": \"The family name.\",\n        \"examples\": [\n          \"Smith\"\n        ],\n        \"type\": \"string\"\n      },\n      \"gender\": {\n        \"title\": \"Gender\",\n        \"enum\": [\"male\", \"female\"]\n      },\n      \"availableToHire\": {\n        \"type\": \"boolean\",\n        \"default\": false\n      },\n      \"age\": {\n        \"description\": \"Age in years\",\n        \"type\": \"integer\",\n        \"minimum\": 0,\n        \"examples\": [28, 32]\n      },\n      \"job\": {\n        \"$ref\": \"job\"\n      }\n    },\n    \"required\": [\"firstName\", \"lastName\"]\n  }\n\n  const job = {\n    \"title\": \"Job description\",\n    \"type\": \"object\",\n    \"required\": [\"address\"],\n    \"properties\": {\n      \"company\": {\n        \"type\": \"string\",\n        \"examples\": [\n          \"ACME\",\n          \"Dexter Industries\"\n        ]\n      },\n      \"role\": {\n        \"description\": \"Job title.\",\n        \"type\": \"string\",\n        \"examples\": [\n          \"Human Resources Coordinator\",\n          \"Software Developer\"\n        ],\n        \"default\": \"Software Developer\"\n      },\n      \"address\": {\n        \"type\": \"string\"\n      },\n      \"salary\": {\n        \"type\": \"number\",\n        \"minimum\": 120,\n        \"examples\": [100, 110, 120]\n      }\n    }\n  }\n\n  const json = {\n    firstName: 'John',\n    lastName: 'Doe',\n    gender: null,\n    age: \"28\",\n    availableToHire: true,\n    favoriteColor: 'red',\n    job: {\n      company: 'freelance',\n      role: 'developer',\n      salary: 100\n    }\n  }\n\n  const options = {\n    schema: schema,\n    schemaRefs: {\"job\": job},\n    mode: 'tree',\n    modes: ['code', 'text', 'tree', 'preview'],\n    popupAnchor: document.getElementById('anchor')\n  }\n\n  // create the editor\n  const container = document.getElementById('jsoneditor')\n  const editor = new JSONEditor(container, options, json)\n  editor.expandAll()\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_preview_load_save.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>JSONEditor | Preview mode load and save</title>\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <script src=\"https://bgrins.github.io/filereader.js/filereader.js\"></script>\n  <script src=\"https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js\"></script>\n\n  <style>\n    html, body {\n      font: 11pt sans-serif;\n    }\n    #jsoneditor {\n      width: 500px;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n<h1>Load and save JSON documents in Preview mode</h1>\n<p>\n  This examples uses HTML5 to load/save local files.\n  Powered by <a href=\"http://bgrins.github.io/filereader.js/\">FileReader.js</a> and\n  <a href=\"https://github.com/eligrey/FileSaver.js\">FileSaver.js</a>.<br>\n  Only supported on modern browsers (Chrome, FireFox, IE10+, Safari 6.1+, Opera 15+).\n</p>\n<p>\n  Load a JSON document: <input type=\"file\" id=\"loadDocument\" value=\"Load\"/>\n</p>\n<p>\n  Save a JSON document: <input type=\"button\" id=\"saveDocument\" value=\"Save\" />\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  // create the editor\n  var editor = new JSONEditor(document.getElementById('jsoneditor'), {\n    mode: 'preview'\n  });\n\n  // Load a JSON document\n  FileReaderJS.setupInput(document.getElementById('loadDocument'), {\n    readAsDefault: 'Text',\n    on: {\n      load: function (event, file) {\n        editor.setText(event.target.result);\n      }\n    }\n  });\n\n  // Save a JSON document\n  document.getElementById('saveDocument').onclick = function () {\n    // Save Dialog\n    fname = window.prompt(\"Save as...\");\n    \n    // Check json extension in file name\n    if(fname.indexOf(\".\")==-1){\n      fname = fname + \".json\";\n    }else{\n      if(fname.split('.').pop().toLowerCase() == \"json\"){\n        // Nothing to do\n      }else{\n        fname = fname.split('.')[0] + \".json\";\n      }\n    } \n    var blob = new Blob([editor.getText()], {type: 'application/json;charset=utf-8'});\n    saveAs(blob, fname);\n  };\n</script>\n</body>\n</html>\n\n\n"
  },
  {
    "path": "test/test_schema.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n\n  <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n  <script src=\"../dist/jsoneditor.js\"></script>\n\n  <style type=\"text/css\">\n    body {\n      font: 10.5pt arial;\n      color: #4d4d4d;\n      line-height: 150%;\n      width: 600px;\n      padding-left: 40px;\n    }\n\n    html, body {\n      width: 100%;\n      padding: 0;\n      box-sizing: border-box;\n    }\n\n    code {\n      background-color: #f5f5f5;\n    }\n\n    #jsoneditor {\n      max-width: 600px;\n      width: 90%;\n      height: 500px;\n    }\n  </style>\n</head>\n<body>\n\n<p>\n  Switch editor mode using the mode box.\n  Note that the mode can be changed programmatically as well using the method\n  <code>editor.setMode(mode)</code>, try it in the console of your browser.\n</p>\n\n<div id=\"jsoneditor\"></div>\n\n<script>\n  var container = document.getElementById('jsoneditor');\n\n  var schema = {\n    \"title\": \"User\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"firstName\": {\n        \"type\": \"string\"\n      },\n      \"lastName\": {\n        \"type\": \"string\"\n      },\n      \"gender\": {\n        \"enum\": [\"male\", \"female\"]\n      },\n      \"age\": {\n        \"description\": \"Age in years\",\n        \"examples\": [18, 65],\n        \"type\": \"integer\",\n        \"minimum\": 0\n      },\n      \"hobbies\": {\n        \"$ref\": \"hobbies.json\"\n      }\n    },\n    \"required\": [\"firstName\", \"lastName\"]\n  };\n\n  var hobbiesSchema = {\n    \"type\": \"array\",\n    \"items\": {\n      \"type\": \"string\"\n    }\n  };\n\n  var options = {\n    mode: 'code',\n    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes\n    schema: schema,\n    schemaRefs: {\"hobbies.json\": hobbiesSchema},\n    onError: function (err) {\n      console.error('ERROR', err);\n    },\n    onChange: async () => {\n      const errors = await editor.validate()\n      if (errors.length === 0) {\n        console.log('validation errors: NONE')\n        // do something, like persisting the JSON\n      } else {\n        // show error to the user or something\n        console.log('validation errors', errors)\n      }\n    }\n  };\n\n  var json = {\n    \"firstName\": \"Jos\",\n    \"lastName\": \"de Jong\",\n    \"gender\": null,\n    \"age\": 34.2,\n    \"hobbies\": [\n        \"programming\",\n        \"movies\",\n        \"bicycling\"\n    ]\n  };\n\n  var editor = new JSONEditor(container, options, json);\n\n  console.log('json', json);\n  console.log('schema', schema);\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "test/test_update.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n    <!-- when using the mode \"code\", it's important to specify charset utf-8 -->\n    <meta charset=\"utf-8\">\n\n    <title>JSONEditor | Update JSON</title>\n\n    <link href=\"../dist/jsoneditor.css\" rel=\"stylesheet\" type=\"text/css\">\n    <script src=\"../dist/jsoneditor.js\"></script>\n\n    <style type=\"text/css\">\n        body, html {\n            font: 10.5pt arial;\n            color: #4d4d4d;\n            line-height: 150%;\n            width: 100%;\n            height: 100%;\n            padding: 0;\n            margin: 0;\n        }\n\n        .main {\n            display: flex;\n            flex-direction: column;\n            width: 100%;\n            height: 100%;\n        }\n\n        .main > div {\n            margin: 10px;\n        }\n\n        #jsoneditor {\n            flex: 1;\n            width: 100%;\n            max-width: 500px;\n            height: 200px;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"main\">\n        <div>\n            <button id=\"update\">update JSON</button>\n            <button id=\"updateText\">update JSON Text</button>\n        </div>\n        <div id=\"jsoneditor\"></div>\n    </div>\n\n    <script>\n      var container = document.getElementById('jsoneditor');\n      var updateButton = document.getElementById('update');\n      var updateTextButton = document.getElementById('updateText');\n\n      var options = {\n        mode: 'tree',\n        modes: ['code', 'form', 'text', 'tree', 'view', 'preview'] // allowed modes\n      };\n\n      var json = {\n        \"arrayToObject\": [1, 2, 3],\n        \"arrayGrow\": [1, 2, 3],\n        \"arrayShrink\": [1, 2, 3],\n        \"autoToArray\": 123,\n        \"arrayToAuto\": [1, 2, 3],\n        \"objectGrow\": {\"a\": \"b\", \"c\": \"d\"},\n        \"objectShrink\": {\"a\": \"b\", \"c\": \"d\"},\n        \"objectToArray\": {\"a\": \"b\", \"c\": \"d\"},\n        \"removeField\": \"old\"\n      };\n\n      var updatedJson = {\n        \"arrayToObject\": {\"a\": \"b\", \"c\": \"d\"},\n        \"arrayGrow\": [1, 2, 3, 4, 5],\n        \"arrayShrink\": [1, 3],\n        \"autoToArray\": [1, 2, 3],\n        \"arrayToAuto\": 123,\n        \"objectGrow\": {\"a\": \"b\", \"c\": \"ddd\", \"e\": \"f\"},\n        \"objectShrink\": {\"c\": \"d\"},\n        \"objectToArray\": [1, 2, 3],\n        \"newField\": \"new\"\n      };\n\n      var editor = new JSONEditor(container, options, json);\n\n      editor.expandAll();\n\n      updateButton.onclick = function () {\n        editor.update(updatedJson);\n      };\n\n      updateTextButton.onclick = function () {\n        editor.updateText(JSON.stringify(updatedJson, null, 2));\n      };\n    </script>\n</body>\n</html>\n"
  },
  {
    "path": "test/util.test.js",
    "content": "import assert from 'assert'\nimport {\n  compileJSONPointer,\n  findUniqueName,\n  formatSize,\n  get,\n  getChildPaths,\n  getIndexForPosition,\n  isObject,\n  isTimestamp,\n  isValidationErrorChanged,\n  limitCharacters,\n  makeFieldTooltip,\n  parsePath,\n  parseString,\n  removeReturnsAndSurroundingWhitespace,\n  sort,\n  sortObjectKeys,\n  stringifyPath,\n  uniqueMergeArrays\n} from '../src/js/util'\n\ndescribe('util', () => {\n  describe('jsonPath', () => {\n    it('should stringify an array of paths', () => {\n      assert.deepStrictEqual(stringifyPath([]), '')\n      assert.deepStrictEqual(stringifyPath(['foo']), '.foo')\n      assert.deepStrictEqual(stringifyPath(['foo', 'bar']), '.foo.bar')\n      assert.deepStrictEqual(stringifyPath(['foo', 2]), '.foo[2]')\n      assert.deepStrictEqual(stringifyPath(['foo', 2, 'bar']), '.foo[2].bar')\n      assert.deepStrictEqual(stringifyPath(['foo', 2, 'bar_baz']), '.foo[2].bar_baz')\n      assert.deepStrictEqual(stringifyPath([2]), '[2]')\n      assert.deepStrictEqual(stringifyPath(['foo', 'prop-with-hyphens']), '.foo[\"prop-with-hyphens\"]')\n      assert.deepStrictEqual(stringifyPath(['foo', 'prop with spaces']), '.foo[\"prop with spaces\"]')\n    })\n\n    it('should parse a json path', () => {\n      assert.deepStrictEqual(parsePath(''), [])\n      assert.deepStrictEqual(parsePath('.foo'), ['foo'])\n      assert.deepStrictEqual(parsePath('.foo.bar'), ['foo', 'bar'])\n      assert.deepStrictEqual(parsePath('.foo[2]'), ['foo', 2])\n      assert.deepStrictEqual(parsePath('.foo[2].bar'), ['foo', 2, 'bar'])\n      assert.deepStrictEqual(parsePath('.foo[\"prop with spaces\"]'), ['foo', 'prop with spaces'])\n      assert.deepStrictEqual(parsePath('.foo[\\'prop with single quotes as outputted by ajv library\\']'), ['foo', 'prop with single quotes as outputted by ajv library'])\n      assert.deepStrictEqual(parsePath('.foo[\"prop with . dot\"]'), ['foo', 'prop with . dot'])\n      assert.deepStrictEqual(parsePath('.foo[\"prop with ] character\"]'), ['foo', 'prop with ] character'])\n      assert.deepStrictEqual(parsePath('.foo[*].bar'), ['foo', '*', 'bar'])\n      assert.deepStrictEqual(parsePath('[2]'), [2])\n    })\n\n    it('should throw an exception in case of an invalid path', () => {\n      assert.throws(() => { parsePath('.') }, /Invalid JSON path: property name expected at index 1/)\n      assert.throws(() => { parsePath('[') }, /Invalid JSON path: unexpected end, character ] expected/)\n      assert.throws(() => { parsePath('[]') }, /Invalid JSON path: array value expected at index 1/)\n      assert.throws(() => { parsePath('.foo[  ]') }, /Invalid JSON path: array value expected at index 7/)\n      assert.throws(() => { parsePath('.[]') }, /Invalid JSON path: property name expected at index 1/)\n      assert.throws(() => { parsePath('[\"23]') }, /Invalid JSON path: unexpected end, character \" expected/)\n      assert.throws(() => { parsePath('.foo bar') }, /Invalid JSON path: unexpected character \" \" at index 4/)\n    })\n  })\n\n  describe('getIndexForPosition', () => {\n    const el = {\n      value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'\n    }\n\n    it('happy flows - row and column in range', () => {\n      assert.strictEqual(getIndexForPosition(el, 1, 1), 0)\n      assert.strictEqual(getIndexForPosition(el, 2, 1), 124)\n      assert.strictEqual(getIndexForPosition(el, 3, 8), 239)\n      assert.strictEqual(getIndexForPosition(el, 4, 22), 356)\n    })\n\n    it('if range exceeds it should be considered as if it is last row or column length', () => {\n      assert.strictEqual(getIndexForPosition(el, 1, 100000), 123)\n      assert.strictEqual(getIndexForPosition(el, 100000, 1), 335)\n      assert.strictEqual(getIndexForPosition(el, 100000, 100000), 445)\n    })\n\n    it('missing or wrong input sould return -1', () => {\n      assert.strictEqual(getIndexForPosition(el), -1)\n      assert.strictEqual(getIndexForPosition(el, undefined, 1), -1)\n      assert.strictEqual(getIndexForPosition(el, 1, undefined), -1)\n      assert.strictEqual(getIndexForPosition(el, -2, -2), -1)\n    })\n  })\n\n  describe('isValidationErrorChanged', () => {\n    const err1 = { type: 'validation', error: { keyword: 'enum', dataPath: '.gender', schemaPath: '#/properties/gender/enum', params: { allowedValues: ['male', 'female'] }, message: 'should be equal to one of: \"male\", \"female\"', schema: ['male', 'female'], parentSchema: { title: 'Gender', enum: ['male', 'female'] }, data: null, type: 'validation' } }\n    const err2 = { type: 'validation', error: { keyword: 'type', dataPath: '.age', schemaPath: '#/properties/age/type', params: { type: 'integer' }, message: 'should be integer', schema: 'integer', parentSchema: { description: 'Age in years', type: 'integer', minimum: 0, examples: [28, 32] }, data: '28', type: 'validation' } }\n    const err3 = { type: 'validation', error: { dataPath: '.gender', message: 'Member must be an object with properties \"name\" and \"age\"' } }\n    const err3b = { type: 'validation', error: { dataPath: '.gender', message: 'Must be an object' } }\n\n    it('empty value for both current and previous error should return false', () => {\n      assert.strictEqual(isValidationErrorChanged(), false)\n    })\n\n    it('empty value for one of current and previous error should return true', () => {\n      assert.strictEqual(isValidationErrorChanged([err1]), true)\n      assert.strictEqual(isValidationErrorChanged(undefined, [err1]), true)\n    })\n\n    it('different length of current and previous errors should return true', () => {\n      assert.strictEqual(isValidationErrorChanged([err1], []), true)\n      assert.strictEqual(isValidationErrorChanged([err1], [err1, err2]), true)\n    })\n\n    it('different values for current and previous errors should return true', () => {\n      assert.strictEqual(isValidationErrorChanged([err1, err2], [err3, err1]), true)\n    })\n\n    it('different message', () => {\n      assert.strictEqual(isValidationErrorChanged([err3], [err3b]), true)\n    })\n  })\n\n  describe('get', () => {\n    it('should get a nested property from an object', () => {\n      const obj = {\n        a: {\n          b: 2\n        },\n        c: 3,\n        d: null,\n        e: undefined\n      }\n\n      assert.strictEqual(get(obj, ['a', 'b']), 2)\n      assert.strictEqual(get(obj, ['c']), 3)\n      assert.deepStrictEqual(get(obj, ['a']), { b: 2 })\n      assert.strictEqual(get(obj, ['a', 'foo']), undefined)\n      assert.strictEqual(get(obj, ['a', 'foo', 'bar']), undefined)\n      assert.strictEqual(get(obj, ['d']), null)\n      assert.strictEqual(get(obj, ['d', 'foo', 'bar']), null)\n      assert.strictEqual(get(obj, ['e']), undefined)\n    })\n  })\n\n  describe('makeFieldTooltip', () => {\n    it('should return empty string when the schema is missing all relevant fields', () => {\n      assert.strictEqual(makeFieldTooltip({}), '')\n      assert.strictEqual(makeFieldTooltip({ additionalProperties: false }), '')\n      assert.strictEqual(makeFieldTooltip(), '')\n    })\n\n    it('should make tooltips with only title', () => {\n      assert.strictEqual(makeFieldTooltip({ title: 'foo' }), 'foo')\n    })\n\n    it('should make tooltips with only description', () => {\n      assert.strictEqual(makeFieldTooltip({ description: 'foo' }), 'foo')\n    })\n\n    it('should make tooltips with only default', () => {\n      assert.strictEqual(makeFieldTooltip({ default: 'foo' }), 'Default\\n\"foo\"')\n    })\n\n    it('should make tooltips with only examples', () => {\n      assert.strictEqual(makeFieldTooltip({ examples: ['foo', 'bar'] }), 'Examples\\n\"foo\"\\n\"bar\"')\n    })\n\n    it('should make tooltips with title and description', () => {\n      assert.strictEqual(makeFieldTooltip({ title: 'foo', description: 'bar' }), 'foo\\nbar')\n\n      const longTitle = 'Lorem Ipsum Dolor'\n      const longDescription = 'Duis id elit non ante gravida vestibulum non nec est. ' +\n        'Proin vitae ligula at elit dapibus tempor. ' +\n        'Etiam lacinia augue vel condimentum interdum. '\n      assert.strictEqual(\n        makeFieldTooltip({ title: longTitle, description: longDescription }),\n        longTitle + '\\n' + longDescription\n      )\n    })\n\n    it('should make tooltips with title, description, and examples', () => {\n      assert.strictEqual(\n        makeFieldTooltip({ title: 'foo', description: 'bar', examples: ['baz'] }),\n        'foo\\nbar\\n\\nExamples\\n\"baz\"'\n      )\n    })\n\n    it('should make tooltips with title, description, default, and examples', () => {\n      assert.strictEqual(\n        makeFieldTooltip({ title: 'foo', description: 'bar', default: 'bat', examples: ['baz'] }),\n        'foo\\nbar\\n\\nDefault\\n\"bat\"\\n\\nExamples\\n\"baz\"'\n      )\n    })\n\n    it('should handle empty fields', () => {\n      assert.strictEqual(makeFieldTooltip({ title: '', description: 'bar' }), 'bar')\n      assert.strictEqual(makeFieldTooltip({ title: 'foo', description: '' }), 'foo')\n      assert.strictEqual(makeFieldTooltip({ description: 'bar', examples: [] }), 'bar')\n      assert.strictEqual(makeFieldTooltip({ description: 'bar', examples: [''] }), 'bar\\n\\nExamples\\n\"\"')\n    })\n\n    it('should internationalize \"Defaults\" correctly', () => {\n      assert.strictEqual(makeFieldTooltip({ default: 'foo' }, 'pt-BR'), 'Revelia\\n\"foo\"')\n    })\n\n    it('should internationalize \"Examples\" correctly', () => {\n      assert.strictEqual(makeFieldTooltip({ examples: ['foo'] }, 'pt-BR'), 'Exemplos\\n\"foo\"')\n    })\n  })\n\n  describe('getChildPaths', () => {\n    it('should extract all child paths of an array containing objects', () => {\n      const json = [\n        { name: 'A', location: { latitude: 1, longitude: 2 } },\n        { name: 'B', location: { latitude: 1, longitude: 2 } },\n        { name: 'C', timestamp: 0 }\n      ]\n\n      assert.deepStrictEqual(getChildPaths(json), [\n        '.location.latitude',\n        '.location.longitude',\n        '.name',\n        '.timestamp'\n      ])\n    })\n\n    it('should extract all child paths of an array containing objects, including objects', () => {\n      const json = [\n        { name: 'A', location: { latitude: 1, longitude: 2 } },\n        { name: 'B', location: { latitude: 1, longitude: 2 } },\n        { name: 'C', timestamp: 0 }\n      ]\n\n      assert.deepStrictEqual(getChildPaths(json, true), [\n        '',\n        '.location',\n        '.location.latitude',\n        '.location.longitude',\n        '.name',\n        '.timestamp'\n      ])\n    })\n\n    it('should extract all child paths of an array containing values', () => {\n      const json = [1, 2, 3]\n\n      assert.deepStrictEqual(getChildPaths(json), [\n        ''\n      ])\n    })\n\n    it('should extract all child paths of a non-array', () => {\n      assert.deepStrictEqual(getChildPaths({ a: 2, b: { c: 3 } }), [''])\n      assert.deepStrictEqual(getChildPaths('foo'), [''])\n      assert.deepStrictEqual(getChildPaths(123), [''])\n    })\n  })\n\n  it('should test whether something is an object', () => {\n    assert.strictEqual(isObject({}), true)\n    assert.strictEqual(isObject(new Date()), true)\n    assert.strictEqual(isObject([]), false)\n    assert.strictEqual(isObject(2), false)\n    assert.strictEqual(isObject(null), false)\n    assert.strictEqual(isObject(undefined), false)\n    assert.strictEqual(isObject(), false)\n  })\n\n  describe('sort', () => {\n    it('should sort an array', () => {\n      const array = [4, 1, 10, 2]\n      assert.deepStrictEqual(sort(array), [1, 2, 4, 10])\n      assert.deepStrictEqual(sort(array, '.', 'desc'), [10, 4, 2, 1])\n    })\n\n    it('should sort an array containing objects', () => {\n      const array = [\n        { value: 4 },\n        { value: 1 },\n        { value: 10 },\n        { value: 2 }\n      ]\n\n      assert.deepStrictEqual(sort(array, '.value'), [\n        { value: 1 },\n        { value: 2 },\n        { value: 4 },\n        { value: 10 }\n      ])\n\n      assert.deepStrictEqual(sort(array, '.value', 'desc'), [\n        { value: 10 },\n        { value: 4 },\n        { value: 2 },\n        { value: 1 }\n      ])\n    })\n  })\n\n  describe('sortObjectKeys', () => {\n    it('should sort the keys of an object', () => {\n      const object = {\n        c: 'c',\n        a: 'a',\n        b: 'b'\n      }\n      assert.strictEqual(JSON.stringify(object), '{\"c\":\"c\",\"a\":\"a\",\"b\":\"b\"}')\n      assert.strictEqual(JSON.stringify(sortObjectKeys(object)), '{\"a\":\"a\",\"b\":\"b\",\"c\":\"c\"}')\n      assert.strictEqual(JSON.stringify(sortObjectKeys(object, 'asc')), '{\"a\":\"a\",\"b\":\"b\",\"c\":\"c\"}')\n      assert.strictEqual(JSON.stringify(sortObjectKeys(object, 'desc')), '{\"c\":\"c\",\"b\":\"b\",\"a\":\"a\"}')\n    })\n  })\n\n  describe('parseString', () => {\n    it('should parse a string', () => {\n      assert.strictEqual(parseString('foo'), 'foo')\n      assert.strictEqual(parseString('234foo'), '234foo')\n      assert.strictEqual(parseString('  234'), 234)\n      assert.strictEqual(parseString('234  '), 234)\n      assert.strictEqual(parseString('2.3'), 2.3)\n      assert.strictEqual(parseString('null'), null)\n      assert.strictEqual(parseString('true'), true)\n      assert.strictEqual(parseString('false'), false)\n      assert.strictEqual(parseString('+1'), 1)\n      assert.strictEqual(parseString('01'), '01')\n      assert.strictEqual(parseString('001'), '001')\n      assert.strictEqual(parseString('0.3'), 0.3)\n      assert.strictEqual(parseString('0e3'), 0)\n      assert.strictEqual(parseString(' '), ' ')\n      assert.strictEqual(parseString(''), '')\n      assert.strictEqual(parseString('\"foo\"'), '\"foo\"')\n      assert.strictEqual(parseString('\"2\"'), '\"2\"')\n      assert.strictEqual(parseString('\\'foo\\''), '\\'foo\\'')\n      assert.strictEqual(parseString('0x1A'), '0x1A')\n      assert.strictEqual(parseString('0x1F'), '0x1F')\n      assert.strictEqual(parseString('0x1a'), '0x1a')\n      assert.strictEqual(parseString('0b1101'), '0b1101')\n      assert.strictEqual(parseString('0o3700'), '0o3700')\n      assert.strictEqual(parseString('0X1a'), '0X1a')\n      assert.strictEqual(parseString('0B1101'), '0B1101')\n      assert.strictEqual(parseString('0O3700'), '0O3700')\n      assert.strictEqual(parseString('7405242042266046865'), '7405242042266046865')\n      assert.strictEqual(parseString('9007199254740991'), 9007199254740991)\n      assert.strictEqual(parseString('9007199254740991'), 9007199254740991)\n      assert.strictEqual(parseString('-9007199254740991'), -9007199254740991)\n      assert.strictEqual(parseString('1e25'), 1e25)\n      assert.strictEqual(parseString('1e308'), 1e308)\n      assert.strictEqual(parseString('1e309'), '1e309')\n    })\n  })\n\n  it('should find a unique name', () => {\n    assert.strictEqual(findUniqueName('other', [\n      'a',\n      'b',\n      'c'\n    ]), 'other')\n\n    assert.strictEqual(findUniqueName('b', [\n      'a',\n      'b',\n      'c'\n    ]), 'b (copy)')\n\n    assert.strictEqual(findUniqueName('b', [\n      'a',\n      'b',\n      'c',\n      'b (copy)'\n    ]), 'b (copy 2)')\n\n    assert.strictEqual(findUniqueName('b', [\n      'a',\n      'b',\n      'c',\n      'b (copy)',\n      'b (copy 2)'\n    ]), 'b (copy 3)')\n\n    assert.strictEqual(findUniqueName('b (copy)', [\n      'a',\n      'b',\n      'b (copy)',\n      'b (copy 2)',\n      'c'\n    ]), 'b (copy 3)')\n\n    assert.strictEqual(findUniqueName('b (copy 2)', [\n      'a',\n      'b',\n      'b (copy)',\n      'b (copy 2)',\n      'c'\n    ]), 'b (copy 3)')\n\n    assert.strictEqual(findUniqueName('b (copy)', [\n      'a',\n      'c'\n    ]), 'b (copy)')\n  })\n\n  it('should format a document size in a human readable way', () => {\n    assert.strictEqual(formatSize(500), '500 B')\n    assert.strictEqual(formatSize(900), '0.9 KB')\n    assert.strictEqual(formatSize(77.89 * 1000), '77.9 KB')\n    assert.strictEqual(formatSize(950 * 1000), '0.9 MB')\n    assert.strictEqual(formatSize(7.22 * 1000 * 1000), '7.2 MB')\n    assert.strictEqual(formatSize(945.4 * 1000 * 1000), '0.9 GB')\n    assert.strictEqual(formatSize(22.37 * 1000 * 1000 * 1000), '22.4 GB')\n    assert.strictEqual(formatSize(1000 * 1000 * 1000 * 1000), '1.0 TB')\n  })\n\n  it('should limit characters', () => {\n    assert.strictEqual(limitCharacters('hello world', 11), 'hello world')\n    assert.strictEqual(limitCharacters('hello world', 5), 'hello...')\n    assert.strictEqual(limitCharacters('hello world', 100), 'hello world')\n  })\n\n  it('should compile a JSON pointer', () => {\n    assert.strictEqual(compileJSONPointer(['foo', 'bar']), '/foo/bar')\n    assert.strictEqual(compileJSONPointer(['foo', '/~ ~/']), '/foo/~1~0 ~0~1')\n    assert.strictEqual(compileJSONPointer(['']), '/')\n    assert.strictEqual(compileJSONPointer([]), '')\n  })\n\n  it('should test whether a field is a timestamp', () => {\n    assert.strictEqual(isTimestamp('foo', 1574809200000), true)\n    assert.strictEqual(isTimestamp('foo', 1574809200000.2), false)\n  })\n\n  it('regex should match whitespace and surrounding whitespace', () => {\n    assert.strictEqual(\n      removeReturnsAndSurroundingWhitespace(' \\n A\\nB  \\nC  \\n  D \\n\\n E F\\n '),\n      'ABCDE F')\n  })\n\n  describe('uniqueMergeArrays', () => {\n    it('should merge arrays with unique values', () => {\n      const arr1 = ['a', 'b', 'c', 'd', 'e']\n      const arr2 = ['c', 'd', 'f', 'g']\n      assert.deepStrictEqual(uniqueMergeArrays(arr1, arr2), ['a', 'b', 'c', 'd', 'e', 'f', 'g'])\n    })\n  })\n\n  // TODO: thoroughly test all util methods\n})\n"
  }
]