[
  {
    "path": ".browserslistrc",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": ".editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  workflow_dispatch:\n    inputs:\n      logLevel:\n        description: 'Log level'\n        required: true\n        default: 'warning'\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  ci:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [16.x, lts/*]\n\n    steps:\n    - uses: actions/checkout@v3\n    - name: Setup Node ${{ matrix.node-version }}\n      uses: actions/setup-node@v3\n      with:\n        node-version: ${{ matrix.node-version }}\n    - run: npm ci\n    - name: Build libs and demo\n      run: npm run build:demo\n    - name: Test libs\n      run: |\n        npm run test:core -- --code-coverage --no-watch --no-progress --browsers=ChromeHeadlessCI\n        npm run test:bs3 -- --code-coverage --no-watch --no-progress --browsers=ChromeHeadlessCI\n        npm run test:bs4 -- --code-coverage --no-watch --no-progress --browsers=ChromeHeadlessCI\n        npm run test:material -- --code-coverage --no-watch --no-progress --browsers=ChromeHeadlessCI\n    - name: Publish code coverage\n      run: npm run publish:coverage\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [main, angular6-json-schema-form]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [main, angular6-json-schema-form]\n  schedule:\n    - cron: '0 18 * * 3'\n\njobs:\n  analyse:\n    name: Analyse\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v3\n      with:\n        # We must fetch at least the immediate parents so that if this is\n        # a pull request then we can checkout the head.\n        fetch-depth: 2\n\n    # If this run was triggered by a pull request event, then checkout\n    # the head of the pull request instead of the merge commit.\n    - run: git checkout HEAD^2\n      if: ${{ github.event_name == 'pull_request' }}\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v1\n      # Override language selection by uncommenting this and choosing your languages\n      # with:\n      #   languages: go, javascript, csharp, python, cpp, java\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v1\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v1\n"
  },
  {
    "path": ".github/workflows/dependabot.yml",
    "content": "version: 2\nupdates:\n  # npm dependencies\n  - package-ecosystem: \"npm\"\n    # package.json directory\n    directory: \"/\"\n    # Check for dependencies update every week on Sunday so owners are not spammed during the week\n    schedule:\n      interval: \"monthly\"\n    # 10 PRs created per week max seems managable?\n    open-pull-requests-limit: 5\n    commit-message:\n      # Prefix all commit messages with \"build\"\n      prefix: \"build\"\n  - package-ecosystem: \"npm\"\n    directory: \"/projects/ajsf-core\"\n    schedule:\n      interval: \"weekly\"\n      day: \"sunday\"\n    open-pull-requests-limit: 5\n    commit-message:\n      prefix: \"build\"\n  - package-ecosystem: \"npm\"\n    directory: \"/projects/ajsf-material\"\n    schedule:\n      interval: \"weekly\"\n      day: \"sunday\"\n    open-pull-requests-limit: 5\n    commit-message:\n      prefix: \"build\"\n  - package-ecosystem: \"npm\"\n    directory: \"/projects/ajsf-bootstrap3\"\n    schedule:\n      interval: \"weekly\"\n      day: \"sunday\"\n    open-pull-requests-limit: 5\n    commit-message:\n      prefix: \"build\"\n  - package-ecosystem: \"npm\"\n    directory: \"/projects/ajsf-bootstrap4\"\n    schedule:\n      interval: \"weekly\"\n      day: \"sunday\"\n    open-pull-requests-limit: 5\n    commit-message:\n      prefix: \"build\"\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy\n\non:\n  push:\n    branches: [ main ]\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: setup Node\n      uses: actions/setup-node@v3\n    - run: npm ci\n    - run: npm run build:demo\n\n    - name: deploy\n      uses: peaceiris/actions-gh-pages@v3\n      with:\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n        publish_dir: ./dist/demo\n        exclude_assets: ''\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Mark stale issues and pull requests\n\non:\n  schedule:\n  - cron: \"0 0 * * *\"\n\njobs:\n  stale:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/stale@v1\n      with:\n        repo-token: ${{ secrets.GITHUB_TOKEN }}\n        stale-issue-message: 'Stale issue'\n        stale-pr-message: 'Stale pull request'\n        stale-issue-label: 'no-issue-activity'\n        stale-pr-label: 'no-pr-activity'\n"
  },
  {
    "path": ".gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.angular/cache\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": ".nvmrc",
    "content": "16.13.2\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to ajsf\n\najsf is an open project and welcomes contributions. These guidelines are provided to help you understand how the project works and to make contributing smooth and fun for everybody involved.\n\nThere are two main forms of contribution: reporting issues and performing code changes.\n\n## Reporting Issues\n\nIf you find a problem with ajsf, report it using [GitHub issues](https://github.com/hamzahamidi/ajsf/issues/new).\n\nBefore reporting a new issue, please take a moment to check whether it has already been reported\n[here](https://github.com/hamzahamidi/ajsf/issues). If this is the case, please:\n\n- Read all the comments to confirm that it's the same issue you're having.\n- Refrain from adding \"same thing here\" or \"+1\" comments. Just hit the\n  \"subscribe\" button to get notifications for this issue.\n- Add a comment only if you can provide helpful information that has not been\n  provided in the discussion yet.\n\nWhen creating a new issue, make sure you include:\n\n1. As much detail as possible about your setup/environment\n1. Steps to reproduce the issue/bug\n1. What you expected to happen\n1. What happened instead\n\nThis information will help to determine the cause and prepare a fix as fast as possible.\n\n## Code Changes\n\nCode contributions come in various forms and sizes, from simple bug fixes to implementation\nof new features.\n\nTo send your code change, use GitHub pull requests. The workflow is as follows:\n\n  1. Fork the project.\n\n  1. Create a branch based on `main`.\n\n  1. Implement your change, including tests and documentation.\n\n  1. Run tests to make sure your change didn't break anything.\n\n  1. Publish the branch and create a pull request.\n\n  1. ajsf developers will review your change and possibly point out issues.\n     Adapt the code under their guidance until all issues are resolved.\n\n  1. Finally, the pull request will get merged or rejected.\n\nSee also [GitHub's guide on contributing](https://help.github.com/articles/fork-a-repo).\n\nIf you want to do multiple unrelated changes, use separate branches and pull\nrequests.\n\n### Start the development environment\n\nLet's first generate all the bundles we need to start the demo:\n\n```bash\n$ cd ajsf\n$ yarn install or npm install\n$ yarn start\n```\n\nYou can stop the demo application.\nThe tricky part now is to run concurrently both the demo application & the library in watch mode.\nSo, first choose which library you want to change then run `$ ng build @ajsf/core --watch` for example\nto build in watch mode the `@ajsf/core`.\nNow let's start the demo application in watch mode too. So, open a new terminal and run `$ ng serve` and there you go.\nThis method is tricky but it works perfectly in all environments (I tried other methods like npm-run-all\nor concurrently packages but angular-cli build doesn't restart after a failed build).\nIf you have a better method please send a PR.\n\n### Commits\n\nEach commit in the pull request should do only one thing, which is clearly\ndescribed by its commit message. Especially avoid mixing formatting changes and\nfunctional changes into one commit. When writing commit messages, adhere to\n[Angular Conventional Commit](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines).\n\n#### Commit Message Format\nEach commit message consists of a **header**, a **body** and a **footer**.  The header has a special\nformat that includes a **type**, a **scope** and a **subject**:\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\nAny line of the commit message cannot be longer than 100 characters! This allows the message to be easier\nto read on GitHub as well as in various git tools.\n\nThe footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.\n\n```markdown\ndocs(changelog): update changelog to beta.5\n```\n\n```markdown\nfix(release): need to depend on latest rxjs and zone.js\n\nThe version in our package.json gets copied to the one we publish, and users need the latest of these.\n```\n\n#### Type\n\nMust be one of the following:\n\n* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)\n* **ci**: Changes to our CI configuration files and scripts (example scopes: Circle, BrowserStack, SauceLabs)\n* **docs**: Documentation only changes\n* **feat**: A new feature\n* **fix**: A bug fix\n* **perf**: A code change that improves performance\n* **refactor**: A code change that neither fixes a bug nor adds a feature\n* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n* **test**: Adding missing tests or correcting existing tests\n\n#### Scope\n\nThe scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages).\n\nThe following is the list of supported scopes:\n\n* **ajsf/core**\n* **ajsf/bs3**\n* **ajsf/bs4**\n* **ajsf/material**\n* **locales**\n* **demo**\n* ...\n\nWhen the commit fixes a bug, put a message in the body of the commit message\npointing to the number of the issue (e.g. \"Fixes #123\").\n\n### Pull requests and branches\n\nAll work happens in branches. The main branch is only used as the target for pull\nrequests.\n\nDuring code review you often need to update pull requests. Usually you do that\nby pushing additional commits.\n\nIn some cases where the commit history of a pull request gets too cumbersome to\nreview or you need bigger changes in the way you approach a problem which needs\nchanging of commits you already did it's more practical to create a new pull\nrequest. This new pull request often will contain squashed versions of the\nprevious pull request. Use that to clarify the changes contained in a pull\nrequest and to make review easier.\n\nWhen you replace a pull request by another one, add a message in the\ndescription of the new pull request on GitHub referencing the pull request it\nreplaces (e.g. \"Supersedes #123\").\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Hamza Hamidi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# AJSF (Angular JSON Schema Form)\n\n**N.B:** For Angular6-json-schema-form please check [this documentation](https://github.com/hamzahamidi/ajsf/tree/angular6-json-schema-form).\n\n<p align=\"center\">\n  <a href=\"https://github.com/hamzahamidi/ajsf/actions?query=workflow%3ACI+branch%3Amain\"><img src=\"https://github.com/hamzahamidi/ajsf/workflows/CI/badge.svg\" alt=\"CI Status\"></a>\n  <a href=\"https://www.npmjs.com/package/@ajsf/core\"><img src=\"https://img.shields.io/npm/dm/@ajsf/core.svg?style=plastic\" alt=\"npm number of downloads\"></a>\n  <a href=\"https://github.com/hamzahamidi/ajsf/blob/master/LICENSE\"><img src=\"https://img.shields.io/github/license/hamzahamidi/ajsf.svg?style=social\" alt=\"LICENSE IMT\"></a>\n  <a href=\"https://app.netlify.com/sites/ajsf/deploys\"><img src=\"https://api.netlify.com/api/v1/badges/6c5b5a1d-db7c-4d0e-8ac1-a4840d8812f0/deploy-status\" alt=\"Netlify Status\"></a>\n</p>\n\nNote: This project is a continuation to [dschnelldavis/Angular2-json-schema-form](https://github.com/dschnelldavis/angular2-json-schema-form) and is not affiliated with any organization.\n\nA [JSON Schema](http://json-schema.org) Form builder for Angular, similar to, and mostly API compatible with:\n\n* [JSON Schema Form](https://github.com/json-schema-form)'s [Angular Schema Form](http://schemaform.io) for [AngularJS](https://angularjs.org) ([examples](http://schemaform.io/examples/bootstrap-example.html))\n* [Mozilla](https://blog.mozilla.org/services/)'s [React JSON Schema Form](https://github.com/mozilla-services/react-jsonschema-form) for [React](https://facebook.github.io/react/) ([examples](https://mozilla-services.github.io/react-jsonschema-form/)), and\n* [Joshfire](http://www.joshfire.com)'s [JSON Form](http://github.com/joshfire/jsonform/wiki) for [jQuery](https://jquery.com) ([examples](http://ulion.github.io/jsonform/playground/))\n\n## Packages\n\n* [`@ajsf/core`](./README.md) [![npm version](https://badge.fury.io/js/%40ajsf%2Fcore.svg)](https://badge.fury.io/js/%40ajsf%2Fcore)\n* [`@ajsf/bootstrap3`](./projects/ajsf-bootstrap3/README.md) [![npm version](https://badge.fury.io/js/%40ajsf%2Fbootstrap3.svg)](https://badge.fury.io/js/%40ajsf%2Fbootstrap3)\n* [`@ajsf/bootstrap4`](./projects/ajsf-bootstrap4/README.md) [![npm version](https://badge.fury.io/js/%40ajsf%2Fbootstrap4.svg)](https://badge.fury.io/js/%40ajsf%2Fbootstrap4)\n* [`@ajsf/material`](./projects/ajsf-material/README.md) [![npm version](https://badge.fury.io/js/%40ajsf%2Fmaterial.svg)](https://badge.fury.io/js/%40ajsf%2Fmaterial)\n\n## Check out the live demo and play with the examples\n\n[Check out some examples here.](https://hamidihamza.com/ajsf)\n\nThis example playground features over 70 different JSON Schemas for you to try (including all examples used by each of the three libraries listed above), and the ability to quickly view any example formatted with Material Design, Bootstrap 3, Bootstrap 4, or without any formatting.\n\n## Installation\n\n### To install from NPM/YARN and use in your own project\n\nIf you want to try out the libraries, you can for example [install @ajsf/material package from NPM](https://www.npmjs.com/package/@ajsf/material) which uses `material-angular` UI. You can use either [NPM](https://www.npmjs.com) or [Yarn](https://yarnpkg.com). To install with NPM, run the following from your terminal:\n\n```shell\nnpm install @ajsf/material@latest\n```\n\nWith YARN, run the following:\n\n```shell\nyarn add @ajsf/material@latest\n```\n\nThen import `MaterialDesignFrameworkModule` in your main application module like this:\n\n```javascript\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\n\nimport { MaterialDesignFrameworkModule } from '@ajsf/material';\n\nimport { AppComponent } from './app.component';\n\n@NgModule({\n  declarations: [ AppComponent ],\n  imports: [\n    MaterialDesignFrameworkModule\n  ],\n  providers: [],\n  bootstrap: [ AppComponent ]\n})\nexport class AppModule { }\n```\n\nFour framework modules are currently included, the import is the same as above :\n\n* MaterialDesignFrameworkModule from @ajsf/material — Material Design\n* Bootstrap3FrameworkModule from @ajsf/bootstrap3 — Bootstrap 3\n* Bootstrap4FrameworkModule from @ajsf/bootstrap4 — Bootstrap 4\n* JsonSchemaFormModule from @ajsf/core — plain HTML (for testing)\n\nIt is also possible to load multiple frameworks and switch between them at runtime, like the example playground on GitHub. But most typical sites will just load one framework.\n\n### To install from GitHub\n\nTo install [the library and the example playground from GitHub](https://github.com/hamzahamidi/ajsf), clone `https://github.com/hamzahamidi/ajsf.git` with your favorite git program. Or, assuming you have [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Node/YARN](https://nodejs.org/en/download/) installed, enter the following in your terminal:\n\n```shell\ngit clone https://github.com/hamzahamidi/ajsf.git ajsf\ncd ajsf\nyarn install\nyarn start\n```\n\nThis should start a server with the example playground, which you can view in your browser at `http://localhost:4200`\n\nThe source code is composed as the following:\n\n* `projects/ajsf-core` - Angular JSON Schema Form main library\n* `projects/ajsf-bootstrap3` - Framework for Bootstrap 3\n* `projects/ajsf-bootstrap4` - Framework for Bootstrap 4\n* `projects/ajsf-material` - Framework for Angular Material\n* `projects/ajsf-core/src/lib/framework-library` - framework library\n* `projects/ajsf-core/src/lib/widget-library` - widget library\n* `projects/ajsf-core/src/lib/shared` - various utilities and helper functions\n* `demo` - the demonstration playground example application\n* `demo/assets/example-schemas` - JSON Schema examples used in the playground\n\nIf you want detailed documentation describing the individual functions used in this library, check the README in each component. (Angular JSON Schema Form is still a work in progress, so right now this documentation varies from highly detailed to completely missing.)\n\n## Using Angular JSON Schema Form\n\n### Basic use\n\nFor basic use, after loading JsonSchemaFormModule as described above, to display a form in your Angular component, simply add the following to your component's template:\n\n```html\n<json-schema-form\n  loadExternalAssets=\"true\"\n  [schema]=\"yourJsonSchema\"\n  framework=\"no-framework\"\n  (onSubmit)=\"yourOnSubmitFn($event)\">\n</json-schema-form>\n```\n\nWhere `schema` is a valid JSON schema object, and `onSubmit` calls a function to process the submitted JSON form data. If you don't already have your own schemas, you can find a bunch of samples to test with in the `demo/assets/example-schemas` folder, as described above.\n\n`framework` is for the template you want to use, the default value is `no-framwork`. The possible values are:\n\n* `material-design` for  Material Design.\n* `bootstrap-3` for Bootstrap 3.\n* `bootstrap-4` for 'Bootstrap 4.\n* `no-framework` for (plain HTML).\n\nSetting `loadExternalAssets=\"true\"` will automatically load any additional assets needed by the display framework. It is useful when you are trying out this library, but production sites should instead load all required assets separately. For full details see 'Changing or adding frameworks', below.\n\n### Data-only mode\n\nAngular JSON Schema Form can also create a form entirely from a JSON object—with no schema—like so:\n\n```html\n<json-schema-form\n  loadExternalAssets=\"true\"\n  [(ngModel)]=\"exampleJsonObject\">\n</json-schema-form>\n```\n\n```javascript\nexampleJsonObject = {\n  \"first_name\": \"Jane\", \"last_name\": \"Doe\", \"age\": 25, \"is_company\": false,\n  \"address\": {\n    \"street_1\": \"123 Main St.\", \"street_2\": null,\n    \"city\": \"Las Vegas\", \"state\": \"NV\", \"zip_code\": \"89123\"\n  },\n  \"phone_numbers\": [\n    { \"number\": \"702-123-4567\", \"type\": \"cell\" },\n    { \"number\": \"702-987-6543\", \"type\": \"work\" }\n  ], \"notes\": \"\"\n};\n```\n\nIn this mode, Angular JSON Schema Form automatically generates a schema from your data. The generated schema is relatively simple, compared to what you could create on your own. However, as the above example shows, it does detect and enforce string, number, and boolean values (nulls are also assumed to be strings), and automatically allows array elements to be added, removed, and reordered.\n\nAfter displaying a form in this mode, you can also use the `formSchema` and `formLayout` outputs (described in 'Debugging inputs and outputs', below), to return the generated schema and layout, which will give you a head start on writing your own schemas and layouts by showing you examples created from your own data.\n\nAlso, notice that the 'ngModel' input supports Angular's 2-way data binding, just like other form controls, which is why it is not always necessary to use an onSubmit function.\n\n### Advanced use\n\n#### Additional inputs an outputs\n\nFor more control over your form, you may provide these additional inputs:\n\n* `layout` array with a custom form layout (see Angular Schema Form's [form definitions](https://github.com/json-schema-form/angular-schema-form/blob/master/docs/index.md#form-definitions) for information about how to construct a form layout)\n* `data` object to populate the form with default or previously submitted values\n* `options` object to set any global options for the form\n* `widgets` object to add custom widgets\n* `language` string to set the error message language (currently supports 'de', 'en', 'es', 'fr', 'it', 'pt', 'zh')\n* `framework` string or object to set which framework to use\n\nFor `framework`, you can pass in your own custom framework object, or, if you've loaded multiple frameworks, you can specify the name of the framework you want to use. To switch between the included frameworks, use 'material-design', 'bootstrap-3', 'bootstrap-4', and 'no-framework'.\n\nIf you want more detailed output, you may provide additional functions for `onChanges` to read the values in real time as the form is being filled out, and you may implement your own custom validation indicators from the boolean `isValid` or the detailed `validationErrors` outputs.\n\nHere is an example:\n\n```html\n<json-schema-form\n  [schema]=\"yourJsonSchema\"\n  [layout]=\"yourJsonFormLayout\"\n  [(data)]=\"yourData\"\n  [options]=\"yourFormOptions\"\n  [widgets]=\"yourCustomWidgets\"\n  language=\"fr\"\n  framework=\"material-design\"\n  loadExternalAssets=\"true\"\n  (onChanges)=\"yourOnChangesFn($event)\"\n  (onSubmit)=\"yourOnSubmitFn($event)\"\n  (isValid)=\"yourIsValidFn($event)\"\n  (validationErrors)=\"yourValidationErrorsFn($event)\">\n</json-schema-form>\n```\n\nNote: If you prefer brackets around all your attributes, the following is functionally equivalent:\n\n```html\n<json-schema-form\n[schema]=\"yourJsonSchema\"\n[layout]=\"yourJsonFormLayout\"\n[(data)]=\"yourData\"\n[options]=\"yourFormOptions\"\n[widgets]=\"yourCustomWidgets\"\n[language]=\"'fr'\"\n[framework]=\"'material-design'\"\n[loadExternalAssets]=\"true\"\n(onChanges)=\"yourOnChangesFn($event)\"\n(onSubmit)=\"yourOnSubmitFn($event)\"\n(isValid)=\"yourIsValidFn($event)\"\n(validationErrors)=\"yourValidationErrorsFn($event)\">\n</json-schema-form>\n```\n\nIf you use this syntax, make sure to include the nested quotes (`\"'` and `'\"`) around the language and framework names. (If you leave out the inner quotes, Angular will read them as a variable names, rather than strings, which will cause errors. All un-bracketed attributes, however, are automatically read as strings, so they don't need inner quotes.)\n\n#### Single-input mode\n\nYou may also combine all your inputs into one compound object and include it as a `form` input, like so:\n\n```javascript\nconst yourCompoundInputObject = {\n  schema:    { ... },  // REQUIRED\n  layout:    [ ... ],  // optional\n  data:      { ... },  // optional\n  options:   { ... },  // optional\n  widgets:   { ... },  // optional\n  language:   '...' ,  // optional\n  framework:  '...'    // (or { ... }) optional\n}\n```\n\n```html\n<json-schema-form\n  [form]=\"yourCompoundInputObject\"\n  (onSubmit)=\"yourOnSubmitFn($event)\">\n</json-schema-form>\n```\n\nYou can also mix these two styles depending on your needs. In the example playground, all examples use the combined `form` input for `schema`, `layout`, and `data`, which enables each example to control those three inputs, but the playground uses separate inputs for `language` and `framework`, enabling it to change those settings independent of the example.\n\nCombining inputs is useful if you have many unique forms and store each form's data and schema together. If you have one form (or many identical forms), it will likely be more useful to use separate inputs for your data and schema. Though even in that case, if you use a custom layout, you could store your schema and layout together and use one input for both.\n\n#### Compatibility modes\n\nIf you have previously used another JSON form creation library—Angular Schema Form (for AngularJS), React JSON Schema Form, or JSON Form (for jQuery)—in order to make the transition easier, Angular JSON Schema Form will recognize the input names and custom input objects used by those libraries. It should automatically work with JSON Schemas in [version 6](http://json-schema.org/draft-06/schema), [version 4](http://json-schema.org/draft-04/schema), [version 3](http://json-schema.org/draft-03/schema), or the [truncated version 3 format supported by JSON Form](https://github.com/joshfire/jsonform/wiki#schema-shortcut). So the following will all work:\n\nAngular Schema Form (AngularJS) compatibility:\n\n```html\n<json-schema-form\n  [schema]=\"yourJsonSchema\"\n  [form]=\"yourAngularSchemaFormLayout\"\n  [(model)]=\"yourData\">\n</json-schema-form>\n```\n\nReact JSON Schema Form compatibility:\n\n```html\n<json-schema-form\n  [schema]=\"yourJsonSchema\"\n  [UISchema]=\"yourReactJsonSchemaFormUISchema\"\n  [(formData)]=\"yourData\">\n</json-schema-form>\n```\n\nJSON Form (jQuery) compatibility:\n\n```html\n<json-schema-form\n  [form]=\"{\n    schema: yourJsonSchema,\n    form: yourJsonFormLayout,\n    customFormItems: yourJsonFormCustomFormItems,\n    value: yourData\n  }\">\n</json-schema-form>\n```\n\nNote: 2-way data binding will work with any dedicated data input, including 'data', 'model', 'ngModel', or 'formData'. However, 2-way binding will _not_ work with the combined 'form' input.\n\n#### Debugging inputs and outputs\n\nFinally, Angular JSON Schema Form includes some additional inputs and outputs for debugging:\n\n* `debug` input — Activates debugging mode.\n* `loadExternalAssets` input — Causes external JavaScript and CSS needed by the selected framework to be automatically loaded from a CDN (this is not 100% reliable, so while this can be helpful during development and testing, it is not recommended for production)—Note: If you are using this mode and get a console error saying an external asset has not loaded (such as jQuery, required for Bootstrap 3) simply reloading your web browser will usually fix it.\n* `formSchema` and `formLayout` outputs — Returns the final schema and layout used to create the form (which will either show how your original input schema and layout were modified, if you provided inputs, or show you the automatically generated ones, if you didn't).\n\n```html\n<json-schema-form\n  [schema]=\"yourJsonSchema\"\n  [debug]=\"true\"\n  loadExternalAssets=\"true\"\n  (formSchema)=\"showFormSchemaFn($event)\"\n  (formLayout)=\"showFormLayoutFn($event)\">\n</json-schema-form>\n```\n\n## Customizing\n\nIn addition to a large number of user-settable options, Angular JSON Schema Form also has the ability to load custom form control widgets and layout frameworks. All forms are constructed from these basic components. The default widget library includes all standard HTML 5 form controls, as well as several common layout patterns, such as multiple checkboxes and tab sets. The default framework library includes templates to style forms using Material Design, Bootstrap 3, or Bootstrap 4 (or plain HTML with no formatting, which is not useful in production, but can be helpful for development and debugging).\n\n### User settings\n\n(TODO: List all available user settings, and configuration options for each.)\n\n### Creating custom input validation error messages\n\nYou can easily add your own custom input validation error messages, either for individual control widgets, or for your entire form.\n\n#### Setting error messages for individual controls or the entire form\n\nTo set messages for individual form controls, add them to that control's node in the form layout, like this:\n\n```javascript\nconst yourFormLayout = [\n  { key: 'name',\n    title: 'Enter your name',\n    validationMessages: {\n      // Put your error messages for the 'name' field here\n    }\n  },\n  { type: 'submit', title: 'Submit' }\n]\n```\n\nTo set messages for the entire form, add them to the form options, inside the defautWidgetOptions validationMessages object, like this:\n\n```javascript\nconst yourFormOptions = {\n  defautWidgetOptions: {\n    validationMessages: {\n      // Put your error messages for the entire form here\n    }\n  }\n}\n```\n\n#### How to format error messages\n\nThe validationMessages object—in either a layout node or the form options—contains the names of each error message you want to set as keys, and the corresponding messages as values. Messages may be in any of the following formats:\n\n* String: A plain text message, which is always the same.\n* String template: A text message that includes Angular template-style {{variables}}, which will be be replaced with values from the returned error object.\n* Function: A JavaScript function which accepts the error object as input, and returns a string error message.\n\nHere are examples of all three error message types:\n\n```javascript\nvalidationMessages: {\n\n  // String error message\n  required: 'This field is required.',\n\n  // String template error message\n  // - minimumLength variable will be replaced\n  minLength: 'Must be at least {{minimumLength}} characters long.',\n\n  // Function error message\n  // - example error object:   { multipleOfValue: 0.01, currentValue: 3.456 }\n  // - resulting error message: 'Must have 2 or fewer decimal places.'\n  multipleOf: function(error) {\n    if ((1 / error.multipleOfValue) % 10 === 0) {\n      const decimals = Math.log10(1 / error.multipleOfValue);\n      return `Must have ${decimals} or fewer decimal places.`;\n    } else {\n      return `Must be a multiple of ${error.multipleOfValue}.`;\n    }\n  }\n}\n```\n\n(Note: These examples are from the default set of built-in error messages, which includes messages for all JSON Schema errors except type, const, enum, and dependencies.)\n\n#### Available input validation errors and object values\n\nHere is a list of all the built-in JSON Schema errors, which data type each error is available for, and the values in their returned error objects:\n\nError name       | Data type | Returned error object values\n-----------------|-----------|-----------------------------------------\nrequired         |  any      | (none)\ntype             |  any      | requiredType,          currentValue\nconst            |  any      | requiredValue,         currentValue\nenum             |  any      | allowedValues,         currentValue\nminLength        |  string   | minimumLength,         currentLength\nmaxLength        |  string   | maximumLength,         currentLength\npattern          |  string   | requiredPattern,       currentValue\nformat           |  string   | requiredFormat,        currentValue\nminimum          |  number   | minimumValue,          currentValue\nexclusiveMinimum |  number   | exclusiveMinimumValue, currentValue\nmaximum          |  number   | maximumValue,          currentValue\nexclusiveMaximum |  number   | exclusiveMaximumValue, currentValue\nmultipleOf       |  number   | multipleOfValue,       currentValue\nminProperties    |  object   | minimumProperties,     currentProperties\nmaxProperties    |  object   | maximumProperties,     currentProperties\n dependencies  * |  object   | (varies, based on dependencies schema)\nminItems         |  array    | minimumItems,          currentItems\nmaxItems         |  array    | maximumItems,          currentItems\nuniqueItems      |  array    | duplicateItems\n contains      * |  array    | requiredItem\n\n* Note: The `contains` and `dependencies` validators are still in development, and do not yet work correctly.\n\n### Changing or adding widgets\n\nTo add a new widget or override an existing widget, either add an object containing your new widgets to the `widgets` input of the `<json-schema-form>` tag, or load the `WidgetLibraryService` and call `registerWidget(widgetType, widgetComponent)`, with a string type name and an Angular component to be used whenever a form needs that widget type.\n\nExample:\n\n```javascript\nimport { YourInputWidgetComponent } from './your-input-widget.component';\nimport { YourCustomWidgetComponent } from './your-custom-widget.component';\n...\nconst yourNewWidgets = {\n  input: YourInputWidgetComponent,          // Replace existing 'input' widget\n  custom-control: YourCustomWidgetComponent // Add new 'custom-control' widget\n}\n```\n\n...and...\n\n```html\n<json-schema-form\n  [schema]=\"yourJsonSchema\"\n  [widgets]=\"yourNewWidgets\">\n</json-schema-form>\n```\n\n...or...\n\n```javascript\nimport { WidgetLibraryService } from '@ajsf/core';\n...\nconstructor(private widgetLibrary: WidgetLibraryService) { }\n...\n// Replace existing 'input' widget:\nwidgetLibrary.registerWidget('input', YourInputWidgetComponent);\n// Add new 'custom-control' widget:\nwidgetLibrary.registerWidget('custom-control', YourCustomWidgetComponent);\n```\n\nTo see many examples of widgets, explore the source code, or call `getAllWidgets()` from the `WidgetLibraryService` to see all widgets currently available in the library. All default widget components are in the `projects/json-schema-form/src/lib/widget-library` folder, and all custom Material Design widget components are in the `projects/json-schema-form/src/lib/framework-library/material-design-framework` folder. (The Bootstrap 3 and Bootstrap 4 frameworks just reformat the default widgets, and so do not include any custom widgets of their own.)\n\n### Changing or adding frameworks\n\nTo change the active framework, either use the `framework` input of the `<json-schema-form>` tag, or load the `FrameworkLibraryService` and call `setFramework(yourCustomFramework)`, with either the name of an available framework ('bootstrap-3', 'bootstrap-4', 'material-design', or 'no-framework'), or with your own custom framework object, like so:\n\n```javascript\nimport { YourFrameworkComponent } from './your-framework.component';\nimport { YourWidgetComponent } from './your-widget.component';\n...\nconst yourCustomFramework = {\n  framework: YourFrameworkComponent,                                // required\n  widgets:     { 'your-widget-name': YourWidgetComponent,   ... },  // optional\n  stylesheets: [ '//url-to-framework-external-style-sheet', ... ],  // optional\n  scripts:     [ '//url-to-framework-external-script',      ... ]   // optional\n}\n```\n\n...and...\n\n```html\n<json-schema-form\n  [schema]=\"yourJsonSchema\"\n  [framework]=\"yourCustomFramework\">\n</json-schema-form>\n```\n\n...or...\n\n```javascript\nimport { FrameworkLibraryService } from '@ajsf/core';\n...\nconstructor(private frameworkLibrary: FrameworkLibraryService) { }\n...\nframeworkLibrary.setFramework(yourCustomFramework);\n```\n\nThe value of the required `framework` key is an Angular component which will be called to format each widget before it is displayed. The optional `widgets` object contains any custom widgets, which will override or supplement the built-in widgets. And the optional `stylesheets` and `scripts` arrays contain URLs to any additional external style sheets or JavaScript libraries required by the framework. These are the external stylesheets and scripts that will be loaded if the \"loadExternalAssets\" option is set to \"true\".\n\n#### Loading external assets required by a framework\n\nMost Web layout framework libraries (including both Bootstrap and Material Design) need additional external JavaScript and/or CSS assets loaded in order to work properly. The best practice is to load these assets separately in your site, before calling Angular JSON Schema Form. (For the included libraries, follow these links for more information about how to do this: [Bootstrap](http://getbootstrap.com/getting-started/) and [Material Design](https://github.com/angular/material2/blob/master/GETTING_STARTED.md).)\n\nAlternately, during development, you may find it helpful to let Angular JSON Schema Form load these resources for you (as wed did in the 'Basic use' example, above), which you can do in several ways:\n\n* Call `setFramework` with a second parameter of `true` (e.g. `setFramework('material-design', true)`), or\n* Add `loadExternalAssets: true` to your `options` object, or\n* Add `loadExternalAssets=\"true\"` to your `<json-schema-form>` tag, as shown above\n\nFinally, if you want to see what scripts a particular framework will automatically load, after setting that framework you can call `getFrameworkStylesheets()` or `getFrameworkScritps()` from the `FrameworkLibraryService` to return the built-in arrays of URLs.\n\nHowever, if you are creating a production site you should load these assets separately, and make sure to remove all references to `loadExternalAssets` to prevent the assets from being loaded twice.\n\n## contributing guide\n\nIf you like this project and want to contribute you can check this [documentation](./CONTRIBUTING.md).\n\n## License\n\n[MIT](/LICENSE)\n"
  },
  {
    "path": "angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"demo\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"demo\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist/demo\",\n            \"index\": \"demo/index.html\",\n            \"main\": \"demo/main.ts\",\n            \"polyfills\": \"demo/polyfills.ts\",\n            \"tsConfig\": \"demo/tsconfig.app.json\",\n            \"assets\": [\n              \"demo/favicon.ico\",\n              \"demo/assets\"\n            ],\n            \"styles\": [\n              \"demo/styles.scss\"\n            ],\n            \"scripts\": [],\n            \"vendorChunk\": true,\n            \"extractLicenses\": false,\n            \"buildOptimizer\": false,\n            \"sourceMap\": true,\n            \"optimization\": false,\n            \"namedChunks\": true\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"demo/environments/environment.ts\",\n                  \"with\": \"demo/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          },\n          \"defaultConfiguration\": \"\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"demo:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"demo:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"demo:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"demo/test.ts\",\n            \"polyfills\": \"demo/polyfills.ts\",\n            \"tsConfig\": \"demo/tsconfig.spec.json\",\n            \"karmaConfig\": \"demo/karma.conf.js\",\n            \"styles\": [\n              \"demo/styles.scss\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"demo/favicon.ico\",\n              \"demo/assets\"\n            ]\n          }\n        }\n      }\n    },\n    \"demo-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"demo:serve\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"devServerTarget\": \"demo:serve:production\"\n            }\n          }\n        }\n      }\n    },\n    \"@ajsf/core\": {\n      \"root\": \"projects/ajsf-core\",\n      \"sourceRoot\": \"projects/ajsf-core/src\",\n      \"projectType\": \"library\",\n      \"prefix\": \"\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:ng-packagr\",\n          \"options\": {\n            \"tsConfig\": \"projects/ajsf-core/tsconfig.lib.json\",\n            \"project\": \"projects/ajsf-core/ng-package.json\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"tsConfig\": \"projects/ajsf-core/tsconfig.lib.prod.json\"\n            }\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"projects/ajsf-core/src/test.ts\",\n            \"tsConfig\": \"projects/ajsf-core/tsconfig.spec.json\",\n            \"karmaConfig\": \"projects/ajsf-core/karma.conf.js\"\n          }\n        }\n      }\n    },\n    \"@ajsf/bootstrap4\": {\n      \"projectType\": \"library\",\n      \"root\": \"projects/ajsf-bootstrap4\",\n      \"sourceRoot\": \"projects/ajsf-bootstrap4/src\",\n      \"prefix\": \"\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:ng-packagr\",\n          \"options\": {\n            \"tsConfig\": \"projects/ajsf-bootstrap4/tsconfig.lib.json\",\n            \"project\": \"projects/ajsf-bootstrap4/ng-package.json\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"tsConfig\": \"projects/ajsf-bootstrap4/tsconfig.lib.prod.json\"\n            }\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"projects/ajsf-bootstrap4/src/test.ts\",\n            \"tsConfig\": \"projects/ajsf-bootstrap4/tsconfig.spec.json\",\n            \"karmaConfig\": \"projects/ajsf-bootstrap4/karma.conf.js\"\n          }\n        }\n      }\n    },\n    \"@ajsf/bootstrap3\": {\n      \"projectType\": \"library\",\n      \"root\": \"projects/ajsf-bootstrap3\",\n      \"sourceRoot\": \"projects/ajsf-bootstrap3/src\",\n      \"prefix\": \"\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:ng-packagr\",\n          \"options\": {\n            \"tsConfig\": \"projects/ajsf-bootstrap3/tsconfig.lib.json\",\n            \"project\": \"projects/ajsf-bootstrap3/ng-package.json\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"tsConfig\": \"projects/ajsf-bootstrap3/tsconfig.lib.prod.json\"\n            }\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"projects/ajsf-bootstrap3/src/test.ts\",\n            \"tsConfig\": \"projects/ajsf-bootstrap3/tsconfig.spec.json\",\n            \"karmaConfig\": \"projects/ajsf-bootstrap3/karma.conf.js\"\n          }\n        }\n      }\n    },\n    \"@ajsf/material\": {\n      \"projectType\": \"library\",\n      \"root\": \"projects/ajsf-material\",\n      \"sourceRoot\": \"projects/ajsf-material/src\",\n      \"prefix\": \"\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:ng-packagr\",\n          \"options\": {\n            \"tsConfig\": \"projects/ajsf-material/tsconfig.lib.json\",\n            \"project\": \"projects/ajsf-material/ng-package.json\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"tsConfig\": \"projects/ajsf-material/tsconfig.lib.prod.json\"\n            }\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"projects/ajsf-material/src/test.ts\",\n            \"tsConfig\": \"projects/ajsf-material/tsconfig.spec.json\",\n            \"karmaConfig\": \"projects/ajsf-material/karma.conf.js\"\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"demo\",\n  \"schematics\": {\n    \"@schematics/angular:component\": {\n      \"style\": \"scss\"\n    }\n  }\n}"
  },
  {
    "path": "demo/app/ace-editor.directive.ts",
    "content": "import { Directive, ElementRef, EventEmitter, Input, Output } from '@angular/core';\nimport ace from 'brace';\nimport 'brace/mode/json';\nimport 'brace/theme/sqlserver';\n\n\n\n@Directive({\n  // tslint:disable-next-line:directive-selector\n  selector: '[ace-editor]'\n})\nexport class AceEditorDirective {\n  _options: any = {};\n  _highlightActiveLine = false;\n  _showGutter = false;\n  _readOnly = false;\n  _theme = 'sqlserver';\n  _mode = 'json';\n  _autoUpdateContent = true;\n  editor: any;\n  oldText: any;\n  @Output('textChanged') textChanged = new EventEmitter();\n\n  constructor(elementRef: ElementRef) {\n    const el = elementRef.nativeElement;\n    this.editor = ace.edit(el);\n    this.init();\n    this.initEvents();\n  }\n\n  init() {\n    this.editor.getSession().setUseWorker(false);\n    this.editor.setOptions(this._options);\n    this.editor.setTheme(`ace/theme/${this._theme}`);\n    this.editor.getSession().setMode(`ace/mode/${this._mode}`);\n    this.editor.setHighlightActiveLine(this._highlightActiveLine);\n    this.editor.renderer.setShowGutter(this._showGutter);\n    this.editor.setReadOnly(this._readOnly);\n    this.editor.$blockScrolling = Infinity;\n  }\n\n  initEvents() {\n    this.editor.on('change', () => {\n      const newVal = this.editor.getValue();\n      if (this.oldText) {\n        this.textChanged.emit(newVal);\n      }\n      this.oldText = newVal;\n    });\n  }\n\n  @Input() set options(options: any) {\n    this._options = options;\n    this.editor.setOptions(options || {});\n  }\n\n  @Input() set readOnly(readOnly: any) {\n    this._readOnly = readOnly;\n    this.editor.setReadOnly(readOnly);\n  }\n\n  @Input() set theme(theme: any) {\n    this._theme = theme;\n    this.editor.setTheme(`ace/theme/${theme}`);\n  }\n\n  @Input() set mode(mode: any) {\n    this._mode = mode;\n    this.editor.getSession().setMode(`ace/mode/${mode}`);\n  }\n\n  @Input() set text(text: any) {\n    if (!text) { text = ''; }\n\n    if (this._autoUpdateContent === true) {\n      this.editor.setValue(text);\n      this.editor.clearSelection();\n      this.editor.focus();\n      this.editor.moveCursorTo(0, 0);\n    }\n  }\n\n  @Input() set autoUpdateContent(status: any) {\n    this._autoUpdateContent = status;\n  }\n}\n"
  },
  {
    "path": "demo/app/demo-root.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'demo-root',\n  template: `<router-outlet></router-outlet>`\n})\nexport class DemoRootComponent { }\n"
  },
  {
    "path": "demo/app/demo.component.html",
    "content": "<div class=\"demo-page-header\">\n  <mat-toolbar class=\"mat-elevation-z4 mat-medium\" color=\"primary\">\n    Angular JSON Schema Form — Demonstration Playground\n  </mat-toolbar>\n  <div class=\"header-content\">\n    An Angular <a href=\"http://json-schema.org/\">JSON Schema</a> Form builder\n    for Angular, similar to, and mostly API compatible with,\n    <span class=\"avoidwrap\">\n      <!-- JSON Schema Form's Angular Schema Form -->\n      <!-- https://github.com/json-schema-form -->\n      <!-- http://schemaform.io -->\n      <a href=\"http://schemaform.io/examples/bootstrap-example.html\">Angular Schema Form</a>,\n      <!-- Mozilla's React JSON Schema Form -->\n      <!-- https://github.com/mozilla-services/react-jsonschema-form -->\n      <a href=\"https://mozilla-services.github.io/react-jsonschema-form/\">React JSON Schema Form</a>,\n      and\n      <!-- Joshfire's JSON Form -->\n      <!-- http://factory.joshfire.com/ -->\n      <!-- http://github.com/joshfire/jsonform/wiki -->\n      <a href=\"http://ulion.github.io/jsonform/playground/\">JSON Form</a>.\n    </span><br>\n    Choose an example, or create your own, and check out the generated form.<br><br>\n\n    <span class=\"menu-label\">Current example:</span>\n    <button mat-raised-button\n      color=\"primary\"\n      [matMenuTriggerFor]=\"exampleMenu\">\n      <mat-icon>menu</mat-icon> {{selectedSetName}} {{selectedExampleName}}\n    </button>\n    <mat-menu #exampleMenu=\"matMenu\" class=\"example-menu\">\n      <button mat-menu-item class=\"mat-medium\"\n        *ngFor=\"let example of examples['ng-jsf'].schemas\"\n        (click)=\"loadSelectedExample('ng-jsf', '', example.file, example.name)\">\n        {{example.name}}\n      </button>\n      <button mat-menu-item class=\"mat-medium\" [matMenuTriggerFor]=\"asfMenu\">\n        <span>Angular Schema Form (AngularJS) examples</span>\n      </button>\n      <mat-menu #asfMenu=\"matMenu\" class=\"example-menu\">\n        <button mat-menu-item class=\"mat-medium\"\n          *ngFor=\"let example of examples.asf.schemas\"\n          (click)=\"loadSelectedExample('asf', 'Angular Schema Form:', example.file, example.name)\">\n          {{example.name}}\n        </button>\n      </mat-menu>\n      <button mat-menu-item class=\"mat-medium\" [matMenuTriggerFor]=\"rjsfMenu\">\n        <span>React JSON Schema Form examples</span>\n      </button>\n      <mat-menu #rjsfMenu=\"matMenu\" class=\"example-menu\">\n        <button mat-menu-item class=\"mat-medium\"\n          *ngFor=\"let example of examples.rjsf.schemas\"\n          (click)=\"loadSelectedExample('rjsf', 'React JSON Schema Form:', example.file, example.name)\">\n          {{example.name}}\n        </button>\n      </mat-menu>\n      <button mat-menu-item class=\"mat-medium\" [matMenuTriggerFor]=\"jsfMenu\">\n        <span>JSONForm (jQuery) examples</span>\n      </button>\n      <mat-menu #jsfMenu=\"matMenu\" class=\"example-menu\">\n        <button mat-menu-item class=\"mat-medium\"\n          *ngFor=\"let example of examples.jsf.schemas\"\n          (click)=\"loadSelectedExample('jsf', 'JSONForm:', example.file, example.name)\">\n          {{example.name}}\n        </button>\n      </mat-menu>\n    </mat-menu>\n  </div>\n</div>\n<div fxLayout=\"row\" fxLayoutAlign=\"space-around start\"\n  fxLayout.lt-sm=\"column\" fxLayoutAlign.lt-sm=\"flex-start center\">\n\n  <mat-card fxFlex=\"0 0 calc(50% - 12px)\">\n    <h4 class=\"default-cursor\" (click)=\"toggleVisible('options')\">\n      {{visible.options ? '▼' : '▶'}} Selected Framework and Options\n    </h4>\n    <div *ngIf=\"visible.options\" fxLayout=\"column\" [@expandSection]=\"true\">\n      <mat-form-field>\n        <mat-select\n          [(ngModel)]=\"selectedFramework\"\n          name=\"framework\"\n          placeholder=\"Framework\">\n          <mat-option\n            *ngFor=\"let framework of frameworkList\"\n            [value]=\"framework\">\n            {{frameworks[framework]}}\n          </mat-option>\n        </mat-select>\n      </mat-form-field>\n      <mat-form-field>\n        <mat-select\n          [(ngModel)]=\"selectedLanguage\"\n          (selectionChange)=\"loadSelectedLanguage()\"\n          name=\"language\"\n          placeholder=\"Language\">\n          <mat-option\n            *ngFor=\"let language of languageList\"\n            [value]=\"language\">\n            {{languages[language]}}\n          </mat-option>\n        </mat-select>\n      </mat-form-field>\n      <div class=\"check-row\">\n        <mat-checkbox color=\"primary\" [(ngModel)]=\"jsonFormOptions.returnEmptyFields\">\n          Return empty fields?\n        </mat-checkbox>\n        (default = true)\n      </div>\n      <div class=\"check-row\">\n        <mat-checkbox color=\"primary\" [(ngModel)]=\"jsonFormOptions.addSubmit\">\n          Add submit button?\n        </mat-checkbox>\n        (default = only add if no layout is defined)\n      </div>\n      <div class=\"check-row\">\n        <mat-checkbox color=\"primary\" [(ngModel)]=\"jsonFormOptions.defautWidgetOptions.feedback\">\n          Show inline fedback?\n        </mat-checkbox>\n        (default = false)\n      </div>\n      <div class=\"check-row\">\n        <mat-checkbox color=\"primary\" [(ngModel)]=\"jsonFormOptions.debug\">\n          Show debuging information?\n        </mat-checkbox>\n        (default = false)\n      </div>\n    </div>\n    <hr>\n    <h4 class=\"default-cursor\" (click)=\"toggleVisible('schema')\">\n      {{visible.schema ? '▼' : '▶'}} Input JSON Schema and Form Layout\n    </h4>\n    <div *ngIf=\"visible.schema\" [@expandSection]=\"true\"\n      ace-editor\n      [text]=\"jsonFormSchema\"\n      [options]=\"aceEditorOptions\"\n      [readOnly]=\"false\"\n      [autoUpdateContent]=\"true\"\n      (textChanged)=\"generateForm($event)\"\n      style=\"width:100%; overflow: auto; border: 1px solid black;\">\n      (loading form specification...)\n    </div>\n  </mat-card>\n\n  <mat-card fxFlex=\"0 0 calc(50% - 12px)\">\n    <h4 class=\"default-cursor\" (click)=\"toggleVisible('form')\">\n      {{visible.form ? '▼' : '▶'}} Generated Form\n    </h4>\n    <div *ngIf=\"visible.form\" class=\"json-schema-form\" [@expandSection]=\"true\">\n      <div *ngIf=\"!formActive\">{{jsonFormStatusMessage}}</div>\n\n      <!-- This is the form! -->\n      <json-schema-form\n        *ngIf=\"formActive\"\n        loadExternalAssets=\"true\"\n        [form]=\"jsonFormObject\"\n        [options]=\"jsonFormOptions\"\n        [framework]=\"selectedFramework\"\n        [language]=\"selectedLanguage\"\n        (onChanges)=\"onChanges($event)\"\n        (onSubmit)=\"onSubmit($event)\"\n        (isValid)=\"isValid($event)\"\n        (validationErrors)=\"validationErrors($event)\">\n      </json-schema-form>\n\n    </div>\n    <hr>\n    <h4 class=\"default-cursor\" (click)=\"toggleVisible('output')\">\n      {{visible.output ? '▼' : '▶'}} Form Output\n    </h4>\n    <div *ngIf=\"visible.output\" fxLayout=\"column\" [@expandSection]=\"true\">\n      <div>\n        Valid?:\n        <strong *ngIf=\"formIsValid || prettyValidationErrors\"\n          [class.text-success]=\"formIsValid\"\n          [class.text-danger]=\"!formIsValid\">\n          {{formIsValid ? 'Yes' : 'No'}}\n        </strong>\n        <span *ngIf=\"!formIsValid && !prettyValidationErrors\">n/a</span>\n        <span *ngIf=\"prettyValidationErrors\">— errors from validationErrors():</span>\n        <div *ngIf=\"prettyValidationErrors\"\n          class=\"data-bad\"\n          [innerHTML]=\"prettyValidationErrors\"></div>\n      </div><br>\n      <div>\n        Live data — from onChanges():\n        <pre\n          [class.data-good]=\"!prettyValidationErrors && prettyLiveFormData !== '{}'\"\n          [class.data-bad]=\"prettyValidationErrors\">{{prettyLiveFormData}}</pre>\n      </div><br>\n      <div>\n        Submitted data — from onSubmit():\n        <pre [class.data-good]=\"prettySubmittedFormData !== 'null'\">{{prettySubmittedFormData}}</pre>\n      </div>\n    </div>\n  </mat-card>\n\n</div>\n"
  },
  {
    "path": "demo/app/demo.component.ts",
    "content": "import { Component, OnInit, ViewChild } from '@angular/core';\nimport { MatMenuTrigger } from '@angular/material/menu';\nimport { trigger, state, style, animate, transition } from '@angular/animations';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { HttpClient } from '@angular/common/http';\n\nimport { Examples } from './example-schemas.model';\nimport { JsonPointer } from '@ajsf/core';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'demo',\n  templateUrl: 'demo.component.html',\n  animations: [\n    trigger('expandSection', [\n      state('in', style({ height: '*' })),\n      transition(':enter', [\n        style({ height: 0 }), animate(100),\n      ]),\n      transition(':leave', [\n        style({ height: '*' }),\n        animate(100, style({ height: 0 })),\n      ]),\n    ]),\n  ],\n})\nexport class DemoComponent implements OnInit {\n  examples: any = Examples;\n  languageList: any = ['de', 'en', 'es', 'fr', 'it', 'pt', 'zh'];\n  languages: any = {\n    'de': 'German',\n    'en': 'English',\n    'es': 'Spanish',\n    'fr': 'French',\n    'it': 'Italian',\n    'pt': 'Portuguese',\n    'zh': 'Chinese'\n  };\n  frameworkList: any = ['material-design', 'bootstrap-3', 'bootstrap-4', 'no-framework'];\n  frameworks: any = {\n    'material-design': 'Material Design',\n    'bootstrap-3': 'Bootstrap 3',\n    'bootstrap-4': 'Bootstrap 4',\n    'no-framework': 'None (plain HTML)',\n  };\n  selectedSet = 'ng-jsf';\n  selectedSetName = '';\n  selectedExample = 'ng-jsf-flex-layout';\n  selectedExampleName = 'Flexbox layout';\n  selectedFramework = 'material-design';\n  selectedLanguage = 'en';\n  visible = {\n    options: true,\n    schema: true,\n    form: true,\n    output: true\n  };\n\n  formActive = false;\n  jsonFormSchema: string;\n  jsonFormValid = false;\n  jsonFormStatusMessage = 'Loading form...';\n  jsonFormObject: any;\n  jsonFormOptions: any = {\n    addSubmit: true, // Add a submit button if layout does not have one\n    debug: false, // Don't show inline debugging information\n    loadExternalAssets: true, // Load external css and JavaScript for frameworks\n    returnEmptyFields: false, // Don't return values for empty input fields\n    setSchemaDefaults: true, // Always use schema defaults for empty fields\n    defautWidgetOptions: { feedback: true }, // Show inline feedback icons\n  };\n  liveFormData: any = {};\n  formValidationErrors: any;\n  formIsValid = null;\n  submittedFormData: any = null;\n  aceEditorOptions: any = {\n    highlightActiveLine: true,\n    maxLines: 1000,\n    printMargin: false,\n    autoScrollEditorIntoView: true,\n  };\n  @ViewChild(MatMenuTrigger, { static: true }) menuTrigger: MatMenuTrigger;\n\n  constructor(\n    private http: HttpClient,\n    private route: ActivatedRoute,\n    private router: Router\n  ) { }\n\n  ngOnInit() {\n    // Subscribe to query string to detect schema to load\n    this.route.queryParams.subscribe(\n      params => {\n        if (params['set']) {\n          this.selectedSet = params['set'];\n          this.selectedSetName = ({\n            'ng-jsf': '',\n            'asf': 'Angular Schema Form:',\n            'rsf': 'React Schema Form:',\n            'jsf': 'JSONForm:'\n          })[this.selectedSet];\n        }\n        if (params['example']) {\n          this.selectedExample = params['example'];\n          this.selectedExampleName = this.examples[this.selectedSet].schemas\n            .find(schema => schema.file === this.selectedExample).name;\n        }\n        if (params['framework']) {\n          this.selectedFramework = params['framework'];\n        }\n        if (params['language']) {\n          this.selectedLanguage = params['language'];\n        }\n        this.loadSelectedExample();\n      }\n    );\n  }\n\n  onSubmit(data: any) {\n    this.submittedFormData = data;\n  }\n\n  get prettySubmittedFormData() {\n    return JSON.stringify(this.submittedFormData, null, 2);\n  }\n\n  onChanges(data: any) {\n    this.liveFormData = data;\n  }\n\n  get prettyLiveFormData() {\n    return JSON.stringify(this.liveFormData, null, 2);\n  }\n\n  isValid(isValid: boolean): void {\n    this.formIsValid = isValid;\n  }\n\n  validationErrors(data: any): void {\n    this.formValidationErrors = data;\n  }\n\n  get prettyValidationErrors() {\n    if (!this.formValidationErrors) { return null; }\n    const errorArray = [];\n    for (const error of this.formValidationErrors) {\n      const message = error.message;\n      const dataPathArray = JsonPointer.parse(error.dataPath);\n      if (dataPathArray.length) {\n        let field = dataPathArray[0];\n        for (let i = 1; i < dataPathArray.length; i++) {\n          const key = dataPathArray[i];\n          field += /^\\d+$/.test(key) ? `[${key}]` : `.${key}`;\n        }\n        errorArray.push(`${field}: ${message}`);\n      } else {\n        errorArray.push(message);\n      }\n    }\n    return errorArray.join('<br>');\n  }\n\n  loadSelectedExample(\n    selectedSet: string = this.selectedSet,\n    selectedSetName: string = this.selectedSetName,\n    selectedExample: string = this.selectedExample,\n    selectedExampleName: string = this.selectedExampleName\n  ) {\n    if (this.menuTrigger.menuOpen) { this.menuTrigger.closeMenu(); }\n    if (selectedExample !== this.selectedExample) {\n      this.formActive = false;\n      this.selectedSet = selectedSet;\n      this.selectedSetName = selectedSetName;\n      this.selectedExample = selectedExample;\n      this.selectedExampleName = selectedExampleName;\n      this.router.navigateByUrl(\n        '/?set=' + selectedSet +\n        '&example=' + selectedExample +\n        '&framework=' + this.selectedFramework +\n        '&language=' + this.selectedLanguage\n      );\n      this.liveFormData = {};\n      this.submittedFormData = null;\n      this.formIsValid = null;\n      this.formValidationErrors = null;\n    }\n    const exampleURL = `assets/example-schemas/${this.selectedExample}.json`;\n    this.http\n      .get(exampleURL, { responseType: 'text' })\n      .subscribe(schema => {\n        this.jsonFormSchema = schema;\n        this.generateForm(this.jsonFormSchema);\n      });\n  }\n\n  loadSelectedLanguage() {\n    window.location.href = `${window.location.pathname}?set=${this.selectedSet}&example=${this.selectedExample}&framework=${this.selectedFramework}&language=${this.selectedLanguage}`;\n  }\n\n  // Display the form entered by the user\n  // (runs whenever the user changes the jsonform object in the ACE input field)\n  generateForm(newFormString: string) {\n    if (!newFormString) { return; }\n    this.jsonFormStatusMessage = 'Loading form...';\n    this.formActive = false;\n    this.liveFormData = {};\n    this.submittedFormData = null;\n\n    // Most examples should be written in pure JSON,\n    // but if an example schema includes a function,\n    // it will be compiled it as Javascript instead\n    try {\n\n      // Parse entered content as JSON\n      this.jsonFormObject = JSON.parse(newFormString);\n      this.jsonFormValid = true;\n    } catch (jsonError) {\n      try {\n\n        // If entered content is not valid JSON,\n        // parse as JavaScript instead to include functions\n        const newFormObject: any = null;\n        /* tslint:disable */\n        eval('newFormObject = ' + newFormString);\n        /* tslint:enable */\n        this.jsonFormObject = newFormObject;\n        this.jsonFormValid = true;\n      } catch (javascriptError) {\n\n        // If entered content is not valid JSON or JavaScript, show error\n        this.jsonFormValid = false;\n        this.jsonFormStatusMessage =\n          'Entered content is not currently a valid JSON Form object.\\n' +\n          'As soon as it is, you will see your form here. So keep typing. :-)\\n\\n' +\n          'JavaScript parser returned:\\n\\n' + jsonError;\n        return;\n      }\n    }\n    this.formActive = true;\n  }\n\n  toggleVisible(item: string) {\n    this.visible[item] = !this.visible[item];\n  }\n\n  toggleFormOption(option: string) {\n    if (option === 'feedback') {\n      this.jsonFormOptions.defautWidgetOptions.feedback =\n        !this.jsonFormOptions.defautWidgetOptions.feedback;\n    } else {\n      this.jsonFormOptions[option] = !this.jsonFormOptions[option];\n    }\n    this.generateForm(this.jsonFormSchema);\n  }\n}\n"
  },
  {
    "path": "demo/app/demo.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\nimport { FlexLayoutModule } from '@angular/flex-layout';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { AceEditorDirective } from './ace-editor.directive';\nimport { DemoComponent } from './demo.component';\nimport { DemoRootComponent } from './demo-root.component';\nimport { routes } from './demo.routes';\nimport { JsonSchemaFormModule } from '@ajsf/core';\nimport { Bootstrap4FrameworkModule } from '@ajsf/bootstrap4';\nimport { Bootstrap3FrameworkModule } from '@ajsf/bootstrap3';\nimport { MaterialDesignFrameworkModule } from '@ajsf/material';\n\n@NgModule({\n  declarations: [AceEditorDirective, DemoComponent, DemoRootComponent],\n  imports: [\n    BrowserModule, BrowserAnimationsModule, FlexLayoutModule, FormsModule,\n    HttpClientModule, MatButtonModule, MatCardModule, MatCheckboxModule,\n    MatIconModule, MatMenuModule, MatSelectModule, MatToolbarModule,\n    RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' }),\n    Bootstrap4FrameworkModule,\n    Bootstrap3FrameworkModule,\n    MaterialDesignFrameworkModule,\n    JsonSchemaFormModule\n  ],\n  bootstrap: [DemoRootComponent]\n})\n\nexport class DemoModule { }\n"
  },
  {
    "path": "demo/app/demo.routes.ts",
    "content": "import { Route } from '@angular/router';\n\nimport { DemoComponent } from './demo.component';\n\nexport const routes: Route[] = [\n  { path: '', component: DemoComponent },\n  { path: '**', component: DemoComponent }\n];\n"
  },
  {
    "path": "demo/app/example-schemas.model.ts",
    "content": "/**\n * Sources:\n *\n * Angular JSON Schema Form examples ('ng-jsf-...') are original\n *\n * JSON Meta-Schemas ('json-schema-...') are from\n *   http://json-schema.org/specification-links.html\n *\n * Angular Schema Form (AngularJS) examples ('asf-...') are from\n *   http://schemaform.io/examples/bootstrap-example.html\n *\n * React JSON Schema Form examples ('rjsf-...') are from\n *   https://mozilla-services.github.io/react-jsonschema-form/\n *\n * JSONForm (jQuery) examples ('jsf-...') are from\n *   http://ulion.github.io/jsonform/playground/\n */\n\nexport const Examples: any = {\n  'ng-jsf': {\n    name: 'Angular JSON Schema Form examples',\n    schemas: [\n      { name: 'Flexbox layout',                  file: 'ng-jsf-flex-layout', },\n      // { name: 'Simple Array',                    file: 'ng-jsf-simple-array', },\n      { name: 'Nested Arrays',                   file: 'ng-jsf-nested-arrays', },\n      { name: 'Deep Recursive References',       file: 'ng-jsf-deep-ref', },\n      { name: 'Select Control Lists',            file: 'ng-jsf-select-list-examples', },\n      // { name: 'Select Control Widgets',          file: 'ng-jsf-select-widget-examples', },\n      { name: 'Data Only (no Schema or Layout)', file: 'ng-jsf-data-only', },\n      // { name: 'Layout Only (no Schema or Data)', file: 'ng-jsf-layout-only', },\n      // { name: 'JSON Meta-Schema - Draft 6',    file: 'json-schema-draft06', },\n      // { name: 'JSON Meta-Schema - Draft 4',    file: 'json-schema-draft04', },\n      // { name: 'JSON Meta-Schema - Draft 3',    file: 'json-schema-draft03', },\n      // { name: 'JSON Meta-Schema - Draft 2',    file: 'json-schema-draft02', },\n      // { name: 'JSON Meta-Schema - Draft 1',    file: 'json-schema-draft01', },\n    ]\n  },\n  'asf': {\n    name: 'Angular Schema Form (AngularJS) examples',\n    url: 'http://schemaform.io/examples/bootstrap-example.html',\n    schemas: [\n      { name: 'Simple',                     file: 'asf-simple', },\n      { name: 'Basic JSON Schema Type',     file: 'asf-basic-json-schema-type', },\n      { name: 'Bootstrap Grid',             file: 'asf-bootstrap-grid', },\n      { name: 'Complex Key Support',        file: 'asf-complex-key-support', },\n      { name: 'Array',                      file: 'asf-array', },\n      { name: 'Tab Array',                  file: 'asf-tab-array', },\n      { name: 'TitleMap Examples',          file: 'asf-titlemap-examples', },\n      { name: 'Kitchen Sink',               file: 'asf-kitchen-sink', },\n      { name: 'Hack: Conditional Required', file: 'asf-hack-conditional-required', },\n    ]\n  },\n  'rjsf': {\n    name: 'React JSON Schema Form examples',\n    url: 'https://mozilla-services.github.io/react-jsonschema-form/',\n    schemas: [\n      { name: 'Simple',                     file: 'rjsf-simple', },\n      { name: 'Nested',                     file: 'rjsf-nested', },\n      { name: 'Arrays',                     file: 'rjsf-arrays', },\n      { name: 'Numbers',                    file: 'rjsf-numbers', },\n      { name: 'Widgets',                    file: 'rjsf-widgets', },\n      { name: 'Ordering',                   file: 'rjsf-ordering', },\n      { name: 'References',                 file: 'rjsf-references', },\n      { name: 'Custom',                     file: 'rjsf-custom', },\n      { name: 'Errors',                     file: 'rjsf-errors', },\n      { name: 'Large',                      file: 'rjsf-large', },\n      { name: 'Date & Time',                file: 'rjsf-date-and-time', },\n      { name: 'Validation',                 file: 'rjsf-validation', },\n      { name: 'Files',                      file: 'rjsf-files', },\n      { name: 'Single',                     file: 'rjsf-single', },\n      // { name: 'Custom Array',               file: 'rjsf-custom-array', },\n      { name: 'Alternatives',               file: 'rjsf-alternatives', },\n    ]\n  },\n  'jsf': {\n    name: 'JSONForm (jQuery) examples',\n    url: 'http://ulion.github.io/jsonform/playground/',\n    schemas: [\n      { name: 'Getting started',\n        file: 'jsf-gettingstarted', urlParameters: '?example=gettingstarted' },\n      { name: 'JSON Schema - A basic example',\n        file: 'jsf-schema-basic', urlParameters: '?example=schema-basic', },\n      { name: 'JSON Schema - Slightly more complex example',\n        file: 'jsf-schema-morecomplex', urlParameters: '?example=schema-morecomplex', },\n      { name: 'JSON Schema - Arrays',\n        file: 'jsf-schema-array', urlParameters: '?example=schema-array', },\n      { name: 'JSON Schema - Required field',\n        file: 'jsf-schema-required', urlParameters: '?example=schema-required', },\n      { name: 'JSON Schema - Default values',\n        file: 'jsf-schema-default', urlParameters: '?example=schema-default', },\n      { name: 'JSON Schema - Inline $ref to definitions',\n        file: 'jsf-schema-inlineref', urlParameters: '?example=schema-inlineref', },\n      { name: 'Fields - Common properties',\n        file: 'jsf-fields-common', urlParameters: '?example=fields-common', },\n      { name: 'Fields - Gathering secrets: the password type',\n        file: 'jsf-fields-password', urlParameters: '?example=fields-password', },\n      { name: 'Fields - Large text: the textarea type',\n        file: 'jsf-fields-textarea', urlParameters: '?example=fields-textarea', },\n      { name: 'Fields - text field with jquery-ui autocomplete',\n        file: 'jsf-fields-autocomplete', urlParameters: '?example=fields-autocomplete', },\n      { name: 'Fields - Code (JavaScript, JSON...): the ace type',\n        file: 'jsf-fields-ace', urlParameters: '?example=fields-ace', },\n      { name: 'Fields - Color picker: the color type',\n        file: 'jsf-fields-color', urlParameters: '?example=fields-color', },\n      { name: 'Fields - Boolean flag: the checkbox type',\n        file: 'jsf-fields-checkbox', urlParameters: '?example=fields-checkbox', },\n      { name: 'Fields - Multiple options: the checkboxes type',\n        file: 'jsf-fields-checkboxes', urlParameters: '?example=fields-checkboxes', },\n      { name: 'Fields - Selection list: the select type',\n        file: 'jsf-fields-select', urlParameters: '?example=fields-select', },\n      { name: 'Fields - A list of radio buttons: the radios type',\n        file: 'jsf-fields-radios', urlParameters: '?example=fields-radios', },\n      { name: 'Fields - Radio buttons as real buttons: the radio buttons type',\n        file: 'jsf-fields-radiobuttons', urlParameters: '?example=fields-radiobuttons', },\n      { name: 'Fields - Checkbox buttons: the checkbox buttons type',\n        file: 'jsf-fields-checkboxbuttons', urlParameters: '?example=fields-checkboxbuttons', },\n      { name: 'Fields - Number: the range type',\n        file: 'jsf-fields-range', urlParameters: '?example=fields-range', },\n      { name: 'Fields - Image selector: the imageselect type',\n        file: 'jsf-fields-imageselect', urlParameters: '?example=fields-imageselect', },\n      { name: 'Fields - Icon selector: the iconselect type',\n        file: 'jsf-fields-iconselect', urlParameters: '?example=fields-iconselect', },\n      { name: 'Fields - Grouping: the fieldset type',\n        file: 'jsf-fields-fieldset', urlParameters: '?example=fields-fieldset', },\n      { name: 'Fields - Advanced options section: the advancedfieldset type',\n        file: 'jsf-fields-advancedfieldset', urlParameters: '?example=fields-advancedfieldset', },\n      { name: 'Fields - Authentication settings section: the authfieldset type',\n        file: 'jsf-fields-authfieldset', urlParameters: '?example=fields-authfieldset', },\n      { name: 'Fields - Generic group: the section type',\n        file: 'jsf-fields-section', urlParameters: '?example=fields-section', },\n      { name: 'Fields - Group of buttons: the actions type',\n        file: 'jsf-fields-actions', urlParameters: '?example=fields-actions', },\n      { name: 'Fields - Generic array: the array type (complex)',\n        file: 'jsf-fields-array', urlParameters: '?example=fields-array', },\n      { name: 'Fields - Generic array: the array type (simple)',\n        file: 'jsf-fields-array-simple', urlParameters: '?example=fields-array-simple', },\n      { name: 'Fields - Arrays with tabs: the tabarray type',\n        file: 'jsf-fields-tabarray', urlParameters: '?example=fields-tabarray', },\n      { name: 'Fields - Arrays with tabs: the tabarray type w/ maxItems',\n        file: 'jsf-fields-tabarray-maxitems', urlParameters: '?example=fields-tabarray-maxitems', },\n      { name: 'Fields - Arrays with tabs: the tabarray type w/ default & legend',\n        file: 'jsf-fields-tabarray-value', urlParameters: '?example=fields-tabarray-value', },\n      { name: 'Fields - Alternative: the selectfieldset type',\n        file: 'jsf-fields-selectfieldset', urlParameters: '?example=fields-selectfieldset', },\n      { name: 'Fields - Alternative with schema key',\n        file: 'jsf-fields-selectfieldset-key', urlParameters: '?example=fields-selectfieldset-key', },\n      { name: 'Fields - Submit the form: the submit type',\n        file: 'jsf-fields-submit', urlParameters: '?example=fields-submit', },\n      { name: 'Fields - Guide users: the help type',\n        file: 'jsf-fields-help', urlParameters: '?example=fields-help', },\n      { name: 'Fields - Hidden form values: the hidden type',\n        file: 'jsf-fields-hidden', urlParameters: '?example=fields-hidden', },\n      { name: 'Fields - Series of questions: the questions type',\n        file: 'jsf-fields-questions', urlParameters: '?example=fields-questions', },\n      { name: 'Templating - item index with idx',\n        file: 'jsf-templating-idx', urlParameters: '?example=templating-idx', },\n      { name: 'Templating - tab legend with value and valueInLegend',\n        file: 'jsf-templating-value', urlParameters: '?example=templating-value', },\n      { name: 'Templating - values.xxx to reference another field',\n        file: 'jsf-templating-values', urlParameters: '?example=templating-values', },\n      { name: 'Templating - Using the tpldata property',\n        file: 'jsf-templating-tpldata', urlParameters: '?example=templating-tpldata', },\n      { name: 'Using event handlers',\n        file: 'jsf-events', urlParameters: '?example=events', },\n      { name: 'Using previously submitted values',\n        file: 'jsf-previousvalues', urlParameters: '?example=previousvalues', },\n      { name: 'Using previously submitted values - Multidimensional arrays',\n        file: 'jsf-previousvalues-multidimensional', urlParameters: '?example=previousvalues-multidimensional', },\n    ]\n  }\n};\n"
  },
  {
    "path": "demo/assets/example-schemas/asf-array.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"Comment\",\n    \"type\": \"object\",\n    \"required\": [ \"comments\" ],\n    \"properties\": {\n      \"comments\": {\n        \"type\": \"array\",\n        \"maxItems\": 2,\n        \"items\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"name\": {\n              \"title\": \"Name\",\n              \"type\": \"string\"\n            },\n            \"email\": {\n              \"title\": \"Email\",\n              \"type\": \"string\",\n              \"pattern\": \"^\\\\S+@\\\\S+$\",\n              \"description\": \"Email will be used for evil.\"\n            },\n            \"spam\": {\n              \"title\": \"Spam\",\n              \"type\": \"boolean\",\n              \"default\": true\n            },\n            \"comment\": {\n              \"title\": \"Comment\",\n              \"type\": \"string\",\n              \"maxLength\": 20,\n              \"validationMessage\": \"Don't be greedy!\"\n            }\n          },\n          \"required\": [ \"name\", \"comment\" ]\n        }\n      }\n    }\n  },\n  \"form\": [\n    { \"type\": \"help\",\n      \"helpvalue\": \"<h4>Array Example</h4><p>Try adding a couple of forms, reorder by drag'n'drop.</p>\"\n    },\n    { \"key\": \"comments\",\n      \"add\": \"New\",\n      \"style\": { \"add\": \"btn-success\" },\n      \"items\": [\n        \"comments[].name\",\n        \"comments[].email\",\n        { \"title\": \"Yes I want spam.\",\n          \"type\": \"checkbox\",\n          \"key\": \"comments[].spam\",\n          \"condition\": \"model.comments[arrayIndex].email\"\n        },\n        { \"type\": \"textarea\",\n          \"key\": \"comments[].comment\"\n        }\n      ]\n    },\n    { \"title\": \"OK\",\n      \"type\": \"submit\",\n      \"style\": \"btn-info\"\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/asf-basic-json-schema-type.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"title\": \"Types\",\n    \"properties\": {\n      \"string\": {\n        \"type\": \"string\",\n        \"minLength\": 3\n      },\n      \"integer\": {\n        \"type\": \"integer\"\n      },\n      \"number\": {\n        \"type\": \"number\"\n      },\n      \"boolean\": {\n        \"type\": \"boolean\"\n      }\n    },\n    \"required\": [\n      \"number\"\n    ]\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/asf-bootstrap-grid.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"title\": \"Comment\",\n    \"properties\": {\n      \"name\": {\n        \"title\": \"Name\",\n        \"type\": \"string\"\n      },\n      \"email\": {\n        \"title\": \"Email\",\n        \"type\": \"string\",\n        \"pattern\": \"^\\\\S+@\\\\S+$\",\n        \"description\": \"Email will be used for evil.\"\n      },\n      \"comment\": {\n        \"title\": \"Comment\",\n        \"type\": \"string\",\n        \"maxLength\": 20,\n        \"validationMessage\": \"Don't be greedy!\"\n      }\n    },\n    \"required\": [\n      \"name\",\n      \"email\",\n      \"comment\"\n    ]\n  },\n  \"form\": [\n    {\n      \"type\": \"help\",\n      \"helpvalue\": \"<div class=\\\"alert alert-info\\\">Grid it up with bootstrap</div>\"\n    },\n    {\n      \"type\": \"section\",\n      \"htmlClass\": \"row\",\n      \"items\": [\n        {\n          \"type\": \"section\",\n          \"htmlClass\": \"col-xs-6\",\n          \"items\": [\n            \"name\"\n          ]\n        },\n        {\n          \"type\": \"section\",\n          \"htmlClass\": \"col-xs-6\",\n          \"items\": [\n            \"email\"\n          ]\n        }\n      ]\n    },\n    {\n      \"key\": \"comment\",\n      \"type\": \"textarea\",\n      \"placeholder\": \"Make a comment\"\n    },\n    {\n      \"type\": \"submit\",\n      \"style\": \"btn-info\",\n      \"title\": \"OK\"\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/asf-complex-key-support.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"title\": \"Complex Key Support\",\n    \"properties\": {\n      \"a[\\\"b\\\"].c\": { \"type\": \"string\" },\n      \"simple\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"prøp\": {\n            \"title\": \"UTF8 in both dot and bracket notation\",\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"array-key\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"a'rr[\\\"l\": {\n              \"title\": \"Control Characters\",\n              \"type\": \"string\"\n            },\n            \"˙∆∂∞˚¬\": { \"type\": \"string\" }\n          },\n          \"required\": [ \"a'rr[\\\"l\", \"˙∆∂∞˚¬\" ]\n        }\n      }\n    }\n  },\n  \"form\": [\n    {\n      \"type\": \"help\",\n      \"helpvalue\": \"Complex keys are supported in Angular 2+.\"\n    },\n    \"['a[\\\"b\\\"].c']\",\n    {\n      \"key\": \"array-key\",\n      \"items\": [\n        \"['array-key'][]['a'rr[\\\"l']\",\n        {\n          \"key\": \"['array-key'][]['˙∆∂∞˚¬']\",\n          \"title\": \"Unicode Characters\"\n        }\n      ]\n    },\n    {\n      \"key\": \"simple\",\n      \"items\": [ \"simple.prøp\" ]\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/asf-hack-conditional-required.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"switch\": {\n        \"title\": \"Spam me, please\",\n        \"type\": \"boolean\"\n      },\n      \"email\": {\n        \"title\": \"Email\",\n        \"type\": \"string\",\n        \"pattern\": \"^\\\\S+@\\\\S+$\",\n        \"description\": \"Email will be used for evil.\"\n      }\n    },\n    \"required\": [\n      \"switch\"\n    ]\n  },\n  \"form\": [\n    {\n      \"type\": \"help\",\n      \"helpvalue\": \"<p>Schema Form does not support oneOf (yet), but you can do a workaround and simulate certain scenarios with 'condition' and 'required'  (and/or 'readonly') in the form.</p>\"\n    },\n    \"switch\",\n    {\n      \"key\": \"email\",\n      \"condition\": \"model.switch\",\n      \"required\": true\n    },\n    {\n      \"key\": \"email\",\n      \"condition\": \"!model.switch\"\n    },\n    {\n      \"type\": \"submit\",\n      \"style\": \"btn-info\",\n      \"title\": \"OK\"\n    }\n  ]\n}"
  },
  {
    "path": "demo/assets/example-schemas/asf-kitchen-sink.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"required\": [ \"name\", \"shoesizeLeft\" ],\n    \"properties\": {\n      \"name\": {\n        \"title\": \"Name\",\n        \"description\": \"Gimme yea name lad\",\n        \"type\": \"string\",\n        \"pattern\": \"^[^/]*$\",\n        \"minLength\": 2\n      },\n      \"invitation\": {\n        \"type\": \"string\",\n        \"format\": \"html\",\n        \"title\": \"Invitation Design\",\n        \"description\": \"Design the invitation in full technicolor HTML\"\n      },\n      \"favorite\": {\n        \"title\": \"Favorite\",\n        \"type\": \"string\",\n        \"enum\": [ \"undefined\", \"null\", \"NaN\" ]\n      },\n      \"shoesizeLeft\": {\n        \"title\": \"Shoe size (left)\",\n        \"default\": 42,\n        \"type\": \"number\"\n      },\n      \"shoesizeRight\": {\n        \"title\": \"Shoe size (right)\",\n        \"default\": 42,\n        \"type\": \"number\"\n      },\n      \"attributes\": {\n        \"type\": \"object\",\n        \"title\": \"Attributes\",\n        \"required\": [ \"eyecolor\" ],\n        \"properties\": {\n          \"eyecolor\": {\n            \"type\": \"string\",\n            \"format\": \"color\",\n            \"title\": \"Eye color\",\n            \"default\": \"pink\"\n          },\n          \"haircolor\": {\n            \"type\": \"string\",\n            \"title\": \"Hair color\"\n          },\n          \"shoulders\": {\n            \"type\": \"object\",\n            \"title\": \"Shoulders\",\n            \"properties\": {\n              \"left\":  { \"type\": \"string\", \"title\": \"Left\"  },\n              \"right\": { \"type\": \"string\", \"title\": \"Right\" }\n            }\n          }\n        }\n      },\n      \"things\": {\n        \"type\": \"array\",\n        \"title\": \"I like...\",\n        \"items\": {\n          \"type\": \"string\",\n          \"enum\": [ \"clowns\", \"compiling\", \"sleeping\" ]\n        }\n      },\n      \"dislike\": {\n        \"type\": \"array\",\n        \"title\": \"I dislike...\",\n        \"items\": {\n          \"type\": \"string\",\n          \"title\": \"I hate\"\n        }\n      },\n      \"soul\": {\n        \"title\": \"Terms Of Service\",\n        \"description\": \"I agree to sell my undying <a href='https://www.youtube.com/watch?v=dQw4w9WgXcQ'>soul</a>\",\n        \"type\": \"boolean\",\n        \"default\": true\n      },\n      \"soulserial\": {\n        \"title\": \"Soul Serial No\",\n        \"type\": \"string\"\n      },\n      \"date\": {\n        \"title\": \"Date of party\",\n        \"type\": \"string\",\n        \"format\": \"date\"\n      },\n      \"radio\": {\n        \"title\": \"Radio type\",\n        \"type\": \"string\",\n        \"enum\": [ \"Transistor\", \"Tube\" ]\n      },\n      \"radio2\": {\n        \"title\": \"My Second Radio\",\n        \"type\": \"string\",\n        \"enum\": [ \"Transistor\", \"Tube\" ]\n      },\n      \"radiobuttons\": {\n        \"type\": \"string\",\n        \"enum\": [ \"Select me!\", \"No me!\" ]\n      }\n    }\n  },\n  \"form\": [\n    {\n      \"type\": \"fieldset\",\n      \"title\": \"Stuff\",\n      \"items\": [\n        {\n          \"type\": \"tabs\",\n          \"tabs\": [\n            {\n              \"title\": \"Simple stuff\",\n              \"items\": [\n                {\n                  \"key\": \"name\",\n                  \"placeholder\": \"Check the console\",\n                  \"onChange\": \"log(modelValue)\",\n                  \"feedback\": \"{ 'glyphicon': true, 'glyphicon-ok': hasSuccess(), 'glyphicon-star': !hasSuccess() }\"\n                },\n                { \"key\": \"favorite\", \"feedback\": false }\n              ]\n            },\n            {\n              \"title\": \"More stuff\",\n              \"items\": [\n                \"attributes.eyecolor\",\n                \"attributes.haircolor\",\n                {\n                  \"key\": \"attributes.shoulders.left\",\n                  \"title\": \"Left shoulder\",\n                  \"description\": \"This value is copied to attributes.shoulders.right in the model\",\n                  \"copyValueTo\": [ \"attributes.shoulders.right\" ]\n                },\n                {\n                  \"key\": \"shoesizeLeft\",\n                  \"feedback\": false,\n                  \"copyValueTo\": [ \"shoesizeRight\" ]\n                },\n                { \"key\": \"shoesizeRight\" },\n                {\n                  \"key\": \"invitation\",\n                  \"tinymceOptions\": {\n                    \"toolbar\": [\n                      \"undo redo| styleselect | bold italic | link image\",\n                      \"alignleft aligncenter alignright\"\n                    ]\n                  }\n                },\n                \"things\",\n                \"dislike\"\n              ]\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"type\": \"help\",\n      \"helpvalue\": \"<hr>\"\n    },\n    \"soul\",\n    {\n      \"type\": \"conditional\",\n      \"condition\": \"modelData.soul\",\n      \"items\": [ {\n        \"key\": \"soulserial\",\n        \"placeholder\": \"ex. 666\"\n      } ]\n    },\n    {\n      \"key\": \"date\",\n      \"minDate\": \"2014-06-20\"\n    },\n    {\n      \"key\": \"radio\",\n      \"type\": \"radios\",\n      \"titleMap\": [\n        { \"value\": \"Transistor\", \"name\": \"Transistor <br> Not the tube kind.\" },\n        { \"value\": \"Tube\", \"name\": \"Tube <br> The tube kind.\" }\n      ]\n    },\n    {\n      \"key\": \"radio2\",\n      \"type\": \"radios-inline\",\n      \"titleMap\": [\n        { \"value\": \"Transistor\", \"name\": \"Transistor <br> Not the tube kind.\" },\n        { \"value\": \"Tube\", \"name\": \"Tube <br> The tube kind.\" }\n      ]\n    },\n    {\n      \"key\": \"radiobuttons\",\n      \"style\": { \"selected\": \"btn-success\", \"unselected\": \"btn-default\" },\n      \"type\": \"radiobuttons\",\n      \"notitle\": true\n    },\n    {\n      \"type\": \"actions\",\n      \"items\": [\n        { \"type\": \"submit\", \"style\": \"btn-info\",   \"title\": \"Do It!\" },\n        { \"type\": \"button\", \"style\": \"btn-danger\", \"title\": \"Noooooooooooo\", \"onClick\": \"sayNo()\" }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/asf-simple.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"title\": \"Comment\",\n    \"properties\": {\n      \"name\": {\n        \"title\": \"Name\",\n        \"type\": \"string\"\n      },\n      \"email\": {\n        \"title\": \"Email\",\n        \"type\": \"string\",\n        \"pattern\": \"^\\\\S+@\\\\S+$\",\n        \"description\": \"Email will be used for evil.\"\n      },\n      \"comment\": {\n        \"title\": \"Comment\",\n        \"type\": \"string\",\n        \"maxLength\": 20,\n        \"validationMessage\": \"Don't be greedy!\"\n      }\n    },\n    \"required\": [\n      \"name\",\n      \"email\",\n      \"comment\"\n    ]\n  },\n  \"form\": [\n    \"name\",\n    \"email\",\n    {\n      \"key\": \"comment\",\n      \"type\": \"textarea\",\n      \"placeholder\": \"Make a comment\"\n    },\n    {\n      \"type\": \"submit\",\n      \"style\": \"btn-info\",\n      \"title\": \"OK\"\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/asf-tab-array.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"title\": \"Comment\",\n    \"properties\": {\n      \"comments\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"name\": {\n              \"title\": \"Name\",\n              \"type\": \"string\"\n            },\n            \"email\": {\n              \"title\": \"Email\",\n              \"type\": \"string\",\n              \"pattern\": \"^\\\\S+@\\\\S+$\",\n              \"description\": \"Email will be used for evil.\"\n            },\n            \"comment\": {\n              \"title\": \"Comment\",\n              \"type\": \"string\",\n              \"maxLength\": 20,\n              \"validationMessage\": \"Don't be greedy!\"\n            }\n          },\n          \"required\": [ \"name\", \"email\", \"comment\" ]\n        }\n      }\n    }\n  },\n  \"form\": [ {\n    \"type\": \"help\",\n    \"helpvalue\": \"<h4>Tabbed Array Example</h4><p>Tab arrays can have tabs to the left, top or right.</p>\"\n  }, {\n    \"key\": \"comments\",\n    \"type\": \"tabarray\",\n    \"add\": \"New\",\n    \"remove\": \"Delete\",\n    \"style\": { \"remove\": \"btn-danger\" },\n    \"title\": \"{{ value.name || 'Tab ' + $index }}\",\n    \"items\": [\n      \"comments[].name\",\n      \"comments[].email\",\n      { \"key\": \"comments[].comment\", \"type\": \"textarea\" }\n    ]\n  }, {\n    \"type\": \"submit\",\n    \"style\": \"btn-default\",\n    \"title\": \"OK\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/asf-titlemap-examples.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"select\": {\n        \"title\": \"Select without titleMap\",\n        \"type\": \"string\",\n        \"enum\": [ \"a\", \"b\", \"c\" ]\n      },\n      \"select2\": {\n        \"title\": \"Select with titleMap (old style)\",\n        \"type\": \"string\",\n        \"enum\": [ \"a\", \"b\", \"c\" ]\n      },\n      \"noenum\": {\n        \"type\": \"string\",\n        \"title\": \"No enum, but forms says it's a select\"\n      },\n      \"array\": {\n        \"title\": \"Array with enum defaults to 'checkboxes'\",\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"string\",\n          \"enum\": [ \"a\", \"b\", \"c\" ]\n        }\n      },\n      \"array2\": {\n        \"title\": \"Array with titleMap\",\n        \"type\": \"array\",\n        \"default\": [ \"b\", \"c\" ],\n        \"items\": {\n          \"type\": \"string\",\n          \"enum\": [ \"a\", \"b\", \"c\" ]\n        }\n      },\n      \"radios\": {\n        \"title\": \"Basic radio button example\",\n        \"type\": \"string\",\n        \"enum\": [ \"a\", \"b\", \"c\" ]\n      },\n      \"radiobuttons\": {\n        \"title\": \"Radio buttons used to switch a boolean\",\n        \"type\": \"boolean\",\n        \"default\": false\n      }\n    }\n  },\n  \"form\": [\n    \"select\",\n    { \"key\": \"select2\",\n      \"type\": \"select\",\n      \"titleMap\": {\n        \"a\": \"A\",\n        \"b\": \"B\",\n        \"c\": \"C\"\n      }\n    },\n    { \"key\": \"noenum\",\n      \"type\": \"select\",\n      \"titleMap\": [\n        { \"value\": \"a\", \"name\": \"A\" },\n        { \"value\": \"b\", \"name\": \"B\" },\n        { \"value\": \"c\", \"name\": \"C\" }\n      ]\n    },\n    \"array\",\n    { \"key\": \"array2\",\n      \"type\": \"checkboxes\",\n      \"titleMap\": [\n        { \"value\": \"a\", \"name\": \"A\" },\n        { \"value\": \"b\", \"name\": \"B\" },\n        { \"value\": \"c\", \"name\": \"C\" }\n      ]\n    },\n    { \"key\": \"radios\",\n      \"type\": \"radios\",\n      \"titleMap\": [\n        { \"value\": \"c\", \"name\": \"C\" },\n        { \"value\": \"b\", \"name\": \"B\" },\n        { \"value\": \"a\", \"name\": \"A\" }\n      ]\n    },\n    { \"key\": \"radiobuttons\",\n      \"type\": \"radiobuttons\",\n      \"titleMap\": [\n        { \"name\": \"No way\", \"value\": false },\n        { \"name\": \"OK\",     \"value\": true  }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-events.json",
    "content": "{\n  \"schema\": {\n    \"text\": {\n      \"type\": \"string\",\n      \"title\": \"Text\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"text\",\n    \"onChange\": function (evt) {\n      var value = $(evt.target).val();\n      if (value) alert(value);\n    }\n  }, {\n    \"type\": \"button\",\n    \"title\": \"Click me\",\n    \"onClick\": function (evt) {\n      evt.preventDefault();\n      alert('Thank you!');\n    }\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-factory-sleek.json",
    "content": "{\n  \"schema\": {\n    \"color\": {\n      \"title\": \"Color\",\n      \"type\": \"string\",\n      \"enum\": [ \"blue\", \"spicy\", \"gray\", \"earth\", \"vegetal\" ],\n      \"default\":\"gray\",\n      \"required\": true\n    },\n    \"backgroundimage\" : {\n      \"title\": \"Background image for TV version\",\n      \"type\": \"object\"\n    },\n    \"tabs\": {\n      \"title\": \"Tabs titles\",\n      \"type\": \"array\",\n      \"items\": {\n        \"title\": \"Short tab title (max. 15 characters)\",\n        \"type\": \"string\",\n        \"maxLength\": 15\n      }\n    },\n    \"tabicons\": {\n      \"title\": \"Tabs icons\",\n      \"maxLength\": 8,\n      \"type\": \"array\",\n      \"items\": {\n        \"title\": \"Tab icon\",\n        \"type\": \"string\",\n        \"enum\": [ \"contact\", \"event\", \"map\", \"news\",\n          \"photo\", \"product\", \"sound\", \"status\", \"video\" ]\n      }\n    }\n  },\n  \"form\": [ {\n    \"type\": \"fieldset\",\n    \"legend\": \"Styles\",\n    \"items\": [\n      \"color\",\n      {\n        \"key\" : \"backgroundimage\",\n        \"type\": \"file-hosted-public\"\n      }\n    ]\n  }, {\n    \"type\": \"fieldset\",\n    \"legend\": \"Tabs\",\n    \"items\": [ {\n      \"type\": \"tabarray\",\n      \"items\": [ {\n        \"type\": \"section\",\n        \"legend\": \"{{value}}\",\n        \"items\": [ {\n          \"key\": \"tabicons[]\",\n          \"type\": \"imageselect\",\n          \"imageWidth\": 32,\n          \"imageHeight\": 42,\n          \"imageButtonClass\": \"btn-inverse\",\n          \"imagePrefix\": \"app/images/tv-\",\n          \"imageSuffix\": \".png\",\n          \"imageSelectorTitle\": \"Based on tab data source\"\n        }, {\n          \"key\": \"tabs[]\",\n          \"valueInLegend\": true,\n          \"value\": \"{{values.datasources.main[]}}\"\n        } ]\n      } ]\n    } ]\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-ace.json",
    "content": "{\n  \"schema\": {\n    \"code\": {\n      \"type\": \"string\",\n      \"title\": \"Some JSON\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"code\",\n    \"type\": \"ace\",\n    \"aceMode\": \"json\",\n    \"aceTheme\": \"twilight\",\n    \"width\": \"100%\",\n    \"height\": \"200px\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-actions.json",
    "content": "{\n  \"schema\": {\n    \"search\": {\n      \"type\": \"string\",\n      \"title\": \"Search\"\n    }\n  },\n  \"form\": [\n    \"search\",\n    {\n      \"type\": \"actions\",\n      \"items\": [ {\n        \"type\": \"submit\",\n        \"title\": \"Submit\"\n      }, {\n        \"type\": \"button\",\n        \"title\": \"Cancel\"\n      } ]\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-advancedfieldset.json",
    "content": "{\n  \"schema\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"title\": \"Name\"\n    },\n    \"age\": {\n      \"type\": \"number\",\n      \"title\": \"Age\"\n    }\n  },\n  \"form\": [\n    \"name\",\n    {\n      \"type\": \"advancedfieldset\",\n      \"items\": [\n        \"age\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-array-simple.json",
    "content": "{\n  \"schema\": {\n    \"friends\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"title\": \"Friend\",\n        \"properties\": {\n          \"nick\": {\n            \"type\": \"string\",\n            \"title\": \"Nickname\"\n          },\n          \"animals\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\",\n              \"title\": \"Animal name\"\n            }\n          }\n        }\n      }\n    }\n  },\n  \"form\": [ {\n    \"type\": \"array\",\n    \"items\": {\n      \"type\": \"section\",\n      \"items\": [\n        \"friends[].nick\",\n        {\n          \"type\": \"array\",\n          \"items\": [\n            \"friends[].animals[]\"\n          ]\n        }\n      ]\n    }\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-array.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"friendsA\": { \"$ref\": \"#/definitions/friends\" },\n      \"friendsB\": { \"$ref\": \"#/definitions/friends\" }\n    },\n    \"definitions\": {\n      \"friends\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"title\": \"Friend\",\n          \"properties\": {\n            \"nick\": {\n              \"type\": \"string\",\n              \"title\": \"Nickname\"\n            },\n            \"animals\": {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\",\n                \"title\": \"Animal name\"\n              },\n              \"default\": [ \"dog\", \"cat\" ]\n            }\n          }\n        }\n      }\n    }\n  },\n  \"customFormItems\": {\n    \"friendsB\": {\n      \"type\": \"array\",\n      \"key\": \"friendsB\",\n      \"title\": \"Friends B\",\n      \"readOnly\": true,\n      \"items\": {\n        \"type\": \"section\",\n        \"items\": [\n          {\n            \"type\": \"array\",\n            \"key\": \"friendsB[].animals\",\n            \"items\": [\n              \"friendsB[].animals[]\"\n            ]\n          },\n          \"friendsB[].nick\"\n        ]\n      }\n    }\n  },\n  \"value\": {\n    \"friendsB\": [ {\n      \"nick\": \"B\",\n      \"animals\": [ \"fish\", \"bee\" ]\n    }, {\n      \"nick\": \"B2\",\n      \"animals\": [ \"bee\", \"cat\" ]\n    } ]\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-authfieldset.json",
    "content": "{\n  \"schema\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"title\": \"Name\"\n    },\n    \"key\": {\n      \"type\": \"string\",\n      \"title\": \"Access key\"\n    }\n  },\n  \"form\": [\n    \"name\",\n    {\n      \"type\": \"authfieldset\",\n      \"items\": [\n        \"key\"\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-autocomplete.json",
    "content": "{\n  \"schema\": {\n    \"name\": {\n      \"title\": \"Name\",\n      \"type\": \"string\"\n    },\n    \"nick\": {\n      \"title\": \"Nick name\",\n      \"type\": \"string\"\n    },\n    \"title\": {\n      \"title\": \"Title\",\n      \"type\": \"string\"\n    },\n    \"tags\": {\n      \"title\": \"Tags\",\n      \"type\": \"array\",\n      \"items\": { \"type\": \"string\" },\n      \"default\": [ \"abc\", \"def\" ]\n    }\n  },\n  \"customFormItems\": {\n    \"name\": {\n      \"type\": \"text\",\n      \"autocomplete\": { \"source\": [ \"abc\", \"bed\", \"bee\", \"dog\", \"cat\" ] }\n    },\n    \"nick\": {\n      \"type\": \"text\",\n      \"typeahead\": { \"source\": [ \"abc\", \"bed\", \"bee\", \"dog\", \"cat\" ] }\n    },\n    \"title\": {\n      \"type\": \"text\",\n      \"tagsinput\": { \"typeahead\": { \"source\": [ \"abc\", \"bed\", \"bee\", \"dog\", \"cat\" ] } }\n    },\n    \"tags\": {\n      \"type\": \"tagsinput\",\n      \"tagsinput\": { \"typeahead\": { \"source\": [ \"abc\", \"bed\", \"bee\", \"dog\", \"cat\" ] } }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-checkbox.json",
    "content": "{\n  \"schema\": {\n    \"properties\": {\n      \"flag\": {\n        \"type\": \"boolean\",\n        \"title\": \"Adult\"\n      },\n      \"adultOnlyInfo\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"field1\": {\n            \"title\": \"Field 1\",\n            \"type\": \"string\"\n          },\n          \"field2\": {\n            \"title\": \"Field 2\",\n            \"type\": \"integer\"\n          }\n        },\n        \"required\": [ \"field1\", \"field2\" ]\n      },\n      \"commonInfo\": {\n        \"title\": \"Common Field\",\n        \"type\": \"string\"\n      }\n    },\n    \"dependencies\": {\n      \"flag\": [ \"adultOnlyInfo\" ]\n    }\n  },\n  \"customFormItems\": {\n    \"flag\": {\n      \"inlinetitle\": \"Check this box if you are over 18\",\n      \"toggleNext\": 1\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-checkboxbuttons.json",
    "content": "{\n  \"schema\": {\n    \"language\": {\n      \"type\": \"array\",\n      \"title\": \"Best language\",\n      \"items\": {\n        \"type\": \"string\",\n        \"enum\": [ \"JavaScript\", \"Python\", \"PHP\", \"Java\", \"C++\", \"other\" ]\n      }\n    }\n  },\n  \"form\": [ {\n    \"key\": \"language\",\n    \"type\": \"checkboxbuttons\",\n    \"activeClass\": \"btn-success\"\n  }, {\n    \"title\": \"Submit\",\n    \"type\": \"submit\"\n  } ],\n  \"value\": {\"language\": \"Python\"}\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-checkboxes.json",
    "content": "{\n  \"schema\": {\n    \"properties\": {\n      \"menu\": {\n        \"type\": \"array\",\n        \"title\": \"Options\",\n        \"items\": {\n          \"type\": \"string\",\n          \"title\": \"Option\",\n          \"enum\": [ \"starter\", \"maincourse\", \"cheese\", \"dessert\", \"OTHER\" ]\n        },\n        \"minItems\": 1\n      },\n      \"menuOther\": { \"type\": \"string\" },\n      \"menu2\": {\n        \"type\": \"array\",\n        \"title\": \"Options Inline\",\n        \"items\": {\n          \"type\": \"string\",\n          \"title\": \"Option\",\n          \"enum\": [ \"starter\", \"maincourse\", \"CUSTOME_OTHER_VALUE\", \"cheese\", \"dessert\" ]\n        }\n      },\n      \"menu2Other\": { \"type\": \"string\" },\n      \"menu3\": {\n        \"type\": \"array\",\n        \"title\": \"Options\",\n        \"description\": \"Other field's value as an element of result array\",\n        \"items\": {\n          \"type\": \"string\"\n        },\n        \"minItems\": 1\n      }\n    }\n  },\n  \"nonDefaultFormItems\": [ \"menuOther\", \"menu2Other\" ],\n  \"customFormItems\": {\n    \"menu\": {\n      \"type\": \"checkboxes\",\n      \"titleMap\": {\n        \"starter\": \"Starter would be great\",\n        \"maincourse\": \"No way I'll skip the main course\",\n        \"cheese\": \"Cheddar rules!\",\n        \"dessert\": \"Thumbs up for a dessert\"\n      },\n      \"otherField\": { \"key\": \"menuOther\", \"inline\": true }\n    },\n    \"menu2\": {\n      \"type\": \"checkboxes\",\n      \"title\": \"Options inline style\",\n      \"inline\": true,\n      \"titleMap\": {\n        \"starter\": \"Starter would be great\",\n        \"maincourse\": \"No way I'll skip the main course\",\n        \"cheese\": \"Cheddar rules!\",\n        \"dessert\": \"Thumbs up for a dessert\"\n      },\n      \"otherField\": {\n        \"key\": \"menu2Other\",\n        \"title\": \"Custom other field title\",\n        \"otherValue\": \"CUSTOME_OTHER_VALUE\"\n      }\n    },\n    \"menu3\": {\n      \"type\": \"checkboxes\",\n      \"options\": {\n        \"starter\": \"Starter would be great\",\n        \"maincourse\": \"No way I'll skip the main course\",\n        \"cheese\": \"Cheddar rules!\",\n        \"dessert\": \"Thumbs up for a dessert\"\n      },\n      \"otherField\": { \"key\": \"menu3[99]\", \"type\": \"text\", \"asArrayValue\": true }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-color.json",
    "content": "{\n  \"schema\": {\n    \"maincolor\": {\n      \"type\": \"string\",\n      \"title\": \"Main color\",\n      \"format\": \"color\"\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-common.json",
    "content": "{\n  \"schema\": {\n    \"shortmood\": {\n      \"type\": \"string\",\n      \"title\": \"Mood of the day\",\n      \"description\": \"Describe how you feel in short\",\n      \"default\": \"happy\",\n      \"required\": true\n    },\n    \"longmood\": {\n      \"type\": \"string\",\n      \"title\": \"Mood of the day\",\n      \"description\": \"Describe how you feel with a rather long adjective-like series of words\"\n    }\n  },\n  \"form\": [\n    \"shortmood\",\n    {\n      \"key\": \"longmood\",\n      \"prepend\": \"I feel\",\n      \"append\": \"today\",\n      \"notitle\": true,\n      \"htmlClass\": \"usermood\",\n      \"fieldHtmlClass\": \"input-xxlarge\",\n      \"placeholder\": \"incredibly and admirably great\"\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-fieldset.json",
    "content": "{\n  \"schema\": {\n    \"comment\": {\n      \"type\": \"string\",\n      \"title\": \"Comment\"\n    },\n    \"name\": {\n      \"type\": \"string\",\n      \"title\": \"Name\"\n    },\n    \"age\": {\n      \"type\": \"number\",\n      \"title\": \"Age\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"comment\",\n    \"type\": \"textarea\"\n  }, {\n    \"type\": \"fieldset\",\n    \"title\": \"Author\",\n    \"expandable\": true,\n    \"items\": [\n      \"name\",\n      \"age\"\n    ]\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-help.json",
    "content": "{\n  \"schema\": {\n    \"name\": {\n      \"title\": \"Name\",\n      \"description\": \"Nickname allowed\",\n      \"type\": \"string\"\n    },\n    \"gender\": {\n      \"title\": \"Gender\",\n      \"description\": \"Your gender\",\n      \"type\": \"string\",\n      \"enum\": [\n        \"male\",\n        \"female\",\n        \"alien\"\n      ]\n    }\n  },\n  \"form\": [\n    \"*\",\n    {\n      \"type\": \"help\",\n      \"helpvalue\": \"<strong>Click on <em>Submit</em></strong> when you're done\"\n    },\n    {\n      \"type\": \"submit\",\n      \"title\": \"Submit\"\n    }\n  ]\n}"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-hidden.json",
    "content": "{\n  \"schema\": {\n    \"apikey\": {\n      \"type\": \"string\",\n      \"title\": \"API key\",\n      \"default\": \"supercalifragilisticexpialidocious\"\n    },\n    \"text\": {\n      \"type\": \"string\",\n      \"title\": \"Search string\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"apikey\",\n    \"type\": \"hidden\"\n  },\n  \"text\",\n  {\n    \"type\": \"submit\",\n    \"title\": \"Search\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-iconselect.json",
    "content": "{\n  \"schema\": {\n    \"icon\": {\n      \"title\": \"Choose an icon\",\n      \"type\": \"string\",\n      \"enum\": [ \"glass\", \"music\", \"search\", \"envelope\", \"heart\", \"star\",\n        \"star-empty\", \"user\", \"film\", \"th-large\", \"th\", \"th-list\", \"ok\",\n        \"remove\", \"zoom-in\", \"zoom-out\", \"off\", \"signal\", \"cog\", \"trash\",\n        \"home\", \"file\", \"time\", \"road\", \"download-alt\", \"download\", \"upload\",\n        \"inbox\", \"play-circle\", \"repeat\", \"refresh\", \"list-alt\", \"lock\", \"flag\",\n        \"headphones\", \"volume-off\", \"volume-down\", \"volume-up\", \"qrcode\",\n        \"barcode\", \"tag\", \"tags\", \"book\", \"bookmark\", \"print\", \"camera\", \"font\",\n        \"bold\", \"italic\", \"text-height\", \"text-width\", \"align-left\",\n        \"align-center\", \"align-right\", \"align-justify\", \"list\", \"indent-left\",\n        \"indent-right\", \"facetime-video\", \"picture\", \"pencil\", \"map-marker\",\n        \"adjust\", \"tint\", \"edit\", \"share\", \"check\", \"move\", \"step-backward\",\n        \"fast-backward\", \"backward\", \"play\", \"pause\", \"stop\", \"forward\",\n        \"fast-forward\", \"step-forward\", \"eject\", \"chevron-left\",\n        \"chevron-right\", \"plus-sign\", \"minus-sign\", \"remove-sign\", \"ok-sign\",\n        \"question-sign\", \"info-sign\", \"screenshot\", \"remove-circle\",\n        \"ok-circle\", \"ban-circle\", \"arrow-left\", \"arrow-right\", \"arrow-up\",\n        \"arrow-down\", \"share-alt\", \"resize-full\", \"resize-small\", \"plus\",\n        \"minus\", \"asterisk\", \"exclamation-sign\", \"gift\", \"leaf\", \"fire\",\n        \"eye-open\", \"eye-close\", \"warning-sign\", \"plane\", \"calendar\", \"random\",\n        \"comment\", \"magnet\", \"chevron-up\", \"chevron-down\", \"retweet\",\n        \"shopping-cart\", \"folder-close\", \"folder-open\", \"resize-vertical\",\n        \"resize-horizontal\", \"hdd\", \"bullhorn\", \"bell\", \"certificate\",\n        \"thumbs-up\", \"thumbs-down\", \"hand-right\", \"hand-left\", \"hand-up\",\n        \"hand-down\", \"circle-arrow-right\", \"circle-arrow-left\",\n        \"circle-arrow-up\", \"circle-arrow-down\", \"globe\", \"wrench\", \"tasks\",\n        \"filter\", \"briefcase\", \"fullscreen\" ]\n    }\n  },\n  \"form\": [ {\n    \"key\": \"icon\",\n    \"type\": \"iconselect\",\n    \"imageButtonClass\": \"btn\",\n    \"imageSelectorColumns\": 10,\n    \"imageSelectorTitle\": \"Select an icon\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-imageselect.json",
    "content": "{\n  \"schema\": {\n    \"icon\": {\n      \"title\": \"Choose an icon\",\n      \"type\": \"string\",\n      \"enum\": [\n        \"address-book\", \"archive\", \"balloon\",\n        \"calendar\", \"camera\", \"cd\", \"disk\",\n        \"heart\", \"home\", \"mail\"\n      ]\n    }\n  },\n  \"form\": [ {\n    \"key\": \"icon\",\n    \"type\": \"imageselect\",\n    \"imageWidth\": 64,\n    \"imageHeight\": 64,\n    \"imageButtonClass\": \"btn-inverse\",\n    \"imagePrefix\": \"http://icons.iconarchive.com/icons/double-j-design/origami-colored-pencil/64/blue-\",\n    \"imageSuffix\": \"-icon.png\",\n    \"imageSelectorColumns\": 4,\n    \"imageSelectorTitle\": \"Random choice\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-password.json",
    "content": "{\n  \"schema\": {\n    \"pwd\": {\n      \"type\": \"string\",\n      \"title\": \"Your secret\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"pwd\",\n    \"type\": \"password\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-questions.json",
    "content": "{\n  \"schema\": {\n    \"response\": {\n      \"type\": \"string\",\n      \"title\": \"Search\"\n    }\n  },\n  \"form\": [ {\n    \"type\": \"questions\",\n    \"key\": \"response\",\n    \"title\": \"Let's check your mood\",\n    \"items\": [ {\n      \"type\": \"question\",\n      \"title\": \"Are you happy?\",\n      \"activeClass\": \"btn-success\",\n      \"optionsType\": \"radiobuttons\",\n      \"options\": [ {\n        \"title\": \"Yes\",\n        \"value\": \"happy\",\n        \"next\": \"q2\",\n        \"htmlClass\": \"btn-primary\"\n      }, {\n        \"title\": \"No\",\n        \"value\": \"sad\",\n        \"submit\": true\n      } ]\n    }, {\n      \"type\": \"question\",\n      \"qid\": \"q2\",\n      \"title\": \"Really happy?\",\n      \"options\": [ {\n        \"title\": \"Yes\",\n        \"value\": \"reallyhappy\",\n        \"submit\": true\n      }, {\n        \"title\": \"No\",\n        \"value\": \"happy\",\n        \"submit\": true\n      } ]\n    } ]\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-radiobuttons.json",
    "content": "{\n  \"schema\": {\n    \"language\": {\n      \"type\": \"string\",\n      \"title\": \"Best language\",\n      \"enum\": [ \"JavaScript\", \"Python\", \"PHP\", \"Java\", \"C++\", \"other\" ]\n    },\n    \"favourite\": {\n      \"type\": \"boolean\",\n      \"title\": \"Is it your favourite?\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"language\",\n    \"type\": \"radiobuttons\",\n    \"activeClass\": \"btn-success\"\n  }, {\n    \"key\": \"favourite\",\n    \"type\": \"radiobuttons\",\n    \"activeClass\": \"btn-danger\",\n    \"titleMap\": {\n      \"true\": \"Yes\",\n      \"false\": \"No\"\n    }\n  }, {\n    \"title\": \"Submit\",\n    \"type\": \"submit\"\n  } ],\n  \"value\": { \"language\": \"Python\" }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-radios.json",
    "content": "{\n  \"schema\": {\n    \"language\": {\n      \"type\": \"string\",\n      \"title\": \"Best language\",\n      \"enum\": [ \"JavaScript\", \"Python\", \"PHP\", \"Java\", \"C++\", \"other\" ]\n    },\n    \"fantastic\": {\n      \"type\": \"boolean\",\n      \"title\": \"Is it fantastic?\",\n      \"required\": true\n    },\n    \"cool\": {\n      \"type\": \"boolean\",\n      \"title\": \"Is it cool?\"\n    },\n    \"cool2\": {\n      \"type\": \"boolean\",\n      \"title\": \"Is it cool again?\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"language\",\n    \"type\": \"radios\"\n  }, {\n    \"key\": \"fantastic\",\n    \"type\": \"radios\",\n    \"inline\": true,\n    \"toggleNextMap\": { \"true\": true }\n  }, {\n    \"key\": \"cool\",\n    \"type\": \"radios\",\n    \"titleMap\": {\n      \"false\": \"Not at all\",\n      \"true\": \"Very cool\"\n    }\n  }, {\n    \"key\": \"cool2\",\n    \"type\": \"radios\",\n    \"options\": {\n      \"false\": \"Not at all\",\n      \"true\": \"Very cool\"\n    }\n  }, {\n    \"title\": \"Submit\",\n    \"type\": \"submit\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-range.json",
    "content": "{\n  \"schema\": {\n    \"range\": {\n      \"type\": \"integer\",\n      \"title\": \"Is JSON Form useful?\",\n      \"description\": \"The field should appear as a range that accepts values between 0 (excluded) and 200 by steps of 20 on browsers that support the \\\"range\\\" input type.<br/>Note that the \\\"step\\\" constraint is not enforced when the form is submitted.\",\n      \"default\": 40,\n      \"minimum\": 0,\n      \"exclusiveMinimum\": true,\n      \"maximum\": 200\n    }\n  },\n  \"form\": [ {\n    \"key\": \"range\",\n    \"type\": \"range\",\n    \"step\": 20\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-section.json",
    "content": "{\n  \"schema\": {\n    \"comment\": {\n      \"type\": \"string\",\n      \"title\": \"Comment\"\n    },\n    \"name\": {\n      \"type\": \"string\",\n      \"title\": \"Name\"\n    },\n    \"age\": {\n      \"type\": \"number\",\n      \"title\": \"Age\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"comment\",\n    \"type\": \"textarea\"\n  }, {\n    \"type\": \"section\",\n    \"title\": \"Author\",\n    \"items\": [\n      \"name\",\n      \"age\"\n    ]\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-select.json",
    "content": "{\n  \"schema\": {\n    \"gender\": {\n      \"type\": \"string\",\n      \"title\": \"Gender\",\n      \"enum\": [ \"male\", \"female\", \"alien\" ]\n    },\n    \"gender2\": {\n      \"type\": \"string\",\n      \"title\": \"Gender\",\n      \"enum\": [ \"male\", \"female\", \"alien\" ],\n      \"required\": true\n    },\n    \"ismale\": {\n      \"type\": \"boolean\",\n      \"title\": \"Is male?\"\n    },\n    \"isfemale\": {\n      \"type\": \"boolean\",\n      \"title\": \"Is female?\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"gender\",\n    \"titleMap\": {\n      \"male\": \"Dude\",\n      \"female\": \"Dudette\",\n      \"alien\": \"I'm from outer space!\"\n    }\n  }, {\n    \"key\": \"gender2\",\n    \"title\": \"Gender re-ordered\",\n    \"options\": {\n      \"\": \"Please select\",\n      \"alien\": \"I'm from outer space!\",\n      \"male\": \"Dude\",\n      \"female\": \"Dudette\"\n    }\n  }, {\n    \"key\": \"ismale\",\n    \"type\": \"select\",\n    \"required\": true\n  }, {\n    \"key\": \"isfemale\",\n    \"type\": \"select\",\n    \"titleMap\": {\n      \"false\": \"No\",\n      \"true\": \"Yes\"\n    }\n  }, {\n    \"title\": \"Submit\",\n    \"type\": \"submit\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-selectfieldset-key.json",
    "content": "{\n  \"schema\": {\n    \"choice\": {\n      \"type\": \"string\",\n      \"enum\": [ \"text\", \"cat\" ]\n    },\n    \"text\": {\n      \"type\": \"string\",\n      \"title\": \"Text\"\n    },\n    \"category\": {\n      \"type\": \"string\",\n      \"title\": \"Category\",\n      \"enum\": [ \"Geography\", \"Entertainment\",\n        \"History\", \"Arts\", \"Science\", \"Sports\" ]\n    }\n  },\n  \"form\": [ {\n    \"type\": \"selectfieldset\",\n    \"key\": \"choice\",\n    \"title\": \"Make a choice\",\n    \"titleMap\": {\n      \"text\": \"Search by text\",\n      \"cat\": \"Search by category\"\n    },\n    \"items\": [ \"text\", \"category\" ]\n  }, {\n    \"type\": \"submit\",\n    \"value\": \"Submit\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-selectfieldset.json",
    "content": "{\n  \"schema\": {\n    \"text\": {\n      \"type\": \"string\",\n      \"title\": \"Text\"\n    },\n    \"category\": {\n      \"type\": \"string\",\n      \"title\": \"Category\",\n      \"enum\": [ \"Geography\", \"Entertainment\",\n        \"History\", \"Arts\", \"Science\", \"Sports\" ]\n    }\n  },\n  \"form\": [ {\n    \"type\": \"selectfieldset\",\n    \"title\": \"Make a choice\",\n    \"items\": [ {\n      \"key\": \"text\",\n      \"legend\": \"Search by text\"\n    }, {\n      \"key\": \"category\",\n      \"legend\": \"Search by category\"\n    } ]\n  }, {\n    \"type\": \"submit\",\n    \"title\": \"Submit\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-submit.json",
    "content": "{\n  \"schema\": {\n  \t\"name\": {\n      \"title\": \"Name\",\n      \"description\": \"Nickname allowed\",\n      \"type\": \"string\"\n    },\n    \"gender\": {\n      \"title\": \"Gender\",\n      \"description\": \"Your gender\",\n      \"type\": \"string\",\n      \"enum\": [ \"male\", \"female\", \"alien\" ]\n    }\n  },\n  \"form\": [\n    \"*\",\n    {\n      \"type\": \"submit\",\n      \"title\": \"OK Go - This Too Shall Pass\"\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-tabarray-maxitems.json",
    "content": "{\n  \"schema\": {\n    \"friends\": {\n      \"type\": \"array\",\n      \"maxItems\": 3,\n      \"items\": {\n        \"type\": \"object\",\n        \"title\": \"Friend\",\n        \"properties\": {\n          \"nick\": {\n            \"type\": \"string\",\n            \"title\": \"Nickname\"\n          },\n          \"animals\": {\n            \"type\": \"array\",\n            \"maxItems\": 2,\n            \"items\": {\n              \"type\": \"string\",\n              \"title\": \"Animal name\"\n            }\n          }\n        }\n      }\n    }\n  },\n  \"form\": [ {\n    \"type\": \"tabarray\",\n    \"items\": {\n      \"type\": \"section\",\n      \"items\": [\n        \"friends[].nick\",\n        {\n          \"type\": \"array\",\n          \"items\": [\n            \"friends[].animals[]\"\n          ]\n        }\n      ]\n    }\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-tabarray-value.json",
    "content": "{\n  \"schema\": {\n    \"thoughts\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\",\n        \"title\": \"Thought\",\n        \"default\": \"wtf\"\n      }\n    }\n  },\n  \"form\": [ {\n    \"type\": \"tabarray\",\n    \"items\": [ {\n      \"type\": \"section\",\n      \"legend\": \"{{idx}}. {{value}}\",\n      \"items\": [ {\n        \"key\": \"thoughts[]\",\n        \"valueInLegend\": true\n      } ]\n    } ]\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-tabarray.json",
    "content": "{\n  \"schema\": {\n  \t\"friends\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"title\": \"Friend\",\n        \"properties\": {\n          \"nick\": {\n            \"type\": \"string\",\n            \"title\": \"Nickname\"\n          },\n          \"animals\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\",\n              \"title\": \"Animal name\"\n            }\n          }\n        }\n      }\n    }\n  },\n  \"form\": [ {\n    \"type\": \"tabarray\",\n    \"items\": {\n      \"type\": \"section\",\n      \"items\": [\n        \"friends[].nick\",\n        {\n          \"type\": \"array\",\n          \"items\": [\n            \"friends[].animals[]\"\n          ]\n        }\n      ]\n    }\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-fields-textarea.json",
    "content": "{\n  \"schema\": {\n    \"comment\": {\n      \"type\": \"string\",\n      \"title\": \"Your thoughts\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"comment\",\n    \"type\": \"textarea\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-gettingstarted.json",
    "content": "{\n  \"schema\": {\n    \"field\": {\n      \"type\": \"string\",\n      \"title\": \"A field\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"field\"\n  }, {\n    \"type\": \"submit\",\n    \"title\": \"Submit\"\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-previousvalues-multidimensional.json",
    "content": "{\n  \"schema\": {\n    \"reminders\": {\n      \"type\": \"array\",\n      \"title\": \"Reminders\",\n      \"items\": {\n        \"type\": \"array\",\n        \"title\": \"Task List\",\n        \"items\": {\n          \"type\": \"string\"\n        }\n      }\n    }\n  },\n  \"value\": {\n    \"reminders\": [\n      [ \"Buy clothes\", \"Pick up kids\" ],\n      [ \"Call John\", \"Send email\" ],\n      [ \"Wash car\" ]\n    ]\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-previousvalues.json",
    "content": "{\n  \"schema\": {\n    \"friends\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"title\": \"Friend\",\n        \"properties\": {\n          \"nick\": {\n            \"type\": \"string\",\n            \"title\": \"Nickname\",\n            \"required\": true\n          },\n          \"gender\": {\n            \"type\": \"string\",\n            \"title\": \"Gender\",\n            \"enum\": [ \"male\", \"female\", \"alien\" ]\n          },\n          \"age\": {\n            \"type\": \"integer\",\n            \"title\": \"Age\"\n          }\n        }\n      }\n    }\n  },\n  \"value\": {\n    \"friends\": [\n      { \"nick\": \"tidoust\", \"gender\": \"male\",   \"age\": 34 },\n      { \"nick\": \"titine\",  \"gender\": \"female\", \"age\": 6 },\n      { \"nick\": \"E.T.\",    \"gender\": \"alien\" }\n    ]\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-schema-array.json",
    "content": "{\n  \"schema\": {\n    \"friends\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"title\": \"Friend\",\n        \"properties\": {\n          \"nick\": {\n            \"type\": \"string\",\n            \"title\": \"Nickname\",\n            \"required\": true\n          },\n          \"gender\": {\n            \"type\": \"string\",\n            \"title\": \"Gender\",\n            \"enum\": [ \"male\", \"female\", \"alien\" ]\n          },\n          \"age\": {\n            \"type\": \"integer\",\n            \"title\": \"Age\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-schema-basic.json",
    "content": "{\n  \"schema\": {\n    \"name\": {\n      \"title\": \"Name\",\n      \"description\": \"Nickname allowed\",\n      \"type\": \"string\"\n    },\n    \"gender\": {\n      \"title\": \"Gender\",\n      \"description\": \"Your gender\",\n      \"type\": \"string\",\n      \"enum\": [ \"male\", \"female\", \"alien\" ]\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-schema-default.json",
    "content": "{\n  \"schema\": {\n    \"friends\": {\n      \"type\": \"array\",\n      \"default\": [ \"foo\", \"bar\" ],\n      \"readOnly\": true,\n      \"items\": {\n        \"type\": \"string\",\n        \"title\": \"Name\",\n        \"readOnly\": true\n      }\n    },\n    \"fiends\": {\n      \"type\": \"array\",\n      \"default\": [\n        { \"name\": \"bob\", \"age\": 24 },\n        { \"name\": \"alice\", \"age\": 42 }\n      ],\n      \"readOnly\": true,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"name\": {\n            \"type\": \"string\",\n            \"title\": \"named\"\n          },\n          \"age\": {\n            \"type\": \"integer\",\n            \"title\": \"Age\",\n            \"default\": 29\n          },\n          \"address\": {\n            \"title\": \"Address\",\n            \"type\": \"object\",\n            \"properties\": {\n              \"street\": { \"type\": \"string\" },\n              \"zip\": { \"type\": \"string\" },\n              \"city\": { \"type\": \"string\" }\n            },\n            \"default\": { \"zip\": \"N/A\" }\n          }\n        },\n        \"default\": { \"name\": \"ah\", \"age\": 55 }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-schema-inlineref.json",
    "content": "{\n  \"schema\": {\n    \"properties\": {\n      \"animal\": { \"$ref\": \"#/definitions/animation\" }\n    },\n    \"definitions\": {\n      \"animation\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"duration\": {\n            \"title\": \"Duration\",\n            \"type\": \"integer\"\n          },\n          \"stepper\": {\n            \"title\": \"Stepper\",\n            \"type\": \"string\"\n          },\n          \"then\": {\n            \"title\": \"Then\",\n            \"type\": \"array\",\n            \"maxItems\": 1,\n            \"items\": { \"$ref\": \"#/definitions/animation\" },\n            \"default\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-schema-morecomplex.json",
    "content": "{\n  \"schema\": {\n    \"message\": {\n      \"type\": \"string\",\n      \"title\": \"Message\"\n    },\n    \"author\": {\n      \"type\": \"object\",\n      \"title\": \"Author\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Name\"\n        },\n        \"gender\": {\n          \"type\": \"string\",\n          \"title\": \"Gender\",\n          \"enum\": [ \"male\", \"female\", \"alien\" ]\n        },\n        \"magic\": {\n          \"type\": \"integer\",\n          \"title\": \"Magic number\",\n          \"default\": 42\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-schema-required.json",
    "content": "{\n  \"schema\": {\n    \"v3customer\": {\n      \"title\": \"Customer\",\n      \"description\": \"json-schema v3 style 'required'\",\n      \"type\": \"object\",\n      \"required\": true,\n      \"properties\": {\n        \"name\": {\n          \"required\": true,\n          \"title\": \"Name\",\n          \"type\": \"string\"\n        },\n        \"address\": {\n          \"title\": \"Address\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"city\": {\n              \"required\": true,\n              \"title\": \"City\",\n              \"type\": \"string\"\n            },\n            \"street\": {\n              \"required\": true,\n              \"title\": \"Street\",\n              \"type\": \"string\"\n            },\n            \"zip\": {\n              \"title\": \"Zip\",\n              \"type\": \"string\"\n            }\n          }\n        }\n      }\n    },\n    \"v4customer\": {\n      \"title\": \"CustomerV4\",\n      \"description\": \"json-schema v4 style 'required'\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"title\": \"Name\",\n          \"type\": \"string\"\n        },\n        \"address\": {\n          \"title\": \"Address\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"city\": {\n              \"title\": \"City\",\n              \"type\": \"string\"\n            },\n            \"street\": {\n              \"title\": \"Street\",\n              \"type\": \"string\"\n            },\n            \"zip\": {\n              \"title\": \"Zip\",\n              \"type\": \"string\"\n            }\n          },\n          \"required\": [ \"street\", \"city\" ]\n        }\n      },\n      \"required\": [ \"name\" ]\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-templating-idx.json",
    "content": "{\n  \"schema\": {\n    \"thoughts\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"title\": \"A thought\",\n        \"type\": \"string\"\n      }\n    }\n  },\n  \"form\": [ {\n    \"type\": \"array\",\n    \"items\": [ {\n      \"key\": \"thoughts[]\",\n      \"title\": \"Thought number {{idx}}\"\n    } ]\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-templating-tpldata.json",
    "content": "{\n  \"schema\": {\n    \"age\": {\n      \"type\": \"integer\",\n      \"title\": \"Age\"\n    }\n  },\n  \"form\": [ {\n    \"key\": \"age\",\n    \"title\": \"{{user.name}}'s age\"\n  } ],\n  \"tpldata\": {\n    \"user\": { \"name\": \"tidoust\" }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-templating-value.json",
    "content": "{\n  \"schema\": {\n    \"thoughts\": {\n      \"type\": \"array\",\n      \"title\": \"Thoughts\",\n      \"items\": {\n        \"type\": \"string\",\n        \"title\": \"A thought\",\n        \"default\": \"Grmpf\"\n      }\n    }\n  },\n  \"form\": [ {\n    \"type\": \"tabarray\",\n    \"items\": [ {\n      \"type\": \"section\",\n      \"legend\": \"{{idx}}. {{value}}\",\n      \"items\": [ {\n        \"key\": \"thoughts[]\",\n        \"title\": \"Thought {{idx}}\",\n        \"valueInLegend\": true\n      } ]\n    } ]\n  } ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/jsf-templating-values.json",
    "content": "{\n  \"schema\": {\n    \"firstname\": { \"type\": \"string\", \"title\": \"First name\" },\n    \"lastname\":  { \"type\": \"string\", \"title\": \"Last name\" },\n    \"fullname\":  { \"type\": \"string\", \"title\": \"Full name\" }\n  },\n  \"form\": [\n    \"firstname\",\n    \"lastname\",\n    {\n      \"key\": \"fullname\",\n      \"value\": \"{{values.firstname}} {{values.lastname}}\"\n    }\n  ],\n  \"value\": {\n    \"firstname\": \"François\",\n    \"lastname\": \"Daoust\"\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/json-schema-draft01.json",
    "content": "{\n\t\"$schema\" : \"http://json-schema.org/draft-01/hyper-schema#\",\n\t\"id\" : \"http://json-schema.org/draft-01/schema#\",\n\t\"type\" : \"object\",\n  \"properties\" : {\n\t\t\"type\" : {\n\t\t\t\"type\" : [ \"string\", \"array\" ],\n\t\t\t\"items\" : { \"type\" : [ \"string\", { \"$ref\" : \"#\" } ] },\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : \"any\"\n\t\t},\n\t\t\"properties\" : {\n\t\t\t\"type\" : \"object\",\n\t\t\t\"additionalProperties\" : { \"$ref\" : \"#\" },\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : {}\n\t\t},\n\t\t\"items\" : {\n\t\t\t\"type\" : [ { \"$ref\" : \"#\" }, \"array\" ],\n\t\t\t\"items\" : { \"$ref\" : \"#\" },\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : {}\n\t\t},\n\t\t\"optional\" : { \"type\" : \"boolean\", \"optional\" : true, \"default\" : false },\n\t\t\"additionalProperties\" : {\n\t\t\t\"type\" : [ { \"$ref\" : \"#\" }, \"boolean\" ],\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : {}\n\t\t},\n\t\t\"requires\" : { \"type\" : [ \"string\", { \"$ref\" : \"#\" } ], \"optional\" : true },\n\t\t\"minimum\" : { \"type\" : \"number\", \"optional\" : true },\n\t\t\"maximum\" : { \"type\" : \"number\", \"optional\" : true },\n\t\t\"minimumCanEqual\" : {\n\t\t\t\"type\" : \"boolean\",\n\t\t\t\"optional\" : true,\n\t\t\t\"requires\" : \"minimum\",\n\t\t\t\"default\" : true\n\t\t},\n\t\t\"maximumCanEqual\" : {\n\t\t\t\"type\" : \"boolean\",\n\t\t\t\"optional\" : true,\n\t\t\t\"requires\" : \"maximum\",\n\t\t\t\"default\" : true\n\t\t},\n\t\t\"minItems\" : {\n\t\t\t\"type\" : \"integer\",\n\t\t\t\"optional\" : true,\n\t\t\t\"minimum\" : 0,\n\t\t\t\"default\" : 0\n\t\t},\n\t\t\"maxItems\" : { \"type\" : \"integer\", \"optional\" : true, \"minimum\" : 0 },\n\t\t\"pattern\" : { \"type\" : \"string\", \"optional\" : true, \"format\" : \"regex\" },\n\t\t\"minLength\" : {\n\t\t\t\"type\" : \"integer\",\n\t\t\t\"optional\" : true,\n\t\t\t\"minimum\" : 0,\n\t\t\t\"default\" : 0\n\t\t},\n\t\t\"maxLength\" : { \"type\" : \"integer\", \"optional\" : true },\n\t\t\"enum\" : { \"type\" : \"array\", \"optional\" : true, \"minItems\" : 1 },\n\t\t\"title\" : { \"type\" : \"string\", \"optional\" : true },\n\t\t\"description\" : { \"type\" : \"string\", \"optional\" : true },\n\t\t\"format\" : { \"type\" : \"string\", \"optional\" : true },\n\t\t\"contentEncoding\" : { \"type\" : \"string\", \"optional\" : true },\n\t\t\"default\" : { \"type\" : \"any\", \"optional\" : true },\n\t\t\"maxDecimal\" : { \"type\" : \"integer\", \"optional\" : true, \"minimum\" : 0 },\n\t\t\"disallow\" : {\n\t\t\t\"type\" : [ \"string\", \"array\" ],\n\t\t\t\"items\" : { \"type\" : \"string\" },\n\t\t\t\"optional\" : true\n\t\t},\n\t\t\"extends\" : {\n\t\t\t\"type\" : [ { \"$ref\" : \"#\" }, \"array\" ],\n\t\t\t\"items\" : { \"$ref\" : \"#\" },\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : {}\n\t\t}\n\t},\n  \"optional\" : true,\n\t\"default\" : {}\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/json-schema-draft02.json",
    "content": "{\n\t\"$schema\" : \"http://json-schema.org/draft-02/hyper-schema#\",\n\t\"id\" : \"http://json-schema.org/draft-02/schema#\",\n\t\"type\" : \"object\",\n\t\"properties\" : {\n\t\t\"type\" : {\n\t\t\t\"type\" : [ \"string\", \"array\" ],\n\t\t\t\"items\" : { \"type\" : [ \"string\", { \"$ref\" : \"#\" } ] },\n\t\t\t\"optional\" : true,\n\t\t\t\"uniqueItems\" : true,\n\t\t\t\"default\" : \"any\"\n\t\t},\n\t\t\"properties\" : {\n\t\t\t\"type\" : \"object\",\n\t\t\t\"additionalProperties\" : { \"$ref\" : \"#\" },\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : {}\n\t\t},\n\t\t\"items\" : {\n\t\t\t\"type\" : [ { \"$ref\" : \"#\" }, \"array\" ],\n\t\t\t\"items\" : { \"$ref\" : \"#\" },\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : {}\n\t\t},\n\t\t\"optional\" : { \"type\" : \"boolean\", \"optional\" : true, \"default\" : false },\n\t\t\"additionalProperties\" : {\n\t\t\t\"type\" : [ { \"$ref\" : \"#\" }, \"boolean\" ],\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : {}\n\t\t},\n\t\t\"requires\" : { \"type\" : [ \"string\", { \"$ref\" : \"#\" } ], \"optional\" : true },\n\t\t\"minimum\" : { \"type\" : \"number\", \"optional\" : true },\n\t\t\"maximum\" : { \"type\" : \"number\", \"optional\" : true },\n\t\t\"minimumCanEqual\" : {\n\t\t\t\"type\" : \"boolean\",\n\t\t\t\"optional\" : true,\n\t\t\t\"requires\" : \"minimum\",\n\t\t\t\"default\" : true\n\t\t},\n\t\t\"maximumCanEqual\" : {\n\t\t\t\"type\" : \"boolean\",\n\t\t\t\"optional\" : true,\n\t\t\t\"requires\" : \"maximum\",\n\t\t\t\"default\" : true\n\t\t},\n\t\t\"minItems\" : {\n\t\t\t\"type\" : \"integer\",\n\t\t\t\"optional\" : true,\n\t\t\t\"minimum\" : 0,\n\t\t\t\"default\" : 0\n\t\t},\n\t\t\"maxItems\" : { \"type\" : \"integer\", \"optional\" : true, \"minimum\" : 0 },\n\t\t\"uniqueItems\" : { \"type\" : \"boolean\", \"optional\" : true, \"default\" : false },\n\t\t\"pattern\" : { \"type\" : \"string\", \"optional\" : true, \"format\" : \"regex\" },\n\t\t\"minLength\" : {\n\t\t\t\"type\" : \"integer\",\n\t\t\t\"optional\" : true,\n\t\t\t\"minimum\" : 0,\n\t\t\t\"default\" : 0\n\t\t},\n\t\t\"maxLength\" : { \"type\" : \"integer\", \"optional\" : true },\n\t\t\"enum\" : {\n\t\t\t\"type\" : \"array\",\n\t\t\t\"optional\" : true,\n\t\t\t\"minItems\" : 1,\n\t\t\t\"uniqueItems\" : true\n\t\t},\n\t\t\"title\" : { \"type\" : \"string\", \"optional\" : true },\n\t\t\"description\" : { \"type\" : \"string\", \"optional\" : true },\n\t\t\"format\" : { \"type\" : \"string\", \"optional\" : true },\n\t\t\"contentEncoding\" : { \"type\" : \"string\", \"optional\" : true },\n\t\t\"default\" : { \"type\" : \"any\", \"optional\" : true },\n\t\t\"divisibleBy\" : {\n\t\t\t\"type\" : \"number\",\n\t\t\t\"minimum\" : 0,\n\t\t\t\"minimumCanEqual\" : false,\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : 1\n\t\t},\n\t\t\"disallow\" : {\n\t\t\t\"type\" : [ \"string\", \"array\" ],\n\t\t\t\"items\" : { \"type\" : \"string\" },\n\t\t\t\"optional\" : true,\n\t\t\t\"uniqueItems\" : true\n\t\t},\n\t\t\"extends\" : {\n\t\t\t\"type\" : [ { \"$ref\" : \"#\" }, \"array\" ],\n\t\t\t\"items\" : { \"$ref\" : \"#\" },\n\t\t\t\"optional\" : true,\n\t\t\t\"default\" : {}\n\t\t}\n\t},\n\t\"optional\" : true,\n\t\"default\" : {}\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/json-schema-draft03.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-03/schema#\",\n  \"id\": \"http://json-schema.org/draft-03/schema#\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"type\": {\n      \"type\": [ \"string\", \"array\" ],\n      \"items\": { \"type\": [ \"string\", { \"$ref\": \"#\" } ] },\n      \"uniqueItems\": true,\n      \"default\": \"any\"\n    },\n    \"properties\": {\n      \"type\": \"object\",\n      \"additionalProperties\": { \"$ref\": \"#\" },\n      \"default\": { }\n    },\n    \"patternProperties\": {\n      \"type\": \"object\",\n      \"additionalProperties\": { \"$ref\": \"#\" },\n      \"default\": { }\n    },\n    \"additionalProperties\": {\n      \"type\": [ { \"$ref\": \"#\" }, \"boolean\" ],\n      \"default\": { }\n    },\n    \"items\": {\n      \"type\": [ { \"$ref\": \"#\" }, \"array\" ],\n      \"items\": { \"$ref\": \"#\" },\n      \"default\": { }\n    },\n    \"additionalItems\": {\n      \"type\": [ { \"$ref\": \"#\" }, \"boolean\" ],\n      \"default\": { }\n    },\n    \"required\": { \"type\": \"boolean\", \"default\": false },\n    \"dependencies\": {\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"type\": [ \"string\", \"array\", { \"$ref\": \"#\" } ],\n        \"items\": { \"type\": \"string\" }\n      },\n      \"default\": { }\n    },\n    \"minimum\": { \"type\": \"number\" },\n    \"maximum\": { \"type\": \"number\" },\n    \"exclusiveMinimum\": { \"type\": \"boolean\", \"default\": false },\n    \"exclusiveMaximum\": { \"type\": \"boolean\", \"default\": false },\n    \"minItems\": { \"type\": \"integer\", \"minimum\": 0, \"default\": 0 },\n    \"maxItems\": { \"type\": \"integer\", \"minimum\": 0 },\n    \"uniqueItems\": { \"type\": \"boolean\", \"default\": false },\n    \"pattern\": { \"type\": \"string\", \"format\": \"regex\" },\n    \"minLength\": { \"type\": \"integer\", \"minimum\": 0, \"default\": 0 },\n    \"maxLength\": { \"type\": \"integer\" },\n    \"enum\": { \"type\": \"array\", \"minItems\": 1, \"uniqueItems\": true },\n    \"default\": { \"type\": \"any\" },\n    \"title\": { \"type\": \"string\" },\n    \"description\": { \"type\": \"string\" },\n    \"format\": { \"type\": \"string\" },\n    \"divisibleBy\": {\n      \"type\": \"number\",\n      \"minimum\": 0,\n      \"exclusiveMinimum\": true,\n      \"default\": 1\n    },\n    \"disallow\": {\n      \"type\": [ \"string\", \"array\" ],\n      \"items\": { \"type\": [ \"string\", { \"$ref\": \"#\" } ] },\n      \"uniqueItems\": true\n    },\n    \"extends\": {\n      \"type\": [ { \"$ref\": \"#\" }, \"array\" ],\n      \"items\": { \"$ref\": \"#\" },\n      \"default\": { }\n    },\n    \"id\": { \"type\": \"string\", \"format\": \"uri\" },\n    \"$ref\": { \"type\": \"string\", \"format\": \"uri\" },\n    \"$schema\": { \"type\": \"string\", \"format\": \"uri\" }\n  },\n  \"dependencies\": {\n    \"exclusiveMinimum\": \"minimum\",\n    \"exclusiveMaximum\": \"maximum\"\n  },\n  \"default\": { }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/json-schema-draft04.json",
    "content": "{\n  \"id\": \"http://json-schema.org/draft-04/schema#\",\n  \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n  \"description\": \"Core schema meta-schema\",\n  \"definitions\": {\n    \"schemaArray\": { \"type\": \"array\", \"minItems\": 1, \"items\": { \"$ref\": \"#\" } },\n    \"positiveInteger\": { \"type\": \"integer\", \"minimum\": 0 },\n    \"positiveIntegerDefault0\": {\n      \"allOf\": [ { \"$ref\": \"#/definitions/positiveInteger\" }, { \"default\": 0 } ]\n    },\n    \"simpleTypes\": { \"enum\": [\n      \"array\", \"boolean\", \"integer\", \"null\", \"number\", \"object\", \"string\"\n    ] },\n    \"stringArray\": {\n      \"type\": \"array\",\n      \"items\": { \"type\": \"string\" },\n      \"minItems\": 1,\n      \"uniqueItems\": true\n    }\n  },\n  \"type\": \"object\",\n  \"properties\": {\n    \"id\": { \"type\": \"string\", \"format\": \"uri\" },\n    \"$schema\": { \"type\": \"string\", \"format\": \"uri\" },\n    \"title\": { \"type\": \"string\" },\n    \"description\": { \"type\": \"string\" },\n    \"default\": { },\n    \"multipleOf\": { \"type\": \"number\", \"minimum\": 0, \"exclusiveMinimum\": true },\n    \"maximum\": { \"type\": \"number\" },\n    \"exclusiveMaximum\": { \"type\": \"boolean\", \"default\": false },\n    \"minimum\": { \"type\": \"number\" },\n    \"exclusiveMinimum\": { \"type\": \"boolean\", \"default\": false },\n    \"maxLength\": { \"$ref\": \"#/definitions/positiveInteger\" },\n    \"minLength\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" },\n    \"pattern\": { \"type\": \"string\", \"format\": \"regex\" },\n    \"additionalItems\": {\n      \"anyOf\": [ { \"type\": \"boolean\" }, { \"$ref\": \"#\" } ], \"default\": { }\n    },\n    \"items\": {\n      \"anyOf\": [ { \"$ref\": \"#\" }, { \"$ref\": \"#/definitions/schemaArray\" } ],\n      \"default\": { }\n    },\n    \"maxItems\": { \"$ref\": \"#/definitions/positiveInteger\" },\n    \"minItems\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" },\n    \"uniqueItems\": { \"type\": \"boolean\", \"default\": false },\n    \"maxProperties\": { \"$ref\": \"#/definitions/positiveInteger\" },\n    \"minProperties\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" },\n    \"required\": { \"$ref\": \"#/definitions/stringArray\" },\n    \"additionalProperties\": {\n      \"anyOf\": [ { \"type\": \"boolean\" }, { \"$ref\": \"#\" } ], \"default\": { }\n    },\n    \"definitions\": {\n      \"type\": \"object\", \"additionalProperties\": { \"$ref\": \"#\" }, \"default\": { }\n    },\n    \"properties\": {\n      \"type\": \"object\", \"additionalProperties\": { \"$ref\": \"#\" }, \"default\": { }\n    },\n    \"patternProperties\": {\n      \"type\": \"object\", \"additionalProperties\": { \"$ref\": \"#\" }, \"default\": { }\n    },\n    \"dependencies\": {\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"anyOf\": [ { \"$ref\": \"#\" }, { \"$ref\": \"#/definitions/stringArray\" } ]\n      }\n    },\n    \"enum\": { \"type\": \"array\", \"minItems\": 1, \"uniqueItems\": true },\n    \"type\": {\n      \"anyOf\": [ {\n        \"$ref\": \"#/definitions/simpleTypes\"\n      }, {\n        \"type\": \"array\",\n        \"items\": { \"$ref\": \"#/definitions/simpleTypes\" },\n        \"minItems\": 1,\n        \"uniqueItems\": true\n      } ]\n    },\n    \"allOf\": { \"$ref\": \"#/definitions/schemaArray\" },\n    \"anyOf\": { \"$ref\": \"#/definitions/schemaArray\" },\n    \"oneOf\": { \"$ref\": \"#/definitions/schemaArray\" },\n    \"not\": { \"$ref\": \"#\" }\n  },\n  \"dependencies\": {\n    \"exclusiveMaximum\": [ \"maximum\" ],\n    \"exclusiveMinimum\": [ \"minimum\" ]\n  },\n  \"default\": { }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/json-schema-draft06.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-06/schema#\",\n  \"$id\": \"http://json-schema.org/draft-06/schema#\",\n  \"title\": \"Core schema meta-schema\",\n  \"definitions\": {\n    \"schemaArray\": { \"type\": \"array\", \"minItems\": 1, \"items\": { \"$ref\": \"#\" } },\n    \"nonNegativeInteger\": { \"type\": \"integer\", \"minimum\": 0 },\n    \"nonNegativeIntegerDefault0\": { \"allOf\": [\n      { \"$ref\": \"#/definitions/nonNegativeInteger\" }, { \"default\": 0 }\n    ] },\n    \"simpleTypes\": { \"enum\": [\n      \"array\", \"boolean\", \"integer\", \"null\", \"number\", \"object\", \"string\"\n    ] },\n    \"stringArray\": {\n      \"type\": \"array\", \"items\": { \"type\": \"string\" }, \"uniqueItems\": true, \"default\": []\n    }\n  },\n  \"type\": [ \"object\", \"boolean\" ],\n  \"properties\": {\n    \"$id\": { \"type\": \"string\", \"format\": \"uri-reference\" },\n    \"$schema\": { \"type\": \"string\", \"format\": \"uri\" },\n    \"$ref\": { \"type\": \"string\", \"format\": \"uri-reference\" },\n    \"title\": { \"type\": \"string\" },\n    \"description\": { \"type\": \"string\" },\n    \"default\": {},\n    \"multipleOf\": { \"type\": \"number\", \"exclusiveMinimum\": 0 },\n    \"maximum\": { \"type\": \"number\" },\n    \"exclusiveMaximum\": { \"type\": \"number\" },\n    \"minimum\": { \"type\": \"number\" },\n    \"exclusiveMinimum\": { \"type\": \"number\" },\n    \"maxLength\": { \"$ref\": \"#/definitions/nonNegativeInteger\" },\n    \"minLength\": { \"$ref\": \"#/definitions/nonNegativeIntegerDefault0\" },\n    \"pattern\": { \"type\": \"string\", \"format\": \"regex\" },\n    \"additionalItems\": { \"$ref\": \"#\" },\n    \"items\": {\n      \"anyOf\": [ { \"$ref\": \"#\" }, { \"$ref\": \"#/definitions/schemaArray\" } ],\n      \"default\": {}\n    },\n    \"maxItems\": { \"$ref\": \"#/definitions/nonNegativeInteger\" },\n    \"minItems\": { \"$ref\": \"#/definitions/nonNegativeIntegerDefault0\" },\n    \"uniqueItems\": { \"type\": \"boolean\", \"default\": false },\n    \"contains\": { \"$ref\": \"#\" },\n    \"maxProperties\": { \"$ref\": \"#/definitions/nonNegativeInteger\" },\n    \"minProperties\": { \"$ref\": \"#/definitions/nonNegativeIntegerDefault0\" },\n    \"required\": { \"$ref\": \"#/definitions/stringArray\" },\n    \"additionalProperties\": { \"$ref\": \"#\" },\n    \"definitions\": {\n      \"type\": \"object\", \"additionalProperties\": { \"$ref\": \"#\" }, \"default\": {}\n    },\n    \"properties\": {\n      \"type\": \"object\", \"additionalProperties\": { \"$ref\": \"#\" }, \"default\": {}\n    },\n    \"patternProperties\": {\n      \"type\": \"object\", \"additionalProperties\": { \"$ref\": \"#\" }, \"default\": {}\n    },\n    \"dependencies\": { \"type\": \"object\", \"additionalProperties\": {\n      \"anyOf\": [ { \"$ref\": \"#\" }, { \"$ref\": \"#/definitions/stringArray\" } ]\n    } },\n    \"propertyNames\": { \"$ref\": \"#\" },\n    \"const\": {},\n    \"enum\": { \"type\": \"array\", \"minItems\": 1, \"uniqueItems\": true },\n    \"type\": { \"anyOf\": [\n      { \"$ref\": \"#/definitions/simpleTypes\" },\n      { \"type\": \"array\",\n        \"items\": { \"$ref\": \"#/definitions/simpleTypes\" },\n        \"minItems\": 1,\n        \"uniqueItems\": true\n      }\n    ] },\n    \"format\": { \"type\": \"string\" },\n    \"allOf\": { \"$ref\": \"#/definitions/schemaArray\" },\n    \"anyOf\": { \"$ref\": \"#/definitions/schemaArray\" },\n    \"oneOf\": { \"$ref\": \"#/definitions/schemaArray\" },\n    \"not\": { \"$ref\": \"#\" }\n  },\n  \"default\": {}\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/ng-jsf-data-only.json",
    "content": "{\n  \"data\": {\n    \"first_name\": \"Jane\",\n    \"last_name\": \"Doe\",\n    \"age\": 25,\n    \"is_company\": false,\n    \"address\": {\n      \"street_1\": \"123 Main St.\",\n      \"street_2\": null,\n      \"city\": \"Las Vegas\",\n      \"state\": \"NV\",\n      \"zip_code\": \"89123\"\n    },\n    \"phone_numbers\": [\n      { \"number\": \"702-123-4567\", \"type\": \"cell\" },\n      { \"number\": \"702-987-6543\", \"type\": \"work\" }\n    ],\n    \"notes\": \"\"\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/ng-jsf-deep-ref.json",
    "content": "{\n  \"schema\": {\n    \"definitions\": {\n      \"int\": {\n        \"type\": \"number\",\n        \"minimum\": 0,\n        \"maximum\": 10\n      },\n      \"string\": {\n        \"type\": \"string\",\n        \"minLength\": 0\n      },\n      \"valueItem\": {\n        \"type\": \"object\",\n        \"properties\": { \"value\": { \"$ref\": \"#/definitions/int\" } }\n      },\n      \"valueItemArray\": {\n        \"type\": \"array\",\n        \"items\": { \"$ref\": \"#/definitions/valueItemArray\" }\n      },\n      \"dtoArray\": {\n        \"type\": \"array\",\n        \"items\": { \"$ref\": \"#/definitions/staffLanguageLevelDto\" }\n      },\n      \"staffLanguageLevelDto\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"id\": { \"$ref\": \"#/definitions/int\" },\n          \"staffId\": { \"allOf\" : [\n            { \"$ref\": \"#/definitions/int\" },\n            { \"maximum\": 5,\n              \"title\": \"staffId (overriden maximum)\" }\n          ] },\n          \"languageId\": { \"allOf\" : [\n            { \"$ref\": \"#/definitions/valueItem\" },\n            { \"title\": \"languageId (object with custom title)\" }\n          ] },\n          \"languageLevelId\": { \"$ref\": \"#/definitions/int\" },\n          \"languageName2\": { \"allOf\" : [\n            { \"$ref\": \"#/definitions/string\" },\n            { \"default\": \"ole\",\n              \"maxLength\": 3,\n              \"title\": \"languageName2 (custom default & maxLength)\" }\n          ] },\n          \"languageLevelName\": { \"$ref\": \"#/definitions/dtoArray\" }\n        }\n      }\n    },\n    \"properties\": {\n      \"staffLanguageLevelDto\": { \"$ref\": \"#/definitions/staffLanguageLevelDto\" }\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/ng-jsf-flex-layout.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"first_name\": { \"type\": \"string\" },\n      \"last_name\": { \"type\": \"string\" },\n      \"address\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"street_1\": { \"type\": \"string\" },\n          \"street_2\": { \"type\": \"string\" },\n          \"city\": { \"type\": \"string\" },\n          \"state\": {\n            \"type\": \"string\",\n            \"enum\": [ \"AL\", \"AK\", \"AS\", \"AZ\", \"AR\", \"CA\", \"CO\", \"CT\", \"DE\",\n                \"DC\", \"FM\", \"FL\", \"GA\", \"GU\", \"HI\", \"ID\", \"IL\", \"IN\", \"IA\",\n                \"KS\", \"KY\", \"LA\", \"ME\", \"MH\", \"MD\", \"MA\", \"MI\", \"MN\", \"MS\",\n                \"MO\", \"MT\", \"NE\", \"NV\", \"NH\", \"NJ\", \"NM\", \"NY\", \"NC\", \"ND\",\n                \"MP\", \"OH\", \"OK\", \"OR\", \"PW\", \"PA\", \"PR\", \"RI\", \"SC\", \"SD\",\n                \"TN\", \"TX\", \"UT\", \"VT\", \"VI\", \"VA\", \"WA\", \"WV\", \"WI\", \"WY\" ]\n          },\n          \"zip_code\": { \"type\": \"string\" }\n        }\n      },\n      \"birthday\": { \"type\": \"string\" },\n      \"notes\": { \"type\": \"string\" },\n      \"phone_numbers\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": { \"type\": \"string\", \"enum\": [ \"cell\", \"home\", \"work\" ] },\n            \"number\": { \"type\": \"string\" }\n          },\n          \"required\": [ \"type\", \"number\" ]\n        }\n      }\n    },\n    \"required\": [ \"last_name\" ]\n  },\n  \"layout\": [\n    { \"type\": \"flex\", \"flex-flow\": \"row wrap\", \"items\": [ \"first_name\", \"last_name\" ] },\n    { \"key\": \"address.street_1\", \"title\": \"Address\", \"placeholder\": \"Street\" },\n    { \"key\": \"address.street_2\", \"notitle\": true },\n    { \"type\": \"div\",\n      \"display\": \"flex\",\n      \"flex-direction\": \"row\",\n      \"items\": [\n        { \"key\": \"address.city\", \"flex\": \"3 3 150px\",\n          \"notitle\": true, \"placeholder\": \"City\"\n        },\n        { \"key\": \"address.state\", \"flex\": \"1 1 50px\",\n          \"notitle\": true, \"placeholder\": \"State\"\n        },\n        { \"key\": \"address.zip_code\", \"flex\": \"2 2 100px\",\n          \"notitle\": true, \"placeholder\": \"Zip Code\"\n        }\n      ]\n    },\n    { \"key\": \"birthday\", \"type\": \"date\" },\n    { \"key\": \"phone_numbers\",\n      \"type\": \"array\",\n      \"listItems\": 3,\n      \"items\": [ {\n        \"type\": \"div\",\n        \"displayFlex\": true,\n        \"flex-direction\": \"row\",\n        \"items\": [\n          { \"key\": \"phone_numbers[].type\", \"flex\": \"1 1 50px\",\n            \"notitle\": true, \"placeholder\": \"Type\"\n          },\n          { \"key\": \"phone_numbers[].number\", \"flex\": \"4 4 200px\",\n            \"notitle\": true, \"placeholder\": \"Phone Number\"\n          }\n        ]\n      } ]\n    },\n    { \"type\": \"section\",\n      \"title\": \"Notes\",\n      \"expandable\": true,\n      \"expanded\": false,\n      \"items\": [ { \"key\": \"notes\", \"type\": \"textarea\", \"notitle\": true } ]\n    }\n  ],\n  \"data\": {\n    \"first_name\": \"Jane\",\n    \"last_name\": \"Doe\",\n    \"address\": {\n      \"street_1\": \"123 Main St.\",\n      \"city\": \"Las Vegas\",\n      \"state\": \"NV\",\n      \"zip_code\": \"89123\"\n    },\n    \"birthday\": \"1999-09-21\",\n    \"phone_numbers\": [\n      { \"type\": \"cell\", \"number\": \"702-123-4567\" },\n      { \"type\": \"work\", \"number\": \"702-987-6543\" }\n    ],\n    \"notes\": \"(This is an example of an uninteresting note.)\"\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/ng-jsf-layout-only.json",
    "content": "{\n  \"layout\": [\n    { \"type\": \"section\", \"title\": \"Name\", \"required\": true,\n      \"items\": [\n        \"first_name\",\n        \"last_name\"\n      ]\n    },\n    { \"key\": \"age\", \"type\": \"integer\" },\n    { \"key\": \"is_company\", \"title\": \"Is this a company?\", \"type\": \"checkbox\" },\n    { \"key\": \"address.street_1\", \"title\": \"Address\" },\n    { \"key\": \"address.street_2\", \"notitle\": true },\n    \"address.city\",\n    \"address.state\",\n    \"address.zip_code\",\n    { \"key\": \"phone_numbers\", \"type\": \"array\",\n      \"items\": [\n        \"phone_numbers[].number\",\n        \"phone_numbers[].type\"\n      ]\n    },\n    { \"key\": \"notes\", \"type\": \"textarea\" },\n    { \"type\": \"submit\" }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/ng-jsf-nested-arrays.json",
    "content": "{\n  \"schema\": {\n    \"definitions\": {\n      \"tiers_obj\": {\n        \"type\": \"object\",\n        \"title\": \"Tier\",\n        \"properties\": {\n          \"min_amount\": { \"type\": \"integer\" },\n          \"max_amount\": { \"type\": \"integer\" },\n          \"fees\": {\n            \"title\": \"Fees (optional—max 2)\",\n            \"type\": \"array\",\n            \"maxItems\": 2,\n            \"items\": { \"$ref\": \"#/definitions/fees_obj\" }\n          }\n        }\n      },\n      \"fees_obj\": {\n        \"type\": \"object\",\n        \"title\": \"Fee\",\n        \"properties\": {\n          \"ongoing_fee\": { \"type\": \"integer\", \"title\": \"Ongoing\" },\n          \"application_fee\": { \"type\": \"integer\", \"title\": \"Application\" }\n        }\n      }\n    },\n    \"type\": \"object\",\n    \"properties\": {\n      \"tiers\": {\n        \"title\": \"Tiers (required—max 3)\",\n        \"type\": \"array\",\n        \"maxItems\": 3,\n        \"items\": { \"$ref\": \"#/definitions/tiers_obj\", \"extendRefs\": true }\n      }\n    },\n    \"required\": [ \"tiers\" ]\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/ng-jsf-select-list-examples.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"Who's your favorite captain?\",\n    \"description\": \"Demonstrates different ways to create select controls from a list of values.\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"captain1\": {\n        \"title\": \"Favorite captain 1\",\n        \"description\": \"enum array in schema (JSON Schema v1+)\",\n        \"type\": \"string\",\n        \"enum\": [ \"William Shatner\",\n                  \"Patrick Stewart\",\n                  \"John Barrowman\",\n                  \"Nathan Fillion\" ]\n      },\n      \"captain2\": {\n        \"title\": \"Favorite captain 2\",\n        \"description\": \"oneOf array with enum items in schema (JSON Schema v4+)\",\n        \"type\": \"string\",\n        \"oneOf\":  [\n          { \"title\": \"James T. Kirk\",    \"enum\": [ \"William Shatner\" ] },\n          { \"title\": \"Jean-Luc Picard\",  \"enum\": [ \"Patrick Stewart\" ] },\n          { \"title\": \"Jack Harkness\",    \"enum\": [ \"John Barrowman\"  ] },\n          { \"title\": \"Malcolm Reynolds\", \"enum\": [ \"Nathan Fillion\"  ] }\n        ]\n      },\n      \"captain3\": {\n        \"title\": \"Favorite captain 3\",\n        \"description\": \"oneOf array with const items in schema (JSON Schema v6+)\",\n        \"type\": \"string\",\n        \"oneOf\":  [\n          { \"title\": \"James T. Kirk\",    \"const\": \"William Shatner\" },\n          { \"title\": \"Jean-Luc Picard\",  \"const\": \"Patrick Stewart\" },\n          { \"title\": \"Jack Harkness\",    \"const\": \"John Barrowman\"  },\n          { \"title\": \"Malcolm Reynolds\", \"const\": \"Nathan Fillion\"  }\n        ]\n      },\n      \"captain4\": {\n        \"title\": \"Favorite captain 4\",\n        \"description\": \"titleMap array in layout (<a href=\\\"http://schemaform.io/examples/bootstrap-example.html#/65b7b86938bddeb30b149d47bd595f56\\\">Angular Schema Form</a>)\",\n        \"type\": \"string\"\n      },\n      \"captain5\": {\n        \"title\": \"Favorite captain 5\",\n        \"description\": \"enum + enumNames in schema (<a href=\\\"https://mozilla-services.github.io/react-jsonschema-form/\\\">React jsonschema form</a>)\",\n        \"type\": \"string\",\n        \"enum\":       [ \"William Shatner\",\n                        \"Patrick Stewart\",\n                        \"John Barrowman\",\n                        \"Nathan Fillion\" ],\n        \"enumNames\":  [ \"James T. Kirk\",\n                        \"Jean-Luc Picard\",\n                        \"Jack Harkness\",\n                        \"Malcolm Reynolds\" ]\n      },\n      \"captain6\": {\n        \"title\": \"Favorite captain 6\",\n        \"description\": \"titleMap object in layout (<a href=\\\"http://ulion.github.io/jsonform/playground/?example=fields-select\\\">JSON Form</a>)\",\n        \"type\": \"string\"\n      },\n      \"captain7\": {\n        \"title\": \"Favorite captain 7\",\n        \"description\": \"flat titleMap array with groups in layout\",\n        \"type\": \"string\"\n      },\n      \"captain8\": {\n        \"title\": \"Favorite captain 8\",\n        \"description\": \"hierarchical titleMap array in layout\",\n        \"type\": \"string\"\n      },\n      \"captain9\": {\n        \"title\": \"Favorite captain 9\",\n        \"description\": \"oneOf array with <strong>title: \\\"group: name\\\"</strong> in schema\",\n        \"type\": \"string\",\n        \"oneOf\": [\n          { \"title\": \"Star Trek: James T. Kirk\",   \"const\": \"William Shatner\" },\n          { \"title\": \"Star Trek: Jean-Luc Picard\", \"const\": \"Patrick Stewart\" },\n          { \"title\": \"Torchwood: Jack Harkness\",   \"const\": \"John Barrowman\"  },\n          { \"title\": \"Firefly: Malcolm Reynolds\",  \"const\": \"Nathan Fillion\"  }\n        ]\n      }\n    }\n  },\n  \"layout\": [\n    { \"widget\": \"message\", \"message\": \"<h3>Values only</h3>\" },\n    \"captain1\",\n    { \"widget\": \"message\", \"message\": \"<h3>Names &amp; Values</h3>\" },\n    \"captain2\",\n    \"captain3\",\n    { \"key\": \"captain4\",\n      \"titleMap\": [\n        { \"name\": \"James T. Kirk\",    \"value\": \"William Shatner\" },\n        { \"name\": \"Jean-Luc Picard\",  \"value\": \"Patrick Stewart\" },\n        { \"name\": \"Jack Harkness\",    \"value\": \"John Barrowman\"  },\n        { \"name\": \"Malcolm Reynolds\", \"value\": \"Nathan Fillion\"  }\n      ]\n    },\n    \"captain5\",\n    { \"key\": \"captain6\",\n      \"titleMap\": {\n        \"William Shatner\": \"James T. Kirk\",\n        \"Patrick Stewart\": \"Jean-Luc Picard\",\n        \"John Barrowman\" : \"Jack Harkness\",\n        \"Nathan Fillion\" : \"Malcolm Reynolds\"\n      }\n    },\n    { \"widget\": \"message\", \"message\": \"<h3>Groups, Names, &amp; Values</h3>\" },\n    { \"key\": \"captain7\",\n      \"titleMap\": [\n        { \"group\": \"Star Trek\", \"name\": \"James T. Kirk\",    \"value\": \"William Shatner\" },\n        { \"group\": \"Star Trek\", \"name\": \"Jean-Luc Picard\",  \"value\": \"Patrick Stewart\" },\n        { \"group\": \"Torchwood\", \"name\": \"Jack Harkness\",    \"value\": \"John Barrowman\"  },\n        { \"group\": \"Firefly\",   \"name\": \"Malcolm Reynolds\", \"value\": \"Nathan Fillion\"  }\n      ]\n    },\n    { \"key\": \"captain8\",\n      \"titleMap\": [\n        { \"group\": \"Star Trek\",\n          \"items\": [ { \"name\": \"James T. Kirk\",    \"value\": \"William Shatner\" },\n                     { \"name\": \"Jean-Luc Picard\",  \"value\": \"Patrick Stewart\" } ]\n        },\n        { \"group\": \"Torchwood\",\n          \"items\": [ { \"name\": \"Jack Harkness\",    \"value\": \"John Barrowman\"  } ]\n        },\n        { \"group\": \"Firefly\",\n          \"items\": [ { \"name\": \"Malcolm Reynolds\", \"value\": \"Nathan Fillion\"  } ]\n        }\n      ]\n    },\n    \"captain9\"\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/ng-jsf-select-widget-examples.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"Select your favorite captain\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"select1\": {\n        \"title\": \"String + enum or titleMap defaults to select\",\n        \"type\": \"string\",\n        \"enum\": [ \"a\", \"b\", \"c\" ]\n      },\n      \"select2\": {\n        \"title\": \"String + titleMap defaults to select\",\n        \"type\": \"string\",\n        \"enum\": [ \"a\", \"b\", \"c\" ]\n      },\n      \"select3\": {\n        \"title\": \"String + enum + titleMap array\",\n        \"type\": \"string\"\n      },\n      \"select4\": {\n        \"title\": \"\",\n        \"type\": \"string\"\n      },\n      \"select5\": {\n        \"title\": \"\",\n        \"type\": \"string\"\n      },\n      \"select6\": {\n        \"title\": \"\",\n        \"type\": \"string\"\n      },\n      \"array1\": {\n        \"title\": \"Array + enum or titleMap defaults to checkboxes\",\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"string\",\n          \"enum\": [ \"a\", \"b\", \"c\" ]\n        }\n      },\n      \"array2\": {\n        \"title\": \"Array + enum + maxItems = 1\",\n        \"type\": \"array\",\n        \"default\": [ \"b\", \"c\" ],\n        \"items\": {\n          \"type\": \"string\",\n          \"enum\": [ \"a\", \"b\", \"c\" ]\n        }\n      },\n      \"radios1\": {\n        \"title\": \"widget = inline-radios\",\n        \"type\": \"string\",\n        \"enum\": [ \"a\", \"b\", \"c\" ]\n      },\n      \"radios2\": {\n        \"title\": \"widget = radios\",\n        \"type\": \"string\",\n        \"enum\": [ \"a\", \"b\", \"c\" ]\n      },\n      \"radiobuttons1\": {\n        \"title\": \"widget = radiobuttons\",\n        \"type\": \"boolean\",\n        \"default\": false\n      },\n      \"radiobuttons2\": {\n        \"title\": \"widget = radiobuttons, vertical = true\",\n        \"type\": \"boolean\",\n        \"default\": false\n      }\n    }\n  },\n  \"layout\": [\n    \"select1\",\n    { \"key\": \"select2\",\n      \"titleMap\": {\n        \"William Shatner\": \"James T. Kirk\",\n        \"Patrick Stewart\": \"Jean-Luc Picard\",\n        \"John Barrowman\": \"Jack Harkness\",\n        \"Nathan Fillion\": \"Malcolm Reynolds\"\n      }\n    },\n    { \"key\": \"select3\",\n      \"titleMap\": [\n        { \"name\": \"James T. Kirk\",    \"value\": \"William Shatner\" },\n        { \"name\": \"Jean-Luc Picard\",  \"value\": \"Patrick Stewart\" },\n        { \"name\": \"Jack Harkness\",    \"value\": \"John Barrowman\"  },\n        { \"name\": \"Malcolm Reynolds\", \"value\": \"Nathan Fillion\"  }\n      ]\n    },\n    \"select4\",\n    \"select5\",\n    \"select6\",\n    \"array1\",\n    { \"key\": \"array2\",\n      \"titleMap\": [\n        { \"name\": \"James T. Kirk\",    \"value\": \"William Shatner\" },\n        { \"name\": \"Jean-Luc Picard\",  \"value\": \"Patrick Stewart\" },\n        { \"name\": \"Jack Harkness\",    \"value\": \"John Barrowman\"  },\n        { \"name\": \"Malcolm Reynolds\", \"value\": \"Nathan Fillion\"  }\n      ]\n    },\n    { \"key\": \"radios1\",\n      \"widget\": \"radios\",\n      \"titleMap\": [\n        { \"name\": \"James T. Kirk\",    \"value\": \"William Shatner\" },\n        { \"name\": \"Jean-Luc Picard\",  \"value\": \"Patrick Stewart\" },\n        { \"name\": \"Jack Harkness\",    \"value\": \"John Barrowman\"  },\n        { \"name\": \"Malcolm Reynolds\", \"value\": \"Nathan Fillion\"  }\n      ]\n    },\n    { \"key\": \"radios2\",\n      \"widget\": \"inline-radios\",\n      \"titleMap\": [\n        { \"name\": \"James T. Kirk\",    \"value\": \"William Shatner\" },\n        { \"name\": \"Jean-Luc Picard\",  \"value\": \"Patrick Stewart\" },\n        { \"name\": \"Jack Harkness\",    \"value\": \"John Barrowman\"  },\n        { \"name\": \"Malcolm Reynolds\", \"value\": \"Nathan Fillion\"  }\n      ]\n    },\n    { \"key\": \"radiobuttons1\",\n      \"widget\": \"radiobuttons\",\n      \"titleMap\": [\n        { \"name\": \"James T. Kirk\",    \"value\": \"William Shatner\" },\n        { \"name\": \"Jean-Luc Picard\",  \"value\": \"Patrick Stewart\" },\n        { \"name\": \"Jack Harkness\",    \"value\": \"John Barrowman\"  },\n        { \"name\": \"Malcolm Reynolds\", \"value\": \"Nathan Fillion\"  }\n      ]\n    },\n    { \"key\": \"radiobuttons2\",\n      \"widget\": \"radiobuttons\",\n      \"vertical\": true,\n      \"titleMap\": [\n        { \"name\": \"James T. Kirk\",    \"value\": \"William Shatner\" },\n        { \"name\": \"Jean-Luc Picard\",  \"value\": \"Patrick Stewart\" },\n        { \"name\": \"Jack Harkness\",    \"value\": \"John Barrowman\"  },\n        { \"name\": \"Malcolm Reynolds\", \"value\": \"Nathan Fillion\"  }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/ng-jsf-simple-array.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"items\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"string\",\n          \"title\": \"Item\",\n          \"default\": \"New Item\"\n        }\n      }\n    }\n  },\n  \"data\": {\n    \"items\": [ \"Item 1\", \"Item 2\", \"Item 3\", \"Item 4\" ]\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-alternatives.json",
    "content": "{\n  \"schema\": {\n    \"definitions\": {\n      \"Color\": {\n        \"title\": \"Color\",\n        \"type\": \"string\",\n        \"anyOf\": [\n          { \"type\": \"string\", \"enum\": [ \"#ff0000\" ], \"title\": \"Red\" },\n          { \"type\": \"string\", \"enum\": [ \"#00ff00\" ], \"title\": \"Green\" },\n          { \"type\": \"string\", \"enum\": [ \"#0000ff\" ], \"title\": \"Blue\" }\n        ]\n      }\n    },\n    \"title\": \"Image editor\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"currentColor\": { \"$ref\": \"#/definitions/Color\", \"title\": \"Brush color\" },\n      \"colorMask\": {\n        \"title\": \"Color mask\",\n        \"type\": \"array\",\n        \"items\": { \"$ref\": \"#/definitions/Color\" },\n        \"uniqueItems\": true\n      },\n      \"colorPalette\": {\n        \"title\": \"Color palette\",\n        \"type\": \"array\",\n        \"items\": { \"$ref\": \"#/definitions/Color\" }\n      },\n      \"blendMode\": {\n        \"title\": \"Blend mode\",\n        \"type\": \"string\",\n        \"enum\": [ \"screen\", \"multiply\", \"overlay\" ],\n        \"enumNames\": [ \"Screen\", \"Multiply\", \"Overlay\" ]\n      }\n    },\n    \"required\": [ \"currentColor\", \"colorMask\", \"blendMode\" ]\n  },\n  \"uiSchema\": {},\n  \"formData\": {\n    \"currentColor\": \"#00ff00\",\n    \"colorMask\": [ \"#0000ff\" ],\n    \"colorPalette\": [ \"#ff0000\" ],\n    \"blendMode\": \"screen\"\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-arrays.json",
    "content": "{\n  \"schema\": {\n    \"definitions\": {\n      \"Thing\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"name\": { \"type\": \"string\", \"default\": \"Default name\" }\n        }\n      }\n    },\n    \"type\": \"object\",\n    \"properties\": {\n      \"listOfStrings\": {\n        \"type\": \"array\",\n        \"title\": \"A list of strings\",\n        \"items\": { \"type\": \"string\", \"default\": \"bazinga\" }\n      },\n      \"multipleChoicesList\": {\n        \"type\": \"array\",\n        \"title\": \"A multiple choices list\",\n        \"items\": { \"type\": \"string\", \"enum\": [ \"foo\", \"bar\", \"fuzz\", \"qux\" ] },\n        \"uniqueItems\": true\n      },\n      \"fixedItemsList\": {\n        \"type\": \"array\",\n        \"title\": \"A list of fixed items\",\n        \"items\": [\n          { \"title\": \"A string value\", \"type\": \"string\", \"default\": \"lorem ipsum\" },\n          { \"title\": \"a boolean value\", \"type\": \"boolean\" }\n        ],\n        \"additionalItems\": { \"title\": \"Additional item\", \"type\": \"number\" }\n      },\n      \"minItemsList\": {\n        \"type\": \"array\",\n        \"title\": \"A list with a minimal number of items\",\n        \"minItems\": 3,\n        \"items\": { \"$ref\": \"#/definitions/Thing\" }\n      },\n      \"defaultsAndMinItems\": {\n        \"type\": \"array\",\n        \"title\": \"List and item level defaults\",\n        \"minItems\": 5,\n        \"default\": [ \"carp\", \"trout\", \"bream\" ],\n        \"items\": { \"type\": \"string\", \"default\": \"unidentified\" }\n      },\n      \"nestedList\": {\n        \"type\": \"array\",\n        \"title\": \"Nested list\",\n        \"items\": {\n          \"type\": \"array\",\n          \"title\": \"Inner list\",\n          \"items\": { \"type\": \"string\", \"default\": \"lorem ipsum\" }\n        }\n      },\n      \"unorderable\": {\n        \"title\": \"Unorderable items\",\n        \"type\": \"array\",\n        \"items\": { \"type\": \"string\", \"default\": \"lorem ipsum\" }\n      },\n      \"unremovable\": {\n        \"title\": \"Unremovable items\",\n        \"type\": \"array\",\n        \"items\": { \"type\": \"string\", \"default\": \"lorem ipsum\" }\n      },\n      \"noToolbar\": {\n        \"title\": \"No add, remove and order buttons\",\n        \"type\": \"array\",\n        \"items\": { \"type\": \"string\", \"default\": \"lorem ipsum\" }\n      },\n      \"fixedNoToolbar\": {\n        \"title\": \"Fixed array without buttons\",\n        \"type\": \"array\",\n        \"items\": [\n          { \"title\": \"A number\",  \"type\": \"number\", \"default\": 42 },\n          { \"title\": \"A boolean\", \"type\": \"boolean\", \"default\": false }\n        ],\n        \"additionalItems\":\n          { \"title\": \"A string\",  \"type\": \"string\", \"default\": \"lorem ipsum\" }\n      }\n    }\n  },\n  \"uiSchema\": {\n    \"listOfStrings\": { \"items\": { \"ui:emptyValue\": \"\" } },\n    \"multipleChoicesList\": { \"ui:widget\": \"checkboxes\" },\n    \"fixedItemsList\": {\n      \"items\": [ { \"ui:widget\": \"textarea\" }, { \"ui:widget\": \"select\" } ],\n      \"additionalItems\": { \"ui:widget\": \"updown\" }\n    },\n    \"unorderable\": { \"ui:options\": {\n      \"orderable\": false\n    } },\n    \"unremovable\": { \"ui:options\": {\n      \"removable\": false\n    } },\n    \"noToolbar\": { \"ui:options\": {\n      \"addable\": false, \"orderable\": false, \"removable\": false\n    } },\n    \"fixedNoToolbar\": { \"ui:options\": {\n      \"addable\": false, \"orderable\": false, \"removable\": false\n    } }\n  },\n  \"formData\": {\n    \"listOfStrings\": [ \"foo\", \"bar\" ],\n    \"multipleChoicesList\": [ \"foo\", \"bar\" ],\n    \"fixedItemsList\": [ \"Some text\", true, 123 ],\n    \"nestedList\": [ [ \"lorem\", \"ipsum\" ], [ \"dolor\" ] ],\n    \"unorderable\": [ \"one\", \"two\" ],\n    \"unremovable\": [ \"one\", \"two\" ],\n    \"noToolbar\": [ \"one\", \"two\" ],\n    \"fixedNoToolbar\": [ 42, true, \"additional item one\", \"additional item two\" ]\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-custom-array.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"Custom array of strings\",\n    \"type\": \"array\",\n    \"items\": {\n      \"type\": \"string\"\n    }\n  },\n  \"formData\": [ \"react\", \"jsonschema\", \"form\" ],\n  \"ArrayFieldTemplate\": function (props) {\n    return (\n      <div className={props.className}>\n        {props.items &&\n          props.items.map(element => (\n            <div key={element.index}>\n              <div>{element.children}</div>\n              {element.hasMoveDown && (\n                <button\n                  onClick={element.onReorderClick(\n                    element.index,\n                    element.index + 1\n                  )}>\n                  Down\n                </button>\n              )}\n              {element.hasMoveUp && (\n                <button\n                  onClick={element.onReorderClick(\n                    element.index,\n                    element.index - 1\n                  )}>\n                  Up\n                </button>\n              )}\n              <button onClick={element.onDropIndexClick(element.index)}>\n                Delete\n              </button>\n              <hr />\n            </div>\n          ))}\n\n        {props.canAdd && (\n          <div className=\"row\">\n            <p className=\"col-xs-3 col-xs-offset-9 array-item-add text-right\">\n              <button onClick={props.onAddClick} type=\"button\">\n                Custom +\n              </button>\n            </p>\n          </div>\n        )}\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-custom.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"A localisation form\",\n    \"type\": \"object\",\n    \"required\": [ \"lat\", \"lon\" ],\n    \"properties\": {\n      \"lat\": { \"type\": \"number\" },\n      \"lon\": { \"type\": \"number\" }\n    }\n  },\n  \"uiSchema\": { \"ui:field\": \"geo\" },\n  \"formData\": { \"lat\": 0, \"lon\": 0 }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-date-and-time.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"Date and time widgets\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"native\": {\n        \"title\": \"Native\",\n        \"description\": \"May not work on some browsers, notably Firefox Desktop and IE.\",\n        \"type\": \"object\",\n        \"properties\": {\n          \"datetime\": { \"type\": \"string\", \"format\": \"date-time\" },\n          \"date\": { \"type\": \"string\", \"format\": \"date\" }\n        }\n      },\n      \"alternative\": {\n        \"title\": \"Alternative\",\n        \"description\": \"These work on most platforms.\",\n        \"type\": \"object\",\n        \"properties\": {\n          \"alt-datetime\": { \"type\": \"string\", \"format\": \"date-time\" },\n          \"alt-date\": { \"type\": \"string\", \"format\": \"date\" }\n        }\n      }\n    }\n  },\n  \"uiSchema\": {\n    \"alternative\": {\n      \"alt-datetime\": { \"ui:widget\": \"alt-datetime\" },\n      \"alt-date\": { \"ui:widget\": \"alt-date\" }\n    }\n  },\n  \"formData\": {}\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-errors.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"Contextualized errors\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"firstName\": {\n        \"type\": \"string\",\n        \"title\": \"First name\",\n        \"minLength\": 8,\n        \"pattern\": \"\\\\d+\"\n      },\n      \"active\": {\n        \"type\": \"boolean\",\n        \"title\": \"Active\"\n      },\n      \"skills\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"string\",\n          \"minLength\": 5\n        }\n      },\n      \"multipleChoicesList\": {\n        \"type\": \"array\",\n        \"title\": \"Pick max two items\",\n        \"uniqueItems\": true,\n        \"maxItems\": 2,\n        \"items\": {\n          \"type\": \"string\",\n          \"enum\": [ \"foo\", \"bar\", \"fuzz\" ]\n        }\n      }\n    }\n  },\n  \"uiSchema\": {},\n  \"formData\": {\n    \"firstName\": \"Chuck\",\n    \"active\": \"wrong\",\n    \"skills\": [ \"karate\", \"budo\", \"aikido\" ],\n    \"multipleChoicesList\": [ \"foo\", \"bar\", \"fuzz\" ]\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-files.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"Files\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"file\": {\n        \"type\": \"string\",\n        \"format\": \"data-url\",\n        \"title\": \"Single file\"\n      },\n      \"files\": {\n        \"type\": \"array\",\n        \"title\": \"Multiple files\",\n        \"items\": {\n          \"type\": \"string\",\n          \"format\": \"data-url\"\n        }\n      }\n    }\n  },\n  \"uiSchema\": { },\n  \"formData\": { }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-large.json",
    "content": "{\n  \"schema\": {\n    \"definitions\": {\n      \"largeEnum\": {\n        \"type\": \"string\",\n        \"enum\": [\n          \"option #0\",  \"option #1\",  \"option #2\",  \"option #3\",  \"option #4\",\n          \"option #5\",  \"option #6\",  \"option #7\",  \"option #8\",  \"option #9\",\n          \"option #10\", \"option #11\", \"option #12\", \"option #13\", \"option #14\",\n          \"option #15\", \"option #16\", \"option #17\", \"option #18\", \"option #19\",\n          \"option #20\", \"option #21\", \"option #22\", \"option #23\", \"option #24\",\n          \"option #25\", \"option #26\", \"option #27\", \"option #28\", \"option #29\",\n          \"option #30\", \"option #31\", \"option #32\", \"option #33\", \"option #34\",\n          \"option #35\", \"option #36\", \"option #37\", \"option #38\", \"option #39\",\n          \"option #40\", \"option #41\", \"option #42\", \"option #43\", \"option #44\",\n          \"option #45\", \"option #46\", \"option #47\", \"option #48\", \"option #49\",\n          \"option #50\", \"option #51\", \"option #52\", \"option #53\", \"option #54\",\n          \"option #55\", \"option #56\", \"option #57\", \"option #58\", \"option #59\",\n          \"option #60\", \"option #61\", \"option #62\", \"option #63\", \"option #64\",\n          \"option #65\", \"option #66\", \"option #67\", \"option #68\", \"option #69\",\n          \"option #70\", \"option #71\", \"option #72\", \"option #73\", \"option #74\",\n          \"option #75\", \"option #76\", \"option #77\", \"option #78\", \"option #79\",\n          \"option #80\", \"option #81\", \"option #82\", \"option #83\", \"option #84\",\n          \"option #85\", \"option #86\", \"option #87\", \"option #88\", \"option #89\",\n          \"option #90\", \"option #91\", \"option #92\", \"option #93\", \"option #94\",\n          \"option #95\", \"option #96\", \"option #97\", \"option #98\", \"option #99\" ]\n      }\n    },\n    \"title\": \"A rather large form\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"string\":   { \"type\": \"string\", \"title\": \"Some string\" },\n      \"choice1\":  { \"$ref\": \"#/definitions/largeEnum\" },\n      \"choice2\":  { \"$ref\": \"#/definitions/largeEnum\" },\n      \"choice3\":  { \"$ref\": \"#/definitions/largeEnum\" },\n      \"choice4\":  { \"$ref\": \"#/definitions/largeEnum\" },\n      \"choice5\":  { \"$ref\": \"#/definitions/largeEnum\" },\n      \"choice6\":  { \"$ref\": \"#/definitions/largeEnum\" },\n      \"choice7\":  { \"$ref\": \"#/definitions/largeEnum\" },\n      \"choice8\":  { \"$ref\": \"#/definitions/largeEnum\" },\n      \"choice9\":  { \"$ref\": \"#/definitions/largeEnum\" },\n      \"choice10\": { \"$ref\": \"#/definitions/largeEnum\" }\n    }\n  },\n  \"UISchema\": { \"choice1\": { \"ui:placeholder\": \"Choose one\" } },\n  \"formData\": {}\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-nested.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"A list of tasks\",\n    \"type\": \"object\",\n    \"required\": [ \"title\" ],\n    \"properties\": {\n      \"title\": {\n        \"title\": \"Task list title\",\n        \"type\": \"string\"\n      },\n      \"tasks\": {\n        \"title\": \"Tasks\",\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"required\": [ \"title\" ],\n          \"properties\": {\n            \"title\": {\n              \"title\": \"Title\",\n              \"type\": \"string\",\n              \"description\": \"A sample title\"\n            },\n            \"details\": {\n              \"title\": \"Task details\",\n              \"type\": \"string\",\n              \"description\": \"Enter the task details\"\n            },\n            \"done\": {\n              \"title\": \"Done?\",\n              \"type\": \"boolean\",\n              \"default\": false\n            }\n          }\n        }\n      }\n    }\n  },\n  \"uiSchema\": {\n    \"tasks\": { \"items\": { \"details\": { \"ui:widget\": \"textarea\" } } }\n  },\n  \"formData\": {\n    \"title\": \"My current tasks\",\n    \"tasks\": [ {\n      \"title\": \"My first task\",\n      \"details\": \"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\",\n      \"done\": true\n    }, {\n      \"title\": \"My second task\",\n      \"details\": \"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur\",\n      \"done\": false\n    } ]\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-numbers.json",
    "content": "{\n  \"schema\": {\n    \"type\": \"object\",\n    \"title\": \"Number fields & widgets\",\n    \"properties\": {\n      \"number\": {\n        \"title\": \"Number\",\n        \"type\": \"number\"\n      },\n      \"integer\": {\n        \"title\": \"Integer\",\n        \"type\": \"integer\"\n      },\n      \"numberEnum\": {\n        \"type\": \"number\",\n        \"title\": \"Number enum\",\n        \"enum\": [ 1, 2, 3 ]\n      },\n      \"numberEnumRadio\": {\n        \"type\": \"number\",\n        \"title\": \"Number enum\",\n        \"enum\": [ 1, 2, 3 ]\n      },\n      \"integerRange\": {\n        \"title\": \"Integer range\",\n        \"type\": \"integer\",\n        \"minimum\": 42,\n        \"maximum\": 100\n      },\n      \"integerRangeSteps\": {\n        \"title\": \"Integer range (by 10)\",\n        \"type\": \"integer\",\n        \"minimum\": 50,\n        \"maximum\": 100,\n        \"multipleOf\": 10\n      }\n    }\n  },\n  \"uiSchema\": {\n    \"integer\": {\n      \"ui:widget\": \"updown\"\n    },\n    \"numberEnumRadio\": { \"ui:widget\": \"radio\", \"ui:options\": { \"inline\": true } },\n    \"integerRange\": {\n      \"ui:widget\": \"range\"\n    },\n    \"integerRangeSteps\": {\n      \"ui:widget\": \"range\"\n    }\n  },\n  \"formData\": {\n    \"number\": 3.14,\n    \"integer\": 42,\n    \"numberEnum\": 2,\n    \"integerRange\": 42,\n    \"integerRangeSteps\": 80\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-ordering.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"A registration form\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"password\":  { \"type\": \"string\",  \"title\": \"Password\" },\n      \"lastName\":  { \"type\": \"string\",  \"title\": \"Last name\" },\n      \"bio\":       { \"type\": \"string\",  \"title\": \"Bio\" },\n      \"firstName\": { \"type\": \"string\",  \"title\": \"First name\" },\n      \"age\":       { \"type\": \"integer\", \"title\": \"Age\" }\n    },\n    \"required\": [ \"firstName\", \"lastName\" ]\n  },\n  \"uiSchema\": {\n    \"ui:order\": [ \"firstName\", \"lastName\", \"*\", \"password\" ],\n    \"age\": { \"ui:widget\": \"updown\" },\n    \"bio\": { \"ui:widget\": \"textarea\" },\n    \"password\": { \"ui:widget\": \"password\" }\n  },\n  \"formData\": {\n    \"firstName\": \"Chuck\",\n    \"lastName\": \"Norris\",\n    \"age\": 75,\n    \"bio\": \"Roundhouse kicking asses since 1940\",\n    \"password\": \"noneed\"\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-references.json",
    "content": "{\n  \"schema\": {\n    \"definitions\": {\n      \"address\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"street_address\": { \"type\": \"string\" },\n          \"city\": { \"type\": \"string\" },\n          \"state\": { \"type\": \"string\" }\n        },\n        \"required\": [ \"street_address\", \"city\", \"state\" ]\n      },\n      \"node\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"name\": { \"type\": \"string\" },\n          \"children\": {\n            \"type\": \"array\",\n            \"items\": { \"$ref\": \"#/definitions/node\" }\n          }\n        }\n      }\n    },\n    \"type\": \"object\",\n    \"properties\": {\n      \"billing_address\": {\n        \"title\": \"Billing address\",\n        \"$ref\": \"#/definitions/address\"\n      },\n      \"shipping_address\": {\n        \"title\": \"Shipping address\",\n        \"$ref\": \"#/definitions/address\"\n      },\n      \"tree\": {\n        \"title\": \"Recursive references\",\n        \"$ref\": \"#/definitions/node\"\n      }\n    }\n  },\n  \"uiSchema\": {\n    \"ui:order\": [ \"shipping_address\", \"billing_address\", \"tree\" ]\n  },\n  \"formData\": {\n    \"billing_address\": {\n      \"street_address\": \"21, Jump Street\",\n      \"city\": \"Babel\",\n      \"state\": \"Neverland\"\n    },\n    \"shipping_address\": {\n      \"street_address\": \"221B, Baker Street\",\n      \"city\": \"London\",\n      \"state\": \"N/A\"\n    },\n    \"tree\": {\n      \"name\": \"root\",\n      \"children\": [ { \"name\": \"leaf\" } ]\n    }\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-simple.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"A registration form\",\n    \"description\": \"A simple form example.\",\n    \"type\": \"object\",\n    \"required\": [ \"firstName\", \"lastName\" ],\n    \"properties\": {\n      \"firstName\": { \"type\": \"string\", \"title\": \"First name\" },\n      \"lastName\": { \"type\": \"string\", \"title\": \"Last name\" },\n      \"age\": { \"type\": \"integer\", \"title\": \"Age\" },\n      \"bio\": { \"type\": \"string\", \"title\": \"Bio\" },\n      \"password\": { \"type\": \"string\", \"title\": \"Password\", \"minLength\": 3 },\n      \"telephone\": { \"type\": \"string\", \"title\": \"Telephone\", \"minLength\": 10 }\n    }\n  },\n  \"uiSchema\": {\n    \"firstName\": {\n      \"ui:autofocus\": true,\n      \"ui:emptyValue\": \"\"\n    },\n    \"age\": {\n      \"ui:widget\": \"updown\",\n      \"ui:title\": \"Age of person\",\n      \"ui:description\": \"(earthian year)\"\n    },\n    \"bio\": {\n      \"ui:widget\": \"textarea\"\n    },\n    \"password\": {\n      \"ui:widget\": \"password\",\n      \"ui:help\": \"Hint: Make it strong!\"\n    },\n    \"date\": {\n      \"ui:widget\": \"alt-datetime\"\n    },\n    \"telephone\": {\n      \"ui:options\": { \"inputType\": \"tel\" }\n    }\n  },\n  \"formData\": {\n    \"firstName\": \"Chuck\",\n    \"lastName\": \"Norris\",\n    \"age\": 75,\n    \"bio\": \"Roundhouse kicking asses since 1940\",\n    \"password\": \"noneed\"\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-single.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"A single-field form\",\n    \"type\": \"string\"\n  },\n  \"formData\": \"initial value\",\n  \"uiSchema\": { }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-validation.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"Custom validation\",\n    \"description\" :\n      \"This form defines custom validation rules checking that the two passwords match.\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"pass1\": {\n        \"title\": \"Password\",\n        \"type\": \"string\",\n        \"minLength\": 3\n      },\n      \"pass2\": {\n        \"title\": \"Repeat password\",\n        \"type\": \"string\",\n        \"minLength\": 3\n      },\n      \"age\": {\n        \"title\": \"Age\",\n        \"type\": \"number\",\n        \"minimum\": 18\n      }\n    }\n  },\n  \"uiSchema\": {\n    \"pass1\": { \"ui:widget\": \"password\" },\n    \"pass2\": { \"ui:widget\": \"password\" }\n  },\n  \"formData\": {},\n  \"validate\": function ({ pass1, pass2 }, errors) {\n    if (pass1 !== pass2) {\n      errors.pass2.addError(\"Passwords don't match.\");\n    }\n    return errors;\n  },\n  \"transformErrors\": function(errors) {\n    return errors.map(error => {\n      if (error.name === \"minimum\" && error.property === \"instance.age\") {\n        return Object.assign({}, error, {\n          message: \"You need to be 18 because of some legal thing\",\n        });\n      }\n      return error;\n    });\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/rjsf-widgets.json",
    "content": "{\n  \"schema\": {\n    \"title\": \"Widgets\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"stringFormats\": {\n        \"type\": \"object\",\n        \"title\": \"String formats\",\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\"\n          },\n          \"uri\": {\n            \"type\": \"string\",\n            \"format\": \"uri\"\n          }\n        }\n      },\n      \"boolean\": {\n        \"type\": \"object\",\n        \"title\": \"Boolean field\",\n        \"properties\": {\n          \"default\": {\n            \"type\": \"boolean\",\n            \"title\": \"checkbox (default)\",\n            \"description\": \"This is the checkbox-description\"\n          },\n          \"radio\": {\n            \"type\": \"boolean\",\n            \"title\": \"radio buttons\",\n            \"description\": \"This is the radio-description\"\n          },\n          \"select\": {\n            \"type\": \"boolean\",\n            \"title\": \"select box\",\n            \"description\": \"This is the select-description\"\n          }\n        }\n      },\n      \"string\": {\n        \"type\": \"object\",\n        \"title\": \"String field\",\n        \"properties\": {\n          \"default\": {\n            \"type\": \"string\",\n            \"title\": \"text input (default)\"\n          },\n          \"textarea\": {\n            \"type\": \"string\",\n            \"title\": \"textarea\"\n          },\n          \"color\": {\n            \"type\": \"string\",\n            \"title\": \"color picker\",\n            \"default\": \"#151ce6\"\n          }\n        }\n      },\n      \"secret\": {\n        \"type\": \"string\",\n        \"default\": \"I'm a hidden string.\"\n      },\n      \"disabled\": {\n        \"type\": \"string\",\n        \"title\": \"A disabled field\",\n        \"default\": \"I am disabled.\"\n      },\n      \"readonly\": {\n        \"type\": \"string\",\n        \"title\": \"A readonly field\",\n        \"default\": \"I am read-only.\"\n      },\n      \"widgetOptions\": {\n        \"title\": \"Custom widget with options\",\n        \"type\": \"string\",\n        \"default\": \"I am yellow\"\n      },\n      \"selectWidgetOptions\": {\n        \"title\": \"Custom select widget with options\",\n        \"type\": \"string\",\n        \"enum\": [ \"foo\", \"bar\" ],\n        \"enumNames\": [ \"Foo\", \"Bar\" ]\n      }\n    }\n  },\n  \"uiSchema\": {\n    \"boolean\": {\n      \"radio\": { \"ui:widget\": \"radio\" },\n      \"select\": { \"ui:widget\": \"select\" }\n    },\n    \"string\": {\n      \"textarea\": { \"ui:widget\": \"textarea\", \"ui:options\": { \"rows\": 5 } },\n      \"color\": { \"ui:widget\": \"color\" }\n    },\n    \"secret\": { \"ui:widget\": \"hidden\" },\n    \"disabled\": { \"ui:disabled\": true },\n    \"readonly\": { \"ui:readonly\": true },\n    \"widgetOptions\": {\n      \"ui:widget\": function ({ value, onChange, options }) {\n        const { backgroundColor } = options;\n        return (\n          <input\n            className=\"form-control\"\n            onChange={event => onChange(event.target.value)}\n            style={{backgroundColor}}\n            value={value}\n          />\n        );\n      },\n      \"ui:options\": { \"backgroundColor\": \"yellow\" }\n    },\n    \"selectWidgetOptions\": {\n      \"ui:widget\": function ({ value, onChange, options }) {\n        const { enumOptions, backgroundColor } = options;\n        return (\n          <select\n            className=\"form-control\"\n            style={{backgroundColor}}\n            value={value}\n            onChange={event => onChange(event.target.value)}>\n            {enumOptions.map(({ label, value }, i) => {\n              return (\n                <option key={i} value={value}>\n                  {label}\n                </option>\n              );\n            })}\n          </select>\n        );\n      },\n      \"ui:options\": { \"backgroundColor\": \"pink\" }\n    }\n  },\n  \"formData\": {\n    \"stringFormats\": {\n      \"email\": \"chuck@norris.net\",\n      \"uri\": \"http://chucknorris.com/\"\n    },\n    \"boolean\": {\n      \"default\": true,\n      \"radio\": true,\n      \"select\": true\n    },\n    \"string\": {\n      \"default\": \"Hello...\",\n      \"textarea\": \"... World\"\n    },\n    \"secret\": \"I'm a hidden string.\"\n  }\n}\n"
  },
  {
    "path": "demo/assets/example-schemas/sources.md",
    "content": "Sources:\n\n* ng-jsf-...json files are new examples created for angular json schema form\n\n* json-schema-draft...json files are JSON Meta-Schemas,\n  [available here](http://json-schema.org/specification-links.html)\n\n* asf-...json files are Angular Schema Form (AngularJS) compatibility examples,\n  [available here](http://schemaform.io/examples/bootstrap-example.html)\n\n* jsf-...json files are JSONForm (jQuery) compatibility examples,\n  [available here](http://ulion.github.io/jsonform/playground/)\n\n* rjsf-...json files are React JSON Schema Form compatibility examples,\n  [available here](https://mozilla-services.github.io/react-jsonschema-form/)\n"
  },
  {
    "path": "demo/environments/environment.prod.ts",
    "content": "export const environment = { production: true };\n"
  },
  {
    "path": "demo/environments/environment.ts",
    "content": "export const environment = { production: false };\n"
  },
  {
    "path": "demo/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Angular JSON Schema Form—Demonstration Playground</title>\n    <base href=\"/\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/icon?family=Material+Icons\">\n    <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\">\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\">\n  </head>\n  <body>\n    <demo-root>Loading...</demo-root>\n  </body>\n</html>\n"
  },
  {
    "path": "demo/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma'),\n    ],\n    client: {\n      clearContext: false, // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reporters: [\n        {\n          type: 'html',\n        },\n        {\n          type: 'lcov',\n        },\n        {\n          type: 'text-summary',\n        },\n      ],\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    singleRun: false,\n    browsers: ['Chrome', 'ChromeHeadlessCI'],\n    customLaunchers: {\n      ChromeHeadlessCI: {\n        base: 'ChromeHeadless',\n        flags: ['--no-sandbox'],\n      },\n    },\n  });\n};\n"
  },
  {
    "path": "demo/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { DemoModule } from './app/demo.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) { enableProdMode(); }\n\nplatformBrowserDynamic().bootstrapModule(DemoModule);\n"
  },
  {
    "path": "demo/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n"
  },
  {
    "path": "demo/styles.scss",
    "content": "// * { border: 1px solid red !important; }\n@use '@angular/material' as mat;\n@include mat.core();\n$demo-app-primary: mat.define-palette(mat.$blue-palette);\n$demo-app-accent:  mat.define-palette(mat.$amber-palette, A200, A100, A400);\n$demo-app-warn:    mat.define-palette(mat.$red-palette);\n$demo-app-theme:   mat.define-light-theme($demo-app-primary, $demo-app-accent, $demo-app-warn);\n@include mat.all-component-themes($demo-app-theme);\n\n$font-family: 'Roboto', 'Noto', 'Helvetica Neue', sans-serif;\n$row-height: 56px;\n\nmat-toolbar {\n  &.mat-medium {\n    min-height: $row-height;\n    mat-toolbar-row { height: $row-height; }\n  }\n}\n\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  background-color: rgb(250, 250, 250) !important;\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  font-family: $font-family;\n  height: 100%;\n  margin: 0;\n  padding: 0;\n}\n\n.demo-page-header {\n  background-color: mat.get-color-from-palette($demo-app-primary, lighter);\n  margin-bottom: 12px;\n  .header-content {\n    font-family: $font-family;\n    line-height: 1.4em;\n    padding: 12px;\n    .menu-label {\n      margin-right: 12px;\n      font-weight: bold;\n    }\n  }\n}\n\n.ace_active-line { background: none !important; }\n\n[ace-editor], .data-good, .data-bad {\n  border-radius: 3px;\n  padding: 6px;\n  border: 1px solid #ccc !important;\n}\n\n[ace-editor] { background-color: rgb(253, 253, 253) !important; }\n\n.avoidwrap { display:inline-block; }\n\n.data-good { background-color: #dfd; }\n\n.data-bad { background-color: #fcc; }\n\n.default-cursor:hover { cursor: default; }\n\n.check-row { margin-top: 8px; }\n\n.cdk-overlay-container .cdk-overlay-pane .mat-menu-panel { max-width: 560px; }\n\n.debug { border: 1px solid red !important; }\n\n.mat-input-container.mat-form-field { width: 100%; }\n"
  },
  {
    "path": "demo/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/long-stack-trace-zone';\nimport 'zone.js/dist/proxy.js';\nimport 'zone.js/dist/sync-test';\nimport 'zone.js/dist/jasmine-patch';\nimport 'zone.js/dist/async-test';\nimport 'zone.js/dist/fake-async-test';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\n// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.\ndeclare var __karma__: any;\ndeclare var require: any;\n\n// Prevent Karma from running prematurely.\n__karma__.loaded = function () {};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(), {\n    teardown: { destroyAfterEach: false }\n}\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n// Finally, start Karma to run the tests.\n__karma__.start();\n"
  },
  {
    "path": "demo/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"main.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"demo/**/*.d.ts\"\n  ]\n}"
  },
  {
    "path": "demo/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "demo/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "docs/issue_template.md",
    "content": "**Describe the bug**\nwhich template:\n\n* [ ] MaterialDesignFrameworkModule — Material Design\n* [ ] Bootstrap3FrameworkModule — Bootstrap 3\n* [ ] Bootstrap4FrameworkModule — Bootstrap 4\n* [ ] NoFrameworkModule — plain HTML\n* [ ] Other (please specify below)\n  \nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n\n1. ...\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n\n- OS: [e.g. iOS]\n- Browser [e.g. chrome, safari]\n- Version [e.g. 22]\n\n**Log output covering before error and any error statements**\n\n```logs\nInsert log hereCopy\n```\n\n**Additional context**\nAdd any other context about the problem here.\n\n\n<!--- For feature requests -->\n**Detailed Description**\n<!--- Provide a detailed description of the change or addition you are proposing -->\n\n**Context**\n<!--- Why is this change important to you? How would you use it? -->\n<!--- How can it benefit other users? -->\n\n**Possible Implementation**\n<!--- Not obligatory, but suggest an idea for implementing addition or change -->\n"
  },
  {
    "path": "e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require('jasmine-spec-reporter');\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\n    './src/**/*.e2e-spec.ts'\n  ],\n  capabilities: {\n    'browserName': 'chrome'\n  },\n  directConnect: true,\n  baseUrl: 'http://localhost:4200/',\n  framework: 'jasmine',\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require('ts-node').register({\n      project: require('path').join(__dirname, './tsconfig.e2e.json')\n    });\n    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};"
  },
  {
    "path": "e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('workspace-project App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getParagraphText()).toEqual('Welcome to @ajsf/core-app!');\n  });\n});\n"
  },
  {
    "path": "e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\nimport { promise as wdpromise } from 'selenium-webdriver';\n\nexport class AppPage {\n  navigateTo(): wdpromise.Promise<any> {\n    return browser.get('/');\n  }\n\n  getParagraphText(): wdpromise.Promise<string> {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"demo\",\n  \"version\": \"15.0.0\",\n  \"author\": \"https://github.com/hamzahamidi/Angular6-json-schema-form/graphs/contributors\",\n  \"description\": \"Angular JSON Schema Form builder Demo\",\n  \"engines\": {\n    \"node\": \"^16.13.2\",\n    \"npm\": \"^8.1.2\"\n  },\n  \"keywords\": [\n    \"Angular\",\n    \"ng\",\n    \"Angular6\",\n    \"Angular 6\",\n    \"ng6\",\n    \"Angular7\",\n    \"Angular 7\",\n    \"ng7\",\n    \"Angular8\",\n    \"Angular 8\",\n    \"ng8\",\n    \"Angular9\",\n    \"Angular 9\",\n    \"ng9\",\n    \"Angular10\",\n    \"Angular 10\",\n    \"ng10\",\n    \"Angular11\",\n    \"Angular 11\",\n    \"ng11\",\n    \"Angular12\",\n    \"Angular 12\",\n    \"ng12\",\n    \"Angular13\",\n    \"Angular 13\",\n    \"ng13\",\n    \"Angular14\",\n    \"Angular 14\",\n    \"ng14\",\n    \"JSON Schema\",\n    \"form\",\n    \"forms\",\n    \"form builder\",\n    \"material\",\n    \"angular material\",\n    \"bootstrap\",\n    \"bootstrap 3\",\n    \"bootstrap 4\",\n    \"ajsf\",\n    \"angular json schema form\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/hamzahamidi/angular6-json-schema-form\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/hamzahamidi/angular6-json-schema-form/issues\"\n  },\n  \"homepage\": \"https://github.com/hamzahamidi/Angular6-json-schema-form#readme\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"prestart\": \"npm run build:libs\",\n    \"start\": \"ng serve\",\n    \"start:demo-only\": \"ng serve\",\n    \"build:core\": \"ng build @ajsf/core --configuration production\",\n    \"postbuild:core\": \"cp README.md LICENSE  dist/@ajsf/core/\",\n    \"build:bs3\": \"ng build @ajsf/bootstrap3 --configuration production\",\n    \"postbuild:bs3\": \"cp projects/ajsf-bootstrap3/README.md LICENSE dist/@ajsf/bootstrap3/\",\n    \"build:bs4\": \"ng build @ajsf/bootstrap4 --configuration production\",\n    \"postbuild:bs4\": \"cp projects/ajsf-bootstrap4/README.md LICENSE dist/@ajsf/bootstrap4/\",\n    \"build:material\": \"ng build @ajsf/material --configuration production\",\n    \"postbuild:material\": \"cp projects/ajsf-material/README.md LICENSE dist/@ajsf/material\",\n    \"build:libs\": \"npm run build:core && npm run build:bs4 && npm run build:bs3 && npm run build:material\",\n    \"build:demo\": \"npm run build:libs && ng build demo --configuration production --base-href ./\",\n    \"build:demo-only\": \"ng build demo --configuration production --base-href ./\",\n    \"prestats\": \"ng build demo --configuration production --stats-json\",\n    \"stats\": \"webpack-bundle-analyzer dist/demo/stats.json\",\n    \"test:core\": \"ng test @ajsf/core\",\n    \"test:bs3\": \"ng test @ajsf/bootstrap3\",\n    \"test:bs4\": \"ng test @ajsf/bootstrap4\",\n    \"test:material\": \"ng test @ajsf/material\",\n    \"publish:coverage\": \"./node_modules/.bin/codecov\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"dependencies\": {\n    \"@angular/animations\": \"^14.2.0\",\n    \"@angular/cdk\": \"^14.1.1\",\n    \"@angular/common\": \"^14.2.0\",\n    \"@angular/compiler\": \"^14.2.0\",\n    \"@angular/core\": \"^14.2.0\",\n    \"@angular/flex-layout\": \"^14.0.0-beta.40\",\n    \"@angular/forms\": \"^14.2.0\",\n    \"@angular/material\": \"^14.1.1\",\n    \"@angular/platform-browser\": \"^14.2.0\",\n    \"@angular/platform-browser-dynamic\": \"^14.2.0\",\n    \"@angular/router\": \"^14.2.0\",\n    \"brace\": \"^0.11.1\",\n    \"core-js\": \"^3.6.5\",\n    \"lodash-es\": \"~4.17.21\",\n    \"rxjs\": \"^7.0.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"^14.2.1\",\n    \"@angular/cli\": \"^14.2.1\",\n    \"@angular/compiler-cli\": \"^14.2.0\",\n    \"@angular/language-service\": \"^14.2.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"@types/lodash\": \"^4.14.182\",\n    \"@types/node\": \"^12.11.1\",\n    \"codecov\": \"^3.8.3\",\n    \"codelyzer\": \"^6.0.0\",\n    \"jasmine-core\": \"~4.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma\": \"~6.4.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.1.0\",\n    \"karma-jasmine-html-reporter\": \"~2.0.0\",\n    \"ng-packagr\": \"^14.1.0\",\n    \"protractor\": \"~7.0.0\",\n    \"ts-node\": \"~8.6.2\",\n    \"tsickle\": \"^0.46.3\",\n    \"tslib\": \"^2.3.1\",\n    \"tslint\": \"~6.1.0\",\n    \"typescript\": \"~4.7.4\",\n    \"webpack-bundle-analyzer\": \"^4.6.1\"\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/README.md",
    "content": "# @ajsf/bootstrap3\n\n## Getting started\n\n```shell\nnpm install @ajsf/bootstrap3@latest\n```\n\nWith YARN, run the following:\n\n```shell\nyarn add @ajsf/bootstrap3@latest\n```\n\nThen import `Bootstrap3FrameworkModule` in your main application module if you want to use `bootstrap3` UI, like this:\n\n```javascript\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\n\nimport { Bootstrap3FrameworkModule } from '@ajsf/bootstrap3';\n\nimport { AppComponent } from './app.component';\n\n@NgModule({\n  declarations: [ AppComponent ],\n  imports: [\n    Bootstrap3FrameworkModule\n  ],\n  providers: [],\n  bootstrap: [ AppComponent ]\n})\nexport class AppModule { }\n```\n\nFor basic use, after loading JsonSchemaFormModule as described above, to display a form in your Angular component, simply add the following to your component's template:\n\n```html\n<json-schema-form\n  loadExternalAssets=\"true\"\n  [schema]=\"yourJsonSchema\"\n  framework=\"bootstrap-3\"\n  (onSubmit)=\"yourOnSubmitFn($event)\">\n</json-schema-form>\n```\n\nWhere `schema` is a valid JSON schema object, and `onSubmit` calls a function to process the submitted JSON form data. If you don't already have your own schemas, you can find a bunch of samples to test with in the `demo/assets/example-schemas` folder, as described above.\n\n`framework` is for the template you want to use, the default value is `no-framwork`. The possible values are:\n\n* `material-design` for  Material Design.\n* `bootstrap-3` for Bootstrap 3.\n* `bootstrap-4` for 'Bootstrap 4.\n* `no-framework` for (plain HTML).\n\n## Code scaffolding\n\nRun `ng generate component component-name --project @ajsf/bootstrap3` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project @ajsf/bootstrap3`.\n> Note: Don't forget to add `--project @ajsf/bootstrap3` or else it will be added to the default project in your `angular.json` file.\n\n## Build\n\nRun `ng build @ajsf/bootstrap3` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test @ajsf/bootstrap3` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma'),\n    ],\n    client: {\n      clearContext: false, // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, '../../coverage/ajsf-bootstrap3'),\n      reporters: [\n        {\n          type: 'html',\n        },\n        {\n          type: 'lcov',\n        },\n        {\n          type: 'text-summary',\n        },\n      ],\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    browsers: ['Chrome', 'ChromeHeadlessCI'],\n    customLaunchers: {\n      ChromeHeadlessCI: {\n        base: 'ChromeHeadless',\n        flags: ['--no-sandbox'],\n      },\n    },\n  });\n};\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/@ajsf/bootstrap3\",\n  \"lib\": {\n    \"entryFile\": \"src/public_api.ts\"\n  },\n  \"allowedNonPeerDependencies\": [\n    \"lodash-es\",\n    \"@ajsf/core\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/package.json",
    "content": "{\n  \"name\": \"@ajsf/bootstrap3\",\n  \"version\": \"0.8.0\",\n  \"description\": \"Angular JSON Schema Form builder using Bootstrap 3 UI\",\n  \"author\": \"https://github.com/hamzahamidi/ajsf/graphs/contributors\",\n  \"keywords\": [\n    \"Angular\",\n    \"ng\",\n    \"Angular14\",\n    \"Angular 14\",\n    \"ng14\",\n    \"JSON Schema\",\n    \"form\",\n    \"forms\",\n    \"form builder\",\n    \"Bootstrap\",\n    \"Bootstrap 3\",\n    \"ajsf\",\n    \"angular json schema form\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/hamzahamidi/ajsf\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/hamzahamidi/ajsf/issues\"\n  },\n  \"private\": false,\n  \"dependencies\": {\n    \"lodash-es\": \"~4.17.21\",\n    \"@ajsf/core\": \"~0.8.0\",\n    \"tslib\": \"^2.0.0\"\n  },\n  \"peerDependencies\": {\n    \"@angular/common\": \">=14.0.0\",\n    \"@angular/core\": \">=14.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/lodash-es\": \"^4.17.3\"\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/src/lib/bootstrap3-framework.component.html",
    "content": "<div\n  [class]=\"options?.htmlClass || ''\"\n  [class.has-feedback]=\"options?.feedback && options?.isInputWidget &&\n        (formControl?.dirty || options?.feedbackOnRender)\"\n  [class.has-error]=\"options?.enableErrorState && formControl?.errors &&\n        (formControl?.dirty || options?.feedbackOnRender)\"\n  [class.has-success]=\"options?.enableSuccessState && !formControl?.errors &&\n        (formControl?.dirty || options?.feedbackOnRender)\">\n\n  <button *ngIf=\"showRemoveButton\"\n          class=\"close pull-right\"\n          type=\"button\"\n          (click)=\"removeItem()\">\n    <span aria-hidden=\"true\">&times;</span>\n    <span class=\"sr-only\">Close</span>\n  </button>\n  <div *ngIf=\"options?.messageLocation === 'top'\">\n    <p *ngIf=\"options?.helpBlock\"\n       class=\"help-block\"\n       [innerHTML]=\"options?.helpBlock\"></p>\n  </div>\n\n  <label *ngIf=\"options?.title && layoutNode?.type !== 'tab'\"\n         [attr.for]=\"'control' + layoutNode?._id\"\n         [class]=\"options?.labelHtmlClass || ''\"\n         [class.sr-only]=\"options?.notitle\"\n         [innerHTML]=\"options?.title\"></label>\n  <p *ngIf=\"layoutNode?.type === 'submit' && jsf?.formOptions?.fieldsRequired\">\n    <strong class=\"text-danger\">*</strong> = required fields\n  </p>\n  <div [class.input-group]=\"options?.fieldAddonLeft || options?.fieldAddonRight\">\n        <span *ngIf=\"options?.fieldAddonLeft\"\n              class=\"input-group-addon\"\n              [innerHTML]=\"options?.fieldAddonLeft\"></span>\n\n    <select-widget-widget\n      [layoutNode]=\"widgetLayoutNode\"\n      [dataIndex]=\"dataIndex\"\n      [layoutIndex]=\"layoutIndex\"></select-widget-widget>\n\n    <span *ngIf=\"options?.fieldAddonRight\"\n          class=\"input-group-addon\"\n          [innerHTML]=\"options?.fieldAddonRight\"></span>\n  </div>\n\n  <span *ngIf=\"options?.feedback && options?.isInputWidget &&\n          !options?.fieldAddonRight && !layoutNode.arrayItem &&\n          (formControl?.dirty || options?.feedbackOnRender)\"\n        [class.glyphicon-ok]=\"options?.enableSuccessState && !formControl?.errors\"\n        [class.glyphicon-remove]=\"options?.enableErrorState && formControl?.errors\"\n        aria-hidden=\"true\"\n        class=\"form-control-feedback glyphicon\"></span>\n  <div *ngIf=\"options?.messageLocation !== 'top'\">\n    <p *ngIf=\"options?.helpBlock\"\n       class=\"help-block\"\n       [innerHTML]=\"options?.helpBlock\"></p>\n  </div>\n</div>\n\n<div *ngIf=\"debug && debugOutput\">debug:\n  <pre>{{debugOutput}}</pre>\n</div>\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/src/lib/bootstrap3-framework.component.scss",
    "content": ":host ::ng-deep {\n  .list-group-item .form-control-feedback {\n    top: 40px;\n  }\n\n  .checkbox,\n  .radio {\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n\n  .checkbox-inline,\n  .checkbox-inline + .checkbox-inline,\n  .checkbox-inline + .radio-inline,\n  .radio-inline,\n  .radio-inline + .radio-inline,\n  .radio-inline + .checkbox-inline {\n    margin-left: 0;\n    margin-right: 10px;\n  }\n\n  .checkbox-inline:last-child,\n  .radio-inline:last-child {\n    margin-right: 0;\n  }\n\n  .ng-invalid.ng-touched {\n    border: 1px solid #f44336;\n  }\n}\n\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/src/lib/bootstrap3-framework.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\nimport { CommonModule } from '@angular/common';\nimport {\n  JsonSchemaFormModule,\n  JsonSchemaFormService,\n  WidgetLibraryModule\n} from '@ajsf/core';\nimport { Bootstrap3FrameworkComponent } from './bootstrap3-framework.component';\n\ndescribe('Bootstrap3FrameworkComponent', () => {\n  let component: Bootstrap3FrameworkComponent;\n  let fixture: ComponentFixture<Bootstrap3FrameworkComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        JsonSchemaFormModule,\n        CommonModule,\n        WidgetLibraryModule,\n      ],\n      declarations: [Bootstrap3FrameworkComponent],\n      providers: [JsonSchemaFormService]\n    })\n      .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(Bootstrap3FrameworkComponent);\n    component = fixture.componentInstance;\n    component.layoutNode = { options: {} };\n    component.layoutIndex = [];\n    component.dataIndex = [];\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/src/lib/bootstrap3-framework.component.ts",
    "content": "import {ChangeDetectorRef, Component, Input, OnChanges, OnInit} from '@angular/core';\nimport cloneDeep from 'lodash/cloneDeep';\nimport map from 'lodash/map';\nimport {JsonSchemaFormService, addClasses, inArray} from '@ajsf/core';\n\n/**\n * Bootstrap 3 framework for Angular JSON Schema Form.\n */\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'bootstrap-3-framework',\n  templateUrl: './bootstrap3-framework.component.html',\n  styleUrls: ['./bootstrap3-framework.component.scss'],\n})\nexport class Bootstrap3FrameworkComponent implements OnInit, OnChanges {\n  frameworkInitialized = false;\n  widgetOptions: any; // Options passed to child widget\n  widgetLayoutNode: any; // layoutNode passed to child widget\n  options: any; // Options used in this framework\n  formControl: any = null;\n  debugOutput: any = '';\n  debug: any = '';\n  parentArray: any = null;\n  isOrderable = false;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    public changeDetector: ChangeDetectorRef,\n    public jsf: JsonSchemaFormService\n  ) {\n  }\n\n  get showRemoveButton(): boolean {\n    if (!this.options.removable || this.options.readonly ||\n      this.layoutNode.type === '$ref'\n    ) {\n      return false;\n    }\n    if (this.layoutNode.recursiveReference) {\n      return true;\n    }\n    if (!this.layoutNode.arrayItem || !this.parentArray) {\n      return false;\n    }\n    // If array length <= minItems, don't allow removing any items\n    return this.parentArray.items.length - 1 <= this.parentArray.options.minItems ? false :\n      // For removable list items, allow removing any item\n      this.layoutNode.arrayItemType === 'list' ? true :\n        // For removable tuple items, only allow removing last item in list\n        this.layoutIndex[this.layoutIndex.length - 1] === this.parentArray.items.length - 2;\n  }\n\n  ngOnInit() {\n    this.initializeFramework();\n    if (this.layoutNode.arrayItem && this.layoutNode.type !== '$ref') {\n      this.parentArray = this.jsf.getParentNode(this);\n      if (this.parentArray) {\n        this.isOrderable = this.layoutNode.arrayItemType === 'list' &&\n          !this.options.readonly && this.parentArray.options.orderable;\n      }\n    }\n  }\n\n  ngOnChanges() {\n    if (!this.frameworkInitialized) {\n      this.initializeFramework();\n    }\n  }\n\n  initializeFramework() {\n    if (this.layoutNode) {\n      this.options = cloneDeep(this.layoutNode.options);\n      this.widgetLayoutNode = {\n        ...this.layoutNode,\n        options: cloneDeep(this.layoutNode.options)\n      };\n      this.widgetOptions = this.widgetLayoutNode.options;\n      this.formControl = this.jsf.getFormControl(this);\n\n      this.options.isInputWidget = inArray(this.layoutNode.type, [\n        'button', 'checkbox', 'checkboxes-inline', 'checkboxes', 'color',\n        'date', 'datetime-local', 'datetime', 'email', 'file', 'hidden',\n        'image', 'integer', 'month', 'number', 'password', 'radio',\n        'radiobuttons', 'radios-inline', 'radios', 'range', 'reset', 'search',\n        'select', 'submit', 'tel', 'text', 'textarea', 'time', 'url', 'week'\n      ]);\n\n      this.options.title = this.setTitle();\n\n      this.options.htmlClass =\n        addClasses(this.options.htmlClass, 'schema-form-' + this.layoutNode.type);\n      if (this.layoutNode.type !== 'flex') {\n        this.options.htmlClass =\n          this.layoutNode.type === 'array' ?\n            addClasses(this.options.htmlClass, 'list-group') :\n            this.layoutNode.arrayItem && this.layoutNode.type !== '$ref' ?\n              addClasses(this.options.htmlClass, 'list-group-item') :\n              addClasses(this.options.htmlClass, 'form-group');\n      }\n      this.widgetOptions.htmlClass = '';\n      this.options.labelHtmlClass =\n        addClasses(this.options.labelHtmlClass, 'control-label');\n      this.widgetOptions.activeClass =\n        addClasses(this.widgetOptions.activeClass, 'active');\n      this.options.fieldAddonLeft =\n        this.options.fieldAddonLeft || this.options.prepend;\n      this.options.fieldAddonRight =\n        this.options.fieldAddonRight || this.options.append;\n\n      // Add asterisk to titles if required\n      if (this.options.title && this.layoutNode.type !== 'tab' &&\n        !this.options.notitle && this.options.required &&\n        !this.options.title.includes('*')\n      ) {\n        this.options.title += ' <strong class=\"text-danger\">*</strong>';\n      }\n      // Set miscelaneous styles and settings for each control type\n      switch (this.layoutNode.type) {\n        // Checkbox controls\n        case 'checkbox':\n        case 'checkboxes':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'checkbox');\n          break;\n        case 'checkboxes-inline':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'checkbox');\n          this.widgetOptions.itemLabelHtmlClass = addClasses(\n            this.widgetOptions.itemLabelHtmlClass, 'checkbox-inline');\n          break;\n        // Radio controls\n        case 'radio':\n        case 'radios':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'radio');\n          break;\n        case 'radios-inline':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'radio');\n          this.widgetOptions.itemLabelHtmlClass = addClasses(\n            this.widgetOptions.itemLabelHtmlClass, 'radio-inline');\n          break;\n        // Button sets - checkboxbuttons and radiobuttons\n        case 'checkboxbuttons':\n        case 'radiobuttons':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'btn-group');\n          this.widgetOptions.itemLabelHtmlClass = addClasses(\n            this.widgetOptions.itemLabelHtmlClass, 'btn');\n          this.widgetOptions.itemLabelHtmlClass = addClasses(\n            this.widgetOptions.itemLabelHtmlClass, this.options.style || 'btn-default');\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'sr-only');\n          break;\n        // Single button controls\n        case 'button':\n        case 'submit':\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'btn');\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, this.options.style || 'btn-info');\n          break;\n        // Containers - arrays and fieldsets\n        case 'array':\n        case 'fieldset':\n        case 'section':\n        case 'conditional':\n        case 'advancedfieldset':\n        case 'authfieldset':\n        case 'selectfieldset':\n        case 'optionfieldset':\n          this.options.messageLocation = 'top';\n          break;\n        case 'tabarray':\n        case 'tabs':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'tab-content');\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'tab-pane');\n          this.widgetOptions.labelHtmlClass = addClasses(\n            this.widgetOptions.labelHtmlClass, 'nav nav-tabs');\n          break;\n        // 'Add' buttons - references\n        case '$ref':\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'btn pull-right');\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, this.options.style || 'btn-default');\n          this.options.icon = 'glyphicon glyphicon-plus';\n          break;\n        // Default - including regular inputs\n        default:\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'form-control');\n      }\n\n      if (this.formControl) {\n        this.updateHelpBlock(this.formControl.status);\n        this.formControl.statusChanges.subscribe(status => this.updateHelpBlock(status));\n\n        if (this.options.debug) {\n          const vars: any[] = [];\n          this.debugOutput = map(vars, thisVar => JSON.stringify(thisVar, null, 2)).join('\\n');\n        }\n      }\n      this.frameworkInitialized = true;\n    }\n\n  }\n\n  updateHelpBlock(status) {\n    this.options.helpBlock = status === 'INVALID' &&\n    this.options.enableErrorState && this.formControl.errors &&\n    (this.formControl.dirty || this.options.feedbackOnRender) ?\n      this.jsf.formatErrors(this.formControl.errors, this.options.validationMessages) :\n      this.options.description || this.options.help || null;\n  }\n\n  setTitle(): string {\n    switch (this.layoutNode.type) {\n      case 'button':\n      case 'checkbox':\n      case 'section':\n      case 'help':\n      case 'msg':\n      case 'submit':\n      case 'message':\n      case 'tabarray':\n      case 'tabs':\n      case '$ref':\n        return null;\n      case 'advancedfieldset':\n        this.widgetOptions.expandable = true;\n        this.widgetOptions.title = 'Advanced options';\n        return null;\n      case 'authfieldset':\n        this.widgetOptions.expandable = true;\n        this.widgetOptions.title = 'Authentication settings';\n        return null;\n      case 'fieldset':\n        this.widgetOptions.title = this.options.title;\n        return null;\n      default:\n        this.widgetOptions.title = null;\n        return this.jsf.setItemTitle(this);\n    }\n  }\n\n  removeItem() {\n    this.jsf.removeItem(this);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/src/lib/bootstrap3-framework.module.ts",
    "content": "import {NgModule} from '@angular/core';\nimport {CommonModule} from '@angular/common';\nimport {\n  Framework,\n  JsonSchemaFormService,\n  WidgetLibraryService,\n  FrameworkLibraryService,\n  JsonSchemaFormModule,\n  WidgetLibraryModule\n} from '@ajsf/core';\nimport {Bootstrap3Framework} from './bootstrap3.framework';\nimport {Bootstrap3FrameworkComponent} from './bootstrap3-framework.component';\n\n@NgModule({\n    imports: [\n        JsonSchemaFormModule,\n        CommonModule,\n        WidgetLibraryModule,\n    ],\n    declarations: [\n        Bootstrap3FrameworkComponent,\n    ],\n    exports: [\n        JsonSchemaFormModule,\n        Bootstrap3FrameworkComponent,\n    ],\n    providers: [\n        JsonSchemaFormService,\n        FrameworkLibraryService,\n        WidgetLibraryService,\n        { provide: Framework, useClass: Bootstrap3Framework, multi: true },\n    ]\n})\nexport class Bootstrap3FrameworkModule {\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/src/lib/bootstrap3.framework.ts",
    "content": "import {Injectable} from '@angular/core';\nimport {Framework} from '@ajsf/core';\nimport {Bootstrap3FrameworkComponent} from './bootstrap3-framework.component';\n\n// Bootstrap 3 Framework\n// https://github.com/valor-software/ng2-bootstrap\n\n@Injectable()\nexport class Bootstrap3Framework extends Framework {\n  name = 'bootstrap-3';\n\n  framework = Bootstrap3FrameworkComponent;\n\n  stylesheets = [\n    '//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css',\n    '//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css',\n  ];\n\n  scripts = [\n    '//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js',\n    '//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js',\n    '//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js',\n  ];\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/src/public_api.ts",
    "content": "/*\n * Public API Surface of @ajsf/bootstrap3\n */\n\nexport * from './lib/bootstrap3.framework';\nexport * from './lib/bootstrap3-framework.component';\nexport * from './lib/bootstrap3-framework.module';\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js';\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(), {\n    teardown: { destroyAfterEach: false }\n}\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/tsconfig.lib.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/lib\",\n    \"declarationMap\": true,\n    \"target\": \"es2020\",\n    \"declaration\": true,\n    \"inlineSources\": true,\n    \"types\": [],\n    \"lib\": [\n      \"dom\",\n      \"es2018\"\n    ]\n  },\n  \"angularCompilerOptions\": {\n    \"skipTemplateCodegen\": true,\n    \"strictMetadataEmit\": true,\n    \"fullTemplateTypeCheck\": true,\n    \"strictInjectionParameters\": true,\n    \"enableResourceInlining\": true\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/tsconfig.lib.prod.json",
    "content": "{\n  \"extends\": \"./tsconfig.lib.json\",\n  \"compilerOptions\": {\n    \"declarationMap\": false\n  },\n  \"angularCompilerOptions\": {\n    \"compilationMode\": \"partial\"\n  }\n}"
  },
  {
    "path": "projects/ajsf-bootstrap3/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap3/tslint.json",
    "content": "{\n  \"extends\": \"../../tslint.json\",\n  \"rules\": {\n    \"directive-selector\": [\n      true,\n      \"attribute\",\n      \"lib\",\n      \"camelCase\"\n    ]\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/README.md",
    "content": "# @ajsf/bootstrap4\n\n## Getting started\n\n```shell\nnpm install @ajsf/bootstrap4@latest\n```\n\nWith YARN, run the following:\n\n```shell\nyarn add @ajsf/bootstrap4@latest\n```\n\nThen import `Bootstrap4FrameworkModule` in your main application module if you want to use `bootstrap4` UI, like this:\n\n```javascript\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\n\nimport { Bootstrap4FrameworkModule } from '@ajsf/bootstrap4';\n\nimport { AppComponent } from './app.component';\n\n@NgModule({\n  declarations: [ AppComponent ],\n  imports: [\n    Bootstrap4FrameworkModule\n  ],\n  providers: [],\n  bootstrap: [ AppComponent ]\n})\nexport class AppModule { }\n```\n\nFor basic use, after loading JsonSchemaFormModule as described above, to display a form in your Angular component, simply add the following to your component's template:\n\n```html\n<json-schema-form\n  loadExternalAssets=\"true\"\n  [schema]=\"yourJsonSchema\"\n  framework=\"bootstrap-4\"\n  (onSubmit)=\"yourOnSubmitFn($event)\">\n</json-schema-form>\n```\n\nWhere `schema` is a valid JSON schema object, and `onSubmit` calls a function to process the submitted JSON form data. If you don't already have your own schemas, you can find a bunch of samples to test with in the `demo/assets/example-schemas` folder, as described above.\n\n`framework` is for the template you want to use, the default value is `no-framwork`. The possible values are:\n\n* `material-design` for  Material Design.\n* `bootstrap-3` for Bootstrap 3.\n* `bootstrap-4` for 'Bootstrap 4.\n* `no-framework` for (plain HTML).\n\n## Code scaffolding\n\nRun `ng generate component component-name --project @ajsf/bootstrap4` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project @ajsf/bootstrap4`.\n> Note: Don't forget to add `--project @ajsf/bootstrap4` or else it will be added to the default project in your `angular.json` file.\n\n## Build\n\nRun `ng build @ajsf/bootstrap4` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test @ajsf/bootstrap4` to execute the unit tests via [Karma](https://karma-runner.github.io).\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma'),\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, '../../coverage/ajsf-bootstrap4'),\n      reporters: [\n        {\n          type: 'html',\n        },\n        {\n          type: 'lcov',\n        },\n        {\n          type: 'text-summary',\n        },\n      ],\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    singleRun: false,\n    browsers: ['Chrome', 'ChromeHeadlessCI'],\n    customLaunchers: {\n      ChromeHeadlessCI: {\n        base: 'ChromeHeadless',\n        flags: ['--no-sandbox'],\n      },\n    },\n  });\n};\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/@ajsf/bootstrap4\",\n  \"lib\": {\n    \"entryFile\": \"src/public_api.ts\"\n  },\n  \"allowedNonPeerDependencies\": [\n    \"lodash-es\",\n    \"@ajsf/core\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/package.json",
    "content": "{\n  \"name\": \"@ajsf/bootstrap4\",\n  \"version\": \"0.8.0\",\n  \"description\": \"Angular JSON Schema Form builder using Bootstrap 4 UI\",\n  \"author\": \"https://github.com/hamzahamidi/ajsf/graphs/contributors\",\n  \"keywords\": [\n    \"Angular\",\n    \"ng\",\n    \"Angular14\",\n    \"Angular 14\",\n    \"ng14\",\n    \"JSON Schema\",\n    \"form\",\n    \"forms\",\n    \"form builder\",\n    \"Bootstrap\",\n    \"Bootstrap 4\",\n    \"ajsf\",\n    \"angular json schema form\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/hamzahamidi/ajsf\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/hamzahamidi/ajsf/issues\"\n  },\n  \"private\": false,\n  \"dependencies\": {\n    \"lodash-es\": \"~4.17.21\",\n    \"@ajsf/core\": \"~0.8.0\",\n    \"tslib\": \"^2.0.0\"\n  },\n  \"peerDependencies\": {\n    \"@angular/common\": \">=14.0.0\",\n    \"@angular/core\": \">=14.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/lodash-es\": \"^4.17.6\"\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/src/lib/bootstrap4-framework.component.html",
    "content": "<div\n  [class]=\"options?.htmlClass || ''\"\n  [class.has-feedback]=\"options?.feedback && options?.isInputWidget &&\n        (formControl?.dirty || options?.feedbackOnRender)\"\n  [class.has-error]=\"options?.enableErrorState && formControl?.errors &&\n        (formControl?.dirty || options?.feedbackOnRender)\"\n  [class.has-success]=\"options?.enableSuccessState && !formControl?.errors &&\n        (formControl?.dirty || options?.feedbackOnRender)\">\n\n  <button *ngIf=\"showRemoveButton\"\n          class=\"close pull-right\"\n          type=\"button\"\n          (click)=\"removeItem()\">\n    <span aria-hidden=\"true\">&times;</span>\n    <span class=\"sr-only\">Close</span>\n  </button>\n  <div *ngIf=\"options?.messageLocation === 'top'\">\n    <p *ngIf=\"options?.helpBlock\"\n       class=\"help-block\"\n       [innerHTML]=\"options?.helpBlock\"></p>\n  </div>\n\n  <label *ngIf=\"options?.title && layoutNode?.type !== 'tab'\"\n         [attr.for]=\"'control' + layoutNode?._id\"\n         [class]=\"options?.labelHtmlClass || ''\"\n         [class.sr-only]=\"options?.notitle\"\n         [innerHTML]=\"options?.title\"></label>\n  <p *ngIf=\"layoutNode?.type === 'submit' && jsf?.formOptions?.fieldsRequired\">\n    <strong class=\"text-danger\">*</strong> = required fields\n  </p>\n  <div [class.input-group]=\"options?.fieldAddonLeft || options?.fieldAddonRight\">\n        <span *ngIf=\"options?.fieldAddonLeft\"\n              class=\"input-group-addon\"\n              [innerHTML]=\"options?.fieldAddonLeft\"></span>\n\n    <select-widget-widget\n      [layoutNode]=\"widgetLayoutNode\"\n      [dataIndex]=\"dataIndex\"\n      [layoutIndex]=\"layoutIndex\"></select-widget-widget>\n\n    <span *ngIf=\"options?.fieldAddonRight\"\n          class=\"input-group-addon\"\n          [innerHTML]=\"options?.fieldAddonRight\"></span>\n  </div>\n\n  <span *ngIf=\"options?.feedback && options?.isInputWidget &&\n          !options?.fieldAddonRight && !layoutNode.arrayItem &&\n          (formControl?.dirty || options?.feedbackOnRender)\"\n        [class.glyphicon-ok]=\"options?.enableSuccessState && !formControl?.errors\"\n        [class.glyphicon-remove]=\"options?.enableErrorState && formControl?.errors\"\n        aria-hidden=\"true\"\n        class=\"form-control-feedback glyphicon\"></span>\n  <div *ngIf=\"options?.messageLocation !== 'top'\">\n    <p *ngIf=\"options?.helpBlock\"\n       class=\"help-block\"\n       [innerHTML]=\"options?.helpBlock\"></p>\n  </div>\n</div>\n\n<div *ngIf=\"debug && debugOutput\">debug:\n  <pre>{{debugOutput}}</pre>\n</div>\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/src/lib/bootstrap4-framework.component.scss",
    "content": ":host ::ng-deep {\n  .list-group-item .form-control-feedback {\n    top: 40px;\n  }\n\n  .checkbox,\n  .radio {\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n\n  .checkbox-inline,\n  .checkbox-inline + .checkbox-inline,\n  .checkbox-inline + .radio-inline,\n  .radio-inline,\n  .radio-inline + .radio-inline,\n  .radio-inline + .checkbox-inline {\n    margin-left: 0;\n    margin-right: 10px;\n  }\n\n  .checkbox-inline:last-child,\n  .radio-inline:last-child {\n    margin-right: 0;\n  }\n\n  .ng-invalid.ng-touched {\n    border: 1px solid #f44336;\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/src/lib/bootstrap4-framework.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\nimport { CommonModule } from '@angular/common';\nimport {\n  JsonSchemaFormModule,\n  JsonSchemaFormService,\n  WidgetLibraryModule\n} from '@ajsf/core';\nimport { Bootstrap4FrameworkComponent } from './bootstrap4-framework.component';\n\ndescribe('FwBootstrap4Component', () => {\n  let component: Bootstrap4FrameworkComponent;\n  let fixture: ComponentFixture<Bootstrap4FrameworkComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        JsonSchemaFormModule,\n        CommonModule,\n        WidgetLibraryModule,\n      ],\n      declarations: [Bootstrap4FrameworkComponent],\n      providers: [JsonSchemaFormService]\n    })\n      .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(Bootstrap4FrameworkComponent);\n    component = fixture.componentInstance;\n    component.layoutNode = { options: {} };\n    component.layoutIndex = [];\n    component.dataIndex = [];\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/src/lib/bootstrap4-framework.component.ts",
    "content": "import {\n  ChangeDetectorRef,\n  Component,\n  Input,\n  OnChanges,\n  OnInit\n} from '@angular/core';\nimport cloneDeep from 'lodash/cloneDeep';\nimport map from 'lodash/map';\nimport {JsonSchemaFormService, addClasses, inArray} from '@ajsf/core';\n\n/**\n * Bootstrap 4 framework for Angular JSON Schema Form.\n *\n */\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'bootstrap-4-framework',\n  templateUrl: './bootstrap4-framework.component.html',\n  styleUrls: ['./bootstrap4-framework.component.scss'],\n})\nexport class Bootstrap4FrameworkComponent implements OnInit, OnChanges {\n  frameworkInitialized = false;\n  widgetOptions: any; // Options passed to child widget\n  widgetLayoutNode: any; // layoutNode passed to child widget\n  options: any; // Options used in this framework\n  formControl: any = null;\n  debugOutput: any = '';\n  debug: any = '';\n  parentArray: any = null;\n  isOrderable = false;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    public changeDetector: ChangeDetectorRef,\n    public jsf: JsonSchemaFormService\n  ) {\n  }\n\n  get showRemoveButton(): boolean {\n    if (!this.options.removable || this.options.readonly ||\n      this.layoutNode.type === '$ref'\n    ) {\n      return false;\n    }\n    if (this.layoutNode.recursiveReference) {\n      return true;\n    }\n    if (!this.layoutNode.arrayItem || !this.parentArray) {\n      return false;\n    }\n    // If array length <= minItems, don't allow removing any items\n    return this.parentArray.items.length - 1 <= this.parentArray.options.minItems ? false :\n      // For removable list items, allow removing any item\n      this.layoutNode.arrayItemType === 'list' ? true :\n        // For removable tuple items, only allow removing last item in list\n        this.layoutIndex[this.layoutIndex.length - 1] === this.parentArray.items.length - 2;\n  }\n\n  ngOnInit() {\n    this.initializeFramework();\n    if (this.layoutNode.arrayItem && this.layoutNode.type !== '$ref') {\n      this.parentArray = this.jsf.getParentNode(this);\n      if (this.parentArray) {\n        this.isOrderable = this.layoutNode.arrayItemType === 'list' &&\n          !this.options.readonly && this.parentArray.options.orderable;\n      }\n    }\n  }\n\n  ngOnChanges() {\n    if (!this.frameworkInitialized) {\n      this.initializeFramework();\n    }\n  }\n\n  initializeFramework() {\n    if (this.layoutNode) {\n      this.options = cloneDeep(this.layoutNode.options);\n      this.widgetLayoutNode = {\n        ...this.layoutNode,\n        options: cloneDeep(this.layoutNode.options)\n      };\n      this.widgetOptions = this.widgetLayoutNode.options;\n      this.formControl = this.jsf.getFormControl(this);\n\n      this.options.isInputWidget = inArray(this.layoutNode.type, [\n        'button', 'checkbox', 'checkboxes-inline', 'checkboxes', 'color',\n        'date', 'datetime-local', 'datetime', 'email', 'file', 'hidden',\n        'image', 'integer', 'month', 'number', 'password', 'radio',\n        'radiobuttons', 'radios-inline', 'radios', 'range', 'reset', 'search',\n        'select', 'submit', 'tel', 'text', 'textarea', 'time', 'url', 'week'\n      ]);\n\n      this.options.title = this.setTitle();\n\n      this.options.htmlClass =\n        addClasses(this.options.htmlClass, 'schema-form-' + this.layoutNode.type);\n      this.options.htmlClass =\n        this.layoutNode.type === 'array' ?\n          addClasses(this.options.htmlClass, 'list-group') :\n          this.layoutNode.arrayItem && this.layoutNode.type !== '$ref' ?\n            addClasses(this.options.htmlClass, 'list-group-item') :\n            addClasses(this.options.htmlClass, 'form-group');\n      this.widgetOptions.htmlClass = '';\n      this.options.labelHtmlClass =\n        addClasses(this.options.labelHtmlClass, 'control-label');\n      this.widgetOptions.activeClass =\n        addClasses(this.widgetOptions.activeClass, 'active');\n      this.options.fieldAddonLeft =\n        this.options.fieldAddonLeft || this.options.prepend;\n      this.options.fieldAddonRight =\n        this.options.fieldAddonRight || this.options.append;\n\n      // Add asterisk to titles if required\n      if (this.options.title && this.layoutNode.type !== 'tab' &&\n        !this.options.notitle && this.options.required &&\n        !this.options.title.includes('*')\n      ) {\n        this.options.title += ' <strong class=\"text-danger\">*</strong>';\n      }\n      // Set miscelaneous styles and settings for each control type\n      switch (this.layoutNode.type) {\n        // Checkbox controls\n        case 'checkbox':\n        case 'checkboxes':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'checkbox');\n          break;\n        case 'checkboxes-inline':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'checkbox');\n          this.widgetOptions.itemLabelHtmlClass = addClasses(\n            this.widgetOptions.itemLabelHtmlClass, 'checkbox-inline');\n          break;\n        // Radio controls\n        case 'radio':\n        case 'radios':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'radio');\n          break;\n        case 'radios-inline':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'radio');\n          this.widgetOptions.itemLabelHtmlClass = addClasses(\n            this.widgetOptions.itemLabelHtmlClass, 'radio-inline');\n          break;\n        // Button sets - checkboxbuttons and radiobuttons\n        case 'checkboxbuttons':\n        case 'radiobuttons':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'btn-group');\n          this.widgetOptions.itemLabelHtmlClass = addClasses(\n            this.widgetOptions.itemLabelHtmlClass, 'btn');\n          this.widgetOptions.itemLabelHtmlClass = addClasses(\n            this.widgetOptions.itemLabelHtmlClass, this.options.style || 'btn-default');\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'sr-only');\n          break;\n        // Single button controls\n        case 'button':\n        case 'submit':\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'btn');\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, this.options.style || 'btn-info');\n          break;\n        // Containers - arrays and fieldsets\n        case 'array':\n        case 'fieldset':\n        case 'section':\n        case 'conditional':\n        case 'advancedfieldset':\n        case 'authfieldset':\n        case 'selectfieldset':\n        case 'optionfieldset':\n          this.options.messageLocation = 'top';\n          break;\n        case 'tabarray':\n        case 'tabs':\n          this.widgetOptions.htmlClass = addClasses(\n            this.widgetOptions.htmlClass, 'tab-content');\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'tab-pane');\n          this.widgetOptions.labelHtmlClass = addClasses(\n            this.widgetOptions.labelHtmlClass, 'nav nav-tabs');\n          break;\n        // 'Add' buttons - references\n        case '$ref':\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'btn pull-right');\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, this.options.style || 'btn-default');\n          this.options.icon = 'glyphicon glyphicon-plus';\n          break;\n        // Default - including regular inputs\n        default:\n          this.widgetOptions.fieldHtmlClass = addClasses(\n            this.widgetOptions.fieldHtmlClass, 'form-control');\n      }\n\n      if (this.formControl) {\n        this.updateHelpBlock(this.formControl.status);\n        this.formControl.statusChanges.subscribe(status => this.updateHelpBlock(status));\n\n        if (this.options.debug) {\n          const vars: any[] = [];\n          this.debugOutput = map(vars, thisVar => JSON.stringify(thisVar, null, 2)).join('\\n');\n        }\n      }\n      this.frameworkInitialized = true;\n    }\n\n  }\n\n  updateHelpBlock(status) {\n    this.options.helpBlock = status === 'INVALID' &&\n    this.options.enableErrorState && this.formControl.errors &&\n    (this.formControl.dirty || this.options.feedbackOnRender) ?\n      this.jsf.formatErrors(this.formControl.errors, this.options.validationMessages) :\n      this.options.description || this.options.help || null;\n  }\n\n  setTitle(): string {\n    switch (this.layoutNode.type) {\n      case 'button':\n      case 'checkbox':\n      case 'section':\n      case 'help':\n      case 'msg':\n      case 'submit':\n      case 'message':\n      case 'tabarray':\n      case 'tabs':\n      case '$ref':\n        return null;\n      case 'advancedfieldset':\n        this.widgetOptions.expandable = true;\n        this.widgetOptions.title = 'Advanced options';\n        return null;\n      case 'authfieldset':\n        this.widgetOptions.expandable = true;\n        this.widgetOptions.title = 'Authentication settings';\n        return null;\n      case 'fieldset':\n        this.widgetOptions.title = this.options.title;\n        return null;\n      default:\n        this.widgetOptions.title = null;\n        return this.jsf.setItemTitle(this);\n    }\n  }\n\n  removeItem() {\n    this.jsf.removeItem(this);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/src/lib/bootstrap4-framework.module.ts",
    "content": "import {NgModule} from '@angular/core';\nimport {CommonModule} from '@angular/common';\nimport {\n  Framework,\n  JsonSchemaFormService,\n  WidgetLibraryService,\n  FrameworkLibraryService,\n  JsonSchemaFormModule,\n  WidgetLibraryModule\n} from '@ajsf/core';\nimport {Bootstrap4Framework} from './bootstrap4.framework';\nimport {Bootstrap4FrameworkComponent} from './bootstrap4-framework.component';\n\n@NgModule({\n    imports: [\n        JsonSchemaFormModule,\n        CommonModule,\n        WidgetLibraryModule,\n    ],\n    declarations: [\n        Bootstrap4FrameworkComponent,\n    ],\n    exports: [\n        JsonSchemaFormModule,\n        Bootstrap4FrameworkComponent,\n    ],\n    providers: [\n        JsonSchemaFormService,\n        FrameworkLibraryService,\n        WidgetLibraryService,\n        { provide: Framework, useClass: Bootstrap4Framework, multi: true },\n    ]\n})\nexport class Bootstrap4FrameworkModule {\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/src/lib/bootstrap4.framework.ts",
    "content": "import {Injectable} from '@angular/core';\nimport {Framework} from '@ajsf/core';\nimport {Bootstrap4FrameworkComponent} from './bootstrap4-framework.component';\n\n// Bootstrap 4 Framework\n// https://github.com/ng-bootstrap/ng-bootstrap\n\n@Injectable()\nexport class Bootstrap4Framework extends Framework {\n  name = 'bootstrap-4';\n\n  framework = Bootstrap4FrameworkComponent;\n\n  stylesheets = [\n    '//stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'\n  ];\n\n  scripts = [\n    '//code.jquery.com/jquery-3.3.1.slim.min.js',\n    '//cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js',\n    '//stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js',\n  ];\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/src/public_api.ts",
    "content": "/*\n * Public API Surface of @ajsf/bootstrap4\n */\n\nexport * from './lib/bootstrap4-framework.module';\nexport * from './lib/bootstrap4-framework.component';\nexport * from './lib/bootstrap4.framework';\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js';\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(), {\n    teardown: { destroyAfterEach: false }\n}\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/tsconfig.lib.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/lib\",\n    \"declarationMap\": true,\n    \"target\": \"es2020\",\n    \"declaration\": true,\n    \"inlineSources\": true,\n    \"types\": [],\n    \"lib\": [\n      \"dom\",\n      \"es2018\"\n    ]\n  },\n  \"angularCompilerOptions\": {\n    \"skipTemplateCodegen\": true,\n    \"strictMetadataEmit\": true,\n    \"fullTemplateTypeCheck\": true,\n    \"strictInjectionParameters\": true,\n    \"enableResourceInlining\": true\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/tsconfig.lib.prod.json",
    "content": "{\n  \"extends\": \"./tsconfig.lib.json\",\n  \"compilerOptions\": {\n    \"declarationMap\": false\n  },\n  \"angularCompilerOptions\": {\n    \"compilationMode\": \"partial\"\n  }\n}"
  },
  {
    "path": "projects/ajsf-bootstrap4/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-bootstrap4/tslint.json",
    "content": "{\n  \"extends\": \"../../tslint.json\",\n  \"rules\": {\n    \"directive-selector\": [\n      true,\n      \"attribute\",\n      \"lib\",\n      \"camelCase\"\n    ]\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma'),\n    ],\n    client: {\n      clearContext: false, // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, '../../coverage/ajsf-core'),\n      reporters: [\n        {\n          type: 'html',\n        },\n        {\n          type: 'lcov',\n        },\n        {\n          type: 'text-summary',\n        },\n      ],\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    singleRun: false,\n    browsers: ['Chrome', 'ChromeHeadlessCI'],\n    customLaunchers: {\n      ChromeHeadlessCI: {\n        base: 'ChromeHeadless',\n        flags: ['--no-sandbox'],\n      },\n    },\n  });\n};\n"
  },
  {
    "path": "projects/ajsf-core/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/@ajsf/core\",\n  \"lib\": {\n    \"entryFile\": \"src/public_api.ts\"\n  },\n  \"allowedNonPeerDependencies\": [\n    \"lodash-es\",\n    \"ajv\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-core/package.json",
    "content": "{\n  \"name\": \"@ajsf/core\",\n  \"version\": \"0.8.0\",\n  \"description\": \"Angular JSON Schema Form builder core\",\n  \"author\": \"https://github.com/hamzahamidi/ajsf/graphs/contributors\",\n  \"keywords\": [\n    \"Angular\",\n    \"ng\",\n    \"Angular14\",\n    \"Angular 14\",\n    \"ng14\",\n    \"JSON Schema\",\n    \"form\",\n    \"forms\",\n    \"form builder\",\n    \"ajsf\",\n    \"angular json schema form\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/hamzahamidi/ajsf\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/hamzahamidi/ajsf/issues\"\n  },\n  \"private\": false,\n  \"dependencies\": {\n    \"lodash-es\": \"~4.17.21\",\n    \"ajv\": \"^6.10.0\",\n    \"tslib\": \"^2.0.0\"\n  },\n  \"peerDependencies\": {\n    \"@angular/common\": \">=14.0.0\",\n    \"@angular/core\": \">=14.0.0\",\n    \"@angular/forms\": \">=14.0.0\",\n    \"@angular/platform-browser\": \">=14.0.0\",\n    \"rxjs\": \"^7.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/lodash-es\": \"^4.17.6\"\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/framework-library/framework-library.service.ts",
    "content": "import { Framework } from './framework';\nimport { hasOwn } from '../shared/utility.functions';\nimport { Inject, Injectable } from '@angular/core';\nimport { WidgetLibraryService } from '../widget-library/widget-library.service';\n\n// Possible future frameworks:\n// - Foundation 6:\n//   http://justindavis.co/2017/06/15/using-foundation-6-in-angular-4/\n//   https://github.com/zurb/foundation-sites\n// - Semantic UI:\n//   https://github.com/edcarroll/ng2-semantic-ui\n//   https://github.com/vladotesanovic/ngSemantic\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class FrameworkLibraryService {\n  activeFramework: Framework = null;\n  stylesheets: (HTMLStyleElement|HTMLLinkElement)[];\n  scripts: HTMLScriptElement[];\n  loadExternalAssets = false;\n  defaultFramework: string;\n  frameworkLibrary: { [name: string]: Framework } = {};\n\n  constructor(\n    @Inject(Framework) private frameworks: any[],\n    @Inject(WidgetLibraryService) private widgetLibrary: WidgetLibraryService\n  ) {\n    this.frameworks.forEach(framework =>\n      this.frameworkLibrary[framework.name] = framework\n    );\n    this.defaultFramework = this.frameworks[0].name;\n    this.setFramework(this.defaultFramework);\n  }\n\n  public setLoadExternalAssets(loadExternalAssets = true): void {\n    this.loadExternalAssets = !!loadExternalAssets;\n  }\n\n  public setFramework(\n    framework: string|Framework = this.defaultFramework,\n    loadExternalAssets = this.loadExternalAssets\n  ): boolean {\n    this.activeFramework =\n      typeof framework === 'string' && this.hasFramework(framework) ?\n        this.frameworkLibrary[framework] :\n      typeof framework === 'object' && hasOwn(framework, 'framework') ?\n        framework :\n        this.frameworkLibrary[this.defaultFramework];\n    return this.registerFrameworkWidgets(this.activeFramework);\n  }\n\n  registerFrameworkWidgets(framework: Framework): boolean {\n    return hasOwn(framework, 'widgets') ?\n      this.widgetLibrary.registerFrameworkWidgets(framework.widgets) :\n      this.widgetLibrary.unRegisterFrameworkWidgets();\n  }\n\n  public hasFramework(type: string): boolean {\n    return hasOwn(this.frameworkLibrary, type);\n  }\n\n  public getFramework(): any {\n    if (!this.activeFramework) { this.setFramework('default', true); }\n    return this.activeFramework.framework;\n  }\n\n  public getFrameworkWidgets(): any {\n    return this.activeFramework.widgets || {};\n  }\n\n  public getFrameworkStylesheets(load: boolean = this.loadExternalAssets): string[] {\n    return (load && this.activeFramework.stylesheets) || [];\n  }\n\n  public getFrameworkScripts(load: boolean = this.loadExternalAssets): string[] {\n    return (load && this.activeFramework.scripts) || [];\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/framework-library/framework.ts",
    "content": "import { Injectable } from '@angular/core';\n\n@Injectable()\nexport class Framework {\n  name: string;\n  framework: any;\n  widgets?: { [key: string]: any } = {};\n  stylesheets?: string[] = [];\n  scripts?: string[] = [];\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/framework-library/no-framework.component.html",
    "content": "<select-widget-widget [dataIndex]=\"dataIndex\" [layoutIndex]=\"layoutIndex\" [layoutNode]=\"layoutNode\">\n</select-widget-widget>"
  },
  {
    "path": "projects/ajsf-core/src/lib/framework-library/no-framework.component.ts",
    "content": "import { Component, Input } from '@angular/core';\n\n@Component({\n  selector: 'no-framework',\n  templateUrl: './no-framework.component.html',\n})\nexport class NoFrameworkComponent {\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/framework-library/no-framework.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { Framework } from './framework';\nimport { NgModule } from '@angular/core';\nimport { NoFramework } from './no.framework';\nimport { NoFrameworkComponent } from './no-framework.component';\nimport { WidgetLibraryModule } from '../widget-library/widget-library.module';\n\n// No framework - plain HTML controls (styles from form layout only)\n\n@NgModule({\n    imports: [CommonModule, WidgetLibraryModule],\n    declarations: [NoFrameworkComponent],\n    exports: [NoFrameworkComponent],\n    providers: [\n        { provide: Framework, useClass: NoFramework, multi: true }\n    ]\n})\nexport class NoFrameworkModule { }\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/framework-library/no.framework.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Framework } from './framework';\nimport { NoFrameworkComponent } from './no-framework.component';\n// No framework - plain HTML controls (styles from form layout only)\n\n@Injectable()\nexport class NoFramework extends Framework {\n  name = 'no-framework';\n\n  framework = NoFrameworkComponent;\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/json-schema-form.component.html",
    "content": "<form [autocomplete]=\"jsf?.formOptions?.autocomplete ? 'on' : 'off'\" class=\"json-schema-form\" (ngSubmit)=\"submitForm()\">\n  <root-widget [layout]=\"jsf?.layout\"></root-widget>\n</form>\n<div *ngIf=\"debug || jsf?.formOptions?.debug\">\n  Debug output:\n  <pre>{{debugOutput}}</pre>\n</div>"
  },
  {
    "path": "projects/ajsf-core/src/lib/json-schema-form.component.ts",
    "content": "import cloneDeep from 'lodash/cloneDeep';\nimport isEqual from 'lodash/isEqual';\n\nimport {\n  ChangeDetectionStrategy,\n  ChangeDetectorRef,\n  Component,\n  EventEmitter,\n  forwardRef,\n  Input,\n  OnChanges,\n  OnInit,\n  Output,\n  SimpleChanges,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { Subject } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\nimport { convertSchemaToDraft6 } from './shared/convert-schema-to-draft6.function';\nimport { forEach, hasOwn } from './shared/utility.functions';\nimport { FrameworkLibraryService } from './framework-library/framework-library.service';\nimport {\n  hasValue,\n  inArray,\n  isArray,\n  isEmpty,\n  isObject\n} from './shared/validator.functions';\nimport { JsonPointer } from './shared/jsonpointer.functions';\nimport { JsonSchemaFormService } from './json-schema-form.service';\nimport { resolveSchemaReferences } from './shared/json-schema.functions';\nimport { WidgetLibraryService } from './widget-library/widget-library.service';\n\nexport const JSON_SCHEMA_FORM_VALUE_ACCESSOR: any = {\n  provide: NG_VALUE_ACCESSOR,\n  useExisting: forwardRef(() => JsonSchemaFormComponent),\n  multi: true,\n};\n\n/**\n * @module 'JsonSchemaFormComponent' - Angular JSON Schema Form\n *\n * Root module of the Angular JSON Schema Form client-side library,\n * an Angular library which generates an HTML form from a JSON schema\n * structured data model and/or a JSON Schema Form layout description.\n *\n * This library also validates input data by the user, using both validators on\n * individual controls to provide real-time feedback while the user is filling\n * out the form, and then validating the entire input against the schema when\n * the form is submitted to make sure the returned JSON data object is valid.\n *\n * This library is similar to, and mostly API compatible with:\n *\n * - JSON Schema Form's Angular Schema Form library for AngularJs\n *   http://schemaform.io\n *   http://schemaform.io/examples/bootstrap-example.html (examples)\n *\n * - Mozilla's react-jsonschema-form library for React\n *   https://github.com/mozilla-services/react-jsonschema-form\n *   https://mozilla-services.github.io/react-jsonschema-form (examples)\n *\n * - Joshfire's JSON Form library for jQuery\n *   https://github.com/joshfire/jsonform\n *   http://ulion.github.io/jsonform/playground (examples)\n *\n * This library depends on:\n *  - Angular (obviously)                  https://angular.io\n *  - lodash, JavaScript utility library   https://github.com/lodash/lodash\n *  - ajv, Another JSON Schema validator   https://github.com/epoberezkin/ajv\n *\n * In addition, the Example Playground also depends on:\n *  - brace, Browserified Ace editor       http://thlorenz.github.io/brace\n */\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'json-schema-form',\n  templateUrl: './json-schema-form.component.html',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  // Adding 'JsonSchemaFormService' here, instead of in the module,\n  // creates a separate instance of the service for each component\n  providers:  [ JsonSchemaFormService, JSON_SCHEMA_FORM_VALUE_ACCESSOR ],\n})\nexport class JsonSchemaFormComponent implements ControlValueAccessor, OnChanges, OnInit {\n  // TODO: quickfix to avoid subscribing twice to the same emitters\n  private unsubscribeOnActivateForm$ = new Subject<void>();\n\n  debugOutput: any; // Debug information, if requested\n  formValueSubscription: any = null;\n  formInitialized = false;\n  objectWrap = false; // Is non-object input schema wrapped in an object?\n\n  formValuesInput: string; // Name of the input providing the form data\n  previousInputs: { // Previous input values, to detect which input triggers onChanges\n    schema: any, layout: any[], data: any, options: any, framework: any | string,\n    widgets: any, form: any, model: any, JSONSchema: any, UISchema: any,\n    formData: any, loadExternalAssets: boolean, debug: boolean,\n  } = {\n      schema: null, layout: null, data: null, options: null, framework: null,\n      widgets: null, form: null, model: null, JSONSchema: null, UISchema: null,\n      formData: null, loadExternalAssets: null, debug: null,\n    };\n\n  // Recommended inputs\n  @Input() schema: any; // The JSON Schema\n  @Input() layout: any[]; // The form layout\n  @Input() data: any; // The form data\n  @Input() options: any; // The global form options\n  @Input() framework: any | string; // The framework to load\n  @Input() widgets: any; // Any custom widgets to load\n\n  // Alternate combined single input\n  @Input() form: any; // For testing, and JSON Schema Form API compatibility\n\n  // Angular Schema Form API compatibility input\n  @Input() model: any; // Alternate input for form data\n\n  // React JSON Schema Form API compatibility inputs\n  @Input() JSONSchema: any; // Alternate input for JSON Schema\n  @Input() UISchema: any; // UI schema - alternate form layout format\n  @Input() formData: any; // Alternate input for form data\n\n  @Input() ngModel: any; // Alternate input for Angular forms\n\n  @Input() language: string; // Language\n\n  // Development inputs, for testing and debugging\n  @Input() loadExternalAssets: boolean; // Load external framework assets?\n  @Input() debug: boolean; // Show debug information?\n\n  @Input()\n  get value(): any {\n    return this.objectWrap ? this.jsf.data['1'] : this.jsf.data;\n  }\n  set value(value: any) {\n    this.setFormValues(value, false);\n  }\n\n  // Outputs\n  @Output() onChanges = new EventEmitter<any>(); // Live unvalidated internal form data\n  @Output() onSubmit = new EventEmitter<any>(); // Complete validated form data\n  @Output() isValid = new EventEmitter<boolean>(); // Is current data valid?\n  @Output() validationErrors = new EventEmitter<any>(); // Validation errors (if any)\n  @Output() formSchema = new EventEmitter<any>(); // Final schema used to create form\n  @Output() formLayout = new EventEmitter<any>(); // Final layout used to create form\n\n  // Outputs for possible 2-way data binding\n  // Only the one input providing the initial form data will be bound.\n  // If there is no inital data, input '{}' to activate 2-way data binding.\n  // There is no 2-way binding if inital data is combined inside the 'form' input.\n  @Output() dataChange = new EventEmitter<any>();\n  @Output() modelChange = new EventEmitter<any>();\n  @Output() formDataChange = new EventEmitter<any>();\n  @Output() ngModelChange = new EventEmitter<any>();\n\n  onChange: Function;\n  onTouched: Function;\n\n  constructor(\n    private changeDetector: ChangeDetectorRef,\n    private frameworkLibrary: FrameworkLibraryService,\n    private widgetLibrary: WidgetLibraryService,\n    public jsf: JsonSchemaFormService,\n  ) { }\n\n  private resetScriptsAndStyleSheets() {\n    document.querySelectorAll('.ajsf').forEach(element => element.remove());\n  }\n  private loadScripts() {\n    const scripts = this.frameworkLibrary.getFrameworkScripts();\n    scripts.map(script => {\n      const scriptTag: HTMLScriptElement = document.createElement('script');\n      scriptTag.src = script;\n      scriptTag.type = 'text/javascript';\n      scriptTag.async = true;\n      scriptTag.setAttribute('class', 'ajsf');\n      document.getElementsByTagName('head')[0].appendChild(scriptTag);\n    });\n  }\n  private loadStyleSheets() {\n    const stylesheets = this.frameworkLibrary.getFrameworkStylesheets();\n    stylesheets.map(stylesheet => {\n      const linkTag: HTMLLinkElement = document.createElement('link');\n      linkTag.rel = 'stylesheet';\n      linkTag.href = stylesheet;\n      linkTag.setAttribute('class', 'ajsf');\n      document.getElementsByTagName('head')[0].appendChild(linkTag);\n    });\n  }\n  private loadAssets() {\n    this.resetScriptsAndStyleSheets();\n    this.loadScripts();\n    this.loadStyleSheets();\n  }\n  ngOnInit() {\n    this.updateForm();\n    this.loadAssets();\n  }\n\n  ngOnChanges(changes: SimpleChanges) {\n    this.updateForm();\n    // Check if there's changes in Framework then load assets if that's the\n    if (changes.framework) {\n      if (!changes.framework.isFirstChange() &&\n        (changes.framework.previousValue !== changes.framework.currentValue)) {\n        this.loadAssets();\n      }\n    }\n  }\n\n  writeValue(value: any) {\n    this.setFormValues(value, false);\n    if (!this.formValuesInput) { this.formValuesInput = 'ngModel'; }\n  }\n\n  registerOnChange(fn: Function) {\n    this.onChange = fn;\n  }\n\n  registerOnTouched(fn: Function) {\n    this.onTouched = fn;\n  }\n\n  setDisabledState(isDisabled: boolean) {\n    if (this.jsf.formOptions.formDisabled !== !!isDisabled) {\n      this.jsf.formOptions.formDisabled = !!isDisabled;\n      this.initializeForm();\n    }\n  }\n\n  updateForm() {\n    if (!this.formInitialized || !this.formValuesInput ||\n      (this.language && this.language !== this.jsf.language)\n    ) {\n      this.initializeForm();\n    } else {\n      if (this.language && this.language !== this.jsf.language) {\n        this.jsf.setLanguage(this.language);\n      }\n\n      // Get names of changed inputs\n      let changedInput = Object.keys(this.previousInputs)\n        .filter(input => this.previousInputs[input] !== this[input]);\n      let resetFirst = true;\n      if (changedInput.length === 1 && changedInput[0] === 'form' &&\n        this.formValuesInput.startsWith('form.')\n      ) {\n        // If only 'form' input changed, get names of changed keys\n        changedInput = Object.keys(this.previousInputs.form || {})\n          .filter(key => !isEqual(this.previousInputs.form[key], this.form[key]))\n          .map(key => `form.${key}`);\n        resetFirst = false;\n      }\n\n      // If only input values have changed, update the form values\n      if (changedInput.length === 1 && changedInput[0] === this.formValuesInput) {\n        if (this.formValuesInput.indexOf('.') === -1) {\n          this.setFormValues(this[this.formValuesInput], resetFirst);\n        } else {\n          const [input, key] = this.formValuesInput.split('.');\n          this.setFormValues(this[input][key], resetFirst);\n        }\n\n        // If anything else has changed, re-render the entire form\n      } else if (changedInput.length) {\n        this.initializeForm();\n        if (this.onChange) { this.onChange(this.jsf.formValues); }\n        if (this.onTouched) { this.onTouched(this.jsf.formValues); }\n      }\n\n      // Update previous inputs\n      Object.keys(this.previousInputs)\n        .filter(input => this.previousInputs[input] !== this[input])\n        .forEach(input => this.previousInputs[input] = this[input]);\n    }\n  }\n\n  setFormValues(formValues: any, resetFirst = true) {\n    if (formValues) {\n      const newFormValues = this.objectWrap ? formValues['1'] : formValues;\n      if (!this.jsf.formGroup) {\n        this.jsf.formValues = formValues;\n        this.activateForm();\n      } else if (resetFirst) {\n        this.jsf.formGroup.reset();\n      }\n      if (this.jsf.formGroup) {\n        this.jsf.formGroup.patchValue(newFormValues);\n      }\n      if (this.onChange) { this.onChange(newFormValues); }\n      if (this.onTouched) { this.onTouched(newFormValues); }\n    } else {\n      this.jsf.formGroup.reset();\n    }\n  }\n\n  submitForm() {\n    const validData = this.jsf.validData;\n    this.onSubmit.emit(this.objectWrap ? validData['1'] : validData);\n  }\n\n  /**\n   * 'initializeForm' function\n   *\n   * - Update 'schema', 'layout', and 'formValues', from inputs.\n   *\n   * - Create 'schemaRefLibrary' and 'schemaRecursiveRefMap'\n   *   to resolve schema $ref links, including recursive $ref links.\n   *\n   * - Create 'dataRecursiveRefMap' to resolve recursive links in data\n   *   and corectly set output formats for recursively nested values.\n   *\n   * - Create 'layoutRefLibrary' and 'templateRefLibrary' to store\n   *   new layout nodes and formGroup elements to use when dynamically\n   *   adding form components to arrays and recursive $ref points.\n   *\n   * - Create 'dataMap' to map the data to the schema and template.\n   *\n   * - Create the master 'formGroupTemplate' then from it 'formGroup'\n   *   the Angular formGroup used to control the reactive form.\n   */\n  initializeForm() {\n    if (\n      this.schema || this.layout || this.data || this.form || this.model ||\n      this.JSONSchema || this.UISchema || this.formData || this.ngModel ||\n      this.jsf.data\n    ) {\n\n      this.jsf.resetAllValues();  // Reset all form values to defaults\n      this.initializeOptions();   // Update options\n      this.initializeSchema();    // Update schema, schemaRefLibrary,\n      // schemaRecursiveRefMap, & dataRecursiveRefMap\n      this.initializeLayout();    // Update layout, layoutRefLibrary,\n      this.initializeData();      // Update formValues\n      this.activateForm();        // Update dataMap, templateRefLibrary,\n      // formGroupTemplate, formGroup\n\n      // Uncomment individual lines to output debugging information to console:\n      // (These always work.)\n      // console.log('loading form...');\n      // console.log('schema', this.jsf.schema);\n      // console.log('layout', this.jsf.layout);\n      // console.log('options', this.options);\n      // console.log('formValues', this.jsf.formValues);\n      // console.log('formGroupTemplate', this.jsf.formGroupTemplate);\n      // console.log('formGroup', this.jsf.formGroup);\n      // console.log('formGroup.value', this.jsf.formGroup.value);\n      // console.log('schemaRefLibrary', this.jsf.schemaRefLibrary);\n      // console.log('layoutRefLibrary', this.jsf.layoutRefLibrary);\n      // console.log('templateRefLibrary', this.jsf.templateRefLibrary);\n      // console.log('dataMap', this.jsf.dataMap);\n      // console.log('arrayMap', this.jsf.arrayMap);\n      // console.log('schemaRecursiveRefMap', this.jsf.schemaRecursiveRefMap);\n      // console.log('dataRecursiveRefMap', this.jsf.dataRecursiveRefMap);\n\n      // Uncomment individual lines to output debugging information to browser:\n      // (These only work if the 'debug' option has also been set to 'true'.)\n      if (this.debug || this.jsf.formOptions.debug) {\n        const vars: any[] = [];\n        // vars.push(this.jsf.schema);\n        // vars.push(this.jsf.layout);\n        // vars.push(this.options);\n        // vars.push(this.jsf.formValues);\n        // vars.push(this.jsf.formGroup.value);\n        // vars.push(this.jsf.formGroupTemplate);\n        // vars.push(this.jsf.formGroup);\n        // vars.push(this.jsf.schemaRefLibrary);\n        // vars.push(this.jsf.layoutRefLibrary);\n        // vars.push(this.jsf.templateRefLibrary);\n        // vars.push(this.jsf.dataMap);\n        // vars.push(this.jsf.arrayMap);\n        // vars.push(this.jsf.schemaRecursiveRefMap);\n        // vars.push(this.jsf.dataRecursiveRefMap);\n        this.debugOutput = vars.map(v => JSON.stringify(v, null, 2)).join('\\n');\n      }\n      this.formInitialized = true;\n    }\n  }\n\n  /**\n   * 'initializeOptions' function\n   *\n   * Initialize 'options' (global form options) and set framework\n   * Combine available inputs:\n   * 1. options - recommended\n   * 2. form.options - Single input style\n   */\n  private initializeOptions() {\n    if (this.language && this.language !== this.jsf.language) {\n      this.jsf.setLanguage(this.language);\n    }\n    this.jsf.setOptions({ debug: !!this.debug });\n    let loadExternalAssets: boolean = this.loadExternalAssets || false;\n    let framework: any = this.framework || 'default';\n    if (isObject(this.options)) {\n      this.jsf.setOptions(this.options);\n      loadExternalAssets = this.options.loadExternalAssets || loadExternalAssets;\n      framework = this.options.framework || framework;\n    }\n    if (isObject(this.form) && isObject(this.form.options)) {\n      this.jsf.setOptions(this.form.options);\n      loadExternalAssets = this.form.options.loadExternalAssets || loadExternalAssets;\n      framework = this.form.options.framework || framework;\n    }\n    if (isObject(this.widgets)) {\n      this.jsf.setOptions({ widgets: this.widgets });\n    }\n    this.frameworkLibrary.setLoadExternalAssets(loadExternalAssets);\n    this.frameworkLibrary.setFramework(framework);\n    this.jsf.framework = this.frameworkLibrary.getFramework();\n    if (isObject(this.jsf.formOptions.widgets)) {\n      for (const widget of Object.keys(this.jsf.formOptions.widgets)) {\n        this.widgetLibrary.registerWidget(widget, this.jsf.formOptions.widgets[widget]);\n      }\n    }\n    if (isObject(this.form) && isObject(this.form.tpldata)) {\n      this.jsf.setTpldata(this.form.tpldata);\n    }\n  }\n\n  /**\n   * 'initializeSchema' function\n   *\n   * Initialize 'schema'\n   * Use first available input:\n   * 1. schema - recommended / Angular Schema Form style\n   * 2. form.schema - Single input / JSON Form style\n   * 3. JSONSchema - React JSON Schema Form style\n   * 4. form.JSONSchema - For testing single input React JSON Schema Forms\n   * 5. form - For testing single schema-only inputs\n   *\n   * ... if no schema input found, the 'activateForm' function, below,\n   *     will make two additional attempts to build a schema\n   * 6. If layout input - build schema from layout\n   * 7. If data input - build schema from data\n   */\n  private initializeSchema() {\n\n    // TODO: update to allow non-object schemas\n\n    if (isObject(this.schema)) {\n      this.jsf.AngularSchemaFormCompatibility = true;\n      this.jsf.schema = cloneDeep(this.schema);\n    } else if (hasOwn(this.form, 'schema') && isObject(this.form.schema)) {\n      this.jsf.schema = cloneDeep(this.form.schema);\n    } else if (isObject(this.JSONSchema)) {\n      this.jsf.ReactJsonSchemaFormCompatibility = true;\n      this.jsf.schema = cloneDeep(this.JSONSchema);\n    } else if (hasOwn(this.form, 'JSONSchema') && isObject(this.form.JSONSchema)) {\n      this.jsf.ReactJsonSchemaFormCompatibility = true;\n      this.jsf.schema = cloneDeep(this.form.JSONSchema);\n    } else if (hasOwn(this.form, 'properties') && isObject(this.form.properties)) {\n      this.jsf.schema = cloneDeep(this.form);\n    } else if (isObject(this.form)) {\n      // TODO: Handle other types of form input\n    }\n\n    if (!isEmpty(this.jsf.schema)) {\n\n      // If other types also allowed, render schema as an object\n      if (inArray('object', this.jsf.schema.type)) {\n        this.jsf.schema.type = 'object';\n      }\n\n      // Wrap non-object schemas in object.\n      if (hasOwn(this.jsf.schema, 'type') && this.jsf.schema.type !== 'object') {\n        this.jsf.schema = {\n          'type': 'object',\n          'properties': { 1: this.jsf.schema }\n        };\n        this.objectWrap = true;\n      } else if (!hasOwn(this.jsf.schema, 'type')) {\n\n        // Add type = 'object' if missing\n        if (isObject(this.jsf.schema.properties) ||\n          isObject(this.jsf.schema.patternProperties) ||\n          isObject(this.jsf.schema.additionalProperties)\n        ) {\n          this.jsf.schema.type = 'object';\n\n          // Fix JSON schema shorthand (JSON Form style)\n        } else {\n          this.jsf.JsonFormCompatibility = true;\n          this.jsf.schema = {\n            'type': 'object',\n            'properties': this.jsf.schema\n          };\n        }\n      }\n\n      // If needed, update JSON Schema to draft 6 format, including\n      // draft 3 (JSON Form style) and draft 4 (Angular Schema Form style)\n      this.jsf.schema = convertSchemaToDraft6(this.jsf.schema);\n\n      // Initialize ajv and compile schema\n      this.jsf.compileAjvSchema();\n\n      // Create schemaRefLibrary, schemaRecursiveRefMap, dataRecursiveRefMap, & arrayMap\n      this.jsf.schema = resolveSchemaReferences(\n        this.jsf.schema, this.jsf.schemaRefLibrary, this.jsf.schemaRecursiveRefMap,\n        this.jsf.dataRecursiveRefMap, this.jsf.arrayMap\n      );\n      if (hasOwn(this.jsf.schemaRefLibrary, '')) {\n        this.jsf.hasRootReference = true;\n      }\n\n      // TODO: (?) Resolve external $ref links\n      // // Create schemaRefLibrary & schemaRecursiveRefMap\n      // this.parser.bundle(this.schema)\n      //   .then(schema => this.schema = resolveSchemaReferences(\n      //     schema, this.jsf.schemaRefLibrary,\n      //     this.jsf.schemaRecursiveRefMap, this.jsf.dataRecursiveRefMap\n      //   ));\n    }\n  }\n\n  /**\n   * 'initializeData' function\n   *\n   * Initialize 'formValues'\n   * defulat or previously submitted values used to populate form\n   * Use first available input:\n   * 1. data - recommended\n   * 2. model - Angular Schema Form style\n   * 3. form.value - JSON Form style\n   * 4. form.data - Single input style\n   * 5. formData - React JSON Schema Form style\n   * 6. form.formData - For easier testing of React JSON Schema Forms\n   * 7. (none) no data - initialize data from schema and layout defaults only\n   */\n  private initializeData() {\n    if (hasValue(this.data)) {\n      this.jsf.formValues = cloneDeep(this.data);\n      this.formValuesInput = 'data';\n    } else if (hasValue(this.model)) {\n      this.jsf.AngularSchemaFormCompatibility = true;\n      this.jsf.formValues = cloneDeep(this.model);\n      this.formValuesInput = 'model';\n    } else if (hasValue(this.ngModel)) {\n      this.jsf.AngularSchemaFormCompatibility = true;\n      this.jsf.formValues = cloneDeep(this.ngModel);\n      this.formValuesInput = 'ngModel';\n    } else if (isObject(this.form) && hasValue(this.form.value)) {\n      this.jsf.JsonFormCompatibility = true;\n      this.jsf.formValues = cloneDeep(this.form.value);\n      this.formValuesInput = 'form.value';\n    } else if (isObject(this.form) && hasValue(this.form.data)) {\n      this.jsf.formValues = cloneDeep(this.form.data);\n      this.formValuesInput = 'form.data';\n    } else if (hasValue(this.formData)) {\n      this.jsf.ReactJsonSchemaFormCompatibility = true;\n      this.formValuesInput = 'formData';\n    } else if (hasOwn(this.form, 'formData') && hasValue(this.form.formData)) {\n      this.jsf.ReactJsonSchemaFormCompatibility = true;\n      this.jsf.formValues = cloneDeep(this.form.formData);\n      this.formValuesInput = 'form.formData';\n    } else {\n      this.formValuesInput = null;\n    }\n  }\n\n  /**\n   * 'initializeLayout' function\n   *\n   * Initialize 'layout'\n   * Use first available array input:\n   * 1. layout - recommended\n   * 2. form - Angular Schema Form style\n   * 3. form.form - JSON Form style\n   * 4. form.layout - Single input style\n   * 5. (none) no layout - set default layout instead\n   *    (full layout will be built later from the schema)\n   *\n   * Also, if alternate layout formats are available,\n   * import from 'UISchema' or 'customFormItems'\n   * used for React JSON Schema Form and JSON Form API compatibility\n   * Use first available input:\n   * 1. UISchema - React JSON Schema Form style\n   * 2. form.UISchema - For testing single input React JSON Schema Forms\n   * 2. form.customFormItems - JSON Form style\n   * 3. (none) no input - don't import\n   */\n  private initializeLayout() {\n\n    // Rename JSON Form-style 'options' lists to\n    // Angular Schema Form-style 'titleMap' lists.\n    const fixJsonFormOptions = (layout: any): any => {\n      if (isObject(layout) || isArray(layout)) {\n        forEach(layout, (value, key) => {\n          if (hasOwn(value, 'options') && isObject(value.options)) {\n            value.titleMap = value.options;\n            delete value.options;\n          }\n        }, 'top-down');\n      }\n      return layout;\n    };\n\n    // Check for layout inputs and, if found, initialize form layout\n    if (isArray(this.layout)) {\n      this.jsf.layout = cloneDeep(this.layout);\n    } else if (isArray(this.form)) {\n      this.jsf.AngularSchemaFormCompatibility = true;\n      this.jsf.layout = cloneDeep(this.form);\n    } else if (this.form && isArray(this.form.form)) {\n      this.jsf.JsonFormCompatibility = true;\n      this.jsf.layout = fixJsonFormOptions(cloneDeep(this.form.form));\n    } else if (this.form && isArray(this.form.layout)) {\n      this.jsf.layout = cloneDeep(this.form.layout);\n    } else {\n      this.jsf.layout = ['*'];\n    }\n\n    // Check for alternate layout inputs\n    let alternateLayout: any = null;\n    if (isObject(this.UISchema)) {\n      this.jsf.ReactJsonSchemaFormCompatibility = true;\n      alternateLayout = cloneDeep(this.UISchema);\n    } else if (hasOwn(this.form, 'UISchema')) {\n      this.jsf.ReactJsonSchemaFormCompatibility = true;\n      alternateLayout = cloneDeep(this.form.UISchema);\n    } else if (hasOwn(this.form, 'uiSchema')) {\n      this.jsf.ReactJsonSchemaFormCompatibility = true;\n      alternateLayout = cloneDeep(this.form.uiSchema);\n    } else if (hasOwn(this.form, 'customFormItems')) {\n      this.jsf.JsonFormCompatibility = true;\n      alternateLayout = fixJsonFormOptions(cloneDeep(this.form.customFormItems));\n    }\n\n    // if alternate layout found, copy alternate layout options into schema\n    if (alternateLayout) {\n      JsonPointer.forEachDeep(alternateLayout, (value, pointer) => {\n        const schemaPointer = pointer\n          .replace(/\\//g, '/properties/')\n          .replace(/\\/properties\\/items\\/properties\\//g, '/items/properties/')\n          .replace(/\\/properties\\/titleMap\\/properties\\//g, '/titleMap/properties/');\n        if (hasValue(value) && hasValue(pointer)) {\n          let key = JsonPointer.toKey(pointer);\n          const groupPointer = (JsonPointer.parse(schemaPointer) || []).slice(0, -2);\n          let itemPointer: string | string[];\n\n          // If 'ui:order' object found, copy into object schema root\n          if (key.toLowerCase() === 'ui:order') {\n            itemPointer = [...groupPointer, 'ui:order'];\n\n            // Copy other alternate layout options to schema 'x-schema-form',\n            // (like Angular Schema Form options) and remove any 'ui:' prefixes\n          } else {\n            if (key.slice(0, 3).toLowerCase() === 'ui:') { key = key.slice(3); }\n            itemPointer = [...groupPointer, 'x-schema-form', key];\n          }\n          if (JsonPointer.has(this.jsf.schema, groupPointer) &&\n            !JsonPointer.has(this.jsf.schema, itemPointer)\n          ) {\n            JsonPointer.set(this.jsf.schema, itemPointer, value);\n          }\n        }\n      });\n    }\n  }\n\n  /**\n   * 'activateForm' function\n   *\n   * ...continued from 'initializeSchema' function, above\n   * If 'schema' has not been initialized (i.e. no schema input found)\n   * 6. If layout input - build schema from layout input\n   * 7. If data input - build schema from data input\n   *\n   * Create final layout,\n   * build the FormGroup template and the Angular FormGroup,\n   * subscribe to changes,\n   * and activate the form.\n   */\n  private activateForm() {\n    this.unsubscribeOnActivateForm$.next();\n    // If 'schema' not initialized\n    if (isEmpty(this.jsf.schema)) {\n\n      // TODO: If full layout input (with no '*'), build schema from layout\n      // if (!this.jsf.layout.includes('*')) {\n      //   this.jsf.buildSchemaFromLayout();\n      // } else\n\n      // If data input, build schema from data\n      if (!isEmpty(this.jsf.formValues)) {\n        this.jsf.buildSchemaFromData();\n      }\n    }\n\n    if (!isEmpty(this.jsf.schema)) {\n\n      // If not already initialized, initialize ajv and compile schema\n      this.jsf.compileAjvSchema();\n\n      // Update all layout elements, add values, widgets, and validators,\n      // replace any '*' with a layout built from all schema elements,\n      // and update the FormGroup template with any new validators\n      this.jsf.buildLayout(this.widgetLibrary);\n\n      // Build the Angular FormGroup template from the schema\n      this.jsf.buildFormGroupTemplate(this.jsf.formValues);\n\n      // Build the real Angular FormGroup from the FormGroup template\n      this.jsf.buildFormGroup();\n    }\n\n    if (this.jsf.formGroup) {\n\n      // Reset initial form values\n      if (!isEmpty(this.jsf.formValues) &&\n        this.jsf.formOptions.setSchemaDefaults !== true &&\n        this.jsf.formOptions.setLayoutDefaults !== true\n      ) {\n        this.setFormValues(this.jsf.formValues);\n      }\n\n      // TODO: Figure out how to display calculated values without changing object data\n      // See http://ulion.github.io/jsonform/playground/?example=templating-values\n      // Calculate references to other fields\n      // if (!isEmpty(this.jsf.formGroup.value)) {\n      //   forEach(this.jsf.formGroup.value, (value, key, object, rootObject) => {\n      //     if (typeof value === 'string') {\n      //       object[key] = this.jsf.parseText(value, value, rootObject, key);\n      //     }\n      //   }, 'top-down');\n      // }\n\n      // Subscribe to form changes to output live data, validation, and errors\n      this.jsf.dataChanges.pipe(takeUntil(this.unsubscribeOnActivateForm$)).subscribe(data => {\n        this.onChanges.emit(this.objectWrap ? data['1'] : data);\n        if (this.formValuesInput && this.formValuesInput.indexOf('.') === -1) {\n          this[`${this.formValuesInput}Change`].emit(this.objectWrap ? data['1'] : data);\n        }\n      });\n\n      // Trigger change detection on statusChanges to show updated errors\n      this.jsf.formGroup.statusChanges.pipe(takeUntil(this.unsubscribeOnActivateForm$)).subscribe(() => this.changeDetector.markForCheck());\n      this.jsf.isValidChanges.pipe(takeUntil(this.unsubscribeOnActivateForm$)).subscribe(isValid => this.isValid.emit(isValid));\n      this.jsf.validationErrorChanges.pipe(takeUntil(this.unsubscribeOnActivateForm$)).subscribe(err => this.validationErrors.emit(err));\n\n      // Output final schema, final layout, and initial data\n      this.formSchema.emit(this.jsf.schema);\n      this.formLayout.emit(this.jsf.layout);\n      this.onChanges.emit(this.objectWrap ? this.jsf.data['1'] : this.jsf.data);\n\n      // If validateOnRender, output initial validation and any errors\n      const validateOnRender =\n        JsonPointer.get(this.jsf, '/formOptions/validateOnRender');\n      if (validateOnRender) { // validateOnRender === 'auto' || true\n        const touchAll = (control) => {\n          if (validateOnRender === true || hasValue(control.value)) {\n            control.markAsTouched();\n          }\n          Object.keys(control.controls || {})\n            .forEach(key => touchAll(control.controls[key]));\n        };\n        touchAll(this.jsf.formGroup);\n        this.isValid.emit(this.jsf.isValid);\n        this.validationErrors.emit(this.jsf.ajvErrors);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/json-schema-form.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { NgModule } from '@angular/core';\nimport { JsonSchemaFormComponent } from './json-schema-form.component';\nimport { NoFrameworkModule } from './framework-library/no-framework.module';\nimport { WidgetLibraryModule } from './widget-library/widget-library.module';\n\n@NgModule({\n  imports: [\n    CommonModule, FormsModule, ReactiveFormsModule,\n    WidgetLibraryModule, NoFrameworkModule\n  ],\n  declarations: [JsonSchemaFormComponent],\n  exports: [JsonSchemaFormComponent, WidgetLibraryModule]\n})\nexport class JsonSchemaFormModule {\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/json-schema-form.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { AbstractControl, UntypedFormArray, UntypedFormGroup } from '@angular/forms';\nimport { Subject } from 'rxjs';\nimport cloneDeep from 'lodash/cloneDeep';\nimport Ajv from 'ajv';\nimport jsonDraft6 from 'ajv/lib/refs/json-schema-draft-06.json';\nimport {\n  buildFormGroup,\n  buildFormGroupTemplate,\n  formatFormData,\n  getControl,\n  fixTitle,\n  forEach,\n  hasOwn,\n  toTitleCase,\n  buildLayout,\n  getLayoutNode,\n  buildSchemaFromData,\n  buildSchemaFromLayout,\n  removeRecursiveReferences,\n  hasValue,\n  isArray,\n  isDefined,\n  isEmpty,\n  isObject,\n  JsonPointer\n} from './shared';\nimport {\n  deValidationMessages,\n  enValidationMessages,\n  esValidationMessages,\n  frValidationMessages,\n  itValidationMessages,\n  ptValidationMessages,\n  zhValidationMessages\n} from './locale';\n\n\nexport interface TitleMapItem {\n  name?: string;\n  value?: any;\n  checked?: boolean;\n  group?: string;\n  items?: TitleMapItem[];\n}\nexport interface ErrorMessages {\n  [control_name: string]: {\n    message: string | Function | Object;\n    code: string;\n  }[];\n}\n\n@Injectable()\nexport class JsonSchemaFormService {\n  JsonFormCompatibility = false;\n  ReactJsonSchemaFormCompatibility = false;\n  AngularSchemaFormCompatibility = false;\n  tpldata: any = {};\n\n  ajvOptions: any = {\n    allErrors: true,\n    jsonPointers: true,\n    unknownFormats: 'ignore'\n  };\n  ajv: any = new Ajv(this.ajvOptions); // AJV: Another JSON Schema Validator\n  validateFormData: any = null; // Compiled AJV function to validate active form's schema\n\n  formValues: any = {}; // Internal form data (may not have correct types)\n  data: any = {}; // Output form data (formValues, formatted with correct data types)\n  schema: any = {}; // Internal JSON Schema\n  layout: any[] = []; // Internal form layout\n  formGroupTemplate: any = {}; // Template used to create formGroup\n  formGroup: any = null; // Angular formGroup, which powers the reactive form\n  framework: any = null; // Active framework component\n  formOptions: any; // Active options, used to configure the form\n\n  validData: any = null; // Valid form data (or null) (=== isValid ? data : null)\n  isValid: boolean = null; // Is current form data valid?\n  ajvErrors: any = null; // Ajv errors for current data\n  validationErrors: any = null; // Any validation errors for current data\n  dataErrors: any = new Map(); //\n  formValueSubscription: any = null; // Subscription to formGroup.valueChanges observable (for un- and re-subscribing)\n  dataChanges: Subject<any> = new Subject(); // Form data observable\n  isValidChanges: Subject<any> = new Subject(); // isValid observable\n  validationErrorChanges: Subject<any> = new Subject(); // validationErrors observable\n\n  arrayMap: Map<string, number> = new Map(); // Maps arrays in data object and number of tuple values\n  dataMap: Map<string, any> = new Map(); // Maps paths in form data to schema and formGroup paths\n  dataRecursiveRefMap: Map<string, string> = new Map(); // Maps recursive reference points in form data\n  schemaRecursiveRefMap: Map<string, string> = new Map(); // Maps recursive reference points in schema\n  schemaRefLibrary: any = {}; // Library of schemas for resolving schema $refs\n  layoutRefLibrary: any = { '': null }; // Library of layout nodes for adding to form\n  templateRefLibrary: any = {}; // Library of formGroup templates for adding to form\n  hasRootReference = false; // Does the form include a recursive reference to itself?\n\n  language = 'en-US'; // Does the form include a recursive reference to itself?\n\n  // Default global form options\n  defaultFormOptions: any = {\n    autocomplete: true, // Allow the web browser to remember previous form submission values as defaults\n    addSubmit: 'auto', // Add a submit button if layout does not have one?\n    // for addSubmit: true = always, false = never,\n    // 'auto' = only if layout is undefined (form is built from schema alone)\n    debug: false, // Show debugging output?\n    disableInvalidSubmit: true, // Disable submit if form invalid?\n    formDisabled: false, // Set entire form as disabled? (not editable, and disables outputs)\n    formReadonly: false, // Set entire form as read only? (not editable, but outputs still enabled)\n    fieldsRequired: false, // (set automatically) Are there any required fields in the form?\n    framework: 'no-framework', // The framework to load\n    loadExternalAssets: false, // Load external css and JavaScript for framework?\n    pristine: { errors: true, success: true },\n    supressPropertyTitles: false,\n    setSchemaDefaults: 'auto', // Set fefault values from schema?\n    // true = always set (unless overridden by layout default or formValues)\n    // false = never set\n    // 'auto' = set in addable components, and everywhere if formValues not set\n    setLayoutDefaults: 'auto', // Set fefault values from layout?\n    // true = always set (unless overridden by formValues)\n    // false = never set\n    // 'auto' = set in addable components, and everywhere if formValues not set\n    validateOnRender: 'auto', // Validate fields immediately, before they are touched?\n    // true = validate all fields immediately\n    // false = only validate fields after they are touched by user\n    // 'auto' = validate fields with values immediately, empty fields after they are touched\n    widgets: {}, // Any custom widgets to load\n    defautWidgetOptions: {\n      // Default options for form control widgets\n      listItems: 1, // Number of list items to initially add to arrays with no default value\n      addable: true, // Allow adding items to an array or $ref point?\n      orderable: true, // Allow reordering items within an array?\n      removable: true, // Allow removing items from an array or $ref point?\n      enableErrorState: true, // Apply 'has-error' class when field fails validation?\n      // disableErrorState: false, // Don't apply 'has-error' class when field fails validation?\n      enableSuccessState: true, // Apply 'has-success' class when field validates?\n      // disableSuccessState: false, // Don't apply 'has-success' class when field validates?\n      feedback: false, // Show inline feedback icons?\n      feedbackOnRender: false, // Show errorMessage on Render?\n      notitle: false, // Hide title?\n      disabled: false, // Set control as disabled? (not editable, and excluded from output)\n      readonly: false, // Set control as read only? (not editable, but included in output)\n      returnEmptyFields: true, // return values for fields that contain no data?\n      validationMessages: {} // set by setLanguage()\n    }\n  };\n\n  constructor() {\n    this.setLanguage(this.language);\n    this.ajv.addMetaSchema(jsonDraft6);\n  }\n\n  setLanguage(language: string = 'en-US') {\n    this.language = language;\n    const languageValidationMessages = {\n      de: deValidationMessages,\n      en: enValidationMessages,\n      es: esValidationMessages,\n      fr: frValidationMessages,\n      it: itValidationMessages,\n      pt: ptValidationMessages,\n      zh: zhValidationMessages,\n    };\n    const languageCode = language.slice(0, 2);\n\n    const validationMessages = languageValidationMessages[languageCode];\n\n    this.defaultFormOptions.defautWidgetOptions.validationMessages = cloneDeep(\n      validationMessages\n    );\n  }\n\n  getData() {\n    return this.data;\n  }\n\n  getSchema() {\n    return this.schema;\n  }\n\n  getLayout() {\n    return this.layout;\n  }\n\n  resetAllValues() {\n    this.JsonFormCompatibility = false;\n    this.ReactJsonSchemaFormCompatibility = false;\n    this.AngularSchemaFormCompatibility = false;\n    this.tpldata = {};\n    this.validateFormData = null;\n    this.formValues = {};\n    this.schema = {};\n    this.layout = [];\n    this.formGroupTemplate = {};\n    this.formGroup = null;\n    this.framework = null;\n    this.data = {};\n    this.validData = null;\n    this.isValid = null;\n    this.validationErrors = null;\n    this.arrayMap = new Map();\n    this.dataMap = new Map();\n    this.dataRecursiveRefMap = new Map();\n    this.schemaRecursiveRefMap = new Map();\n    this.layoutRefLibrary = {};\n    this.schemaRefLibrary = {};\n    this.templateRefLibrary = {};\n    this.formOptions = cloneDeep(this.defaultFormOptions);\n  }\n\n  /**\n   * 'buildRemoteError' function\n   *\n   * Example errors:\n   * {\n   *   last_name: [ {\n   *     message: 'Last name must by start with capital letter.',\n   *     code: 'capital_letter'\n   *   } ],\n   *   email: [ {\n   *     message: 'Email must be from example.com domain.',\n   *     code: 'special_domain'\n   *   }, {\n   *     message: 'Email must contain an @ symbol.',\n   *     code: 'at_symbol'\n   *   } ]\n   * }\n   * //{ErrorMessages} errors\n   */\n  buildRemoteError(errors: ErrorMessages) {\n    forEach(errors, (value, key) => {\n      if (key in this.formGroup.controls) {\n        for (const error of value) {\n          const err = {};\n          err[error['code']] = error['message'];\n          this.formGroup.get(key).setErrors(err, { emitEvent: true });\n        }\n      }\n    });\n  }\n\n  validateData(newValue: any, updateSubscriptions = true): void {\n    // Format raw form data to correct data types\n    this.data = formatFormData(\n      newValue,\n      this.dataMap,\n      this.dataRecursiveRefMap,\n      this.arrayMap,\n      this.formOptions.returnEmptyFields\n    );\n    this.isValid = this.validateFormData(this.data);\n    this.validData = this.isValid ? this.data : null;\n    const compileErrors = errors => {\n      const compiledErrors = {};\n      (errors || []).forEach(error => {\n        if (!compiledErrors[error.dataPath]) {\n          compiledErrors[error.dataPath] = [];\n        }\n        compiledErrors[error.dataPath].push(error.message);\n      });\n      return compiledErrors;\n    };\n    this.ajvErrors = this.validateFormData.errors;\n    this.validationErrors = compileErrors(this.validateFormData.errors);\n    if (updateSubscriptions) {\n      this.dataChanges.next(this.data);\n      this.isValidChanges.next(this.isValid);\n      this.validationErrorChanges.next(this.ajvErrors);\n    }\n  }\n\n  buildFormGroupTemplate(formValues: any = null, setValues = true) {\n    this.formGroupTemplate = buildFormGroupTemplate(\n      this,\n      formValues,\n      setValues\n    );\n  }\n\n  buildFormGroup() {\n    this.formGroup = <UntypedFormGroup>buildFormGroup(this.formGroupTemplate);\n    if (this.formGroup) {\n      this.compileAjvSchema();\n      this.validateData(this.formGroup.value);\n\n      // Set up observables to emit data and validation info when form data changes\n      if (this.formValueSubscription) {\n        this.formValueSubscription.unsubscribe();\n      }\n      this.formValueSubscription = this.formGroup.valueChanges.subscribe(\n        formValue => this.validateData(formValue)\n      );\n    }\n  }\n\n  buildLayout(widgetLibrary: any) {\n    this.layout = buildLayout(this, widgetLibrary);\n  }\n\n  setOptions(newOptions: any) {\n    if (isObject(newOptions)) {\n      const addOptions = cloneDeep(newOptions);\n      // Backward compatibility for 'defaultOptions' (renamed 'defautWidgetOptions')\n      if (isObject(addOptions.defaultOptions)) {\n        Object.assign(\n          this.formOptions.defautWidgetOptions,\n          addOptions.defaultOptions\n        );\n        delete addOptions.defaultOptions;\n      }\n      if (isObject(addOptions.defautWidgetOptions)) {\n        Object.assign(\n          this.formOptions.defautWidgetOptions,\n          addOptions.defautWidgetOptions\n        );\n        delete addOptions.defautWidgetOptions;\n      }\n      Object.assign(this.formOptions, addOptions);\n\n      // convert disableErrorState / disableSuccessState to enable...\n      const globalDefaults = this.formOptions.defautWidgetOptions;\n      ['ErrorState', 'SuccessState']\n        .filter(suffix => hasOwn(globalDefaults, 'disable' + suffix))\n        .forEach(suffix => {\n          globalDefaults['enable' + suffix] = !globalDefaults[\n            'disable' + suffix\n          ];\n          delete globalDefaults['disable' + suffix];\n        });\n    }\n  }\n\n  compileAjvSchema() {\n    if (!this.validateFormData) {\n      // if 'ui:order' exists in properties, move it to root before compiling with ajv\n      if (Array.isArray(this.schema.properties['ui:order'])) {\n        this.schema['ui:order'] = this.schema.properties['ui:order'];\n        delete this.schema.properties['ui:order'];\n      }\n      this.ajv.removeSchema(this.schema);\n      this.validateFormData = this.ajv.compile(this.schema);\n    }\n  }\n\n  buildSchemaFromData(data?: any, requireAllFields = false): any {\n    if (data) {\n      return buildSchemaFromData(data, requireAllFields);\n    }\n    this.schema = buildSchemaFromData(this.formValues, requireAllFields);\n  }\n\n  buildSchemaFromLayout(layout?: any): any {\n    if (layout) {\n      return buildSchemaFromLayout(layout);\n    }\n    this.schema = buildSchemaFromLayout(this.layout);\n  }\n\n  setTpldata(newTpldata: any = {}): void {\n    this.tpldata = newTpldata;\n  }\n\n  parseText(\n    text = '',\n    value: any = {},\n    values: any = {},\n    key: number | string = null\n  ): string {\n    if (!text || !/{{.+?}}/.test(text)) {\n      return text;\n    }\n    return text.replace(/{{(.+?)}}/g, (...a) =>\n      this.parseExpression(a[1], value, values, key, this.tpldata)\n    );\n  }\n\n  parseExpression(\n    expression = '',\n    value: any = {},\n    values: any = {},\n    key: number | string = null,\n    tpldata: any = null\n  ) {\n    if (typeof expression !== 'string') {\n      return '';\n    }\n    const index = typeof key === 'number' ? key + 1 + '' : key || '';\n    expression = expression.trim();\n    if (\n      (expression[0] === \"'\" || expression[0] === '\"') &&\n      expression[0] === expression[expression.length - 1] &&\n      expression.slice(1, expression.length - 1).indexOf(expression[0]) === -1\n    ) {\n      return expression.slice(1, expression.length - 1);\n    }\n    if (expression === 'idx' || expression === '$index') {\n      return index;\n    }\n    if (expression === 'value' && !hasOwn(values, 'value')) {\n      return value;\n    }\n    if (\n      ['\"', \"'\", ' ', '||', '&&', '+'].every(\n        delim => expression.indexOf(delim) === -1\n      )\n    ) {\n      const pointer = JsonPointer.parseObjectPath(expression);\n      return pointer[0] === 'value' && JsonPointer.has(value, pointer.slice(1))\n        ? JsonPointer.get(value, pointer.slice(1))\n        : pointer[0] === 'values' && JsonPointer.has(values, pointer.slice(1))\n          ? JsonPointer.get(values, pointer.slice(1))\n          : pointer[0] === 'tpldata' && JsonPointer.has(tpldata, pointer.slice(1))\n            ? JsonPointer.get(tpldata, pointer.slice(1))\n            : JsonPointer.has(values, pointer)\n              ? JsonPointer.get(values, pointer)\n              : '';\n    }\n    if (expression.indexOf('[idx]') > -1) {\n      expression = expression.replace(/\\[idx\\]/g, <string>index);\n    }\n    if (expression.indexOf('[$index]') > -1) {\n      expression = expression.replace(/\\[$index\\]/g, <string>index);\n    }\n    // TODO: Improve expression evaluation by parsing quoted strings first\n    // let expressionArray = expression.match(/([^\"']+|\"[^\"]+\"|'[^']+')/g);\n    if (expression.indexOf('||') > -1) {\n      return expression\n        .split('||')\n        .reduce(\n          (all, term) =>\n            all || this.parseExpression(term, value, values, key, tpldata),\n          ''\n        );\n    }\n    if (expression.indexOf('&&') > -1) {\n      return expression\n        .split('&&')\n        .reduce(\n          (all, term) =>\n            all && this.parseExpression(term, value, values, key, tpldata),\n          ' '\n        )\n        .trim();\n    }\n    if (expression.indexOf('+') > -1) {\n      return expression\n        .split('+')\n        .map(term => this.parseExpression(term, value, values, key, tpldata))\n        .join('');\n    }\n    return '';\n  }\n\n  setArrayItemTitle(\n    parentCtx: any = {},\n    childNode: any = null,\n    index: number = null\n  ): string {\n    const parentNode = parentCtx.layoutNode;\n    const parentValues: any = this.getFormControlValue(parentCtx);\n    const isArrayItem =\n      (parentNode.type || '').slice(-5) === 'array' && isArray(parentValues);\n    const text = JsonPointer.getFirst(\n      isArrayItem && childNode.type !== '$ref'\n        ? [\n          [childNode, '/options/legend'],\n          [childNode, '/options/title'],\n          [parentNode, '/options/title'],\n          [parentNode, '/options/legend']\n        ]\n        : [\n          [childNode, '/options/title'],\n          [childNode, '/options/legend'],\n          [parentNode, '/options/title'],\n          [parentNode, '/options/legend']\n        ]\n    );\n    if (!text) {\n      return text;\n    }\n    const childValue =\n      isArray(parentValues) && index < parentValues.length\n        ? parentValues[index]\n        : parentValues;\n    return this.parseText(text, childValue, parentValues, index);\n  }\n\n  setItemTitle(ctx: any) {\n    return !ctx.options.title && /^(\\d+|-)$/.test(ctx.layoutNode.name)\n      ? null\n      : this.parseText(\n        ctx.options.title || toTitleCase(ctx.layoutNode.name),\n        this.getFormControlValue(this),\n        (this.getFormControlGroup(this) || <any>{}).value,\n        ctx.dataIndex[ctx.dataIndex.length - 1]\n      );\n  }\n\n  evaluateCondition(layoutNode: any, dataIndex: number[]): boolean {\n    const arrayIndex = dataIndex && dataIndex[dataIndex.length - 1];\n    let result = true;\n    if (hasValue((layoutNode.options || {}).condition)) {\n      if (typeof layoutNode.options.condition === 'string') {\n        let pointer = layoutNode.options.condition;\n        if (hasValue(arrayIndex)) {\n          pointer = pointer.replace('[arrayIndex]', `[${arrayIndex}]`);\n        }\n        pointer = JsonPointer.parseObjectPath(pointer);\n        result = !!JsonPointer.get(this.data, pointer);\n        if (!result && pointer[0] === 'model') {\n          result = !!JsonPointer.get({ model: this.data }, pointer);\n        }\n      } else if (typeof layoutNode.options.condition === 'function') {\n        result = layoutNode.options.condition(this.data);\n      } else if (\n        typeof layoutNode.options.condition.functionBody === 'string'\n      ) {\n        try {\n          const dynFn = new Function(\n            'model',\n            'arrayIndices',\n            layoutNode.options.condition.functionBody\n          );\n          result = dynFn(this.data, dataIndex);\n        } catch (e) {\n          result = true;\n          console.error(\n            'condition functionBody errored out on evaluation: ' +\n            layoutNode.options.condition.functionBody\n          );\n        }\n      }\n    }\n    return result;\n  }\n\n  initializeControl(ctx: any, bind = true): boolean {\n    if (!isObject(ctx)) {\n      return false;\n    }\n    if (isEmpty(ctx.options)) {\n      ctx.options = !isEmpty((ctx.layoutNode || {}).options)\n        ? ctx.layoutNode.options\n        : cloneDeep(this.formOptions);\n    }\n    ctx.formControl = this.getFormControl(ctx);\n    ctx.boundControl = bind && !!ctx.formControl;\n    if (ctx.formControl) {\n      ctx.controlName = this.getFormControlName(ctx);\n      ctx.controlValue = ctx.formControl.value;\n      ctx.controlDisabled = ctx.formControl.disabled;\n      ctx.options.errorMessage =\n        ctx.formControl.status === 'VALID'\n          ? null\n          : this.formatErrors(\n            ctx.formControl.errors,\n            ctx.options.validationMessages\n          );\n      ctx.options.showErrors =\n        this.formOptions.validateOnRender === true ||\n        (this.formOptions.validateOnRender === 'auto' &&\n          hasValue(ctx.controlValue));\n      ctx.formControl.statusChanges.subscribe(\n        status =>\n          (ctx.options.errorMessage =\n            status === 'VALID'\n              ? null\n              : this.formatErrors(\n                ctx.formControl.errors,\n                ctx.options.validationMessages\n              ))\n      );\n      ctx.formControl.valueChanges.subscribe(value => {\n        if (!!value) {\n          ctx.controlValue = value;\n        }\n      });\n    } else {\n      ctx.controlName = ctx.layoutNode.name;\n      ctx.controlValue = ctx.layoutNode.value || null;\n      const dataPointer = this.getDataPointer(ctx);\n      if (bind && dataPointer) {\n        console.error(\n          `warning: control \"${dataPointer}\" is not bound to the Angular FormGroup.`\n        );\n      }\n    }\n    return ctx.boundControl;\n  }\n\n  formatErrors(errors: any, validationMessages: any = {}): string {\n    if (isEmpty(errors)) {\n      return null;\n    }\n    if (!isObject(validationMessages)) {\n      validationMessages = {};\n    }\n    const addSpaces = string =>\n      string[0].toUpperCase() +\n      (string.slice(1) || '')\n        .replace(/([a-z])([A-Z])/g, '$1 $2')\n        .replace(/_/g, ' ');\n    const formatError = error =>\n      typeof error === 'object'\n        ? Object.keys(error)\n          .map(key =>\n            error[key] === true\n              ? addSpaces(key)\n              : error[key] === false\n                ? 'Not ' + addSpaces(key)\n                : addSpaces(key) + ': ' + formatError(error[key])\n          )\n          .join(', ')\n        : addSpaces(error.toString());\n    const messages = [];\n    return (\n      Object.keys(errors)\n        // Hide 'required' error, unless it is the only one\n        .filter(\n          errorKey =>\n            errorKey !== 'required' || Object.keys(errors).length === 1\n        )\n        .map(errorKey =>\n          // If validationMessages is a string, return it\n          typeof validationMessages === 'string'\n            ? validationMessages\n            : // If custom error message is a function, return function result\n            typeof validationMessages[errorKey] === 'function'\n              ? validationMessages[errorKey](errors[errorKey])\n              : // If custom error message is a string, replace placeholders and return\n              typeof validationMessages[errorKey] === 'string'\n                ? // Does error message have any {{property}} placeholders?\n                !/{{.+?}}/.test(validationMessages[errorKey])\n                  ? validationMessages[errorKey]\n                  : // Replace {{property}} placeholders with values\n                  Object.keys(errors[errorKey]).reduce(\n                    (errorMessage, errorProperty) =>\n                      errorMessage.replace(\n                        new RegExp('{{' + errorProperty + '}}', 'g'),\n                        errors[errorKey][errorProperty]\n                      ),\n                    validationMessages[errorKey]\n                  )\n                : // If no custom error message, return formatted error data instead\n                addSpaces(errorKey) + ' Error: ' + formatError(errors[errorKey])\n        )\n        .join('<br>')\n    );\n  }\n\n  updateValue(ctx: any, value: any): void {\n    // Set value of current control\n    ctx.controlValue = value;\n    if (ctx.boundControl) {\n      ctx.formControl.setValue(value);\n      ctx.formControl.markAsDirty();\n    }\n    ctx.layoutNode.value = value;\n\n    // Set values of any related controls in copyValueTo array\n    if (isArray(ctx.options.copyValueTo)) {\n      for (const item of ctx.options.copyValueTo) {\n        const targetControl = getControl(this.formGroup, item);\n        if (\n          isObject(targetControl) &&\n          typeof targetControl.setValue === 'function'\n        ) {\n          targetControl.setValue(value);\n          targetControl.markAsDirty();\n        }\n      }\n    }\n  }\n\n  updateArrayCheckboxList(ctx: any, checkboxList: TitleMapItem[]): void {\n    const formArray = <UntypedFormArray>this.getFormControl(ctx);\n\n    // Remove all existing items\n    while (formArray.value.length) {\n      formArray.removeAt(0);\n    }\n\n    // Re-add an item for each checked box\n    const refPointer = removeRecursiveReferences(\n      ctx.layoutNode.dataPointer + '/-',\n      this.dataRecursiveRefMap,\n      this.arrayMap\n    );\n    for (const checkboxItem of checkboxList) {\n      if (checkboxItem.checked) {\n        const newFormControl = buildFormGroup(\n          this.templateRefLibrary[refPointer]\n        );\n        newFormControl.setValue(checkboxItem.value);\n        formArray.push(newFormControl);\n      }\n    }\n    formArray.markAsDirty();\n  }\n\n  getFormControl(ctx: any): AbstractControl {\n    if (\n      !ctx.layoutNode ||\n      !isDefined(ctx.layoutNode.dataPointer) ||\n      ctx.layoutNode.type === '$ref'\n    ) {\n      return null;\n    }\n    return getControl(this.formGroup, this.getDataPointer(ctx));\n  }\n\n  getFormControlValue(ctx: any): AbstractControl {\n    if (\n      !ctx.layoutNode ||\n      !isDefined(ctx.layoutNode.dataPointer) ||\n      ctx.layoutNode.type === '$ref'\n    ) {\n      return null;\n    }\n    const control = getControl(this.formGroup, this.getDataPointer(ctx));\n    return control ? control.value : null;\n  }\n\n  getFormControlGroup(ctx: any): UntypedFormArray | UntypedFormGroup {\n    if (!ctx.layoutNode || !isDefined(ctx.layoutNode.dataPointer)) {\n      return null;\n    }\n    return getControl(this.formGroup, this.getDataPointer(ctx), true);\n  }\n\n  getFormControlName(ctx: any): string {\n    if (\n      !ctx.layoutNode ||\n      !isDefined(ctx.layoutNode.dataPointer) ||\n      !hasValue(ctx.dataIndex)\n    ) {\n      return null;\n    }\n    return JsonPointer.toKey(this.getDataPointer(ctx));\n  }\n\n  getLayoutArray(ctx: any): any[] {\n    return JsonPointer.get(this.layout, this.getLayoutPointer(ctx), 0, -1);\n  }\n\n  getParentNode(ctx: any): any {\n    return JsonPointer.get(this.layout, this.getLayoutPointer(ctx), 0, -2);\n  }\n\n  getDataPointer(ctx: any): string {\n    if (\n      !ctx.layoutNode ||\n      !isDefined(ctx.layoutNode.dataPointer) ||\n      !hasValue(ctx.dataIndex)\n    ) {\n      return null;\n    }\n    return JsonPointer.toIndexedPointer(\n      ctx.layoutNode.dataPointer,\n      ctx.dataIndex,\n      this.arrayMap\n    );\n  }\n\n  getLayoutPointer(ctx: any): string {\n    if (!hasValue(ctx.layoutIndex)) {\n      return null;\n    }\n    return '/' + ctx.layoutIndex.join('/items/');\n  }\n\n  isControlBound(ctx: any): boolean {\n    if (\n      !ctx.layoutNode ||\n      !isDefined(ctx.layoutNode.dataPointer) ||\n      !hasValue(ctx.dataIndex)\n    ) {\n      return false;\n    }\n    const controlGroup = this.getFormControlGroup(ctx);\n    const name = this.getFormControlName(ctx);\n    return controlGroup ? hasOwn(controlGroup.controls, name) : false;\n  }\n\n  addItem(ctx: any, name?: string): boolean {\n    if (\n      !ctx.layoutNode ||\n      !isDefined(ctx.layoutNode.$ref) ||\n      !hasValue(ctx.dataIndex) ||\n      !hasValue(ctx.layoutIndex)\n    ) {\n      return false;\n    }\n\n    // Create a new Angular form control from a template in templateRefLibrary\n    const newFormGroup = buildFormGroup(\n      this.templateRefLibrary[ctx.layoutNode.$ref]\n    );\n\n    // Add the new form control to the parent formArray or formGroup\n    if (ctx.layoutNode.arrayItem) {\n      // Add new array item to formArray\n      (<UntypedFormArray>this.getFormControlGroup(ctx)).push(newFormGroup);\n    } else {\n      // Add new $ref item to formGroup\n      (<UntypedFormGroup>this.getFormControlGroup(ctx)).addControl(\n        name || this.getFormControlName(ctx),\n        newFormGroup\n      );\n    }\n\n    // Copy a new layoutNode from layoutRefLibrary\n    const newLayoutNode = getLayoutNode(ctx.layoutNode, this);\n    newLayoutNode.arrayItem = ctx.layoutNode.arrayItem;\n    if (ctx.layoutNode.arrayItemType) {\n      newLayoutNode.arrayItemType = ctx.layoutNode.arrayItemType;\n    } else {\n      delete newLayoutNode.arrayItemType;\n    }\n    if (name) {\n      newLayoutNode.name = name;\n      newLayoutNode.dataPointer += '/' + JsonPointer.escape(name);\n      newLayoutNode.options.title = fixTitle(name);\n    }\n\n    // Add the new layoutNode to the form layout\n    JsonPointer.insert(this.layout, this.getLayoutPointer(ctx), newLayoutNode);\n\n    return true;\n  }\n\n  moveArrayItem(ctx: any, oldIndex: number, newIndex: number): boolean {\n    if (\n      !ctx.layoutNode ||\n      !isDefined(ctx.layoutNode.dataPointer) ||\n      !hasValue(ctx.dataIndex) ||\n      !hasValue(ctx.layoutIndex) ||\n      !isDefined(oldIndex) ||\n      !isDefined(newIndex) ||\n      oldIndex === newIndex\n    ) {\n      return false;\n    }\n\n    // Move item in the formArray\n    const formArray = <UntypedFormArray>this.getFormControlGroup(ctx);\n    const arrayItem = formArray.at(oldIndex);\n    formArray.removeAt(oldIndex);\n    formArray.insert(newIndex, arrayItem);\n    formArray.updateValueAndValidity();\n\n    // Move layout item\n    const layoutArray = this.getLayoutArray(ctx);\n    layoutArray.splice(newIndex, 0, layoutArray.splice(oldIndex, 1)[0]);\n    return true;\n  }\n\n  removeItem(ctx: any): boolean {\n    if (\n      !ctx.layoutNode ||\n      !isDefined(ctx.layoutNode.dataPointer) ||\n      !hasValue(ctx.dataIndex) ||\n      !hasValue(ctx.layoutIndex)\n    ) {\n      return false;\n    }\n\n    // Remove the Angular form control from the parent formArray or formGroup\n    if (ctx.layoutNode.arrayItem) {\n      // Remove array item from formArray\n      (<UntypedFormArray>this.getFormControlGroup(ctx)).removeAt(\n        ctx.dataIndex[ctx.dataIndex.length - 1]\n      );\n    } else {\n      // Remove $ref item from formGroup\n      (<UntypedFormGroup>this.getFormControlGroup(ctx)).removeControl(\n        this.getFormControlName(ctx)\n      );\n    }\n\n    // Remove layoutNode from layout\n    JsonPointer.remove(this.layout, this.getLayoutPointer(ctx));\n    return true;\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/locale/de-validation-messages.ts",
    "content": "export const deValidationMessages: any = { // Default German error messages\n  required: 'Darf nicht leer sein',\n  minLength: 'Mindestens {{minimumLength}} Zeichen benötigt (aktuell: {{currentLength}})',\n  maxLength: 'Maximal {{maximumLength}} Zeichen erlaubt (aktuell: {{currentLength}})',\n  pattern: 'Entspricht nicht diesem regulären Ausdruck: {{requiredPattern}}',\n  format: function (error) {\n    switch (error.requiredFormat) {\n      case 'date':\n        return 'Muss ein Datum sein, z. B. \"2000-12-31\"';\n      case 'time':\n        return 'Muss eine Zeitangabe sein, z. B. \"16:20\" oder \"03:14:15.9265\"';\n      case 'date-time':\n        return 'Muss Datum mit Zeit beinhalten, z. B. \"2000-03-14T01:59\" oder \"2000-03-14T01:59:26.535Z\"';\n      case 'email':\n        return 'Keine gültige E-Mail-Adresse (z. B. \"name@example.com\")';\n      case 'hostname':\n        return 'Kein gültiger Hostname (z. B. \"example.com\")';\n      case 'ipv4':\n        return 'Keine gültige IPv4-Adresse (z. B. \"127.0.0.1\")';\n      case 'ipv6':\n        return 'Keine gültige IPv6-Adresse (z. B. \"1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0\")';\n      // TODO: add examples for 'uri', 'uri-reference', and 'uri-template'\n      // case 'uri': case 'uri-reference': case 'uri-template':\n      case 'url':\n        return 'Keine gültige URL (z. B. \"http://www.example.com/page.html\")';\n      case 'uuid':\n        return 'Keine gültige UUID (z. B. \"12345678-9ABC-DEF0-1234-56789ABCDEF0\")';\n      case 'color':\n        return 'Kein gültiger Farbwert (z. B. \"#FFFFFF\" oder \"rgb(255, 255, 255)\")';\n      case 'json-pointer':\n        return 'Kein gültiger JSON-Pointer (z. B. \"/pointer/to/something\")';\n      case 'relative-json-pointer':\n        return 'Kein gültiger relativer JSON-Pointer (z. B. \"2/pointer/to/something\")';\n      case 'regex':\n        return 'Kein gültiger regulärer Ausdruck (z. B. \"(1-)?\\\\d{3}-\\\\d{3}-\\\\d{4}\")';\n      default:\n        return 'Muss diesem Format entsprechen: ' + error.requiredFormat;\n    }\n  },\n  minimum: 'Muss mindestens {{minimumValue}} sein',\n  exclusiveMinimum: 'Muss größer als {{exclusiveMinimumValue}} sein',\n  maximum: 'Darf maximal {{maximumValue}} sein',\n  exclusiveMaximum: 'Muss kleiner als {{exclusiveMaximumValue}} sein',\n  multipleOf: function (error) {\n    if ((1 / error.multipleOfValue) % 10 === 0) {\n      const decimals = Math.log10(1 / error.multipleOfValue);\n      return `Maximal ${decimals} Dezimalstellen erlaubt`;\n    } else {\n      return `Muss ein Vielfaches von ${error.multipleOfValue} sein`;\n    }\n  },\n  minProperties: 'Mindestens {{minimumProperties}} Attribute erforderlich (aktuell: {{currentProperties}})',\n  maxProperties: 'Maximal {{maximumProperties}} Attribute erlaubt (aktuell: {{currentProperties}})',\n  minItems: 'Mindestens {{minimumItems}} Werte erforderlich (aktuell: {{currentItems}})',\n  maxItems: 'Maximal {{maximumItems}} Werte erlaubt (aktuell: {{currentItems}})',\n  uniqueItems: 'Alle Werte müssen eindeutig sein',\n  // Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'\n};\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/locale/en-validation-messages.ts",
    "content": "export const enValidationMessages: any = { // Default English error messages\n  required: 'This field is required.',\n  minLength: 'Must be {{minimumLength}} characters or longer (current length: {{currentLength}})',\n  maxLength: 'Must be {{maximumLength}} characters or shorter (current length: {{currentLength}})',\n  pattern: 'Must match pattern: {{requiredPattern}}',\n  format: function (error) {\n    switch (error.requiredFormat) {\n      case 'date':\n        return 'Must be a date, like \"2000-12-31\"';\n      case 'time':\n        return 'Must be a time, like \"16:20\" or \"03:14:15.9265\"';\n      case 'date-time':\n        return 'Must be a date-time, like \"2000-03-14T01:59\" or \"2000-03-14T01:59:26.535Z\"';\n      case 'email':\n        return 'Must be an email address, like \"name@example.com\"';\n      case 'hostname':\n        return 'Must be a hostname, like \"example.com\"';\n      case 'ipv4':\n        return 'Must be an IPv4 address, like \"127.0.0.1\"';\n      case 'ipv6':\n        return 'Must be an IPv6 address, like \"1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0\"';\n      // TODO: add examples for 'uri', 'uri-reference', and 'uri-template'\n      // case 'uri': case 'uri-reference': case 'uri-template':\n      case 'url':\n        return 'Must be a url, like \"http://www.example.com/page.html\"';\n      case 'uuid':\n        return 'Must be a uuid, like \"12345678-9ABC-DEF0-1234-56789ABCDEF0\"';\n      case 'color':\n        return 'Must be a color, like \"#FFFFFF\" or \"rgb(255, 255, 255)\"';\n      case 'json-pointer':\n        return 'Must be a JSON Pointer, like \"/pointer/to/something\"';\n      case 'relative-json-pointer':\n        return 'Must be a relative JSON Pointer, like \"2/pointer/to/something\"';\n      case 'regex':\n        return 'Must be a regular expression, like \"(1-)?\\\\d{3}-\\\\d{3}-\\\\d{4}\"';\n      default:\n        return 'Must be a correctly formatted ' + error.requiredFormat;\n    }\n  },\n  minimum: 'Must be {{minimumValue}} or more',\n  exclusiveMinimum: 'Must be more than {{exclusiveMinimumValue}}',\n  maximum: 'Must be {{maximumValue}} or less',\n  exclusiveMaximum: 'Must be less than {{exclusiveMaximumValue}}',\n  multipleOf: function (error) {\n    if ((1 / error.multipleOfValue) % 10 === 0) {\n      const decimals = Math.log10(1 / error.multipleOfValue);\n      return `Must have ${decimals} or fewer decimal places.`;\n    } else {\n      return `Must be a multiple of ${error.multipleOfValue}.`;\n    }\n  },\n  minProperties: 'Must have {{minimumProperties}} or more items (current items: {{currentProperties}})',\n  maxProperties: 'Must have {{maximumProperties}} or fewer items (current items: {{currentProperties}})',\n  minItems: 'Must have {{minimumItems}} or more items (current items: {{currentItems}})',\n  maxItems: 'Must have {{maximumItems}} or fewer items (current items: {{currentItems}})',\n  uniqueItems: 'All items must be unique',\n  // Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'\n};\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/locale/es-validation-messages.ts",
    "content": "export const esValidationMessages: any = { // Default Spanish error messages\n  required: 'Este campo está requerido.',\n  minLength: 'Debe tener {{minimumLength}} caracteres o más longitud (longitud actual: {{currentLength}})',\n  maxLength: 'Debe tener {{maximumLength}} caracteres o menos longitud (longitud actual: {{currentLength}})',\n  pattern: 'Must match pattern: {{requiredPattern}}',\n  format: function (error) {\n    switch (error.requiredFormat) {\n      case 'date':\n        return 'Debe tener una fecha, ej \"2000-12-31\"';\n      case 'time':\n        return 'Debe tener una hora, ej \"16:20\" o \"03:14:15.9265\"';\n      case 'date-time':\n        return 'Debe tener fecha y hora, ej \"2000-03-14T01:59\" o \"2000-03-14T01:59:26.535Z\"';\n      case 'email':\n        return 'No hay dirección de correo electrónico válida, ej \"name@example.com\"';\n      case 'hostname':\n        return 'Debe ser un nombre de host válido, ej \"example.com\"';\n      case 'ipv4':\n        return 'Debe ser una dirección de IPv4, ej \"127.0.0.1\"';\n      case 'ipv6':\n        return 'Debe ser una dirección de IPv6, ej \"1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0\"';\n      case 'url':\n        return 'Debe ser una URL, ej \"http://www.example.com/page.html\"';\n      case 'uuid':\n        return 'Debe ser un UUID, ej \"12345678-9ABC-DEF0-1234-56789ABCDEF0\"';\n      case 'color':\n        return 'Debe ser un color, ej \"#FFFFFF\" or \"rgb(255, 255, 255)\"';\n      case 'json-pointer':\n        return 'Debe ser un JSON Pointer, ej \"/pointer/to/something\"';\n      case 'relative-json-pointer':\n        return 'Debe ser un JSON Pointer relativo, ej \"2/pointer/to/something\"';\n      case 'regex':\n        return 'Debe ser una expresión regular, ej \"(1-)?\\\\d{3}-\\\\d{3}-\\\\d{4}\"';\n      default:\n        return 'Debe tener el formato correcto ' + error.requiredFormat;\n    }\n  },\n  minimum: 'Debe ser {{minimumValue}} o más',\n  exclusiveMinimum: 'Debe ser superior a {{exclusiveMinimumValue}}',\n  maximum: 'Debe ser {{maximumValue}} o menos',\n  exclusiveMaximum: 'Debe ser menor que {{exclusiveMaximumValue}}',\n  multipleOf: function (error) {\n    if ((1 / error.multipleOfValue) % 10 === 0) {\n      const decimals = Math.log10(1 / error.multipleOfValue);\n      return `Se permite un máximo de ${decimals} decimales`;\n    } else {\n      return `Debe ser múltiplo de ${error.multipleOfValue}.`;\n    }\n  },\n  minProperties: 'Debe tener {{minimumProperties}} o más elementos (elementos actuales: {{currentProperties}})',\n  maxProperties: 'Debe tener {{maximumProperties}} o menos elementos (elementos actuales: {{currentProperties}})',\n  minItems: 'Debe tener {{minimumItems}} o más elementos (elementos actuales: {{currentItems}})',\n  maxItems: 'Debe tener {{maximumItems}} o menos elementos (elementos actuales: {{currentItems}})',\n  uniqueItems: 'Todos los elementos deben ser únicos',\n};\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/locale/fr-validation-messages.ts",
    "content": "export const frValidationMessages: any = { // French error messages\n  required: 'Est obligatoire.',\n  minLength: 'Doit avoir minimum {{minimumLength}} caractères (actuellement: {{currentLength}})',\n  maxLength: 'Doit avoir maximum {{maximumLength}} caractères (actuellement: {{currentLength}})',\n  pattern: 'Doit respecter: {{requiredPattern}}',\n  format: function (error) {\n    switch (error.requiredFormat) {\n      case 'date':\n        return 'Doit être une date, tel que \"2000-12-31\"';\n      case 'time':\n        return 'Doit être une heure, tel que \"16:20\" ou \"03:14:15.9265\"';\n      case 'date-time':\n        return 'Doit être une date et une heure, tel que \"2000-03-14T01:59\" ou \"2000-03-14T01:59:26.535Z\"';\n      case 'email':\n        return 'Doit être une adresse e-mail, tel que \"name@example.com\"';\n      case 'hostname':\n        return 'Doit être un nom de domaine, tel que \"example.com\"';\n      case 'ipv4':\n        return 'Doit être une adresse IPv4, tel que \"127.0.0.1\"';\n      case 'ipv6':\n        return 'Doit être une adresse IPv6, tel que \"1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0\"';\n      // TODO: add examples for 'uri', 'uri-reference', and 'uri-template'\n      // case 'uri': case 'uri-reference': case 'uri-template':\n      case 'url':\n        return 'Doit être une URL, tel que \"http://www.example.com/page.html\"';\n      case 'uuid':\n        return 'Doit être un UUID, tel que \"12345678-9ABC-DEF0-1234-56789ABCDEF0\"';\n      case 'color':\n        return 'Doit être une couleur, tel que \"#FFFFFF\" or \"rgb(255, 255, 255)\"';\n      case 'json-pointer':\n        return 'Doit être un JSON Pointer, tel que \"/pointer/to/something\"';\n      case 'relative-json-pointer':\n        return 'Doit être un relative JSON Pointer, tel que \"2/pointer/to/something\"';\n      case 'regex':\n        return 'Doit être une expression régulière, tel que \"(1-)?\\\\d{3}-\\\\d{3}-\\\\d{4}\"';\n      default:\n        return 'Doit être avoir le format correct: ' + error.requiredFormat;\n    }\n  },\n  minimum: 'Doit être supérieur à {{minimumValue}}',\n  exclusiveMinimum: 'Doit avoir minimum {{exclusiveMinimumValue}} charactères',\n  maximum: 'Doit être inférieur à {{maximumValue}}',\n  exclusiveMaximum: 'Doit avoir maximum {{exclusiveMaximumValue}} charactères',\n  multipleOf: function (error) {\n    if ((1 / error.multipleOfValue) % 10 === 0) {\n      const decimals = Math.log10(1 / error.multipleOfValue);\n      return `Doit comporter ${decimals} ou moins de decimales.`;\n    } else {\n      return `Doit être un multiple de ${error.multipleOfValue}.`;\n    }\n  },\n  minProperties: 'Doit comporter au minimum {{minimumProperties}} éléments',\n  maxProperties: 'Doit comporter au maximum {{maximumProperties}} éléments',\n  minItems: 'Doit comporter au minimum {{minimumItems}} éléments',\n  maxItems: 'Doit comporter au maximum {{minimumItems}} éléments',\n  uniqueItems: 'Tous les éléments doivent être uniques',\n  // Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'\n};\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/locale/index.ts",
    "content": "export { deValidationMessages } from './de-validation-messages';\nexport { enValidationMessages } from './en-validation-messages';\nexport { esValidationMessages } from './es-validation-messages';\nexport { frValidationMessages } from './fr-validation-messages';\nexport { itValidationMessages } from './it-validation-messages';\nexport { ptValidationMessages } from './pt-validation-messages';\nexport { zhValidationMessages } from './zh-validation-messages';\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/locale/it-validation-messages.ts",
    "content": "export const itValidationMessages: any = { // Default Italian error messages\n  required: 'Il campo è obbligatorio',\n  minLength: 'Deve inserire almeno {{minimumLength}} caratteri (lunghezza corrente: {{currentLength}})',\n  maxLength: 'Il numero massimo di caratteri consentito è {{maximumLength}} (lunghezza corrente: {{currentLength}})',\n  pattern: 'Devi rispettare il pattern : {{requiredPattern}}',\n  format: function (error) {\n    switch (error.requiredFormat) {\n      case 'date':\n        return 'Deve essere una data, come \"31-12-2000\"';\n      case 'time':\n        return 'Deve essere un orario, come \"16:20\" o \"03:14:15.9265\"';\n      case 'date-time':\n        return 'Deve essere data-orario, come \"14-03-2000T01:59\" or \"14-03-2000T01:59:26.535Z\"';\n      case 'email':\n        return 'Deve essere un indirzzo email, come \"name@example.com\"';\n      case 'hostname':\n        return 'Deve essere un hostname, come \"example.com\"';\n      case 'ipv4':\n        return 'Deve essere un indirizzo IPv4, come \"127.0.0.1\"';\n      case 'ipv6':\n        return 'Deve essere un indirizzo IPv6, come \"1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0\"';\n      // TODO: add examples for 'uri', 'uri-reference', and 'uri-template'\n      // case 'uri': case 'uri-reference': case 'uri-template':\n      case 'url':\n        return 'Deve essere un url, come \"http://www.example.com/page.html\"';\n      case 'uuid':\n        return 'Deve essere un uuid, come \"12345678-9ABC-DEF0-1234-56789ABCDEF0\"';\n      case 'color':\n        return 'Deve essere un colore, come \"#FFFFFF\" o \"rgb(255, 255, 255)\"';\n      case 'json-pointer':\n        return 'Deve essere un JSON Pointer, come \"/pointer/to/something\"';\n      case 'relative-json-pointer':\n        return 'Deve essere un JSON Pointer relativo, come \"2/pointer/to/something\"';\n      case 'regex':\n        return 'Deve essere una regular expression, come \"(1-)?\\\\d{3}-\\\\d{3}-\\\\d{4}\"';\n      default:\n        return 'Deve essere formattato correttamente ' + error.requiredFormat;\n    }\n  },\n  minimum: 'Deve essere {{minimumValue}} o più',\n  exclusiveMinimum: 'Deve essere più di {{exclusiveMinimumValue}}',\n  maximum: 'Deve essere {{maximumValue}} o meno',\n  exclusiveMaximum: 'Deve essere minore di {{exclusiveMaximumValue}}',\n  multipleOf: function (error) {\n    if ((1 / error.multipleOfValue) % 10 === 0) {\n      const decimals = Math.log10(1 / error.multipleOfValue);\n      return `Deve avere ${decimals} o meno decimali.`;\n    } else {\n      return `Deve essere multiplo di ${error.multipleOfValue}.`;\n    }\n  },\n  minProperties: 'Deve avere {{minimumProperties}} o più elementi (elementi correnti: {{currentProperties}})',\n  maxProperties: 'Deve avere {{maximumProperties}} o meno elementi (elementi correnti: {{currentProperties}})',\n  minItems: 'Deve avere {{minimumItems}} o più elementi (elementi correnti: {{currentItems}})',\n  maxItems: 'Deve avere {{maximumItems}} o meno elementi (elementi correnti: {{currentItems}})',\n  uniqueItems: 'Tutti gli elementi devono essere unici',\n  // Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'\n};\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/locale/pt-validation-messages.ts",
    "content": "export const ptValidationMessages: any = { // Brazilian Portuguese error messages\n  required: 'Este campo é obrigatório.',\n  minLength: 'É preciso no mínimo {{minimumLength}} caracteres ou mais (tamanho atual: {{currentLength}})',\n  maxLength: 'É preciso no máximo  {{maximumLength}} caracteres ou menos (tamanho atual: {{currentLength}})',\n  pattern: 'Tem que ajustar ao formato: {{requiredPattern}}',\n  format: function (error) {\n    switch (error.requiredFormat) {\n      case 'date':\n        return 'Tem que ser uma data, por exemplo \"2000-12-31\"';\n      case 'time':\n        return 'Tem que ser horário, por exemplo \"16:20\" ou \"03:14:15.9265\"';\n      case 'date-time':\n        return 'Tem que ser data e hora, por exemplo \"2000-03-14T01:59\" ou \"2000-03-14T01:59:26.535Z\"';\n      case 'email':\n        return 'Tem que ser um email, por exemplo \"fulano@exemplo.com.br\"';\n      case 'hostname':\n        return 'Tem que ser uma nome de domínio, por exemplo \"exemplo.com.br\"';\n      case 'ipv4':\n        return 'Tem que ser um endereço IPv4, por exemplo \"127.0.0.1\"';\n      case 'ipv6':\n        return 'Tem que ser um endereço IPv6, por exemplo \"1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0\"';\n      // TODO: add examples for 'uri', 'uri-reference', and 'uri-template'\n      // case 'uri': case 'uri-reference': case 'uri-template':\n      case 'url':\n        return 'Tem que ser uma URL, por exemplo \"http://www.exemplo.com.br/pagina.html\"';\n      case 'uuid':\n        return 'Tem que ser um uuid, por exemplo \"12345678-9ABC-DEF0-1234-56789ABCDEF0\"';\n      case 'color':\n        return 'Tem que ser uma cor, por exemplo \"#FFFFFF\" ou \"rgb(255, 255, 255)\"';\n      case 'json-pointer':\n        return 'Tem que ser um JSON Pointer, por exemplo \"/referencia/para/algo\"';\n      case 'relative-json-pointer':\n        return 'Tem que ser um JSON Pointer relativo, por exemplo \"2/referencia/para/algo\"';\n      case 'regex':\n        return 'Tem que ser uma expressão regular, por exemplo \"(1-)?\\\\d{3}-\\\\d{3}-\\\\d{4}\"';\n      default:\n        return 'Tem que ser no formato: ' + error.requiredFormat;\n    }\n  },\n  minimum: 'Tem que ser {{minimumValue}} ou mais',\n  exclusiveMinimum: 'Tem que ser mais que {{exclusiveMinimumValue}}',\n  maximum: 'Tem que ser {{maximumValue}} ou menos',\n  exclusiveMaximum: 'Tem que ser menor que {{exclusiveMaximumValue}}',\n  multipleOf: function (error) {\n    if ((1 / error.multipleOfValue) % 10 === 0) {\n      const decimals = Math.log10(1 / error.multipleOfValue);\n      return `Tem que ter ${decimals} ou menos casas decimais.`;\n    } else {\n      return `Tem que ser um múltiplo de ${error.multipleOfValue}.`;\n    }\n  },\n  minProperties: 'Deve ter {{minimumProperties}} ou mais itens (itens até o momento: {{currentProperties}})',\n  maxProperties: 'Deve ter {{maximumProperties}} ou menos intens (itens até o momento: {{currentProperties}})',\n  minItems: 'Deve ter {{minimumItems}} ou mais itens (itens até o momento: {{currentItems}})',\n  maxItems: 'Deve ter {{maximumItems}} ou menos itens (itens até o momento: {{currentItems}})',\n  uniqueItems: 'Todos os itens devem ser únicos',\n  // Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'\n};\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/locale/zh-validation-messages.ts",
    "content": "export const zhValidationMessages: any = { // Chinese error messages\n  required: '必填字段.',\n  minLength: '字符长度必须大于或者等于 {{minimumLength}} (当前长度: {{currentLength}})',\n  maxLength: '字符长度必须小于或者等于 {{maximumLength}} (当前长度: {{currentLength}})',\n  pattern: '必须匹配正则表达式: {{requiredPattern}}',\n  format: function (error) {\n    switch (error.requiredFormat) {\n      case 'date':\n        return '必须为日期格式, 比如 \"2000-12-31\"';\n      case 'time':\n        return '必须为时间格式, 比如 \"16:20\" 或者 \"03:14:15.9265\"';\n      case 'date-time':\n        return '必须为日期时间格式, 比如 \"2000-03-14T01:59\" 或者 \"2000-03-14T01:59:26.535Z\"';\n      case 'email':\n        return '必须为邮箱地址, 比如 \"name@example.com\"';\n      case 'hostname':\n        return '必须为主机名, 比如 \"example.com\"';\n      case 'ipv4':\n        return '必须为 IPv4 地址, 比如 \"127.0.0.1\"';\n      case 'ipv6':\n        return '必须为 IPv6 地址, 比如 \"1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0\"';\n      // TODO: add examples for 'uri', 'uri-reference', and 'uri-template'\n      // case 'uri': case 'uri-reference': case 'uri-template':\n      case 'url':\n        return '必须为 url, 比如 \"http://www.example.com/page.html\"';\n      case 'uuid':\n        return '必须为 uuid, 比如 \"12345678-9ABC-DEF0-1234-56789ABCDEF0\"';\n      case 'color':\n        return '必须为颜色值, 比如 \"#FFFFFF\" 或者 \"rgb(255, 255, 255)\"';\n      case 'json-pointer':\n        return '必须为 JSON Pointer, 比如 \"/pointer/to/something\"';\n      case 'relative-json-pointer':\n        return '必须为相对的 JSON Pointer, 比如 \"2/pointer/to/something\"';\n      case 'regex':\n        return '必须为正则表达式, 比如 \"(1-)?\\\\d{3}-\\\\d{3}-\\\\d{4}\"';\n      default:\n        return '必须为格式正确的 ' + error.requiredFormat;\n    }\n  },\n  minimum: '必须大于或者等于最小值: {{minimumValue}}',\n  exclusiveMinimum: '必须大于最小值: {{exclusiveMinimumValue}}',\n  maximum: '必须小于或者等于最大值: {{maximumValue}}',\n  exclusiveMaximum: '必须小于最大值: {{exclusiveMaximumValue}}',\n  multipleOf: function (error) {\n    if ((1 / error.multipleOfValue) % 10 === 0) {\n      const decimals = Math.log10(1 / error.multipleOfValue);\n      return `必须有 ${decimals} 位或更少的小数位`;\n    } else {\n      return `必须为 ${error.multipleOfValue} 的倍数`;\n    }\n  },\n  minProperties: '项目数必须大于或者等于 {{minimumProperties}} (当前项目数: {{currentProperties}})',\n  maxProperties: '项目数必须小于或者等于 {{maximumProperties}} (当前项目数: {{currentProperties}})',\n  minItems: '项目数必须大于或者等于 {{minimumItems}} (当前项目数: {{currentItems}})',\n  maxItems: '项目数必须小于或者等于 {{maximumItems}} (当前项目数: {{currentItems}})',\n  uniqueItems: '所有项目必须是唯一的',\n  // Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'\n};\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/locale-dates/en-US.ts",
    "content": "export const longMonths = ['January', 'February', 'March', 'April', 'May', 'June',\n    'July', 'August', 'September', 'October', 'November', 'December'];\nexport const longDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\nexport const shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\nexport const shortDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/convert-schema-to-draft6.function.ts",
    "content": "import cloneDeep from 'lodash/cloneDeep';\n\n/**\n * 'convertSchemaToDraft6' function\n *\n * Converts a JSON Schema from draft 1 through 4 format to draft 6 format\n *\n * Inspired by on geraintluff's JSON Schema 3 to 4 compatibility function:\n *   https://github.com/geraintluff/json-schema-compatibility\n * Also uses suggestions from AJV's JSON Schema 4 to 6 migration guide:\n *   https://github.com/epoberezkin/ajv/releases/tag/5.0.0\n * And additional details from the official JSON Schema documentation:\n *   http://json-schema.org\n *\n * //  { object } originalSchema - JSON schema (draft 1, 2, 3, 4, or 6)\n * //  { OptionObject = {} } options - options: parent schema changed?, schema draft number?\n * // { object } - JSON schema (draft 6)\n */\nexport interface OptionObject { changed?: boolean; draft?: number; }\nexport function convertSchemaToDraft6(schema, options: OptionObject = {}) {\n  let draft: number = options.draft || null;\n  let changed: boolean = options.changed || false;\n\n  if (typeof schema !== 'object') { return schema; }\n  if (typeof schema.map === 'function') {\n    return [...schema.map(subSchema => convertSchemaToDraft6(subSchema, { changed, draft }))];\n  }\n  let newSchema = { ...schema };\n  const simpleTypes = ['array', 'boolean', 'integer', 'null', 'number', 'object', 'string'];\n\n  if (typeof newSchema.$schema === 'string' &&\n    /http\\:\\/\\/json\\-schema\\.org\\/draft\\-0\\d\\/schema\\#/.test(newSchema.$schema)\n  ) {\n    draft = newSchema.$schema[30];\n  }\n\n  // Convert v1-v2 'contentEncoding' to 'media.binaryEncoding'\n  // Note: This is only used in JSON hyper-schema (not regular JSON schema)\n  if (newSchema.contentEncoding) {\n    newSchema.media = { binaryEncoding: newSchema.contentEncoding };\n    delete newSchema.contentEncoding;\n    changed = true;\n  }\n\n  // Convert v1-v3 'extends' to 'allOf'\n  if (typeof newSchema.extends === 'object') {\n    newSchema.allOf = typeof newSchema.extends.map === 'function' ?\n      newSchema.extends.map(subSchema => convertSchemaToDraft6(subSchema, { changed, draft })) :\n      [convertSchemaToDraft6(newSchema.extends, { changed, draft })];\n    delete newSchema.extends;\n    changed = true;\n  }\n\n  // Convert v1-v3 'disallow' to 'not'\n  if (newSchema.disallow) {\n    if (typeof newSchema.disallow === 'string') {\n      newSchema.not = { type: newSchema.disallow };\n    } else if (typeof newSchema.disallow.map === 'function') {\n      newSchema.not = {\n        anyOf: newSchema.disallow\n          .map(type => typeof type === 'object' ? type : { type })\n      };\n    }\n    delete newSchema.disallow;\n    changed = true;\n  }\n\n  // Convert v3 string 'dependencies' properties to arrays\n  if (typeof newSchema.dependencies === 'object' &&\n    Object.keys(newSchema.dependencies)\n      .some(key => typeof newSchema.dependencies[key] === 'string')\n  ) {\n    newSchema.dependencies = { ...newSchema.dependencies };\n    Object.keys(newSchema.dependencies)\n      .filter(key => typeof newSchema.dependencies[key] === 'string')\n      .forEach(key => newSchema.dependencies[key] = [newSchema.dependencies[key]]);\n    changed = true;\n  }\n\n  // Convert v1 'maxDecimal' to 'multipleOf'\n  if (typeof newSchema.maxDecimal === 'number') {\n    newSchema.multipleOf = 1 / Math.pow(10, newSchema.maxDecimal);\n    delete newSchema.divisibleBy;\n    changed = true;\n    if (!draft || draft === 2) { draft = 1; }\n  }\n\n  // Convert v2-v3 'divisibleBy' to 'multipleOf'\n  if (typeof newSchema.divisibleBy === 'number') {\n    newSchema.multipleOf = newSchema.divisibleBy;\n    delete newSchema.divisibleBy;\n    changed = true;\n  }\n\n  // Convert v1-v2 boolean 'minimumCanEqual' to 'exclusiveMinimum'\n  if (typeof newSchema.minimum === 'number' && newSchema.minimumCanEqual === false) {\n    newSchema.exclusiveMinimum = newSchema.minimum;\n    delete newSchema.minimum;\n    changed = true;\n    if (!draft) { draft = 2; }\n  } else if (typeof newSchema.minimumCanEqual === 'boolean') {\n    delete newSchema.minimumCanEqual;\n    changed = true;\n    if (!draft) { draft = 2; }\n  }\n\n  // Convert v3-v4 boolean 'exclusiveMinimum' to numeric\n  if (typeof newSchema.minimum === 'number' && newSchema.exclusiveMinimum === true) {\n    newSchema.exclusiveMinimum = newSchema.minimum;\n    delete newSchema.minimum;\n    changed = true;\n  } else if (typeof newSchema.exclusiveMinimum === 'boolean') {\n    delete newSchema.exclusiveMinimum;\n    changed = true;\n  }\n\n  // Convert v1-v2 boolean 'maximumCanEqual' to 'exclusiveMaximum'\n  if (typeof newSchema.maximum === 'number' && newSchema.maximumCanEqual === false) {\n    newSchema.exclusiveMaximum = newSchema.maximum;\n    delete newSchema.maximum;\n    changed = true;\n    if (!draft) { draft = 2; }\n  } else if (typeof newSchema.maximumCanEqual === 'boolean') {\n    delete newSchema.maximumCanEqual;\n    changed = true;\n    if (!draft) { draft = 2; }\n  }\n\n  // Convert v3-v4 boolean 'exclusiveMaximum' to numeric\n  if (typeof newSchema.maximum === 'number' && newSchema.exclusiveMaximum === true) {\n    newSchema.exclusiveMaximum = newSchema.maximum;\n    delete newSchema.maximum;\n    changed = true;\n  } else if (typeof newSchema.exclusiveMaximum === 'boolean') {\n    delete newSchema.exclusiveMaximum;\n    changed = true;\n  }\n\n  // Search object 'properties' for 'optional', 'required', and 'requires' items,\n  // and convert them into object 'required' arrays and 'dependencies' objects\n  if (typeof newSchema.properties === 'object') {\n    const properties = { ...newSchema.properties };\n    const requiredKeys = Array.isArray(newSchema.required) ?\n      new Set(newSchema.required) : new Set();\n\n    // Convert v1-v2 boolean 'optional' properties to 'required' array\n    if (draft === 1 || draft === 2 ||\n      Object.keys(properties).some(key => properties[key].optional === true)\n    ) {\n      Object.keys(properties)\n        .filter(key => properties[key].optional !== true)\n        .forEach(key => requiredKeys.add(key));\n      changed = true;\n      if (!draft) { draft = 2; }\n    }\n\n    // Convert v3 boolean 'required' properties to 'required' array\n    if (Object.keys(properties).some(key => properties[key].required === true)) {\n      Object.keys(properties)\n        .filter(key => properties[key].required === true)\n        .forEach(key => requiredKeys.add(key));\n      changed = true;\n    }\n\n    if (requiredKeys.size) { newSchema.required = Array.from(requiredKeys); }\n\n    // Convert v1-v2 array or string 'requires' properties to 'dependencies' object\n    if (Object.keys(properties).some(key => properties[key].requires)) {\n      const dependencies = typeof newSchema.dependencies === 'object' ?\n        { ...newSchema.dependencies } : {};\n      Object.keys(properties)\n        .filter(key => properties[key].requires)\n        .forEach(key => dependencies[key] =\n          typeof properties[key].requires === 'string' ?\n            [properties[key].requires] : properties[key].requires\n        );\n      newSchema.dependencies = dependencies;\n      changed = true;\n      if (!draft) { draft = 2; }\n    }\n\n    newSchema.properties = properties;\n  }\n\n  // Revove v1-v2 boolean 'optional' key\n  if (typeof newSchema.optional === 'boolean') {\n    delete newSchema.optional;\n    changed = true;\n    if (!draft) { draft = 2; }\n  }\n\n  // Revove v1-v2 'requires' key\n  if (newSchema.requires) {\n    delete newSchema.requires;\n  }\n\n  // Revove v3 boolean 'required' key\n  if (typeof newSchema.required === 'boolean') {\n    delete newSchema.required;\n  }\n\n  // Convert id to $id\n  if (typeof newSchema.id === 'string' && !newSchema.$id) {\n    if (newSchema.id.slice(-1) === '#') {\n      newSchema.id = newSchema.id.slice(0, -1);\n    }\n    newSchema.$id = newSchema.id + '-CONVERTED-TO-DRAFT-06#';\n    delete newSchema.id;\n    changed = true;\n  }\n\n  // Check if v1-v3 'any' or object types will be converted\n  if (newSchema.type && (typeof newSchema.type.every === 'function' ?\n    !newSchema.type.every(type => simpleTypes.includes(type)) :\n    !simpleTypes.includes(newSchema.type)\n  )) {\n    changed = true;\n  }\n\n  // If schema changed, update or remove $schema identifier\n  if (typeof newSchema.$schema === 'string' &&\n    /http\\:\\/\\/json\\-schema\\.org\\/draft\\-0[1-4]\\/schema\\#/.test(newSchema.$schema)\n  ) {\n    newSchema.$schema = 'http://json-schema.org/draft-06/schema#';\n    changed = true;\n  } else if (changed && typeof newSchema.$schema === 'string') {\n    const addToDescription = 'Converted to draft 6 from ' + newSchema.$schema;\n    if (typeof newSchema.description === 'string' && newSchema.description.length) {\n      newSchema.description += '\\n' + addToDescription;\n    } else {\n      newSchema.description = addToDescription;\n    }\n    delete newSchema.$schema;\n  }\n\n  // Convert v1-v3 'any' and object types\n  if (newSchema.type && (typeof newSchema.type.every === 'function' ?\n    !newSchema.type.every(type => simpleTypes.includes(type)) :\n    !simpleTypes.includes(newSchema.type)\n  )) {\n    if (newSchema.type.length === 1) { newSchema.type = newSchema.type[0]; }\n    if (typeof newSchema.type === 'string') {\n      // Convert string 'any' type to array of all standard types\n      if (newSchema.type === 'any') {\n        newSchema.type = simpleTypes;\n        // Delete non-standard string type\n      } else {\n        delete newSchema.type;\n      }\n    } else if (typeof newSchema.type === 'object') {\n      if (typeof newSchema.type.every === 'function') {\n        // If array of strings, only allow standard types\n        if (newSchema.type.every(type => typeof type === 'string')) {\n          newSchema.type = newSchema.type.some(type => type === 'any') ?\n            newSchema.type = simpleTypes :\n            newSchema.type.filter(type => simpleTypes.includes(type));\n          // If type is an array with objects, convert the current schema to an 'anyOf' array\n        } else if (newSchema.type.length > 1) {\n          const arrayKeys = ['additionalItems', 'items', 'maxItems', 'minItems', 'uniqueItems', 'contains'];\n          const numberKeys = ['multipleOf', 'maximum', 'exclusiveMaximum', 'minimum', 'exclusiveMinimum'];\n          const objectKeys = ['maxProperties', 'minProperties', 'required', 'additionalProperties',\n            'properties', 'patternProperties', 'dependencies', 'propertyNames'];\n          const stringKeys = ['maxLength', 'minLength', 'pattern', 'format'];\n          const filterKeys = {\n            'array': [...numberKeys, ...objectKeys, ...stringKeys],\n            'integer': [...arrayKeys, ...objectKeys, ...stringKeys],\n            'number': [...arrayKeys, ...objectKeys, ...stringKeys],\n            'object': [...arrayKeys, ...numberKeys, ...stringKeys],\n            'string': [...arrayKeys, ...numberKeys, ...objectKeys],\n            'all': [...arrayKeys, ...numberKeys, ...objectKeys, ...stringKeys],\n          };\n          const anyOf = [];\n          for (const type of newSchema.type) {\n            const newType = typeof type === 'string' ? { type } : { ...type };\n            Object.keys(newSchema)\n              .filter(key => !newType.hasOwnProperty(key) &&\n                ![...(filterKeys[newType.type] || filterKeys.all), 'type', 'default']\n                  .includes(key)\n              )\n              .forEach(key => newType[key] = newSchema[key]);\n            anyOf.push(newType);\n          }\n          newSchema = newSchema.hasOwnProperty('default') ?\n            { anyOf, default: newSchema.default } : { anyOf };\n          // If type is an object, merge it with the current schema\n        } else {\n          const typeSchema = newSchema.type;\n          delete newSchema.type;\n          Object.assign(newSchema, typeSchema);\n        }\n      }\n    } else {\n      delete newSchema.type;\n    }\n  }\n\n  // Convert sub schemas\n  Object.keys(newSchema)\n    .filter(key => typeof newSchema[key] === 'object')\n    .forEach(key => {\n      if (\n        ['definitions', 'dependencies', 'properties', 'patternProperties']\n          .includes(key) && typeof newSchema[key].map !== 'function'\n      ) {\n        const newKey = {};\n        Object.keys(newSchema[key]).forEach(subKey => newKey[subKey] =\n          convertSchemaToDraft6(newSchema[key][subKey], { changed, draft })\n        );\n        newSchema[key] = newKey;\n      } else if (\n        ['items', 'additionalItems', 'additionalProperties',\n          'allOf', 'anyOf', 'oneOf', 'not'].includes(key)\n      ) {\n        newSchema[key] = convertSchemaToDraft6(newSchema[key], { changed, draft });\n      } else {\n        newSchema[key] = cloneDeep(newSchema[key]);\n      }\n    });\n\n  return newSchema;\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/form-group.functions.ts",
    "content": "import cloneDeep from 'lodash/cloneDeep';\nimport filter from 'lodash/filter';\nimport map from 'lodash/map';\nimport {\n  AbstractControl,\n  UntypedFormArray,\n  UntypedFormControl,\n  UntypedFormGroup,\n  ValidatorFn\n} from '@angular/forms';\nimport { forEach, hasOwn } from './utility.functions';\nimport { getControlValidators, removeRecursiveReferences } from './json-schema.functions';\nimport {\n  hasValue,\n  inArray,\n  isArray,\n  isDate,\n  isDefined,\n  isEmpty,\n  isObject,\n  isPrimitive,\n  SchemaPrimitiveType,\n  toJavaScriptType,\n  toSchemaType\n} from './validator.functions';\nimport { JsonPointer, Pointer } from './jsonpointer.functions';\nimport { JsonValidators } from './json.validators';\n\n\n\n/**\n * FormGroup function library:\n *\n * buildFormGroupTemplate:  Builds a FormGroupTemplate from schema\n *\n * buildFormGroup:          Builds an Angular FormGroup from a FormGroupTemplate\n *\n * mergeValues:\n *\n * setRequiredFields:\n *\n * formatFormData:\n *\n * getControl:\n *\n * ---- TODO: ----\n * TODO: add buildFormGroupTemplateFromLayout function\n * buildFormGroupTemplateFromLayout: Builds a FormGroupTemplate from a form layout\n */\n\n/**\n * 'buildFormGroupTemplate' function\n *\n * Builds a template for an Angular FormGroup from a JSON Schema.\n *\n * TODO: add support for pattern properties\n * https://spacetelescope.github.io/understanding-json-schema/reference/object.html\n *\n * //  {any} jsf -\n * //  {any = null} nodeValue -\n * //  {boolean = true} mapArrays -\n * //  {string = ''} schemaPointer -\n * //  {string = ''} dataPointer -\n * //  {any = ''} templatePointer -\n * // {any} -\n */\nexport function buildFormGroupTemplate(\n  jsf: any, nodeValue: any = null, setValues = true,\n  schemaPointer = '', dataPointer = '', templatePointer = ''\n) {\n  const schema = JsonPointer.get(jsf.schema, schemaPointer);\n  if (setValues) {\n    if (!isDefined(nodeValue) && (\n      jsf.formOptions.setSchemaDefaults === true ||\n      (jsf.formOptions.setSchemaDefaults === 'auto' && isEmpty(jsf.formValues))\n    )) {\n      nodeValue = JsonPointer.get(jsf.schema, schemaPointer + '/default');\n    }\n  } else {\n    nodeValue = null;\n  }\n  // TODO: If nodeValue still not set, check layout for default value\n  const schemaType: string | string[] = JsonPointer.get(schema, '/type');\n  const controlType =\n    (hasOwn(schema, 'properties') || hasOwn(schema, 'additionalProperties')) &&\n      schemaType === 'object' ? 'FormGroup' :\n      (hasOwn(schema, 'items') || hasOwn(schema, 'additionalItems')) &&\n        schemaType === 'array' ? 'FormArray' :\n        !schemaType && hasOwn(schema, '$ref') ? '$ref' : 'FormControl';\n  const shortDataPointer =\n    removeRecursiveReferences(dataPointer, jsf.dataRecursiveRefMap, jsf.arrayMap);\n  if (!jsf.dataMap.has(shortDataPointer)) {\n    jsf.dataMap.set(shortDataPointer, new Map());\n  }\n  const nodeOptions = jsf.dataMap.get(shortDataPointer);\n  if (!nodeOptions.has('schemaType')) {\n    nodeOptions.set('schemaPointer', schemaPointer);\n    nodeOptions.set('schemaType', schema.type);\n    if (schema.format) {\n      nodeOptions.set('schemaFormat', schema.format);\n      if (!schema.type) { nodeOptions.set('schemaType', 'string'); }\n    }\n    if (controlType) {\n      nodeOptions.set('templatePointer', templatePointer);\n      nodeOptions.set('templateType', controlType);\n    }\n  }\n  let controls: any;\n  const validators = getControlValidators(schema);\n  switch (controlType) {\n\n    case 'FormGroup':\n      controls = {};\n      if (hasOwn(schema, 'ui:order') || hasOwn(schema, 'properties')) {\n        const propertyKeys = schema['ui:order'] || Object.keys(schema.properties);\n        if (propertyKeys.includes('*') && !hasOwn(schema.properties, '*')) {\n          const unnamedKeys = Object.keys(schema.properties)\n            .filter(key => !propertyKeys.includes(key));\n          for (let i = propertyKeys.length - 1; i >= 0; i--) {\n            if (propertyKeys[i] === '*') {\n              propertyKeys.splice(i, 1, ...unnamedKeys);\n            }\n          }\n        }\n        propertyKeys\n          .filter(key => hasOwn(schema.properties, key) ||\n            hasOwn(schema, 'additionalProperties')\n          )\n          .forEach(key => controls[key] = buildFormGroupTemplate(\n            jsf, JsonPointer.get(nodeValue, [<string>key]), setValues,\n            schemaPointer + (hasOwn(schema.properties, key) ?\n              '/properties/' + key : '/additionalProperties'\n            ),\n            dataPointer + '/' + key,\n            templatePointer + '/controls/' + key\n          ));\n        jsf.formOptions.fieldsRequired = setRequiredFields(schema, controls);\n      }\n      return { controlType, controls, validators };\n\n    case 'FormArray':\n      controls = [];\n      const minItems =\n        Math.max(schema.minItems || 0, nodeOptions.get('minItems') || 0);\n      const maxItems =\n        Math.min(schema.maxItems || 1000, nodeOptions.get('maxItems') || 1000);\n      let additionalItemsPointer: string = null;\n      if (isArray(schema.items)) { // 'items' is an array = tuple items\n        const tupleItems = nodeOptions.get('tupleItems') ||\n          (isArray(schema.items) ? Math.min(schema.items.length, maxItems) : 0);\n        for (let i = 0; i < tupleItems; i++) {\n          if (i < minItems) {\n            controls.push(buildFormGroupTemplate(\n              jsf, isArray(nodeValue) ? nodeValue[i] : nodeValue, setValues,\n              schemaPointer + '/items/' + i,\n              dataPointer + '/' + i,\n              templatePointer + '/controls/' + i\n            ));\n          } else {\n            const schemaRefPointer = removeRecursiveReferences(\n              schemaPointer + '/items/' + i, jsf.schemaRecursiveRefMap\n            );\n            const itemRefPointer = removeRecursiveReferences(\n              shortDataPointer + '/' + i, jsf.dataRecursiveRefMap, jsf.arrayMap\n            );\n            const itemRecursive = itemRefPointer !== shortDataPointer + '/' + i;\n            if (!hasOwn(jsf.templateRefLibrary, itemRefPointer)) {\n              jsf.templateRefLibrary[itemRefPointer] = null;\n              jsf.templateRefLibrary[itemRefPointer] = buildFormGroupTemplate(\n                jsf, null, setValues,\n                schemaRefPointer,\n                itemRefPointer,\n                templatePointer + '/controls/' + i\n              );\n            }\n            controls.push(\n              isArray(nodeValue) ?\n                buildFormGroupTemplate(\n                  jsf, nodeValue[i], setValues,\n                  schemaPointer + '/items/' + i,\n                  dataPointer + '/' + i,\n                  templatePointer + '/controls/' + i\n                ) :\n                itemRecursive ?\n                  null : cloneDeep(jsf.templateRefLibrary[itemRefPointer])\n            );\n          }\n        }\n\n        // If 'additionalItems' is an object = additional list items (after tuple items)\n        if (schema.items.length < maxItems && isObject(schema.additionalItems)) {\n          additionalItemsPointer = schemaPointer + '/additionalItems';\n        }\n\n        // If 'items' is an object = list items only (no tuple items)\n      } else {\n        additionalItemsPointer = schemaPointer + '/items';\n      }\n\n      if (additionalItemsPointer) {\n        const schemaRefPointer = removeRecursiveReferences(\n          additionalItemsPointer, jsf.schemaRecursiveRefMap\n        );\n        const itemRefPointer = removeRecursiveReferences(\n          shortDataPointer + '/-', jsf.dataRecursiveRefMap, jsf.arrayMap\n        );\n        const itemRecursive = itemRefPointer !== shortDataPointer + '/-';\n        if (!hasOwn(jsf.templateRefLibrary, itemRefPointer)) {\n          jsf.templateRefLibrary[itemRefPointer] = null;\n          jsf.templateRefLibrary[itemRefPointer] = buildFormGroupTemplate(\n            jsf, null, setValues,\n            schemaRefPointer,\n            itemRefPointer,\n            templatePointer + '/controls/-'\n          );\n        }\n        // const itemOptions = jsf.dataMap.get(itemRefPointer) || new Map();\n        const itemOptions = nodeOptions;\n        if (!itemRecursive || hasOwn(validators, 'required')) {\n          const arrayLength = Math.min(Math.max(\n            itemRecursive ? 0 :\n              (itemOptions.get('tupleItems') + itemOptions.get('listItems')) || 0,\n            isArray(nodeValue) ? nodeValue.length : 0\n          ), maxItems);\n          for (let i = controls.length; i < arrayLength; i++) {\n            controls.push(\n              isArray(nodeValue) ?\n                buildFormGroupTemplate(\n                  jsf, nodeValue[i], setValues,\n                  schemaRefPointer,\n                  dataPointer + '/-',\n                  templatePointer + '/controls/-'\n                ) :\n                itemRecursive ?\n                  null : cloneDeep(jsf.templateRefLibrary[itemRefPointer])\n            );\n          }\n        }\n      }\n      return { controlType, controls, validators };\n\n    case '$ref':\n      const schemaRef = JsonPointer.compile(schema.$ref);\n      const dataRef = JsonPointer.toDataPointer(schemaRef, schema);\n      const refPointer = removeRecursiveReferences(\n        dataRef, jsf.dataRecursiveRefMap, jsf.arrayMap\n      );\n      if (refPointer && !hasOwn(jsf.templateRefLibrary, refPointer)) {\n        // Set to null first to prevent recursive reference from causing endless loop\n        jsf.templateRefLibrary[refPointer] = null;\n        const newTemplate = buildFormGroupTemplate(jsf, setValues, setValues, schemaRef);\n        if (newTemplate) {\n          jsf.templateRefLibrary[refPointer] = newTemplate;\n        } else {\n          delete jsf.templateRefLibrary[refPointer];\n        }\n      }\n      return null;\n\n    case 'FormControl':\n      const value = {\n        value: setValues && isPrimitive(nodeValue) ? nodeValue : null,\n        disabled: nodeOptions.get('disabled') || false\n      };\n      return { controlType, value, validators };\n\n    default:\n      return null;\n  }\n}\n\n/**\n * 'buildFormGroup' function\n *\n * // {any} template -\n * // {AbstractControl}\n*/\nexport function buildFormGroup(template: any): AbstractControl {\n  const validatorFns: ValidatorFn[] = [];\n  let validatorFn: ValidatorFn = null;\n  if (hasOwn(template, 'validators')) {\n    forEach(template.validators, (parameters, validator) => {\n      if (typeof JsonValidators[validator] === 'function') {\n        validatorFns.push(JsonValidators[validator].apply(null, parameters));\n      }\n    });\n    if (validatorFns.length &&\n      inArray(template.controlType, ['FormGroup', 'FormArray'])\n    ) {\n      validatorFn = validatorFns.length > 1 ?\n        JsonValidators.compose(validatorFns) : validatorFns[0];\n    }\n  }\n  if (hasOwn(template, 'controlType')) {\n    switch (template.controlType) {\n      case 'FormGroup':\n        const groupControls: { [key: string]: AbstractControl } = {};\n        forEach(template.controls, (controls, key) => {\n          const newControl: AbstractControl = buildFormGroup(controls);\n          if (newControl) { groupControls[key] = newControl; }\n        });\n        return new UntypedFormGroup(groupControls, validatorFn);\n      case 'FormArray':\n        return new UntypedFormArray(filter(map(template.controls,\n          controls => buildFormGroup(controls)\n        )), validatorFn);\n      case 'FormControl':\n        return new UntypedFormControl(template.value, validatorFns);\n    }\n  }\n  return null;\n}\n\n/**\n * 'mergeValues' function\n *\n * //  {any[]} ...valuesToMerge - Multiple values to merge\n * // {any} - Merged values\n */\nexport function mergeValues(...valuesToMerge) {\n  let mergedValues: any = null;\n  for (const currentValue of valuesToMerge) {\n    if (!isEmpty(currentValue)) {\n      if (typeof currentValue === 'object' &&\n        (isEmpty(mergedValues) || typeof mergedValues !== 'object')\n      ) {\n        if (isArray(currentValue)) {\n          mergedValues = [...currentValue];\n        } else if (isObject(currentValue)) {\n          mergedValues = { ...currentValue };\n        }\n      } else if (typeof currentValue !== 'object') {\n        mergedValues = currentValue;\n      } else if (isObject(mergedValues) && isObject(currentValue)) {\n        Object.assign(mergedValues, currentValue);\n      } else if (isObject(mergedValues) && isArray(currentValue)) {\n        const newValues = [];\n        for (const value of currentValue) {\n          newValues.push(mergeValues(mergedValues, value));\n        }\n        mergedValues = newValues;\n      } else if (isArray(mergedValues) && isObject(currentValue)) {\n        const newValues = [];\n        for (const value of mergedValues) {\n          newValues.push(mergeValues(value, currentValue));\n        }\n        mergedValues = newValues;\n      } else if (isArray(mergedValues) && isArray(currentValue)) {\n        const newValues = [];\n        for (let i = 0; i < Math.max(mergedValues.length, currentValue.length); i++) {\n          if (i < mergedValues.length && i < currentValue.length) {\n            newValues.push(mergeValues(mergedValues[i], currentValue[i]));\n          } else if (i < mergedValues.length) {\n            newValues.push(mergedValues[i]);\n          } else if (i < currentValue.length) {\n            newValues.push(currentValue[i]);\n          }\n        }\n        mergedValues = newValues;\n      }\n    }\n  }\n  return mergedValues;\n}\n\n/**\n * 'setRequiredFields' function\n *\n * // {schema} schema - JSON Schema\n * // {object} formControlTemplate - Form Control Template object\n * // {boolean} - true if any fields have been set to required, false if not\n */\nexport function setRequiredFields(schema: any, formControlTemplate: any): boolean {\n  let fieldsRequired = false;\n  if (hasOwn(schema, 'required') && !isEmpty(schema.required)) {\n    fieldsRequired = true;\n    let requiredArray = isArray(schema.required) ? schema.required : [schema.required];\n    requiredArray = forEach(requiredArray,\n      key => JsonPointer.set(formControlTemplate, '/' + key + '/validators/required', [])\n    );\n  }\n  return fieldsRequired;\n\n  // TODO: Add support for patternProperties\n  // https://spacetelescope.github.io/understanding-json-schema/reference/object.html#pattern-properties\n}\n\n/**\n * 'formatFormData' function\n *\n * // {any} formData - Angular FormGroup data object\n * // {Map<string, any>} dataMap -\n * // {Map<string, string>} recursiveRefMap -\n * // {Map<string, number>} arrayMap -\n * // {boolean = false} fixErrors - if TRUE, tries to fix data\n * // {any} - formatted data object\n */\nexport function formatFormData(\n  formData: any, dataMap: Map<string, any>,\n  recursiveRefMap: Map<string, string>, arrayMap: Map<string, number>,\n  returnEmptyFields = false, fixErrors = false\n): any {\n  if (formData === null || typeof formData !== 'object') { return formData; }\n  const formattedData = isArray(formData) ? [] : {};\n  JsonPointer.forEachDeep(formData, (value, dataPointer) => {\n\n    // If returnEmptyFields === true,\n    // add empty arrays and objects to all allowed keys\n    if (returnEmptyFields && isArray(value)) {\n      JsonPointer.set(formattedData, dataPointer, []);\n    } else if (returnEmptyFields && isObject(value) && !isDate(value)) {\n      JsonPointer.set(formattedData, dataPointer, {});\n    } else {\n      const genericPointer =\n        JsonPointer.has(dataMap, [dataPointer, 'schemaType']) ? dataPointer :\n          removeRecursiveReferences(dataPointer, recursiveRefMap, arrayMap);\n      if (JsonPointer.has(dataMap, [genericPointer, 'schemaType'])) {\n        const schemaType: SchemaPrimitiveType | SchemaPrimitiveType[] =\n          dataMap.get(genericPointer).get('schemaType');\n        if (schemaType === 'null') {\n          JsonPointer.set(formattedData, dataPointer, null);\n        } else if ((hasValue(value) || returnEmptyFields) &&\n          inArray(schemaType, ['string', 'integer', 'number', 'boolean'])\n        ) {\n          const newValue = (fixErrors || (value === null && returnEmptyFields)) ?\n            toSchemaType(value, schemaType) : toJavaScriptType(value, schemaType);\n          if (isDefined(newValue) || returnEmptyFields) {\n            JsonPointer.set(formattedData, dataPointer, newValue);\n          }\n        }\n\n        // Finish incomplete 'date-time' entries\n        if (dataMap.get(genericPointer).get('schemaFormat') === 'date-time') {\n          // \"2000-03-14T01:59:26.535\" -> \"2000-03-14T01:59:26.535Z\" (add \"Z\")\n          if (/^\\d\\d\\d\\d-[0-1]\\d-[0-3]\\d[t\\s][0-2]\\d:[0-5]\\d:[0-5]\\d(?:\\.\\d+)?$/i.test(value)) {\n            JsonPointer.set(formattedData, dataPointer, `${value}Z`);\n            // \"2000-03-14T01:59\" -> \"2000-03-14T01:59:00Z\" (add \":00Z\")\n          } else if (/^\\d\\d\\d\\d-[0-1]\\d-[0-3]\\d[t\\s][0-2]\\d:[0-5]\\d$/i.test(value)) {\n            JsonPointer.set(formattedData, dataPointer, `${value}:00Z`);\n            // \"2000-03-14\" -> \"2000-03-14T00:00:00Z\" (add \"T00:00:00Z\")\n          } else if (fixErrors && /^\\d\\d\\d\\d-[0-1]\\d-[0-3]\\d$/i.test(value)) {\n            JsonPointer.set(formattedData, dataPointer, `${value}:00:00:00Z`);\n          }\n        }\n      } else if (typeof value !== 'object' || isDate(value) ||\n        (value === null && returnEmptyFields)\n      ) {\n        console.error('formatFormData error: ' +\n          `Schema type not found for form value at ${genericPointer}`);\n        console.error('dataMap', dataMap);\n        console.error('recursiveRefMap', recursiveRefMap);\n        console.error('genericPointer', genericPointer);\n      }\n    }\n  });\n  return formattedData;\n}\n\n/**\n * 'getControl' function\n *\n * Uses a JSON Pointer for a data object to retrieve a control from\n * an Angular formGroup or formGroup template. (Note: though a formGroup\n * template is much simpler, its basic structure is idential to a formGroup).\n *\n * If the optional third parameter 'returnGroup' is set to TRUE, the group\n * containing the control is returned, rather than the control itself.\n *\n * // {FormGroup} formGroup - Angular FormGroup to get value from\n * // {Pointer} dataPointer - JSON Pointer (string or array)\n * // {boolean = false} returnGroup - If true, return group containing control\n * // {group} - Located value (or null, if no control found)\n */\nexport function getControl(\n  formGroup: any, dataPointer: Pointer, returnGroup = false\n): any {\n  if (!isObject(formGroup) || !JsonPointer.isJsonPointer(dataPointer)) {\n    if (!JsonPointer.isJsonPointer(dataPointer)) {\n      // If dataPointer input is not a valid JSON pointer, check to\n      // see if it is instead a valid object path, using dot notaion\n      if (typeof dataPointer === 'string') {\n        const formControl = formGroup.get(dataPointer);\n        if (formControl) { return formControl; }\n      }\n      console.error(`getControl error: Invalid JSON Pointer: ${dataPointer}`);\n    }\n    if (!isObject(formGroup)) {\n      console.error(`getControl error: Invalid formGroup: ${formGroup}`);\n    }\n    return null;\n  }\n  let dataPointerArray = JsonPointer.parse(dataPointer);\n  if (returnGroup) { dataPointerArray = dataPointerArray.slice(0, -1); }\n\n  // If formGroup input is a real formGroup (not a formGroup template)\n  // try using formGroup.get() to return the control\n  if (typeof formGroup.get === 'function' &&\n    dataPointerArray.every(key => key.indexOf('.') === -1)\n  ) {\n    const formControl = formGroup.get(dataPointerArray.join('.'));\n    if (formControl) { return formControl; }\n  }\n\n  // If formGroup input is a formGroup template,\n  // or formGroup.get() failed to return the control,\n  // search the formGroup object for dataPointer's control\n  let subGroup = formGroup;\n  for (const key of dataPointerArray) {\n    if (hasOwn(subGroup, 'controls')) { subGroup = subGroup.controls; }\n    if (isArray(subGroup) && (key === '-')) {\n      subGroup = subGroup[subGroup.length - 1];\n    } else if (hasOwn(subGroup, key)) {\n      subGroup = subGroup[key];\n    } else {\n      console.error(`getControl error: Unable to find \"${key}\" item in FormGroup.`);\n      console.error(dataPointer);\n      console.error(formGroup);\n      return;\n    }\n  }\n  return subGroup;\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/format-regex.constants.ts",
    "content": "// updated from AJV fast format regular expressions:\n// https://github.com/epoberezkin/ajv/blob/master/lib/compile/formats.js\n\nexport const jsonSchemaFormatTests = {\n\n  'date': /^\\d\\d\\d\\d-[0-1]\\d-[0-3]\\d$/,\n\n  'time': /^[0-2]\\d:[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:z|[+-]\\d\\d:\\d\\d)?$/i,\n\n  // Modified to allow incomplete entries, such as\n  // \"2000-03-14T01:59:26.535\" (needs \"Z\") or \"2000-03-14T01:59\" (needs \":00Z\")\n  'date-time': /^\\d\\d\\d\\d-[0-1]\\d-[0-3]\\d[t\\s][0-2]\\d:[0-5]\\d(?::[0-5]\\d)?(?:\\.\\d+)?(?:z|[+-]\\d\\d:\\d\\d)?$/i,\n\n  // email (sources from jsen validator):\n  // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363\n  // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'willful violation')\n  'email': /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i,\n\n  'hostname': /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*$/i,\n\n  // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html\n  'ipv4': /^(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$/,\n\n  // optimized http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses\n  // tslint:disable-next-line:max-line-length\n  'ipv6': /^\\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(?:%.+)?\\s*$/i,\n\n  // uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js\n  'uri': /^(?:[a-z][a-z0-9+-.]*)(?::|\\/)\\/?[^\\s]*$/i,\n\n  // uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A\n  'uri-reference': /^(?:(?:[a-z][a-z0-9+-.]*:)?\\/\\/)?[^\\s]*$/i,\n\n  // uri-template: https://tools.ietf.org/html/rfc6570\n  // tslint:disable-next-line:max-line-length\n  'uri-template': /^(?:(?:[^\\x00-\\x20\"'<>%\\\\^`{|}]|%[0-9a-f]{2})|\\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\\*)?)*\\})*$/i,\n\n  // For the source: https://gist.github.com/dperini/729294\n  // For test cases: https://mathiasbynens.be/demo/url-regex\n  // tslint:disable-next-line:max-line-length\n  // @todo Delete current URL in favour of the commented out URL rule when this ajv issue is fixed https://github.com/eslint/eslint/issues/7983.\n  // tslint:disable-next-line:max-line-length\n  // URL: /^(?:(?:https?|ftp):\\/\\/)(?:\\S+(?::\\S*)?@)?(?:(?!10(?:\\.\\d{1,3}){3})(?!127(?:\\.\\d{1,3}){3})(?!169\\.254(?:\\.\\d{1,3}){2})(?!192\\.168(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u{00a1}-\\u{ffff}0-9]+-?)*[a-z\\u{00a1}-\\u{ffff}0-9]+)(?:\\.(?:[a-z\\u{00a1}-\\u{ffff}0-9]+-?)*[a-z\\u{00a1}-\\u{ffff}0-9]+)*(?:\\.(?:[a-z\\u{00a1}-\\u{ffff}]{2,})))(?::\\d{2,5})?(?:\\/[^\\s]*)?$/iu,\n  // tslint:disable-next-line:max-line-length\n  'url': /^(?:(?:http[s\\u017F]?|ftp):\\/\\/)(?:(?:[\\0-\\x08\\x0E-\\x1F!-\\x9F\\xA1-\\u167F\\u1681-\\u1FFF\\u200B-\\u2027\\u202A-\\u202E\\u2030-\\u205E\\u2060-\\u2FFF\\u3001-\\uD7FF\\uE000-\\uFEFE\\uFF00-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])+(?::(?:[\\0-\\x08\\x0E-\\x1F!-\\x9F\\xA1-\\u167F\\u1681-\\u1FFF\\u200B-\\u2027\\u202A-\\u202E\\u2030-\\u205E\\u2060-\\u2FFF\\u3001-\\uD7FF\\uE000-\\uFEFE\\uFF00-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])*)?@)?(?:(?!10(?:\\.[0-9]{1,3}){3})(?!127(?:\\.[0-9]{1,3}){3})(?!169\\.254(?:\\.[0-9]{1,3}){2})(?!192\\.168(?:\\.[0-9]{1,3}){2})(?!172\\.(?:1[6-9]|2[0-9]|3[01])(?:\\.[0-9]{1,3}){2})(?:[1-9][0-9]?|1[0-9][0-9]|2[01][0-9]|22[0-3])(?:\\.(?:1?[0-9]{1,2}|2[0-4][0-9]|25[0-5])){2}(?:\\.(?:[1-9][0-9]?|1[0-9][0-9]|2[0-4][0-9]|25[0-4]))|(?:(?:(?:[0-9KSa-z\\xA1-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])+-?)*(?:[0-9KSa-z\\xA1-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])+)(?:\\.(?:(?:[0-9KSa-z\\xA1-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])+-?)*(?:[0-9KSa-z\\xA1-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])+)*(?:\\.(?:(?:[KSa-z\\xA1-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]){2,})))(?::[0-9]{2,5})?(?:\\/(?:[\\0-\\x08\\x0E-\\x1F!-\\x9F\\xA1-\\u167F\\u1681-\\u1FFF\\u200B-\\u2027\\u202A-\\u202E\\u2030-\\u205E\\u2060-\\u2FFF\\u3001-\\uD7FF\\uE000-\\uFEFE\\uFF00-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])*)?$/i,\n\n  // uuid: http://tools.ietf.org/html/rfc4122\n  'uuid': /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i,\n\n  // optimized https://gist.github.com/olmokramer/82ccce673f86db7cda5e\n  // tslint:disable-next-line:max-line-length\n  'color': /^\\s*(#(?:[\\da-f]{3}){1,2}|rgb\\((?:\\d{1,3},\\s*){2}\\d{1,3}\\)|rgba\\((?:\\d{1,3},\\s*){3}\\d*\\.?\\d+\\)|hsl\\(\\d{1,3}(?:,\\s*\\d{1,3}%){2}\\)|hsla\\(\\d{1,3}(?:,\\s*\\d{1,3}%){2},\\s*\\d*\\.?\\d+\\))\\s*$/gi,\n\n  // JSON-pointer: https://tools.ietf.org/html/rfc6901\n  'json-pointer': /^(?:\\/(?:[^~/]|~0|~1)*)*$|^#(?:\\/(?:[a-z0-9_\\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i,\n\n  'relative-json-pointer': /^(?:0|[1-9][0-9]*)(?:#|(?:\\/(?:[^~/]|~0|~1)*)*)$/,\n\n  'regex': function (str) {\n    if (/[^\\\\]\\\\Z/.test(str)) { return false; }\n    try {\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n};\n\nexport type JsonSchemaFormatNames =\n  'date' | 'time' | 'date-time' | 'email' | 'hostname' | 'ipv4' | 'ipv6' |\n  'uri' | 'uri-reference' | 'uri-template' | 'url' | 'uuid' | 'color' |\n  'json-pointer' | 'relative-json-pointer' | 'regex';\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/index.ts",
    "content": "// Warning: Changing the following order may cause errors if the new order\n// causes a library to be imported before another library it depends on.\n\nexport {\n  _executeValidators, _executeAsyncValidators, _mergeObjects, _mergeErrors,\n  isDefined, hasValue, isEmpty, isString, isNumber, isInteger, isBoolean,\n  isFunction, isObject, isArray, isDate, isMap, isSet, isPromise, isObservable,\n  getType, isType, isPrimitive, toJavaScriptType, toSchemaType, _toPromise,\n  toObservable, inArray, xor, SchemaPrimitiveType, SchemaType, JavaScriptPrimitiveType,\n  JavaScriptType, PrimitiveValue, PlainObject, IValidatorFn, AsyncIValidatorFn\n} from './validator.functions';\n\nexport {\n  addClasses, copy, forEach, forEachCopy, hasOwn, mergeFilteredObject,\n  uniqueItems, commonItems, fixTitle, toTitleCase\n} from './utility.functions';\n\nexport { Pointer, JsonPointer } from './jsonpointer.functions';\n\nexport { JsonValidators } from './json.validators';\n\nexport {\n  buildSchemaFromLayout, buildSchemaFromData, getFromSchema,\n  removeRecursiveReferences, getInputType, checkInlineType, isInputRequired,\n  updateInputOptions, getTitleMapFromOneOf, getControlValidators,\n  resolveSchemaReferences, getSubSchema, combineAllOf, fixRequiredArrayProperties\n} from './json-schema.functions';\n\nexport { convertSchemaToDraft6 } from './convert-schema-to-draft6.function';\n\nexport { mergeSchemas } from './merge-schemas.function';\n\nexport {\n  buildFormGroupTemplate, buildFormGroup, formatFormData,\n  getControl, setRequiredFields\n} from './form-group.functions';\n\nexport {\n  buildLayout, buildLayoutFromSchema, mapLayout, getLayoutNode, buildTitleMap\n} from './layout.functions';\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/json-schema.functions.ts",
    "content": "import cloneDeep from 'lodash/cloneDeep';\nimport { forEach, hasOwn, mergeFilteredObject } from './utility.functions';\nimport {\n  getType,\n  hasValue,\n  inArray,\n  isArray,\n  isNumber,\n  isObject,\n  isString\n  } from './validator.functions';\nimport { JsonPointer } from './jsonpointer.functions';\nimport { mergeSchemas } from './merge-schemas.function';\n\n\n/**\n * JSON Schema function library:\n *\n * buildSchemaFromLayout:   TODO: Write this function\n *\n * buildSchemaFromData:\n *\n * getFromSchema:\n *\n * removeRecursiveReferences:\n *\n * getInputType:\n *\n * checkInlineType:\n *\n * isInputRequired:\n *\n * updateInputOptions:\n *\n * getTitleMapFromOneOf:\n *\n * getControlValidators:\n *\n * resolveSchemaReferences:\n *\n * getSubSchema:\n *\n * combineAllOf:\n *\n * fixRequiredArrayProperties:\n */\n\n/**\n * 'buildSchemaFromLayout' function\n *\n * TODO: Build a JSON Schema from a JSON Form layout\n *\n * //   layout - The JSON Form layout\n * //  - The new JSON Schema\n */\nexport function buildSchemaFromLayout(layout) {\n  return;\n  // let newSchema: any = { };\n  // const walkLayout = (layoutItems: any[], callback: Function): any[] => {\n  //   let returnArray: any[] = [];\n  //   for (let layoutItem of layoutItems) {\n  //     const returnItem: any = callback(layoutItem);\n  //     if (returnItem) { returnArray = returnArray.concat(callback(layoutItem)); }\n  //     if (layoutItem.items) {\n  //       returnArray = returnArray.concat(walkLayout(layoutItem.items, callback));\n  //     }\n  //   }\n  //   return returnArray;\n  // };\n  // walkLayout(layout, layoutItem => {\n  //   let itemKey: string;\n  //   if (typeof layoutItem === 'string') {\n  //     itemKey = layoutItem;\n  //   } else if (layoutItem.key) {\n  //     itemKey = layoutItem.key;\n  //   }\n  //   if (!itemKey) { return; }\n  //   //\n  // });\n}\n\n/**\n * 'buildSchemaFromData' function\n *\n * Build a JSON Schema from a data object\n *\n * //   data - The data object\n * //  { boolean = false } requireAllFields - Require all fields?\n * //  { boolean = true } isRoot - is root\n * //  - The new JSON Schema\n */\nexport function buildSchemaFromData(\n  data, requireAllFields = false, isRoot = true\n) {\n  const newSchema: any = {};\n  const getFieldType = (value: any): string => {\n    const fieldType = getType(value, 'strict');\n    return { integer: 'number', null: 'string' }[fieldType] || fieldType;\n  };\n  const buildSubSchema = (value) =>\n    buildSchemaFromData(value, requireAllFields, false);\n  if (isRoot) { newSchema.$schema = 'http://json-schema.org/draft-06/schema#'; }\n  newSchema.type = getFieldType(data);\n  if (newSchema.type === 'object') {\n    newSchema.properties = {};\n    if (requireAllFields) { newSchema.required = []; }\n    for (const key of Object.keys(data)) {\n      newSchema.properties[key] = buildSubSchema(data[key]);\n      if (requireAllFields) { newSchema.required.push(key); }\n    }\n  } else if (newSchema.type === 'array') {\n    newSchema.items = data.map(buildSubSchema);\n    // If all items are the same type, use an object for items instead of an array\n    if ((new Set(data.map(getFieldType))).size === 1) {\n      newSchema.items = newSchema.items.reduce((a, b) => ({ ...a, ...b }), {});\n    }\n    if (requireAllFields) { newSchema.minItems = 1; }\n  }\n  return newSchema;\n}\n\n/**\n * 'getFromSchema' function\n *\n * Uses a JSON Pointer for a value within a data object to retrieve\n * the schema for that value within schema for the data object.\n *\n * The optional third parameter can also be set to return something else:\n * 'schema' (default): the schema for the value indicated by the data pointer\n * 'parentSchema': the schema for the value's parent object or array\n * 'schemaPointer': a pointer to the value's schema within the object's schema\n * 'parentSchemaPointer': a pointer to the schema for the value's parent object or array\n *\n * //   schema - The schema to get the sub-schema from\n * //  { Pointer } dataPointer - JSON Pointer (string or array)\n * //  { string = 'schema' } returnType - what to return?\n * //  - The located sub-schema\n */\nexport function getFromSchema(schema, dataPointer, returnType = 'schema') {\n  const dataPointerArray: any[] = JsonPointer.parse(dataPointer);\n  if (dataPointerArray === null) {\n    console.error(`getFromSchema error: Invalid JSON Pointer: ${dataPointer}`);\n    return null;\n  }\n  let subSchema = schema;\n  const schemaPointer = [];\n  const length = dataPointerArray.length;\n  if (returnType.slice(0, 6) === 'parent') { dataPointerArray.length--; }\n  for (let i = 0; i < length; ++i) {\n    const parentSchema = subSchema;\n    const key = dataPointerArray[i];\n    let subSchemaFound = false;\n    if (typeof subSchema !== 'object') {\n      console.error(`getFromSchema error: Unable to find \"${key}\" key in schema.`);\n      console.error(schema);\n      console.error(dataPointer);\n      return null;\n    }\n    if (subSchema.type === 'array' && (!isNaN(key) || key === '-')) {\n      if (hasOwn(subSchema, 'items')) {\n        if (isObject(subSchema.items)) {\n          subSchemaFound = true;\n          subSchema = subSchema.items;\n          schemaPointer.push('items');\n        } else if (isArray(subSchema.items)) {\n          if (!isNaN(key) && subSchema.items.length >= +key) {\n            subSchemaFound = true;\n            subSchema = subSchema.items[+key];\n            schemaPointer.push('items', key);\n          }\n        }\n      }\n      if (!subSchemaFound && isObject(subSchema.additionalItems)) {\n        subSchemaFound = true;\n        subSchema = subSchema.additionalItems;\n        schemaPointer.push('additionalItems');\n      } else if (subSchema.additionalItems !== false) {\n        subSchemaFound = true;\n        subSchema = { };\n        schemaPointer.push('additionalItems');\n      }\n    } else if (subSchema.type === 'object') {\n      if (isObject(subSchema.properties) && hasOwn(subSchema.properties, key)) {\n        subSchemaFound = true;\n        subSchema = subSchema.properties[key];\n        schemaPointer.push('properties', key);\n      } else if (isObject(subSchema.additionalProperties)) {\n        subSchemaFound = true;\n        subSchema = subSchema.additionalProperties;\n        schemaPointer.push('additionalProperties');\n      } else if (subSchema.additionalProperties !== false) {\n        subSchemaFound = true;\n        subSchema = { };\n        schemaPointer.push('additionalProperties');\n      }\n    }\n    if (!subSchemaFound) {\n      console.error(`getFromSchema error: Unable to find \"${key}\" item in schema.`);\n      console.error(schema);\n      console.error(dataPointer);\n      return;\n    }\n  }\n  return returnType.slice(-7) === 'Pointer' ? schemaPointer : subSchema;\n}\n\n/**\n * 'removeRecursiveReferences' function\n *\n * Checks a JSON Pointer against a map of recursive references and returns\n * a JSON Pointer to the shallowest equivalent location in the same object.\n *\n * Using this functions enables an object to be constructed with unlimited\n * recursion, while maintaing a fixed set of metadata, such as field data types.\n * The object can grow as large as it wants, and deeply recursed nodes can\n * just refer to the metadata for their shallow equivalents, instead of having\n * to add additional redundant metadata for each recursively added node.\n *\n * Example:\n *\n * pointer:         '/stuff/and/more/and/more/and/more/and/more/stuff'\n * recursiveRefMap: [['/stuff/and/more/and/more', '/stuff/and/more/']]\n * returned:        '/stuff/and/more/stuff'\n *\n * //  { Pointer } pointer -\n * //  { Map<string, string> } recursiveRefMap -\n * //  { Map<string, number> = new Map() } arrayMap - optional\n * // { string } -\n */\nexport function removeRecursiveReferences(\n  pointer, recursiveRefMap, arrayMap = new Map()\n) {\n  if (!pointer) { return ''; }\n  let genericPointer =\n    JsonPointer.toGenericPointer(JsonPointer.compile(pointer), arrayMap);\n  if (genericPointer.indexOf('/') === -1) { return genericPointer; }\n  let possibleReferences = true;\n  while (possibleReferences) {\n    possibleReferences = false;\n    recursiveRefMap.forEach((toPointer, fromPointer) => {\n      if (JsonPointer.isSubPointer(toPointer, fromPointer)) {\n        while (JsonPointer.isSubPointer(fromPointer, genericPointer, true)) {\n          genericPointer = JsonPointer.toGenericPointer(\n            toPointer + genericPointer.slice(fromPointer.length), arrayMap\n          );\n          possibleReferences = true;\n        }\n      }\n    });\n  }\n  return genericPointer;\n}\n\n/**\n * 'getInputType' function\n *\n * //   schema\n * //  { any = null } layoutNode\n * // { string }\n */\nexport function getInputType(schema, layoutNode: any = null) {\n  // x-schema-form = Angular Schema Form compatibility\n  // widget & component = React Jsonschema Form compatibility\n  const controlType = JsonPointer.getFirst([\n    [schema, '/x-schema-form/type'],\n    [schema, '/x-schema-form/widget/component'],\n    [schema, '/x-schema-form/widget'],\n    [schema, '/widget/component'],\n    [schema, '/widget']\n  ]);\n  if (isString(controlType)) { return checkInlineType(controlType, schema, layoutNode); }\n  let schemaType = schema.type;\n  if (schemaType) {\n    if (isArray(schemaType)) { // If multiple types listed, use most inclusive type\n      schemaType =\n        inArray('object', schemaType) && hasOwn(schema, 'properties') ? 'object' :\n        inArray('array', schemaType) && hasOwn(schema, 'items') ? 'array' :\n        inArray('array', schemaType) && hasOwn(schema, 'additionalItems') ? 'array' :\n        inArray('string', schemaType) ? 'string' :\n        inArray('number', schemaType) ? 'number' :\n        inArray('integer', schemaType) ? 'integer' :\n        inArray('boolean', schemaType) ? 'boolean' : 'unknown';\n    }\n    if (schemaType === 'boolean') { return 'checkbox'; }\n    if (schemaType === 'object') {\n      if (hasOwn(schema, 'properties') || hasOwn(schema, 'additionalProperties')) {\n        return 'section';\n      }\n      // TODO: Figure out how to handle additionalProperties\n      if (hasOwn(schema, '$ref')) { return '$ref'; }\n    }\n    if (schemaType === 'array') {\n      const itemsObject = JsonPointer.getFirst([\n        [schema, '/items'],\n        [schema, '/additionalItems']\n      ]) || {};\n      return hasOwn(itemsObject, 'enum') && schema.maxItems !== 1 ?\n        checkInlineType('checkboxes', schema, layoutNode) : 'array';\n    }\n    if (schemaType === 'null') { return 'none'; }\n    if (JsonPointer.has(layoutNode, '/options/titleMap') ||\n      hasOwn(schema, 'enum') || getTitleMapFromOneOf(schema, null, true)\n    ) { return 'select'; }\n    if (schemaType === 'number' || schemaType === 'integer') {\n      return (schemaType === 'integer' || hasOwn(schema, 'multipleOf')) &&\n        hasOwn(schema, 'maximum') && hasOwn(schema, 'minimum') ? 'range' : schemaType;\n    }\n    if (schemaType === 'string') {\n      return {\n        'color': 'color',\n        'date': 'date',\n        'date-time': 'datetime-local',\n        'email': 'email',\n        'uri': 'url',\n      }[schema.format] || 'text';\n    }\n  }\n  if (hasOwn(schema, '$ref')) { return '$ref'; }\n  if (isArray(schema.oneOf) || isArray(schema.anyOf)) { return 'one-of'; }\n  console.error(`getInputType error: Unable to determine input type for ${schemaType}`);\n  console.error('schema', schema);\n  if (layoutNode) { console.error('layoutNode', layoutNode); }\n  return 'none';\n}\n\n/**\n * 'checkInlineType' function\n *\n * Checks layout and schema nodes for 'inline: true', and converts\n * 'radios' or 'checkboxes' to 'radios-inline' or 'checkboxes-inline'\n *\n * //  { string } controlType -\n * //   schema -\n * //  { any = null } layoutNode -\n * // { string }\n */\nexport function checkInlineType(controlType, schema, layoutNode: any = null) {\n  if (!isString(controlType) || (\n    controlType.slice(0, 8) !== 'checkbox' && controlType.slice(0, 5) !== 'radio'\n  )) {\n    return controlType;\n  }\n  if (\n    JsonPointer.getFirst([\n      [layoutNode, '/inline'],\n      [layoutNode, '/options/inline'],\n      [schema, '/inline'],\n      [schema, '/x-schema-form/inline'],\n      [schema, '/x-schema-form/options/inline'],\n      [schema, '/x-schema-form/widget/inline'],\n      [schema, '/x-schema-form/widget/component/inline'],\n      [schema, '/x-schema-form/widget/component/options/inline'],\n      [schema, '/widget/inline'],\n      [schema, '/widget/component/inline'],\n      [schema, '/widget/component/options/inline'],\n    ]) === true\n  ) {\n    return controlType.slice(0, 5) === 'radio' ?\n      'radios-inline' : 'checkboxes-inline';\n  } else {\n    return controlType;\n  }\n}\n\n/**\n * 'isInputRequired' function\n *\n * Checks a JSON Schema to see if an item is required\n *\n * //   schema - the schema to check\n * //  { string } schemaPointer - the pointer to the item to check\n * // { boolean } - true if the item is required, false if not\n */\nexport function isInputRequired(schema, schemaPointer) {\n  if (!isObject(schema)) {\n    console.error('isInputRequired error: Input schema must be an object.');\n    return false;\n  }\n  const listPointerArray = JsonPointer.parse(schemaPointer);\n  if (isArray(listPointerArray)) {\n    if (!listPointerArray.length) { return schema.required === true; }\n    const keyName = listPointerArray.pop();\n    const nextToLastKey = listPointerArray[listPointerArray.length - 1];\n    if (['properties', 'additionalProperties', 'patternProperties', 'items', 'additionalItems']\n      .includes(nextToLastKey)\n    ) {\n      listPointerArray.pop();\n    }\n    const parentSchema = JsonPointer.get(schema, listPointerArray) || {};\n    if (isArray(parentSchema.required)) {\n      return parentSchema.required.includes(keyName);\n    }\n    if (parentSchema.type === 'array') {\n      return hasOwn(parentSchema, 'minItems') &&\n        isNumber(keyName) &&\n        +parentSchema.minItems > +keyName;\n    }\n  }\n  return false;\n}\n\n/**\n * 'updateInputOptions' function\n *\n * //   layoutNode\n * //   schema\n * //   jsf\n * // { void }\n */\nexport function updateInputOptions(layoutNode, schema, jsf) {\n  if (!isObject(layoutNode) || !isObject(layoutNode.options)) { return; }\n\n  // Set all option values in layoutNode.options\n  const newOptions: any = { };\n  const fixUiKeys = key => key.slice(0, 3).toLowerCase() === 'ui:' ? key.slice(3) : key;\n  mergeFilteredObject(newOptions, jsf.formOptions.defautWidgetOptions, [], fixUiKeys);\n  [ [ JsonPointer.get(schema, '/ui:widget/options'), [] ],\n    [ JsonPointer.get(schema, '/ui:widget'), [] ],\n    [ schema, [\n      'additionalProperties', 'additionalItems', 'properties', 'items',\n      'required', 'type', 'x-schema-form', '$ref'\n    ] ],\n    [ JsonPointer.get(schema, '/x-schema-form/options'), [] ],\n    [ JsonPointer.get(schema, '/x-schema-form'), ['items', 'options'] ],\n    [ layoutNode, [\n      '_id', '$ref', 'arrayItem', 'arrayItemType', 'dataPointer', 'dataType',\n      'items', 'key', 'name', 'options', 'recursiveReference', 'type', 'widget'\n    ] ],\n    [ layoutNode.options, [] ],\n  ].forEach(([ object, excludeKeys ]) =>\n    mergeFilteredObject(newOptions, object, excludeKeys, fixUiKeys)\n  );\n  if (!hasOwn(newOptions, 'titleMap')) {\n    let newTitleMap: any = null;\n    newTitleMap = getTitleMapFromOneOf(schema, newOptions.flatList);\n    if (newTitleMap) { newOptions.titleMap = newTitleMap; }\n    if (!hasOwn(newOptions, 'titleMap') && !hasOwn(newOptions, 'enum') && hasOwn(schema, 'items')) {\n      if (JsonPointer.has(schema, '/items/titleMap')) {\n        newOptions.titleMap = schema.items.titleMap;\n      } else if (JsonPointer.has(schema, '/items/enum')) {\n        newOptions.enum = schema.items.enum;\n        if (!hasOwn(newOptions, 'enumNames') && JsonPointer.has(schema, '/items/enumNames')) {\n          newOptions.enumNames = schema.items.enumNames;\n        }\n      } else if (JsonPointer.has(schema, '/items/oneOf')) {\n        newTitleMap = getTitleMapFromOneOf(schema.items, newOptions.flatList);\n        if (newTitleMap) { newOptions.titleMap = newTitleMap; }\n      }\n    }\n  }\n\n  // If schema type is integer, enforce by setting multipleOf = 1\n  if (schema.type === 'integer' && !hasValue(newOptions.multipleOf)) {\n    newOptions.multipleOf = 1;\n  }\n\n  // Copy any typeahead word lists to options.typeahead.source\n  if (JsonPointer.has(newOptions, '/autocomplete/source')) {\n    newOptions.typeahead = newOptions.autocomplete;\n  } else if (JsonPointer.has(newOptions, '/tagsinput/source')) {\n    newOptions.typeahead = newOptions.tagsinput;\n  } else if (JsonPointer.has(newOptions, '/tagsinput/typeahead/source')) {\n    newOptions.typeahead = newOptions.tagsinput.typeahead;\n  }\n\n  layoutNode.options = newOptions;\n}\n\n/**\n * 'getTitleMapFromOneOf' function\n *\n * //  { schema } schema\n * //  { boolean = null } flatList\n * //  { boolean = false } validateOnly\n * // { validators }\n */\nexport function getTitleMapFromOneOf(\n  schema: any = {}, flatList: boolean = null, validateOnly = false\n) {\n  let titleMap = null;\n  const oneOf = schema.oneOf || schema.anyOf || null;\n  if (isArray(oneOf) && oneOf.every(item => item.title)) {\n    if (oneOf.every(item => isArray(item.enum) && item.enum.length === 1)) {\n      if (validateOnly) { return true; }\n      titleMap = oneOf.map(item => ({ name: item.title, value: item.enum[0] }));\n    } else if (oneOf.every(item => item.const)) {\n      if (validateOnly) { return true; }\n      titleMap = oneOf.map(item => ({ name: item.title, value: item.const }));\n    }\n\n    // if flatList !== false and some items have colons, make grouped map\n    if (flatList !== false && (titleMap || [])\n      .filter(title => ((title || {}).name || '').indexOf(': ')).length > 1\n    ) {\n\n      // Split name on first colon to create grouped map (name -> group: name)\n      const newTitleMap = titleMap.map(title => {\n        const [group, name] = title.name.split(/: (.+)/);\n        return group && name ? { ...title, group, name } : title;\n      });\n\n      // If flatList === true or at least one group has multiple items, use grouped map\n      if (flatList === true || newTitleMap.some((title, index) => index &&\n        hasOwn(title, 'group') && title.group === newTitleMap[index - 1].group\n      )) {\n        titleMap = newTitleMap;\n      }\n    }\n  }\n  return validateOnly ? false : titleMap;\n}\n\n/**\n * 'getControlValidators' function\n *\n * //  schema\n * // { validators }\n */\nexport function getControlValidators(schema) {\n  if (!isObject(schema)) { return null; }\n  const validators: any = { };\n  if (hasOwn(schema, 'type')) {\n    switch (schema.type) {\n      case 'string':\n        forEach(['pattern', 'format', 'minLength', 'maxLength'], (prop) => {\n          if (hasOwn(schema, prop)) { validators[prop] = [schema[prop]]; }\n        });\n      break;\n      case 'number': case 'integer':\n        forEach(['Minimum', 'Maximum'], (ucLimit) => {\n          const eLimit = 'exclusive' + ucLimit;\n          const limit = ucLimit.toLowerCase();\n          if (hasOwn(schema, limit)) {\n            const exclusive = hasOwn(schema, eLimit) && schema[eLimit] === true;\n            validators[limit] = [schema[limit], exclusive];\n          }\n        });\n        forEach(['multipleOf', 'type'], (prop) => {\n          if (hasOwn(schema, prop)) { validators[prop] = [schema[prop]]; }\n        });\n      break;\n      case 'object':\n        forEach(['minProperties', 'maxProperties', 'dependencies'], (prop) => {\n          if (hasOwn(schema, prop)) { validators[prop] = [schema[prop]]; }\n        });\n      break;\n      case 'array':\n        forEach(['minItems', 'maxItems', 'uniqueItems'], (prop) => {\n          if (hasOwn(schema, prop)) { validators[prop] = [schema[prop]]; }\n        });\n      break;\n    }\n  }\n  if (hasOwn(schema, 'enum')) { validators.enum = [schema.enum]; }\n  return validators;\n}\n\n/**\n * 'resolveSchemaReferences' function\n *\n * Find all $ref links in schema and save links and referenced schemas in\n * schemaRefLibrary, schemaRecursiveRefMap, and dataRecursiveRefMap\n *\n * //  schema\n * //  schemaRefLibrary\n * // { Map<string, string> } schemaRecursiveRefMap\n * // { Map<string, string> } dataRecursiveRefMap\n * // { Map<string, number> } arrayMap\n * //\n */\nexport function resolveSchemaReferences(\n  schema, schemaRefLibrary, schemaRecursiveRefMap, dataRecursiveRefMap, arrayMap\n) {\n  if (!isObject(schema)) {\n    console.error('resolveSchemaReferences error: schema must be an object.');\n    return;\n  }\n  const refLinks = new Set<string>();\n  const refMapSet = new Set<string>();\n  const refMap = new Map<string, string>();\n  const recursiveRefMap = new Map<string, string>();\n  const refLibrary: any = {};\n\n  // Search schema for all $ref links, and build full refLibrary\n  JsonPointer.forEachDeep(schema, (subSchema, subSchemaPointer) => {\n    if (hasOwn(subSchema, '$ref') && isString(subSchema['$ref'])) {\n      const refPointer = JsonPointer.compile(subSchema['$ref']);\n      refLinks.add(refPointer);\n      refMapSet.add(subSchemaPointer + '~~' + refPointer);\n      refMap.set(subSchemaPointer, refPointer);\n    }\n  });\n  refLinks.forEach(ref => refLibrary[ref] = getSubSchema(schema, ref));\n\n  // Follow all ref links and save in refMapSet,\n  // to find any multi-link recursive refernces\n  let checkRefLinks = true;\n  while (checkRefLinks) {\n    checkRefLinks = false;\n    Array.from(refMap).forEach(([fromRef1, toRef1]) => Array.from(refMap)\n      .filter(([fromRef2, toRef2]) =>\n        JsonPointer.isSubPointer(toRef1, fromRef2, true) &&\n        !JsonPointer.isSubPointer(toRef2, toRef1, true) &&\n        !refMapSet.has(fromRef1 + fromRef2.slice(toRef1.length) + '~~' + toRef2)\n      )\n      .forEach(([fromRef2, toRef2]) => {\n        refMapSet.add(fromRef1 + fromRef2.slice(toRef1.length) + '~~' + toRef2);\n        checkRefLinks = true;\n      })\n    );\n  }\n\n  // Build full recursiveRefMap\n  // First pass - save all internally recursive refs from refMapSet\n  Array.from(refMapSet)\n    .map(refLink => refLink.split('~~'))\n    .filter(([fromRef, toRef]) => JsonPointer.isSubPointer(toRef, fromRef))\n    .forEach(([fromRef, toRef]) => recursiveRefMap.set(fromRef, toRef));\n  // Second pass - create recursive versions of any other refs that link to recursive refs\n  Array.from(refMap)\n    .filter(([fromRef1, toRef1]) => Array.from(recursiveRefMap.keys())\n      .every(fromRef2 => !JsonPointer.isSubPointer(fromRef1, fromRef2, true))\n    )\n    .forEach(([fromRef1, toRef1]) => Array.from(recursiveRefMap)\n      .filter(([fromRef2, toRef2]) =>\n        !recursiveRefMap.has(fromRef1 + fromRef2.slice(toRef1.length)) &&\n        JsonPointer.isSubPointer(toRef1, fromRef2, true) &&\n        !JsonPointer.isSubPointer(toRef1, fromRef1, true)\n      )\n      .forEach(([fromRef2, toRef2]) => recursiveRefMap.set(\n        fromRef1 + fromRef2.slice(toRef1.length),\n        fromRef1 + toRef2.slice(toRef1.length)\n      ))\n    );\n\n  // Create compiled schema by replacing all non-recursive $ref links with\n  // thieir linked schemas and, where possible, combining schemas in allOf arrays.\n  let compiledSchema = { ...schema };\n  delete compiledSchema.definitions;\n  compiledSchema =\n    getSubSchema(compiledSchema, '', refLibrary, recursiveRefMap);\n\n  // Make sure all remaining schema $refs are recursive, and build final\n  // schemaRefLibrary, schemaRecursiveRefMap, dataRecursiveRefMap, & arrayMap\n  JsonPointer.forEachDeep(compiledSchema, (subSchema, subSchemaPointer) => {\n    if (isString(subSchema['$ref'])) {\n      let refPointer = JsonPointer.compile(subSchema['$ref']);\n      if (!JsonPointer.isSubPointer(refPointer, subSchemaPointer, true)) {\n        refPointer = removeRecursiveReferences(subSchemaPointer, recursiveRefMap);\n        JsonPointer.set(compiledSchema, subSchemaPointer, { $ref: `#${refPointer}` });\n      }\n      if (!hasOwn(schemaRefLibrary, 'refPointer')) {\n        schemaRefLibrary[refPointer] = !refPointer.length ? compiledSchema :\n          getSubSchema(compiledSchema, refPointer, schemaRefLibrary, recursiveRefMap);\n      }\n      if (!schemaRecursiveRefMap.has(subSchemaPointer)) {\n        schemaRecursiveRefMap.set(subSchemaPointer, refPointer);\n      }\n      const fromDataRef = JsonPointer.toDataPointer(subSchemaPointer, compiledSchema);\n      if (!dataRecursiveRefMap.has(fromDataRef)) {\n        const toDataRef = JsonPointer.toDataPointer(refPointer, compiledSchema);\n        dataRecursiveRefMap.set(fromDataRef, toDataRef);\n      }\n    }\n    if (subSchema.type === 'array' &&\n      (hasOwn(subSchema, 'items') || hasOwn(subSchema, 'additionalItems'))\n    ) {\n      const dataPointer = JsonPointer.toDataPointer(subSchemaPointer, compiledSchema);\n      if (!arrayMap.has(dataPointer)) {\n        const tupleItems = isArray(subSchema.items) ? subSchema.items.length : 0;\n        arrayMap.set(dataPointer, tupleItems);\n      }\n    }\n  }, true);\n  return compiledSchema;\n}\n\n/**\n * 'getSubSchema' function\n *\n * //   schema\n * //  { Pointer } pointer\n * //  { object } schemaRefLibrary\n * //  { Map<string, string> } schemaRecursiveRefMap\n * //  { string[] = [] } usedPointers\n * //\n */\nexport function getSubSchema(\n  schema, pointer, schemaRefLibrary = null,\n  schemaRecursiveRefMap: Map<string, string> = null, usedPointers: string[] = []\n) {\n  if (!schemaRefLibrary || !schemaRecursiveRefMap) {\n    return JsonPointer.getCopy(schema, pointer);\n  }\n  if (typeof pointer !== 'string') { pointer = JsonPointer.compile(pointer); }\n  usedPointers = [ ...usedPointers, pointer ];\n  let newSchema: any = null;\n  if (pointer === '') {\n    newSchema = cloneDeep(schema);\n  } else {\n    const shortPointer = removeRecursiveReferences(pointer, schemaRecursiveRefMap);\n    if (shortPointer !== pointer) { usedPointers = [ ...usedPointers, shortPointer ]; }\n    newSchema = JsonPointer.getFirstCopy([\n      [schemaRefLibrary, [shortPointer]],\n      [schema, pointer],\n      [schema, shortPointer]\n    ]);\n  }\n  return JsonPointer.forEachDeepCopy(newSchema, (subSchema, subPointer) => {\n    if (isObject(subSchema)) {\n\n      // Replace non-recursive $ref links with referenced schemas\n      if (isString(subSchema.$ref)) {\n        const refPointer = JsonPointer.compile(subSchema.$ref);\n        if (refPointer.length && usedPointers.every(ptr =>\n          !JsonPointer.isSubPointer(refPointer, ptr, true)\n        )) {\n          const refSchema = getSubSchema(\n            schema, refPointer, schemaRefLibrary, schemaRecursiveRefMap, usedPointers\n          );\n          if (Object.keys(subSchema).length === 1) {\n            return refSchema;\n          } else {\n            const extraKeys = { ...subSchema };\n            delete extraKeys.$ref;\n            return mergeSchemas(refSchema, extraKeys);\n          }\n        }\n      }\n\n      // TODO: Convert schemas with 'type' arrays to 'oneOf'\n\n      // Combine allOf subSchemas\n      if (isArray(subSchema.allOf)) { return combineAllOf(subSchema); }\n\n      // Fix incorrectly placed array object required lists\n      if (subSchema.type === 'array' && isArray(subSchema.required)) {\n        return fixRequiredArrayProperties(subSchema);\n      }\n    }\n    return subSchema;\n  }, true, <string>pointer);\n}\n\n/**\n * 'combineAllOf' function\n *\n * Attempt to convert an allOf schema object into\n * a non-allOf schema object with equivalent rules.\n *\n * //   schema - allOf schema object\n * //  - converted schema object\n */\nexport function combineAllOf(schema) {\n  if (!isObject(schema) || !isArray(schema.allOf)) { return schema; }\n  let mergedSchema = mergeSchemas(...schema.allOf);\n  if (Object.keys(schema).length > 1) {\n    const extraKeys = { ...schema };\n    delete extraKeys.allOf;\n    mergedSchema = mergeSchemas(mergedSchema, extraKeys);\n  }\n  return mergedSchema;\n}\n\n/**\n * 'fixRequiredArrayProperties' function\n *\n * Fixes an incorrectly placed required list inside an array schema, by moving\n * it into items.properties or additionalItems.properties, where it belongs.\n *\n * //   schema - allOf schema object\n * //  - converted schema object\n */\nexport function fixRequiredArrayProperties(schema) {\n  if (schema.type === 'array' && isArray(schema.required)) {\n    const itemsObject = hasOwn(schema.items, 'properties') ? 'items' :\n      hasOwn(schema.additionalItems, 'properties') ? 'additionalItems' : null;\n    if (itemsObject && !hasOwn(schema[itemsObject], 'required') && (\n      hasOwn(schema[itemsObject], 'additionalProperties') ||\n      schema.required.every(key => hasOwn(schema[itemsObject].properties, key))\n    )) {\n      schema = cloneDeep(schema);\n      schema[itemsObject].required = schema.required;\n      delete schema.required;\n    }\n  }\n  return schema;\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/json.validators.ts",
    "content": "import isEqual from 'lodash/isEqual';\nimport {\n  _executeAsyncValidators,\n  _executeValidators,\n  _mergeErrors,\n  _mergeObjects,\n  AsyncIValidatorFn,\n  getType,\n  hasValue,\n  isArray,\n  isBoolean,\n  isDefined,\n  isEmpty,\n  isNumber,\n  isString,\n  isType,\n  IValidatorFn,\n  SchemaPrimitiveType,\n  toJavaScriptType,\n  toObservable,\n  xor\n  } from './validator.functions';\nimport { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';\nimport { forEachCopy } from './utility.functions';\nimport { forkJoin } from 'rxjs';\nimport { JsonSchemaFormatNames, jsonSchemaFormatTests } from './format-regex.constants';\nimport { map } from 'rxjs/operators';\n\n\n\n/**\n * 'JsonValidators' class\n *\n * Provides an extended set of validators to be used by form controls,\n * compatible with standard JSON Schema validation options.\n * http://json-schema.org/latest/json-schema-validation.html\n *\n * Note: This library is designed as a drop-in replacement for the Angular\n * Validators library, and except for one small breaking change to the 'pattern'\n * validator (described below) it can even be imported as a substitute, like so:\n *\n *   import { JsonValidators as Validators } from 'json-validators';\n *\n * and it should work with existing code as a complete replacement.\n *\n * The one exception is the 'pattern' validator, which has been changed to\n * matche partial values by default (the standard 'pattern' validator wrapped\n * all patterns in '^' and '$', forcing them to always match an entire value).\n * However, the old behavior can be restored by simply adding '^' and '$'\n * around your patterns, or by passing an optional second parameter of TRUE.\n * This change is to make the 'pattern' validator match the behavior of a\n * JSON Schema pattern, which allows partial matches, rather than the behavior\n * of an HTML input control pattern, which does not.\n *\n * This library replaces Angular's validators and combination functions\n * with the following validators and transformation functions:\n *\n * Validators:\n *   For all formControls:     required (*), type, enum, const\n *   For text formControls:    minLength (*), maxLength (*), pattern (*), format\n *   For numeric formControls: maximum, exclusiveMaximum,\n *                             minimum, exclusiveMinimum, multipleOf\n *   For formGroup objects:    minProperties, maxProperties, dependencies\n *   For formArray arrays:     minItems, maxItems, uniqueItems, contains\n *   Not used by JSON Schema:  min (*), max (*), requiredTrue (*), email (*)\n * (Validators originally included with Angular are maked with (*).)\n *\n * NOTE / TODO: The dependencies validator is not complete.\n * NOTE / TODO: The contains validator is not complete.\n *\n * Validators not used by JSON Schema (but included for compatibility)\n * and their JSON Schema equivalents:\n *\n *   Angular validator | JSON Schema equivalent\n *   ------------------|-----------------------\n *     min(number)     |   minimum(number)\n *     max(number)     |   maximum(number)\n *     requiredTrue()  |   const(true)\n *     email()         |   format('email')\n *\n * Validator transformation functions:\n *   composeAnyOf, composeOneOf, composeAllOf, composeNot\n * (Angular's original combination funciton, 'compose', is also included for\n * backward compatibility, though it is functionally equivalent to composeAllOf,\n * asside from its more generic error message.)\n *\n * All validators have also been extended to accept an optional second argument\n * which, if passed a TRUE value, causes the validator to perform the opposite\n * of its original finction. (This is used internally to enable 'not' and\n * 'composeOneOf' to function and return useful error messages.)\n *\n * The 'required' validator has also been overloaded so that if called with\n * a boolean parameter (or no parameters) it returns the original validator\n * function (rather than executing it). However, if it is called with an\n * AbstractControl parameter (as was previously required), it behaves\n * exactly as before.\n *\n * This enables all validators (including 'required') to be constructed in\n * exactly the same way, so they can be automatically applied using the\n * equivalent key names and values taken directly from a JSON Schema.\n *\n * This source code is partially derived from Angular,\n * which is Copyright (c) 2014-2017 Google, Inc.\n * Use of this source code is therefore governed by the same MIT-style license\n * that can be found in the LICENSE file at https://angular.io/license\n *\n * Original Angular Validators:\n * https://github.com/angular/angular/blob/master/packages/forms/src/validators.ts\n */\nexport class JsonValidators {\n\n  /**\n   * Validator functions:\n   *\n   * For all formControls:     required, type, enum, const\n   * For text formControls:    minLength, maxLength, pattern, format\n   * For numeric formControls: maximum, exclusiveMaximum,\n   *                           minimum, exclusiveMinimum, multipleOf\n   * For formGroup objects:    minProperties, maxProperties, dependencies\n   * For formArray arrays:     minItems, maxItems, uniqueItems, contains\n   *\n   * TODO: finish dependencies validator\n   */\n\n  /**\n   * 'required' validator\n   *\n   * This validator is overloaded, compared to the default required validator.\n   * If called with no parameters, or TRUE, this validator returns the\n   * 'required' validator function (rather than executing it). This matches\n   * the behavior of all other validators in this library.\n   *\n   * If this validator is called with an AbstractControl parameter\n   * (as was previously required) it behaves the same as Angular's default\n   * required validator, and returns an error if the control is empty.\n   *\n   * Old behavior: (if input type = AbstractControl)\n   * // {AbstractControl} control - required control\n   * // {{[key: string]: boolean}} - returns error message if no input\n   *\n   * New behavior: (if no input, or input type = boolean)\n   * // {boolean = true} required? - true to validate, false to disable\n   * // {IValidatorFn} - returns the 'required' validator function itself\n   */\n  static required(input: AbstractControl): ValidationErrors|null;\n  static required(input?: boolean): IValidatorFn;\n\n  static required(input?: AbstractControl|boolean): ValidationErrors|null|IValidatorFn {\n    if (input === undefined) { input = true; }\n    switch (input) {\n      case true: // Return required function (do not execute it yet)\n        return (control: AbstractControl, invert = false): ValidationErrors|null => {\n          if (invert) { return null; } // if not required, always return valid\n          return hasValue(control.value) ? null : { 'required': true };\n        };\n      case false: // Do nothing (if field is not required, it is always valid)\n        return JsonValidators.nullValidator;\n      default: // Execute required function\n        return hasValue((<AbstractControl>input).value) ? null : { 'required': true };\n    }\n  }\n\n  /**\n   * 'type' validator\n   *\n   * Requires a control to only accept values of a specified type,\n   * or one of an array of types.\n   *\n   * Note: SchemaPrimitiveType = 'string'|'number'|'integer'|'boolean'|'null'\n   *\n   * // {SchemaPrimitiveType|SchemaPrimitiveType[]} type - type(s) to accept\n   * // {IValidatorFn}\n   */\n  static type(requiredType: SchemaPrimitiveType|SchemaPrimitiveType[]): IValidatorFn {\n    if (!hasValue(requiredType)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentValue: any = control.value;\n      const isValid = isArray(requiredType) ?\n        (<SchemaPrimitiveType[]>requiredType).some(type => isType(currentValue, type)) :\n        isType(currentValue, <SchemaPrimitiveType>requiredType);\n      return xor(isValid, invert) ?\n        null : { 'type': { requiredType, currentValue } };\n    };\n  }\n\n  /**\n   * 'enum' validator\n   *\n   * Requires a control to have a value from an enumerated list of values.\n   *\n   * Converts types as needed to allow string inputs to still correctly\n   * match number, boolean, and null enum values.\n   *\n   * // {any[]} allowedValues - array of acceptable values\n   * // {IValidatorFn}\n   */\n  static enum(allowedValues: any[]): IValidatorFn {\n    if (!isArray(allowedValues)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentValue: any = control.value;\n      const isEqualVal = (enumValue, inputValue) =>\n        enumValue === inputValue ||\n        (isNumber(enumValue) && +inputValue === +enumValue) ||\n        (isBoolean(enumValue, 'strict') &&\n          toJavaScriptType(inputValue, 'boolean') === enumValue) ||\n        (enumValue === null && !hasValue(inputValue)) ||\n        isEqual(enumValue, inputValue);\n      const isValid = isArray(currentValue) ?\n        currentValue.every(inputValue => allowedValues.some(enumValue =>\n          isEqualVal(enumValue, inputValue)\n        )) :\n        allowedValues.some(enumValue => isEqualVal(enumValue, currentValue));\n      return xor(isValid, invert) ?\n        null : { 'enum': { allowedValues, currentValue } };\n    };\n  }\n\n  /**\n   * 'const' validator\n   *\n   * Requires a control to have a specific value.\n   *\n   * Converts types as needed to allow string inputs to still correctly\n   * match number, boolean, and null values.\n   *\n   * TODO: modify to work with objects\n   *\n   * // {any[]} requiredValue - required value\n   * // {IValidatorFn}\n   */\n  static const(requiredValue: any): IValidatorFn {\n    if (!hasValue(requiredValue)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentValue: any = control.value;\n      const isEqualVal = (constValue, inputValue) =>\n        constValue === inputValue ||\n        isNumber(constValue) && +inputValue === +constValue ||\n        isBoolean(constValue, 'strict') &&\n          toJavaScriptType(inputValue, 'boolean') === constValue ||\n        constValue === null && !hasValue(inputValue);\n      const isValid = isEqualVal(requiredValue, currentValue);\n      return xor(isValid, invert) ?\n        null : { 'const': { requiredValue, currentValue } };\n    };\n  }\n\n  /**\n   * 'minLength' validator\n   *\n   * Requires a control's text value to be greater than a specified length.\n   *\n   * // {number} minimumLength - minimum allowed string length\n   * // {boolean = false} invert - instead return error object only if valid\n   * // {IValidatorFn}\n   */\n  static minLength(minimumLength: number): IValidatorFn {\n    if (!hasValue(minimumLength)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentLength = isString(control.value) ? control.value.length : 0;\n      const isValid = currentLength >= minimumLength;\n      return xor(isValid, invert) ?\n        null : { 'minLength': { minimumLength, currentLength } };\n    };\n  }\n\n  /**\n   * 'maxLength' validator\n   *\n   * Requires a control's text value to be less than a specified length.\n   *\n   * // {number} maximumLength - maximum allowed string length\n   * // {boolean = false} invert - instead return error object only if valid\n   * // {IValidatorFn}\n   */\n  static maxLength(maximumLength: number): IValidatorFn {\n    if (!hasValue(maximumLength)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      const currentLength = isString(control.value) ? control.value.length : 0;\n      const isValid = currentLength <= maximumLength;\n      return xor(isValid, invert) ?\n        null : { 'maxLength': { maximumLength, currentLength } };\n    };\n  }\n\n  /**\n   * 'pattern' validator\n   *\n   * Note: NOT the same as Angular's default pattern validator.\n   *\n   * Requires a control's value to match a specified regular expression pattern.\n   *\n   * This validator changes the behavior of default pattern validator\n   * by replacing RegExp(`^${pattern}$`) with RegExp(`${pattern}`),\n   * which allows for partial matches.\n   *\n   * To return to the default funcitonality, and match the entire string,\n   * pass TRUE as the optional second parameter.\n   *\n   * // {string} pattern - regular expression pattern\n   * // {boolean = false} wholeString - match whole value string?\n   * // {IValidatorFn}\n   */\n  static pattern(pattern: string|RegExp, wholeString = false): IValidatorFn {\n    if (!hasValue(pattern)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      let regex: RegExp;\n      let requiredPattern: string;\n      if (typeof pattern === 'string') {\n        requiredPattern = (wholeString) ? `^${pattern}$` : pattern;\n        regex = new RegExp(requiredPattern);\n      } else {\n        requiredPattern = pattern.toString();\n        regex = pattern;\n      }\n      const currentValue: string = control.value;\n      const isValid = isString(currentValue) ? regex.test(currentValue) : false;\n      return xor(isValid, invert) ?\n        null : { 'pattern': { requiredPattern, currentValue } };\n    };\n  }\n\n  /**\n   * 'format' validator\n   *\n   * Requires a control to have a value of a certain format.\n   *\n   * This validator currently checks the following formsts:\n   *   date, time, date-time, email, hostname, ipv4, ipv6,\n   *   uri, uri-reference, uri-template, url, uuid, color,\n   *   json-pointer, relative-json-pointer, regex\n   *\n   * Fast format regular expressions copied from AJV:\n   * https://github.com/epoberezkin/ajv/blob/master/lib/compile/formats.js\n   *\n   * // {JsonSchemaFormatNames} requiredFormat - format to check\n   * // {IValidatorFn}\n   */\n  static format(requiredFormat: JsonSchemaFormatNames): IValidatorFn {\n    if (!hasValue(requiredFormat)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      let isValid: boolean;\n      const currentValue: string|Date = control.value;\n      if (isString(currentValue)) {\n        const formatTest: Function|RegExp = jsonSchemaFormatTests[requiredFormat];\n        if (typeof formatTest === 'object') {\n          isValid = (<RegExp>formatTest).test(<string>currentValue);\n        } else if (typeof formatTest === 'function') {\n          isValid = (<Function>formatTest)(<string>currentValue);\n        } else {\n          console.error(`format validator error: \"${requiredFormat}\" is not a recognized format.`);\n          isValid = true;\n        }\n      } else {\n        // Allow JavaScript Date objects\n        isValid = ['date', 'time', 'date-time'].includes(requiredFormat) &&\n          Object.prototype.toString.call(currentValue) === '[object Date]';\n      }\n      return xor(isValid, invert) ?\n        null : { 'format': { requiredFormat, currentValue } };\n    };\n  }\n\n  /**\n   * 'minimum' validator\n   *\n   * Requires a control's numeric value to be greater than or equal to\n   * a minimum amount.\n   *\n   * Any non-numeric value is also valid (according to the HTML forms spec,\n   * a non-numeric value doesn't have a minimum).\n   * https://www.w3.org/TR/html5/forms.html#attr-input-max\n   *\n   * // {number} minimum - minimum allowed value\n   * // {IValidatorFn}\n   */\n  static minimum(minimumValue: number): IValidatorFn {\n    if (!hasValue(minimumValue)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentValue = control.value;\n      const isValid = !isNumber(currentValue) || currentValue >= minimumValue;\n      return xor(isValid, invert) ?\n        null : { 'minimum': { minimumValue, currentValue } };\n    };\n  }\n\n  /**\n   * 'exclusiveMinimum' validator\n   *\n   * Requires a control's numeric value to be less than a maximum amount.\n   *\n   * Any non-numeric value is also valid (according to the HTML forms spec,\n   * a non-numeric value doesn't have a maximum).\n   * https://www.w3.org/TR/html5/forms.html#attr-input-max\n   *\n   * // {number} exclusiveMinimumValue - maximum allowed value\n   * // {IValidatorFn}\n   */\n  static exclusiveMinimum(exclusiveMinimumValue: number): IValidatorFn {\n    if (!hasValue(exclusiveMinimumValue)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentValue = control.value;\n      const isValid = !isNumber(currentValue) || +currentValue < exclusiveMinimumValue;\n      return xor(isValid, invert) ?\n        null : { 'exclusiveMinimum': { exclusiveMinimumValue, currentValue } };\n    };\n  }\n\n  /**\n   * 'maximum' validator\n   *\n   * Requires a control's numeric value to be less than or equal to\n   * a maximum amount.\n   *\n   * Any non-numeric value is also valid (according to the HTML forms spec,\n   * a non-numeric value doesn't have a maximum).\n   * https://www.w3.org/TR/html5/forms.html#attr-input-max\n   *\n   * // {number} maximumValue - maximum allowed value\n   * // {IValidatorFn}\n   */\n  static maximum(maximumValue: number): IValidatorFn {\n    if (!hasValue(maximumValue)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentValue = control.value;\n      const isValid = !isNumber(currentValue) || +currentValue <= maximumValue;\n      return xor(isValid, invert) ?\n        null : { 'maximum': { maximumValue, currentValue } };\n    };\n  }\n\n  /**\n   * 'exclusiveMaximum' validator\n   *\n   * Requires a control's numeric value to be less than a maximum amount.\n   *\n   * Any non-numeric value is also valid (according to the HTML forms spec,\n   * a non-numeric value doesn't have a maximum).\n   * https://www.w3.org/TR/html5/forms.html#attr-input-max\n   *\n   * // {number} exclusiveMaximumValue - maximum allowed value\n   * // {IValidatorFn}\n   */\n  static exclusiveMaximum(exclusiveMaximumValue: number): IValidatorFn {\n    if (!hasValue(exclusiveMaximumValue)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentValue = control.value;\n      const isValid = !isNumber(currentValue) || +currentValue < exclusiveMaximumValue;\n      return xor(isValid, invert) ?\n        null : { 'exclusiveMaximum': { exclusiveMaximumValue, currentValue } };\n    };\n  }\n\n  /**\n   * 'multipleOf' validator\n   *\n   * Requires a control to have a numeric value that is a multiple\n   * of a specified number.\n   *\n   * // {number} multipleOfValue - number value must be a multiple of\n   * // {IValidatorFn}\n   */\n  static multipleOf(multipleOfValue: number): IValidatorFn {\n    if (!hasValue(multipleOfValue)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentValue = control.value;\n      const isValid = isNumber(currentValue) &&\n        currentValue % multipleOfValue === 0;\n      return xor(isValid, invert) ?\n        null : { 'multipleOf': { multipleOfValue, currentValue } };\n    };\n  }\n\n  /**\n   * 'minProperties' validator\n   *\n   * Requires a form group to have a minimum number of properties (i.e. have\n   * values entered in a minimum number of controls within the group).\n   *\n   * // {number} minimumProperties - minimum number of properties allowed\n   * // {IValidatorFn}\n   */\n  static minProperties(minimumProperties: number): IValidatorFn {\n    if (!hasValue(minimumProperties)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentProperties = Object.keys(control.value).length || 0;\n      const isValid = currentProperties >= minimumProperties;\n      return xor(isValid, invert) ?\n        null : { 'minProperties': { minimumProperties, currentProperties } };\n    };\n  }\n\n  /**\n   * 'maxProperties' validator\n   *\n   * Requires a form group to have a maximum number of properties (i.e. have\n   * values entered in a maximum number of controls within the group).\n   *\n   * Note: Has no effect if the form group does not contain more than the\n   * maximum number of controls.\n   *\n   * // {number} maximumProperties - maximum number of properties allowed\n   * // {IValidatorFn}\n   */\n  static maxProperties(maximumProperties: number): IValidatorFn {\n    if (!hasValue(maximumProperties)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      const currentProperties = Object.keys(control.value).length || 0;\n      const isValid = currentProperties <= maximumProperties;\n      return xor(isValid, invert) ?\n        null : { 'maxProperties': { maximumProperties, currentProperties } };\n    };\n  }\n\n  /**\n   * 'dependencies' validator\n   *\n   * Requires the controls in a form group to meet additional validation\n   * criteria, depending on the values of other controls in the group.\n   *\n   * Examples:\n   * https://spacetelescope.github.io/understanding-json-schema/reference/object.html#dependencies\n   *\n   * // {any} dependencies - required dependencies\n   * // {IValidatorFn}\n   */\n  static dependencies(dependencies: any): IValidatorFn {\n    if (getType(dependencies) !== 'object' || isEmpty(dependencies)) {\n      return JsonValidators.nullValidator;\n    }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const allErrors = _mergeObjects(\n        forEachCopy(dependencies, (value, requiringField) => {\n          if (!hasValue(control.value[requiringField])) { return null; }\n          let requiringFieldErrors: ValidationErrors = { };\n          let requiredFields: string[];\n          let properties: ValidationErrors = { };\n          if (getType(dependencies[requiringField]) === 'array') {\n            requiredFields = dependencies[requiringField];\n          } else if (getType(dependencies[requiringField]) === 'object') {\n            requiredFields = dependencies[requiringField]['required'] || [];\n            properties = dependencies[requiringField]['properties'] || { };\n          }\n\n          // Validate property dependencies\n          for (const requiredField of requiredFields) {\n            if (xor(!hasValue(control.value[requiredField]), invert)) {\n              requiringFieldErrors[requiredField] = { 'required': true };\n            }\n          }\n\n          // Validate schema dependencies\n          requiringFieldErrors = _mergeObjects(requiringFieldErrors,\n            forEachCopy(properties, (requirements, requiredField) => {\n              const requiredFieldErrors = _mergeObjects(\n                forEachCopy(requirements, (requirement, parameter) => {\n                  let validator: IValidatorFn = null;\n                  if (requirement === 'maximum' || requirement === 'minimum') {\n                    const exclusive = !!requirements['exclusiveM' + requirement.slice(1)];\n                    validator = JsonValidators[requirement](parameter, exclusive);\n                  } else if (typeof JsonValidators[requirement] === 'function') {\n                    validator = JsonValidators[requirement](parameter);\n                  }\n                  return !isDefined(validator) ?\n                    null : validator(control.value[requiredField]);\n                })\n              );\n              return isEmpty(requiredFieldErrors) ?\n                null : { [requiredField]: requiredFieldErrors };\n            })\n          );\n          return isEmpty(requiringFieldErrors) ?\n            null : { [requiringField]: requiringFieldErrors };\n        })\n      );\n      return isEmpty(allErrors) ? null : allErrors;\n    };\n  }\n\n  /**\n   * 'minItems' validator\n   *\n   * Requires a form array to have a minimum number of values.\n   *\n   * // {number} minimumItems - minimum number of items allowed\n   * // {IValidatorFn}\n   */\n  static minItems(minimumItems: number): IValidatorFn {\n    if (!hasValue(minimumItems)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const currentItems = isArray(control.value) ? control.value.length : 0;\n      const isValid = currentItems >= minimumItems;\n      return xor(isValid, invert) ?\n        null : { 'minItems': { minimumItems, currentItems } };\n    };\n  }\n\n  /**\n   * 'maxItems' validator\n   *\n   * Requires a form array to have a maximum number of values.\n   *\n   * // {number} maximumItems - maximum number of items allowed\n   * // {IValidatorFn}\n   */\n  static maxItems(maximumItems: number): IValidatorFn {\n    if (!hasValue(maximumItems)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      const currentItems = isArray(control.value) ? control.value.length : 0;\n      const isValid = currentItems <= maximumItems;\n      return xor(isValid, invert) ?\n        null : { 'maxItems': { maximumItems, currentItems } };\n    };\n  }\n\n  /**\n   * 'uniqueItems' validator\n   *\n   * Requires values in a form array to be unique.\n   *\n   * // {boolean = true} unique? - true to validate, false to disable\n   * // {IValidatorFn}\n   */\n  static uniqueItems(unique = true): IValidatorFn {\n    if (!unique) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const sorted: any[] = control.value.slice().sort();\n      const duplicateItems = [];\n      for (let i = 1; i < sorted.length; i++) {\n        if (sorted[i - 1] === sorted[i] && duplicateItems.includes(sorted[i])) {\n          duplicateItems.push(sorted[i]);\n        }\n      }\n      const isValid = !duplicateItems.length;\n      return xor(isValid, invert) ?\n        null : { 'uniqueItems': { duplicateItems } };\n    };\n  }\n\n  /**\n   * 'contains' validator\n   *\n   * TODO: Complete this validator\n   *\n   * Requires values in a form array to be unique.\n   *\n   * // {boolean = true} unique? - true to validate, false to disable\n   * // {IValidatorFn}\n   */\n  static contains(requiredItem = true): IValidatorFn {\n    if (!requiredItem) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value) || !isArray(control.value)) { return null; }\n      const currentItems = control.value;\n      // const isValid = currentItems.some(item =>\n      //\n      // );\n      const isValid = true;\n      return xor(isValid, invert) ?\n        null : { 'contains': { requiredItem, currentItems } };\n    };\n  }\n\n  /**\n   * No-op validator. Included for backward compatibility.\n   */\n  static nullValidator(control: AbstractControl): ValidationErrors|null {\n    return null;\n  }\n\n  /**\n   * Validator transformation functions:\n   * composeAnyOf, composeOneOf, composeAllOf, composeNot,\n   * compose, composeAsync\n   *\n   * TODO: Add composeAnyOfAsync, composeOneOfAsync,\n   *           composeAllOfAsync, composeNotAsync\n   */\n\n  /**\n   * 'composeAnyOf' validator combination function\n   *\n   * Accepts an array of validators and returns a single validator that\n   * evaluates to valid if any one or more of the submitted validators are\n   * valid. If every validator is invalid, it returns combined errors from\n   * all validators.\n   *\n   * // {IValidatorFn[]} validators - array of validators to combine\n   * // {IValidatorFn} - single combined validator function\n   */\n  static composeAnyOf(validators: IValidatorFn[]): IValidatorFn {\n    if (!validators) { return null; }\n    const presentValidators = validators.filter(isDefined);\n    if (presentValidators.length === 0) { return null; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      const arrayOfErrors =\n        _executeValidators(control, presentValidators, invert).filter(isDefined);\n      const isValid = validators.length > arrayOfErrors.length;\n      return xor(isValid, invert) ?\n        null : _mergeObjects(...arrayOfErrors, { 'anyOf': !invert });\n    };\n  }\n\n  /**\n   * 'composeOneOf' validator combination function\n   *\n   * Accepts an array of validators and returns a single validator that\n   * evaluates to valid only if exactly one of the submitted validators\n   * is valid. Otherwise returns combined information from all validators,\n   * both valid and invalid.\n   *\n   * // {IValidatorFn[]} validators - array of validators to combine\n   * // {IValidatorFn} - single combined validator function\n   */\n  static composeOneOf(validators: IValidatorFn[]): IValidatorFn {\n    if (!validators) { return null; }\n    const presentValidators = validators.filter(isDefined);\n    if (presentValidators.length === 0) { return null; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      const arrayOfErrors =\n        _executeValidators(control, presentValidators);\n      const validControls =\n        validators.length - arrayOfErrors.filter(isDefined).length;\n      const isValid = validControls === 1;\n      if (xor(isValid, invert)) { return null; }\n      const arrayOfValids =\n        _executeValidators(control, presentValidators, invert);\n      return _mergeObjects(...arrayOfErrors, ...arrayOfValids, { 'oneOf': !invert });\n    };\n  }\n\n  /**\n   * 'composeAllOf' validator combination function\n   *\n   * Accepts an array of validators and returns a single validator that\n   * evaluates to valid only if all the submitted validators are individually\n   * valid. Otherwise it returns combined errors from all invalid validators.\n   *\n   * // {IValidatorFn[]} validators - array of validators to combine\n   * // {IValidatorFn} - single combined validator function\n   */\n  static composeAllOf(validators: IValidatorFn[]): IValidatorFn {\n    if (!validators) { return null; }\n    const presentValidators = validators.filter(isDefined);\n    if (presentValidators.length === 0) { return null; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      const combinedErrors = _mergeErrors(\n        _executeValidators(control, presentValidators, invert)\n      );\n      const isValid = combinedErrors === null;\n      return (xor(isValid, invert)) ?\n        null : _mergeObjects(combinedErrors, { 'allOf': !invert });\n    };\n  }\n\n  /**\n   * 'composeNot' validator inversion function\n   *\n   * Accepts a single validator function and inverts its result.\n   * Returns valid if the submitted validator is invalid, and\n   * returns invalid if the submitted validator is valid.\n   * (Note: this function can itself be inverted\n   *   - e.g. composeNot(composeNot(validator)) -\n   *   but this can be confusing and is therefore not recommended.)\n   *\n   * // {IValidatorFn[]} validators - validator(s) to invert\n   * // {IValidatorFn} - new validator function that returns opposite result\n   */\n  static composeNot(validator: IValidatorFn): IValidatorFn {\n    if (!validator) { return null; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null => {\n      if (isEmpty(control.value)) { return null; }\n      const error = validator(control, !invert);\n      const isValid = error === null;\n      return (xor(isValid, invert)) ?\n        null : _mergeObjects(error, { 'not': !invert });\n    };\n  }\n\n  /**\n   * 'compose' validator combination function\n   *\n   * // {IValidatorFn[]} validators - array of validators to combine\n   * // {IValidatorFn} - single combined validator function\n   */\n  static compose(validators: IValidatorFn[]): IValidatorFn {\n    if (!validators) { return null; }\n    const presentValidators = validators.filter(isDefined);\n    if (presentValidators.length === 0) { return null; }\n    return (control: AbstractControl, invert = false): ValidationErrors|null =>\n      _mergeErrors(_executeValidators(control, presentValidators, invert));\n  }\n\n  /**\n   * 'composeAsync' async validator combination function\n   *\n   * // {AsyncIValidatorFn[]} async validators - array of async validators\n   * // {AsyncIValidatorFn} - single combined async validator function\n   */\n  static composeAsync(validators: AsyncIValidatorFn[]): AsyncIValidatorFn {\n    if (!validators) { return null; }\n    const presentValidators = validators.filter(isDefined);\n    if (presentValidators.length === 0) { return null; }\n    return (control: AbstractControl) => {\n      const observables =\n        _executeAsyncValidators(control, presentValidators).map(toObservable);\n      return map.call(forkJoin(observables), _mergeErrors);\n    };\n  }\n\n  // Additional angular validators (not used by Angualr JSON Schema Form)\n  // From https://github.com/angular/angular/blob/master/packages/forms/src/validators.ts\n\n  /**\n   * Validator that requires controls to have a value greater than a number.\n   */\n  static min(min: number): ValidatorFn {\n    if (!hasValue(min)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl): ValidationErrors|null => {\n      // don't validate empty values to allow optional controls\n      if (isEmpty(control.value) || isEmpty(min)) { return null; }\n      const value = parseFloat(control.value);\n      const actual = control.value;\n      // Controls with NaN values after parsing should be treated as not having a\n      // minimum, per the HTML forms spec: https://www.w3.org/TR/html5/forms.html#attr-input-min\n      return isNaN(value) || value >= min ? null : { 'min': { min, actual } };\n    };\n  }\n\n  /**\n   * Validator that requires controls to have a value less than a number.\n   */\n  static max(max: number): ValidatorFn {\n    if (!hasValue(max)) { return JsonValidators.nullValidator; }\n    return (control: AbstractControl): ValidationErrors|null => {\n      // don't validate empty values to allow optional controls\n      if (isEmpty(control.value) || isEmpty(max)) { return null; }\n      const value = parseFloat(control.value);\n      const actual = control.value;\n      // Controls with NaN values after parsing should be treated as not having a\n      // maximum, per the HTML forms spec: https://www.w3.org/TR/html5/forms.html#attr-input-max\n      return isNaN(value) || value <= max ? null : { 'max': { max, actual } };\n    };\n  }\n\n  /**\n   * Validator that requires control value to be true.\n   */\n  static requiredTrue(control: AbstractControl): ValidationErrors|null {\n    if (!control) { return JsonValidators.nullValidator; }\n    return control.value === true ? null : { 'required': true };\n  }\n\n  /**\n   * Validator that performs email validation.\n   */\n  static email(control: AbstractControl): ValidationErrors|null {\n    if (!control) { return JsonValidators.nullValidator; }\n    const EMAIL_REGEXP =\n      // tslint:disable-next-line:max-line-length\n      /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;\n    return EMAIL_REGEXP.test(control.value) ? null : { 'email': true };\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/jsonpointer.functions.ts",
    "content": "import {\n  cleanValueOfQuotes,\n  copy,\n  ExpressionType,\n  getExpressionType,\n  getKeyAndValueByExpressionType,\n  hasOwn,\n  isEqual,\n  isNotEqual,\n  isNotExpression\n} from './utility.functions';\nimport {Injectable} from '@angular/core';\nimport {isArray, isDefined, isEmpty, isMap, isNumber, isObject, isString} from './validator.functions';\n\n/**\n * 'JsonPointer' class\n *\n * Some utilities for using JSON Pointers with JSON objects\n * https://tools.ietf.org/html/rfc6901\n *\n * get, getCopy, getFirst, set, setCopy, insert, insertCopy, remove, has, dict,\n * forEachDeep, forEachDeepCopy, escape, unescape, parse, compile, toKey,\n * isJsonPointer, isSubPointer, toIndexedPointer, toGenericPointer,\n * toControlPointer, toSchemaPointer, toDataPointer, parseObjectPath\n *\n * Some functions based on manuelstofer's json-pointer utilities\n * https://github.com/manuelstofer/json-pointer\n */\nexport type Pointer = string | string[];\n\n@Injectable()\nexport class JsonPointer {\n\n  /**\n   * 'get' function\n   *\n   * Uses a JSON Pointer to retrieve a value from an object.\n   *\n   * //  { object } object - Object to get value from\n   * //  { Pointer } pointer - JSON Pointer (string or array)\n   * //  { number = 0 } startSlice - Zero-based index of first Pointer key to use\n   * //  { number } endSlice - Zero-based index of last Pointer key to use\n   * //  { boolean = false } getBoolean - Return only true or false?\n   * //  { boolean = false } errors - Show error if not found?\n   * // { object } - Located value (or true or false if getBoolean = true)\n   */\n  static get(\n    object, pointer, startSlice = 0, endSlice: number = null,\n    getBoolean = false, errors = false\n  ) {\n    if (object === null) { return getBoolean ? false : undefined; }\n    let keyArray: any[] = this.parse(pointer, errors);\n    if (typeof object === 'object' && keyArray !== null) {\n      let subObject = object;\n      if (startSlice >= keyArray.length || endSlice <= -keyArray.length) { return object; }\n      if (startSlice <= -keyArray.length) { startSlice = 0; }\n      if (!isDefined(endSlice) || endSlice >= keyArray.length) { endSlice = keyArray.length; }\n      keyArray = keyArray.slice(startSlice, endSlice);\n      for (let key of keyArray) {\n        if (key === '-' && isArray(subObject) && subObject.length) {\n          key = subObject.length - 1;\n        }\n        if (isMap(subObject) && subObject.has(key)) {\n          subObject = subObject.get(key);\n        } else if (typeof subObject === 'object' && subObject !== null &&\n          hasOwn(subObject, key)\n        ) {\n          subObject = subObject[key];\n        } else {\n          const evaluatedExpression = JsonPointer.evaluateExpression(subObject, key);\n          if (evaluatedExpression.passed) {\n            subObject = evaluatedExpression.key ? subObject[evaluatedExpression.key] : subObject;\n          } else {\n            this.logErrors(errors, key, pointer, object);\n            return getBoolean ? false : undefined;\n          }\n        }\n      }\n      return getBoolean ? true : subObject;\n    }\n    if (errors && keyArray === null) {\n      console.error(`get error: Invalid JSON Pointer: ${pointer}`);\n    }\n    if (errors && typeof object !== 'object') {\n      console.error('get error: Invalid object:');\n      console.error(object);\n    }\n    return getBoolean ? false : undefined;\n  }\n\n  private static logErrors(errors, key, pointer, object) {\n    if (errors) {\n      console.error(`get error: \"${key}\" key not found in object.`);\n      console.error(pointer);\n      console.error(object);\n    }\n  }\n\n  /**\n   * Evaluates conditional expression in form of `model.<property>==<value>` or\n   * `model.<property>!=<value>` where the first one means that the value must match to be\n   * shown in a form, while the former shows the property only when the property value is not\n   * set, or does not equal the given value.\n   *\n   * // { subObject } subObject -  an object containing the data values of properties\n   * // { key } key - the key from the for loop in a form of `<property>==<value>`\n   *\n   * Returns the object with two properties. The property passed informs whether\n   * the expression evaluated successfully and the property key returns either the same\n   * key if it is not contained inside the subObject or the key of the property if it is contained.\n   */\n  static evaluateExpression(subObject: Object, key: any) {\n    const defaultResult = {passed: false, key: key};\n    const keysAndExpression = this.parseKeysAndExpression(key, subObject);\n    if (!keysAndExpression) {\n      return defaultResult;\n    }\n\n    const ownCheckResult = this.doOwnCheckResult(subObject, keysAndExpression);\n    if (ownCheckResult) {\n      return ownCheckResult;\n    }\n\n    const cleanedValue = cleanValueOfQuotes(keysAndExpression.keyAndValue[1]);\n\n    const evaluatedResult = this.performExpressionOnValue(keysAndExpression, cleanedValue, subObject);\n    if (evaluatedResult) {\n      return evaluatedResult;\n    }\n\n    return defaultResult;\n  }\n\n  /**\n   * Performs the actual evaluation on the given expression with given values and keys.\n   * // { cleanedValue } cleanedValue - the given valued cleaned of quotes if it had any\n   * // { subObject } subObject - the object with properties values\n   * // { keysAndExpression } keysAndExpression - an object holding the expressions with\n   */\n  private static performExpressionOnValue(keysAndExpression: any, cleanedValue: String, subObject: Object) {\n    const propertyByKey = subObject[keysAndExpression.keyAndValue[0]];\n    if (this.doComparisonByExpressionType(keysAndExpression.expressionType, propertyByKey, cleanedValue)) {\n      return {passed: true, key: keysAndExpression.keyAndValue[0]};\n    }\n\n    return null;\n  }\n\n  private static doComparisonByExpressionType(expressionType: ExpressionType, propertyByKey, cleanedValue: String): Boolean {\n    if (isEqual(expressionType)) {\n      return propertyByKey === cleanedValue;\n    }\n    if (isNotEqual(expressionType)) {\n      return propertyByKey !== cleanedValue;\n    }\n    return false;\n  }\n\n  /**\n   * Does the checks when the parsed key is actually no a property inside subObject.\n   * That would mean that the equal comparison makes no sense and thus the negative result\n   * is returned, and the not equal comparison is not necessary because it doesn't equal\n   * obviously. Returns null when the given key is a real property inside the subObject.\n   * // { subObject } subObject - the object with properties values\n   * // { keysAndExpression } keysAndExpression - an object holding the expressions with\n   * the associated keys.\n   */\n  private static doOwnCheckResult(subObject: Object, keysAndExpression) {\n    let ownCheckResult = null;\n    if (!hasOwn(subObject, keysAndExpression.keyAndValue[0])) {\n      if (isEqual(keysAndExpression.expressionType)) {\n        ownCheckResult = {passed: false, key: null};\n      }\n      if (isNotEqual(keysAndExpression.expressionType)) {\n        ownCheckResult = {passed: true, key: null};\n      }\n    }\n    return ownCheckResult;\n  }\n\n  /**\n   * Does the basic checks and tries to parse an expression and a pair\n   * of key and value.\n   * // { key } key - the original for loop created value containing key and value in one string\n   * // { subObject } subObject - the object with properties values\n   */\n  private static parseKeysAndExpression(key: string, subObject) {\n    if (this.keyOrSubObjEmpty(key, subObject)) {\n      return null;\n    }\n    const expressionType = getExpressionType(key.toString());\n    if (isNotExpression(expressionType)) {\n      return null;\n    }\n    const keyAndValue = getKeyAndValueByExpressionType(expressionType, key);\n    if (!keyAndValue || !keyAndValue[0] || !keyAndValue[1]) {\n      return null;\n    }\n    return {expressionType: expressionType, keyAndValue: keyAndValue};\n  }\n\n  private static keyOrSubObjEmpty(key: any, subObject: Object) {\n    return !key || !subObject;\n  }\n\n  /**\n   * 'getCopy' function\n   *\n   * Uses a JSON Pointer to deeply clone a value from an object.\n   *\n   * //  { object } object - Object to get value from\n   * //  { Pointer } pointer - JSON Pointer (string or array)\n   * //  { number = 0 } startSlice - Zero-based index of first Pointer key to use\n   * //  { number } endSlice - Zero-based index of last Pointer key to use\n   * //  { boolean = false } getBoolean - Return only true or false?\n   * //  { boolean = false } errors - Show error if not found?\n   * // { object } - Located value (or true or false if getBoolean = true)\n   */\n  static getCopy(\n    object, pointer, startSlice = 0, endSlice: number = null,\n    getBoolean = false, errors = false\n  ) {\n    const objectToCopy =\n      this.get(object, pointer, startSlice, endSlice, getBoolean, errors);\n    return this.forEachDeepCopy(objectToCopy);\n  }\n\n  /**\n   * 'getFirst' function\n   *\n   * Takes an array of JSON Pointers and objects,\n   * checks each object for a value specified by the pointer,\n   * and returns the first value found.\n   *\n   * //  { [object, pointer][] } items - Array of objects and pointers to check\n   * //  { any = null } defaultValue - Value to return if nothing found\n   * //  { boolean = false } getCopy - Return a copy instead?\n   * //  - First value found\n   */\n  static getFirst(items, defaultValue: any = null, getCopy = false) {\n    if (isEmpty(items)) { return; }\n    if (isArray(items)) {\n      for (const item of items) {\n        if (isEmpty(item)) { continue; }\n        if (isArray(item) && item.length >= 2) {\n          if (isEmpty(item[0]) || isEmpty(item[1])) { continue; }\n          const value = getCopy ?\n            this.getCopy(item[0], item[1]) :\n            this.get(item[0], item[1]);\n          if (value) { return value; }\n          continue;\n        }\n        console.error('getFirst error: Input not in correct format.\\n' +\n          'Should be: [ [ object1, pointer1 ], [ object 2, pointer2 ], etc... ]');\n        return;\n      }\n      return defaultValue;\n    }\n    if (isMap(items)) {\n      for (const [object, pointer] of items) {\n        if (object === null || !this.isJsonPointer(pointer)) { continue; }\n        const value = getCopy ?\n          this.getCopy(object, pointer) :\n          this.get(object, pointer);\n        if (value) { return value; }\n      }\n      return defaultValue;\n    }\n    console.error('getFirst error: Input not in correct format.\\n' +\n      'Should be: [ [ object1, pointer1 ], [ object 2, pointer2 ], etc... ]');\n    return defaultValue;\n  }\n\n  /**\n   * 'getFirstCopy' function\n   *\n   * Similar to getFirst, but always returns a copy.\n   *\n   * //  { [object, pointer][] } items - Array of objects and pointers to check\n   * //  { any = null } defaultValue - Value to return if nothing found\n   * //  - Copy of first value found\n   */\n  static getFirstCopy(items, defaultValue: any = null) {\n    const firstCopy = this.getFirst(items, defaultValue, true);\n    return firstCopy;\n  }\n\n  /**\n   * 'set' function\n   *\n   * Uses a JSON Pointer to set a value on an object.\n   * Also creates any missing sub objects or arrays to contain that value.\n   *\n   * If the optional fourth parameter is TRUE and the inner-most container\n   * is an array, the function will insert the value as a new item at the\n   * specified location in the array, rather than overwriting the existing\n   * value (if any) at that location.\n   *\n   * So set([1, 2, 3], '/1', 4) => [1, 4, 3]\n   * and\n   * So set([1, 2, 3], '/1', 4, true) => [1, 4, 2, 3]\n   *\n   * //  { object } object - The object to set value in\n   * //  { Pointer } pointer - The JSON Pointer (string or array)\n   * //   value - The new value to set\n   * //  { boolean } insert - insert value?\n   * // { object } - The original object, modified with the set value\n   */\n  static set(object, pointer, value, insert = false) {\n    const keyArray = this.parse(pointer);\n    if (keyArray !== null && keyArray.length) {\n      let subObject = object;\n      for (let i = 0; i < keyArray.length - 1; ++i) {\n        let key = keyArray[i];\n        if (key === '-' && isArray(subObject)) {\n          key = subObject.length;\n        }\n        if (isMap(subObject) && subObject.has(key)) {\n          subObject = subObject.get(key);\n        } else {\n          if (!hasOwn(subObject, key)) {\n            subObject[key] = (keyArray[i + 1].match(/^(\\d+|-)$/)) ? [] : {};\n          }\n          subObject = subObject[key];\n        }\n      }\n      const lastKey = keyArray[keyArray.length - 1];\n      if (isArray(subObject) && lastKey === '-') {\n        subObject.push(value);\n      } else if (insert && isArray(subObject) && !isNaN(+lastKey)) {\n        subObject.splice(lastKey, 0, value);\n      } else if (isMap(subObject)) {\n        subObject.set(lastKey, value);\n      } else {\n        subObject[lastKey] = value;\n      }\n      return object;\n    }\n    console.error(`set error: Invalid JSON Pointer: ${pointer}`);\n    return object;\n  }\n\n  /**\n   * 'setCopy' function\n   *\n   * Copies an object and uses a JSON Pointer to set a value on the copy.\n   * Also creates any missing sub objects or arrays to contain that value.\n   *\n   * If the optional fourth parameter is TRUE and the inner-most container\n   * is an array, the function will insert the value as a new item at the\n   * specified location in the array, rather than overwriting the existing value.\n   *\n   * //  { object } object - The object to copy and set value in\n   * //  { Pointer } pointer - The JSON Pointer (string or array)\n   * //   value - The value to set\n   * //  { boolean } insert - insert value?\n   * // { object } - The new object with the set value\n   */\n  static setCopy(object, pointer, value, insert = false) {\n    const keyArray = this.parse(pointer);\n    if (keyArray !== null) {\n      const newObject = copy(object);\n      let subObject = newObject;\n      for (let i = 0; i < keyArray.length - 1; ++i) {\n        let key = keyArray[i];\n        if (key === '-' && isArray(subObject)) {\n          key = subObject.length;\n        }\n        if (isMap(subObject) && subObject.has(key)) {\n          subObject.set(key, copy(subObject.get(key)));\n          subObject = subObject.get(key);\n        } else {\n          if (!hasOwn(subObject, key)) {\n            subObject[key] = (keyArray[i + 1].match(/^(\\d+|-)$/)) ? [] : {};\n          }\n          subObject[key] = copy(subObject[key]);\n          subObject = subObject[key];\n        }\n      }\n      const lastKey = keyArray[keyArray.length - 1];\n      if (isArray(subObject) && lastKey === '-') {\n        subObject.push(value);\n      } else if (insert && isArray(subObject) && !isNaN(+lastKey)) {\n        subObject.splice(lastKey, 0, value);\n      } else if (isMap(subObject)) {\n        subObject.set(lastKey, value);\n      } else {\n        subObject[lastKey] = value;\n      }\n      return newObject;\n    }\n    console.error(`setCopy error: Invalid JSON Pointer: ${pointer}`);\n    return object;\n  }\n\n  /**\n   * 'insert' function\n   *\n   * Calls 'set' with insert = TRUE\n   *\n   * //  { object } object - object to insert value in\n   * //  { Pointer } pointer - JSON Pointer (string or array)\n   * //   value - value to insert\n   * // { object }\n   */\n  static insert(object, pointer, value) {\n    const updatedObject = this.set(object, pointer, value, true);\n    return updatedObject;\n  }\n\n  /**\n   * 'insertCopy' function\n   *\n   * Calls 'setCopy' with insert = TRUE\n   *\n   * //  { object } object - object to insert value in\n   * //  { Pointer } pointer - JSON Pointer (string or array)\n   * //   value - value to insert\n   * // { object }\n   */\n  static insertCopy(object, pointer, value) {\n    const updatedObject = this.setCopy(object, pointer, value, true);\n    return updatedObject;\n  }\n\n  /**\n   * 'remove' function\n   *\n   * Uses a JSON Pointer to remove a key and its attribute from an object\n   *\n   * //  { object } object - object to delete attribute from\n   * //  { Pointer } pointer - JSON Pointer (string or array)\n   * // { object }\n   */\n  static remove(object, pointer) {\n    const keyArray = this.parse(pointer);\n    if (keyArray !== null && keyArray.length) {\n      let lastKey = keyArray.pop();\n      const parentObject = this.get(object, keyArray);\n      if (isArray(parentObject)) {\n        if (lastKey === '-') { lastKey = parentObject.length - 1; }\n        parentObject.splice(lastKey, 1);\n      } else if (isObject(parentObject)) {\n        delete parentObject[lastKey];\n      }\n      return object;\n    }\n    console.error(`remove error: Invalid JSON Pointer: ${pointer}`);\n    return object;\n  }\n\n  /**\n   * 'has' function\n   *\n   * Tests if an object has a value at the location specified by a JSON Pointer\n   *\n   * //  { object } object - object to chek for value\n   * //  { Pointer } pointer - JSON Pointer (string or array)\n   * // { boolean }\n   */\n  static has(object, pointer) {\n    const hasValue = this.get(object, pointer, 0, null, true);\n    return hasValue;\n  }\n\n  /**\n   * 'dict' function\n   *\n   * Returns a (pointer -> value) dictionary for an object\n   *\n   * //  { object } object - The object to create a dictionary from\n   * // { object } - The resulting dictionary object\n   */\n  static dict(object) {\n    const results: any = {};\n    this.forEachDeep(object, (value, pointer) => {\n      if (typeof value !== 'object') { results[pointer] = value; }\n    });\n    return results;\n  }\n\n  /**\n   * 'forEachDeep' function\n   *\n   * Iterates over own enumerable properties of an object or items in an array\n   * and invokes an iteratee function for each key/value or index/value pair.\n   * By default, iterates over items within objects and arrays after calling\n   * the iteratee function on the containing object or array itself.\n   *\n   * The iteratee is invoked with three arguments: (value, pointer, rootObject),\n   * where pointer is a JSON pointer indicating the location of the current\n   * value within the root object, and rootObject is the root object initially\n   * submitted to th function.\n   *\n   * If a third optional parameter 'bottomUp' is set to TRUE, the iterator\n   * function will be called on sub-objects and arrays after being\n   * called on their contents, rather than before, which is the default.\n   *\n   * This function can also optionally be called directly on a sub-object by\n   * including optional 4th and 5th parameterss to specify the initial\n   * root object and pointer.\n   *\n   * //  { object } object - the initial object or array\n   * //  { (v: any, p?: string, o?: any) => any } function - iteratee function\n   * //  { boolean = false } bottomUp - optional, set to TRUE to reverse direction\n   * //  { object = object } rootObject - optional, root object or array\n   * //  { string = '' } pointer - optional, JSON Pointer to object within rootObject\n   * // { object } - The modified object\n   */\n  static forEachDeep(\n    object, fn: (v: any, p?: string, o?: any) => any = (v) => v,\n    bottomUp = false, pointer = '', rootObject = object\n  ) {\n    if (typeof fn !== 'function') {\n      console.error(`forEachDeep error: Iterator is not a function:`, fn);\n      return;\n    }\n    if (!bottomUp) { fn(object, pointer, rootObject); }\n    if (isObject(object) || isArray(object)) {\n      for (const key of Object.keys(object)) {\n        const newPointer = pointer + '/' + this.escape(key);\n        this.forEachDeep(object[key], fn, bottomUp, newPointer, rootObject);\n      }\n    }\n    if (bottomUp) { fn(object, pointer, rootObject); }\n  }\n\n  /**\n   * 'forEachDeepCopy' function\n   *\n   * Similar to forEachDeep, but returns a copy of the original object, with\n   * the same keys and indexes, but with values replaced with the result of\n   * the iteratee function.\n   *\n   * //  { object } object - the initial object or array\n   * //  { (v: any, k?: string, o?: any, p?: any) => any } function - iteratee function\n   * //  { boolean = false } bottomUp - optional, set to TRUE to reverse direction\n   * //  { object = object } rootObject - optional, root object or array\n   * //  { string = '' } pointer - optional, JSON Pointer to object within rootObject\n   * // { object } - The copied object\n   */\n  static forEachDeepCopy(\n    object, fn: (v: any, p?: string, o?: any) => any = (v) => v,\n    bottomUp = false, pointer = '', rootObject = object\n  ) {\n    if (typeof fn !== 'function') {\n      console.error(`forEachDeepCopy error: Iterator is not a function:`, fn);\n      return null;\n    }\n    if (isObject(object) || isArray(object)) {\n      let newObject = isArray(object) ? [ ...object ] : { ...object };\n      if (!bottomUp) { newObject = fn(newObject, pointer, rootObject); }\n      for (const key of Object.keys(newObject)) {\n        const newPointer = pointer + '/' + this.escape(key);\n        newObject[key] = this.forEachDeepCopy(\n          newObject[key], fn, bottomUp, newPointer, rootObject\n        );\n      }\n      if (bottomUp) { newObject = fn(newObject, pointer, rootObject); }\n      return newObject;\n    } else {\n      return fn(object, pointer, rootObject);\n    }\n  }\n\n  /**\n   * 'escape' function\n   *\n   * Escapes a string reference key\n   *\n   * //  { string } key - string key to escape\n   * // { string } - escaped key\n   */\n  static escape(key) {\n    const escaped = key.toString().replace(/~/g, '~0').replace(/\\//g, '~1');\n    return escaped;\n  }\n\n  /**\n   * 'unescape' function\n   *\n   * Unescapes a string reference key\n   *\n   * //  { string } key - string key to unescape\n   * // { string } - unescaped key\n   */\n  static unescape(key) {\n    const unescaped = key.toString().replace(/~1/g, '/').replace(/~0/g, '~');\n    return unescaped;\n  }\n\n  /**\n   * 'parse' function\n   *\n   * Converts a string JSON Pointer into a array of keys\n   * (if input is already an an array of keys, it is returned unchanged)\n   *\n   * //  { Pointer } pointer - JSON Pointer (string or array)\n   * //  { boolean = false } errors - Show error if invalid pointer?\n   * // { string[] } - JSON Pointer array of keys\n   */\n  static parse(pointer, errors = false) {\n    if (!this.isJsonPointer(pointer)) {\n      if (errors) { console.error(`parse error: Invalid JSON Pointer: ${pointer}`); }\n      return null;\n    }\n    if (isArray(pointer)) { return <string[]>pointer; }\n    if (typeof pointer === 'string') {\n      if ((<string>pointer)[0] === '#') { pointer = pointer.slice(1); }\n      if (<string>pointer === '' || <string>pointer === '/') { return []; }\n      return (<string>pointer).slice(1).split('/').map(this.unescape);\n    }\n  }\n\n  /**\n   * 'compile' function\n   *\n   * Converts an array of keys into a JSON Pointer string\n   * (if input is already a string, it is normalized and returned)\n   *\n   * The optional second parameter is a default which will replace any empty keys.\n   *\n   * //  { Pointer } pointer - JSON Pointer (string or array)\n   * //  { string | number = '' } defaultValue - Default value\n   * //  { boolean = false } errors - Show error if invalid pointer?\n   * // { string } - JSON Pointer string\n   */\n  static compile(pointer, defaultValue = '', errors = false) {\n    if (pointer === '#') { return ''; }\n    if (!this.isJsonPointer(pointer)) {\n      if (errors) { console.error(`compile error: Invalid JSON Pointer: ${pointer}`); }\n      return null;\n    }\n    if (isArray(pointer)) {\n      if ((<string[]>pointer).length === 0) { return ''; }\n      return '/' + (<string[]>pointer).map(\n        key => key === '' ? defaultValue : this.escape(key)\n      ).join('/');\n    }\n    if (typeof pointer === 'string') {\n      if (pointer[0] === '#') { pointer = pointer.slice(1); }\n      return pointer;\n    }\n  }\n\n  /**\n   * 'toKey' function\n   *\n   * Extracts name of the final key from a JSON Pointer.\n   *\n   * //  { Pointer } pointer - JSON Pointer (string or array)\n   * //  { boolean = false } errors - Show error if invalid pointer?\n   * // { string } - the extracted key\n   */\n  static toKey(pointer, errors = false) {\n    const keyArray = this.parse(pointer, errors);\n    if (keyArray === null) { return null; }\n    if (!keyArray.length) { return ''; }\n    return keyArray[keyArray.length - 1];\n  }\n\n  /**\n   * 'isJsonPointer' function\n   *\n   * Checks a string or array value to determine if it is a valid JSON Pointer.\n   * Returns true if a string is empty, or starts with '/' or '#/'.\n   * Returns true if an array contains only string values.\n   *\n   * //   value - value to check\n   * // { boolean } - true if value is a valid JSON Pointer, otherwise false\n   */\n  static isJsonPointer(value) {\n    if (isArray(value)) {\n      return value.every(key => typeof key === 'string');\n    } else if (isString(value)) {\n      if (value === '' || value === '#') { return true; }\n      if (value[0] === '/' || value.slice(0, 2) === '#/') {\n        return !/(~[^01]|~$)/g.test(value);\n      }\n    }\n    return false;\n  }\n\n  /**\n   * 'isSubPointer' function\n   *\n   * Checks whether one JSON Pointer is a subset of another.\n   *\n   * //  { Pointer } shortPointer - potential subset JSON Pointer\n   * //  { Pointer } longPointer - potential superset JSON Pointer\n   * //  { boolean = false } trueIfMatching - return true if pointers match?\n   * //  { boolean = false } errors - Show error if invalid pointer?\n   * // { boolean } - true if shortPointer is a subset of longPointer, false if not\n   */\n  static isSubPointer(\n    shortPointer, longPointer, trueIfMatching = false, errors = false\n  ) {\n    if (!this.isJsonPointer(shortPointer) || !this.isJsonPointer(longPointer)) {\n      if (errors) {\n        let invalid = '';\n        if (!this.isJsonPointer(shortPointer)) { invalid += ` 1: ${shortPointer}`; }\n        if (!this.isJsonPointer(longPointer)) { invalid += ` 2: ${longPointer}`; }\n        console.error(`isSubPointer error: Invalid JSON Pointer ${invalid}`);\n      }\n      return;\n    }\n    shortPointer = this.compile(shortPointer, '', errors);\n    longPointer = this.compile(longPointer, '', errors);\n    return shortPointer === longPointer ? trueIfMatching :\n      `${shortPointer}/` === longPointer.slice(0, shortPointer.length + 1);\n  }\n\n  /**\n   * 'toIndexedPointer' function\n   *\n   * Merges an array of numeric indexes and a generic pointer to create an\n   * indexed pointer for a specific item.\n   *\n   * For example, merging the generic pointer '/foo/-/bar/-/baz' and\n   * the array [4, 2] would result in the indexed pointer '/foo/4/bar/2/baz'\n   *\n   *\n   * //  { Pointer } genericPointer - The generic pointer\n   * //  { number[] } indexArray - The array of numeric indexes\n   * //  { Map<string, number> } arrayMap - An optional array map\n   * // { string } - The merged pointer with indexes\n   */\n  static toIndexedPointer(\n    genericPointer, indexArray, arrayMap: Map<string, number> = null\n  ) {\n    if (this.isJsonPointer(genericPointer) && isArray(indexArray)) {\n      let indexedPointer = this.compile(genericPointer);\n      if (isMap(arrayMap)) {\n        let arrayIndex = 0;\n        return indexedPointer.replace(/\\/\\-(?=\\/|$)/g, (key, stringIndex) =>\n          arrayMap.has((<string>indexedPointer).slice(0, stringIndex)) ?\n            '/' + indexArray[arrayIndex++] : key\n        );\n      } else {\n        for (const pointerIndex of indexArray) {\n          indexedPointer = indexedPointer.replace('/-', '/' + pointerIndex);\n        }\n        return indexedPointer;\n      }\n    }\n    if (!this.isJsonPointer(genericPointer)) {\n      console.error(`toIndexedPointer error: Invalid JSON Pointer: ${genericPointer}`);\n    }\n    if (!isArray(indexArray)) {\n      console.error(`toIndexedPointer error: Invalid indexArray: ${indexArray}`);\n    }\n  }\n\n  /**\n   * 'toGenericPointer' function\n   *\n   * Compares an indexed pointer to an array map and removes list array\n   * indexes (but leaves tuple arrray indexes and all object keys, including\n   * numeric keys) to create a generic pointer.\n   *\n   * For example, using the indexed pointer '/foo/1/bar/2/baz/3' and\n   * the arrayMap [['/foo', 0], ['/foo/-/bar', 3], ['/foo/-/bar/-/baz', 0]]\n   * would result in the generic pointer '/foo/-/bar/2/baz/-'\n   * Using the indexed pointer '/foo/1/bar/4/baz/3' and the same arrayMap\n   * would result in the generic pointer '/foo/-/bar/-/baz/-'\n   * (the bar array has 3 tuple items, so index 2 is retained, but 4 is removed)\n   *\n   * The structure of the arrayMap is: [['path to array', number of tuple items]...]\n   *\n   *\n   * //  { Pointer } indexedPointer - The indexed pointer (array or string)\n   * //  { Map<string, number> } arrayMap - The optional array map (for preserving tuple indexes)\n   * // { string } - The generic pointer with indexes removed\n   */\n  static toGenericPointer(indexedPointer, arrayMap = new Map<string, number>()) {\n    if (this.isJsonPointer(indexedPointer) && isMap(arrayMap)) {\n      const pointerArray = this.parse(indexedPointer);\n      for (let i = 1; i < pointerArray.length; i++) {\n        const subPointer = this.compile(pointerArray.slice(0, i));\n        if (arrayMap.has(subPointer) &&\n          arrayMap.get(subPointer) <= +pointerArray[i]\n        ) {\n          pointerArray[i] = '-';\n        }\n      }\n      return this.compile(pointerArray);\n    }\n    if (!this.isJsonPointer(indexedPointer)) {\n      console.error(`toGenericPointer error: invalid JSON Pointer: ${indexedPointer}`);\n    }\n    if (!isMap(arrayMap)) {\n      console.error(`toGenericPointer error: invalid arrayMap: ${arrayMap}`);\n    }\n  }\n\n  /**\n   * 'toControlPointer' function\n   *\n   * Accepts a JSON Pointer for a data object and returns a JSON Pointer for the\n   * matching control in an Angular FormGroup.\n   *\n   * //  { Pointer } dataPointer - JSON Pointer (string or array) to a data object\n   * //  { FormGroup } formGroup - Angular FormGroup to get value from\n   * //  { boolean = false } controlMustExist - Only return if control exists?\n   * // { Pointer } - JSON Pointer (string) to the formGroup object\n   */\n  static toControlPointer(dataPointer, formGroup, controlMustExist = false) {\n    const dataPointerArray = this.parse(dataPointer);\n    const controlPointerArray: string[] = [];\n    let subGroup = formGroup;\n    if (dataPointerArray !== null) {\n      for (const key of dataPointerArray) {\n        if (hasOwn(subGroup, 'controls')) {\n          controlPointerArray.push('controls');\n          subGroup = subGroup.controls;\n        }\n        if (isArray(subGroup) && (key === '-')) {\n          controlPointerArray.push((subGroup.length - 1).toString());\n          subGroup = subGroup[subGroup.length - 1];\n        } else if (hasOwn(subGroup, key)) {\n          controlPointerArray.push(key);\n          subGroup = subGroup[key];\n        } else if (controlMustExist) {\n          console.error(`toControlPointer error: Unable to find \"${key}\" item in FormGroup.`);\n          console.error(dataPointer);\n          console.error(formGroup);\n          return;\n        } else {\n          controlPointerArray.push(key);\n          subGroup = { controls: {} };\n        }\n      }\n      return this.compile(controlPointerArray);\n    }\n    console.error(`toControlPointer error: Invalid JSON Pointer: ${dataPointer}`);\n  }\n\n  /**\n   * 'toSchemaPointer' function\n   *\n   * Accepts a JSON Pointer to a value inside a data object and a JSON schema\n   * for that object.\n   *\n   * Returns a Pointer to the sub-schema for the value inside the object's schema.\n   *\n   * //  { Pointer } dataPointer - JSON Pointer (string or array) to an object\n   * //   schema - JSON schema for the object\n   * // { Pointer } - JSON Pointer (string) to the object's schema\n   */\n  static toSchemaPointer(dataPointer, schema) {\n    if (this.isJsonPointer(dataPointer) && typeof schema === 'object') {\n      const pointerArray = this.parse(dataPointer);\n      if (!pointerArray.length) { return ''; }\n      const firstKey = pointerArray.shift();\n      if (schema.type === 'object' || schema.properties || schema.additionalProperties) {\n        if ((schema.properties || {})[firstKey]) {\n          return `/properties/${this.escape(firstKey)}` +\n            this.toSchemaPointer(pointerArray, schema.properties[firstKey]);\n        } else  if (schema.additionalProperties) {\n          return '/additionalProperties' +\n            this.toSchemaPointer(pointerArray, schema.additionalProperties);\n        }\n      }\n      if ((schema.type === 'array' || schema.items) &&\n        (isNumber(firstKey) || firstKey === '-' || firstKey === '')\n      ) {\n        const arrayItem = firstKey === '-' || firstKey === '' ? 0 : +firstKey;\n        if (isArray(schema.items)) {\n          if (arrayItem < schema.items.length) {\n            return '/items/' + arrayItem +\n              this.toSchemaPointer(pointerArray, schema.items[arrayItem]);\n          } else if (schema.additionalItems) {\n            return '/additionalItems' +\n              this.toSchemaPointer(pointerArray, schema.additionalItems);\n          }\n        } else if (isObject(schema.items)) {\n          return '/items' + this.toSchemaPointer(pointerArray, schema.items);\n        } else if (isObject(schema.additionalItems)) {\n          return '/additionalItems' +\n            this.toSchemaPointer(pointerArray, schema.additionalItems);\n        }\n      }\n      console.error(`toSchemaPointer error: Data pointer ${dataPointer} ` +\n        `not compatible with schema ${schema}`);\n      return null;\n    }\n    if (!this.isJsonPointer(dataPointer)) {\n      console.error(`toSchemaPointer error: Invalid JSON Pointer: ${dataPointer}`);\n    }\n    if (typeof schema !== 'object') {\n      console.error(`toSchemaPointer error: Invalid JSON Schema: ${schema}`);\n    }\n    return null;\n  }\n\n  /**\n   * 'toDataPointer' function\n   *\n   * Accepts a JSON Pointer to a sub-schema inside a JSON schema and the schema.\n   *\n   * If possible, returns a generic Pointer to the corresponding value inside\n   * the data object described by the JSON schema.\n   *\n   * Returns null if the sub-schema is in an ambiguous location (such as\n   * definitions or additionalProperties) where the corresponding value\n   * location cannot be determined.\n   *\n   * //  { Pointer } schemaPointer - JSON Pointer (string or array) to a JSON schema\n   * //   schema - the JSON schema\n   * //  { boolean = false } errors - Show errors?\n   * // { Pointer } - JSON Pointer (string) to the value in the data object\n   */\n  static toDataPointer(schemaPointer, schema, errors = false) {\n    if (this.isJsonPointer(schemaPointer) && typeof schema === 'object' &&\n      this.has(schema, schemaPointer)\n    ) {\n      const pointerArray = this.parse(schemaPointer);\n      if (!pointerArray.length) { return ''; }\n      const firstKey = pointerArray.shift();\n      if (firstKey === 'properties' ||\n        (firstKey === 'items' && isArray(schema.items))\n      ) {\n        const secondKey = pointerArray.shift();\n        const pointerSuffix = this.toDataPointer(pointerArray, schema[firstKey][secondKey]);\n        return pointerSuffix === null ? null : '/' + secondKey + pointerSuffix;\n      } else if (firstKey === 'additionalItems' ||\n        (firstKey === 'items' && isObject(schema.items))\n      ) {\n        const pointerSuffix = this.toDataPointer(pointerArray, schema[firstKey]);\n        return pointerSuffix === null ? null : '/-' + pointerSuffix;\n      } else if (['allOf', 'anyOf', 'oneOf'].includes(firstKey)) {\n        const secondKey = pointerArray.shift();\n        return this.toDataPointer(pointerArray, schema[firstKey][secondKey]);\n      } else if (firstKey === 'not') {\n        return this.toDataPointer(pointerArray, schema[firstKey]);\n      } else if (['contains', 'definitions', 'dependencies', 'additionalItems',\n        'additionalProperties', 'patternProperties', 'propertyNames'].includes(firstKey)\n      ) {\n        if (errors) { console.error(`toDataPointer error: Ambiguous location`); }\n      }\n      return '';\n    }\n    if (errors) {\n      if (!this.isJsonPointer(schemaPointer)) {\n        console.error(`toDataPointer error: Invalid JSON Pointer: ${schemaPointer}`);\n      }\n      if (typeof schema !== 'object') {\n        console.error(`toDataPointer error: Invalid JSON Schema: ${schema}`);\n      }\n      if (typeof schema !== 'object') {\n        console.error(`toDataPointer error: Pointer ${schemaPointer} invalid for Schema: ${schema}`);\n      }\n    }\n    return null;\n  }\n\n  /**\n   * 'parseObjectPath' function\n   *\n   * Parses a JavaScript object path into an array of keys, which\n   * can then be passed to compile() to convert into a string JSON Pointer.\n   *\n   * Based on mike-marcacci's excellent objectpath parse function:\n   * https://github.com/mike-marcacci/objectpath\n   *\n   * //  { Pointer } path - The object path to parse\n   * // { string[] } - The resulting array of keys\n   */\n  static parseObjectPath(path) {\n    if (isArray(path)) { return <string[]>path; }\n    if (this.isJsonPointer(path)) { return this.parse(path); }\n    if (typeof path === 'string') {\n      let index = 0;\n      const parts: string[] = [];\n      while (index < path.length) {\n        const nextDot = path.indexOf('.', index);\n        const nextOB = path.indexOf('[', index); // next open bracket\n        if (nextDot === -1 && nextOB === -1) { // last item\n          parts.push(path.slice(index));\n          index = path.length;\n        } else if (nextDot !== -1 && (nextDot < nextOB || nextOB === -1)) { // dot notation\n          parts.push(path.slice(index, nextDot));\n          index = nextDot + 1;\n        } else { // bracket notation\n          if (nextOB > index) {\n            parts.push(path.slice(index, nextOB));\n            index = nextOB;\n          }\n          const quote = path.charAt(nextOB + 1);\n          if (quote === '\"' || quote === '\\'') { // enclosing quotes\n            let nextCB = path.indexOf(quote + ']', nextOB); // next close bracket\n            while (nextCB !== -1 && path.charAt(nextCB - 1) === '\\\\') {\n              nextCB = path.indexOf(quote + ']', nextCB + 2);\n            }\n            if (nextCB === -1) { nextCB = path.length; }\n            parts.push(path.slice(index + 2, nextCB)\n              .replace(new RegExp('\\\\' + quote, 'g'), quote));\n            index = nextCB + 2;\n          } else { // no enclosing quotes\n            let nextCB = path.indexOf(']', nextOB); // next close bracket\n            if (nextCB === -1) { nextCB = path.length; }\n            parts.push(path.slice(index + 1, nextCB));\n            index = nextCB + 1;\n          }\n          if (path.charAt(index) === '.') { index++; }\n        }\n      }\n      return parts;\n    }\n    console.error('parseObjectPath error: Input object path must be a string.');\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/jspointer.functions.json.spec.ts",
    "content": "import {JsonPointer} from './jsonpointer.functions';\n\nconst subObjectWithEvaluatedProperty = {name: 'abc', other_property: false};\n\ndescribe('JsonPointer.evaluateExpression', () => {\n\n  it('should return true when subObject and corresponding key is given', () => {\n    const result = JsonPointer.evaluateExpression({name: 'abc', other_property: false}, 'name==\\'abc\\'');\n\n    expect(result.passed).toEqual(true);\n  });\n\n  it('should not fail when subObject is null', () => {\n    const result = JsonPointer.evaluateExpression(null, 'name==\\'abc\\'');\n\n    expect(result).toBeDefined();\n  });\n\n  it('should not fail when subObject is undefined', () => {\n    const result = JsonPointer.evaluateExpression(undefined, 'name==\\'abc\\'');\n\n    expect(result).toBeDefined();\n  });\n\n  it('should return false when key is undefined', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, undefined);\n\n    expect(result.passed).toBeFalsy();\n  });\n\n  it('should return false when key is null', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, null);\n\n    expect(result.passed).toBeFalsy();\n  });\n\n  it('should return false when key corrupted', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, 'name=\\'abc\\'');\n\n    expect(result.passed).toBeFalsy();\n  });\n\n  it('should return the same key when key corrupted', () => {\n    const key = 'name=\\'abc\\'';\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, key);\n\n    expect(result.key).toEqual(key);\n  });\n\n  it('should return the first part of key when key contains equals', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, 'name==\\'abc\\'');\n\n    expect(result.key).toEqual('name');\n  });\n\n  it('should not return the first part of key when key contains not equals', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, 'name!=\\'abc\\'');\n\n    expect(result.key).not.toEqual('name');\n  });\n\n  it('should return false when key equals does not correspond to the subObject property', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, 'somethingElse==\\'abc\\'');\n\n    expect(result.passed).toBeFalsy();\n  });\n\n  it('should return true when key not equals does not correspond to the subObject property', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, 'somethingElse!=\\'abc\\'');\n\n    expect(result.passed).toBeTruthy();\n  });\n\n  it('should return the first part of key when key does not equal to the property value', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, 'name!=\\'cba\\'');\n\n    expect(result.key).toEqual('name');\n  });\n\n  it('should return the first part of key when key does equal to the property value', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, 'name==\\'abc\\'');\n\n    expect(result.key).toEqual('name');\n  });\n\n  it('should return false when key is different', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, 'eman==\\'abc\\'');\n\n    expect(result.passed).toBeFalsy();\n  });\n\n  it('should return true when key is without quotes', () => {\n    const result = JsonPointer.evaluateExpression({name: 'abc', other_property: false}, 'name==abc');\n\n    expect(result.passed).toEqual(true);\n  });\n\n  it('should return false when key has different value', () => {\n    const result = JsonPointer.evaluateExpression(subObjectWithEvaluatedProperty, 'name==\\'cba\\'');\n\n    expect(result.passed).toBeFalsy();\n  });\n});\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/layout.functions.ts",
    "content": "import uniqueId from 'lodash/uniqueId';\nimport cloneDeep from 'lodash/cloneDeep';\nimport {\n  checkInlineType,\n  getFromSchema,\n  getInputType,\n  isInputRequired,\n  removeRecursiveReferences,\n  updateInputOptions\n  } from './json-schema.functions';\nimport {\n  copy,\n  fixTitle,\n  forEach,\n  hasOwn\n  } from './utility.functions';\nimport {\n  inArray,\n  isArray,\n  isDefined,\n  isEmpty,\n  isNumber,\n  isObject,\n  isString\n  } from './validator.functions';\nimport { JsonPointer } from './jsonpointer.functions';\nimport { TitleMapItem } from '../json-schema-form.service';\n\n\n\n/**\n * Layout function library:\n *\n * buildLayout:            Builds a complete layout from an input layout and schema\n *\n * buildLayoutFromSchema:  Builds a complete layout entirely from an input schema\n *\n * mapLayout:\n *\n * getLayoutNode:\n *\n * buildTitleMap:\n */\n\n/**\n * 'buildLayout' function\n *\n * //   jsf\n * //   widgetLibrary\n * //\n */\nexport function buildLayout(jsf, widgetLibrary) {\n  let hasSubmitButton = !JsonPointer.get(jsf, '/formOptions/addSubmit');\n  const formLayout = mapLayout(jsf.layout, (layoutItem, index, layoutPointer) => {\n    const newNode: any = {\n      _id: uniqueId(),\n      options: {},\n    };\n    if (isObject(layoutItem)) {\n      Object.assign(newNode, layoutItem);\n      Object.keys(newNode)\n        .filter(option => !inArray(option, [\n          '_id', '$ref', 'arrayItem', 'arrayItemType', 'dataPointer', 'dataType',\n          'items', 'key', 'name', 'options', 'recursiveReference', 'type', 'widget'\n        ]))\n        .forEach(option => {\n          newNode.options[option] = newNode[option];\n          delete newNode[option];\n        });\n      if (!hasOwn(newNode, 'type') && isString(newNode.widget)) {\n        newNode.type = newNode.widget;\n        delete newNode.widget;\n      }\n      if (!hasOwn(newNode.options, 'title')) {\n        if (hasOwn(newNode.options, 'legend')) {\n          newNode.options.title = newNode.options.legend;\n          delete newNode.options.legend;\n        }\n      }\n      if (!hasOwn(newNode.options, 'validationMessages')) {\n        if (hasOwn(newNode.options, 'errorMessages')) {\n          newNode.options.validationMessages = newNode.options.errorMessages;\n          delete newNode.options.errorMessages;\n\n          // Convert Angular Schema Form (AngularJS) 'validationMessage' to\n          // Angular JSON Schema Form 'validationMessages'\n          // TV4 codes from https://github.com/geraintluff/tv4/blob/master/source/api.js\n        } else if (hasOwn(newNode.options, 'validationMessage')) {\n          if (typeof newNode.options.validationMessage === 'string') {\n            newNode.options.validationMessages = newNode.options.validationMessage;\n          } else {\n            newNode.options.validationMessages = {};\n            Object.keys(newNode.options.validationMessage).forEach(key => {\n              const code = key + '';\n              const newKey =\n                code === '0' ? 'type' :\n                  code === '1' ? 'enum' :\n                    code === '100' ? 'multipleOf' :\n                      code === '101' ? 'minimum' :\n                        code === '102' ? 'exclusiveMinimum' :\n                          code === '103' ? 'maximum' :\n                            code === '104' ? 'exclusiveMaximum' :\n                              code === '200' ? 'minLength' :\n                                code === '201' ? 'maxLength' :\n                                  code === '202' ? 'pattern' :\n                                    code === '300' ? 'minProperties' :\n                                      code === '301' ? 'maxProperties' :\n                                        code === '302' ? 'required' :\n                                          code === '304' ? 'dependencies' :\n                                            code === '400' ? 'minItems' :\n                                              code === '401' ? 'maxItems' :\n                                                code === '402' ? 'uniqueItems' :\n                                                  code === '500' ? 'format' : code + '';\n              newNode.options.validationMessages[newKey] = newNode.options.validationMessage[key];\n            });\n          }\n          delete newNode.options.validationMessage;\n        }\n      }\n    } else if (JsonPointer.isJsonPointer(layoutItem)) {\n      newNode.dataPointer = layoutItem;\n    } else if (isString(layoutItem)) {\n      newNode.key = layoutItem;\n    } else {\n      console.error('buildLayout error: Form layout element not recognized:');\n      console.error(layoutItem);\n      return null;\n    }\n    let nodeSchema: any = null;\n\n    // If newNode does not have a dataPointer, try to find an equivalent\n    if (!hasOwn(newNode, 'dataPointer')) {\n\n      // If newNode has a key, change it to a dataPointer\n      if (hasOwn(newNode, 'key')) {\n        newNode.dataPointer = newNode.key === '*' ? newNode.key :\n          JsonPointer.compile(JsonPointer.parseObjectPath(newNode.key), '-');\n        delete newNode.key;\n\n        // If newNode is an array, search for dataPointer in child nodes\n      } else if (hasOwn(newNode, 'type') && newNode.type.slice(-5) === 'array') {\n        const findDataPointer = (items) => {\n          if (items === null || typeof items !== 'object') { return; }\n          if (hasOwn(items, 'dataPointer')) { return items.dataPointer; }\n          if (isArray(items.items)) {\n            for (const item of items.items) {\n              if (hasOwn(item, 'dataPointer') && item.dataPointer.indexOf('/-') !== -1) {\n                return item.dataPointer;\n              }\n              if (hasOwn(item, 'items')) {\n                const searchItem = findDataPointer(item);\n                if (searchItem) { return searchItem; }\n              }\n            }\n          }\n        };\n        const childDataPointer = findDataPointer(newNode);\n        if (childDataPointer) {\n          newNode.dataPointer =\n            childDataPointer.slice(0, childDataPointer.lastIndexOf('/-'));\n        }\n      }\n    }\n\n    if (hasOwn(newNode, 'dataPointer')) {\n      if (newNode.dataPointer === '*') {\n        return buildLayoutFromSchema(jsf, widgetLibrary, jsf.formValues);\n      }\n      const nodeValue =\n        JsonPointer.get(jsf.formValues, newNode.dataPointer.replace(/\\/-/g, '/1'));\n\n      // TODO: Create function getFormValues(jsf, dataPointer, forRefLibrary)\n      // check formOptions.setSchemaDefaults and formOptions.setLayoutDefaults\n      // then set apropriate values from initialVaues, schema, or layout\n\n      newNode.dataPointer =\n        JsonPointer.toGenericPointer(newNode.dataPointer, jsf.arrayMap);\n      const LastKey = JsonPointer.toKey(newNode.dataPointer);\n      if (!newNode.name && isString(LastKey) && LastKey !== '-') {\n        newNode.name = LastKey;\n      }\n      const shortDataPointer = removeRecursiveReferences(\n        newNode.dataPointer, jsf.dataRecursiveRefMap, jsf.arrayMap\n      );\n      const recursive = !shortDataPointer.length ||\n        shortDataPointer !== newNode.dataPointer;\n      let schemaPointer: string;\n      if (!jsf.dataMap.has(shortDataPointer)) {\n        jsf.dataMap.set(shortDataPointer, new Map());\n      }\n      const nodeDataMap = jsf.dataMap.get(shortDataPointer);\n      if (nodeDataMap.has('schemaPointer')) {\n        schemaPointer = nodeDataMap.get('schemaPointer');\n      } else {\n        schemaPointer = JsonPointer.toSchemaPointer(shortDataPointer, jsf.schema);\n        nodeDataMap.set('schemaPointer', schemaPointer);\n      }\n      nodeDataMap.set('disabled', !!newNode.options.disabled);\n      nodeSchema = JsonPointer.get(jsf.schema, schemaPointer);\n      if (nodeSchema) {\n        if (!hasOwn(newNode, 'type')) {\n          newNode.type = getInputType(nodeSchema, newNode);\n        } else if (!widgetLibrary.hasWidget(newNode.type)) {\n          const oldWidgetType = newNode.type;\n          newNode.type = getInputType(nodeSchema, newNode);\n          console.error(`error: widget type \"${oldWidgetType}\" ` +\n            `not found in library. Replacing with \"${newNode.type}\".`);\n        } else {\n          newNode.type = checkInlineType(newNode.type, nodeSchema, newNode);\n        }\n        if (nodeSchema.type === 'object' && isArray(nodeSchema.required)) {\n          nodeDataMap.set('required', nodeSchema.required);\n        }\n        newNode.dataType =\n          nodeSchema.type || (hasOwn(nodeSchema, '$ref') ? '$ref' : null);\n        updateInputOptions(newNode, nodeSchema, jsf);\n\n        // Present checkboxes as single control, rather than array\n        if (newNode.type === 'checkboxes' && hasOwn(nodeSchema, 'items')) {\n          updateInputOptions(newNode, nodeSchema.items, jsf);\n        } else if (newNode.dataType === 'array') {\n          newNode.options.maxItems = Math.min(\n            nodeSchema.maxItems || 1000, newNode.options.maxItems || 1000\n          );\n          newNode.options.minItems = Math.max(\n            nodeSchema.minItems || 0, newNode.options.minItems || 0\n          );\n          newNode.options.listItems = Math.max(\n            newNode.options.listItems || 0, isArray(nodeValue) ? nodeValue.length : 0\n          );\n          newNode.options.tupleItems =\n            isArray(nodeSchema.items) ? nodeSchema.items.length : 0;\n          if (newNode.options.maxItems < newNode.options.tupleItems) {\n            newNode.options.tupleItems = newNode.options.maxItems;\n            newNode.options.listItems = 0;\n          } else if (newNode.options.maxItems <\n            newNode.options.tupleItems + newNode.options.listItems\n          ) {\n            newNode.options.listItems =\n              newNode.options.maxItems - newNode.options.tupleItems;\n          } else if (newNode.options.minItems >\n            newNode.options.tupleItems + newNode.options.listItems\n          ) {\n            newNode.options.listItems =\n              newNode.options.minItems - newNode.options.tupleItems;\n          }\n          if (!nodeDataMap.has('maxItems')) {\n            nodeDataMap.set('maxItems', newNode.options.maxItems);\n            nodeDataMap.set('minItems', newNode.options.minItems);\n            nodeDataMap.set('tupleItems', newNode.options.tupleItems);\n            nodeDataMap.set('listItems', newNode.options.listItems);\n          }\n          if (!jsf.arrayMap.has(shortDataPointer)) {\n            jsf.arrayMap.set(shortDataPointer, newNode.options.tupleItems);\n          }\n        }\n        if (isInputRequired(jsf.schema, schemaPointer)) {\n          newNode.options.required = true;\n          jsf.fieldsRequired = true;\n        }\n      } else {\n        // TODO: create item in FormGroup model from layout key (?)\n        updateInputOptions(newNode, {}, jsf);\n      }\n\n      if (!newNode.options.title && !/^\\d+$/.test(newNode.name)) {\n        newNode.options.title = fixTitle(newNode.name);\n      }\n\n      if (hasOwn(newNode.options, 'copyValueTo')) {\n        if (typeof newNode.options.copyValueTo === 'string') {\n          newNode.options.copyValueTo = [newNode.options.copyValueTo];\n        }\n        if (isArray(newNode.options.copyValueTo)) {\n          newNode.options.copyValueTo = newNode.options.copyValueTo.map(item =>\n            JsonPointer.compile(JsonPointer.parseObjectPath(item), '-')\n          );\n        }\n      }\n\n      newNode.widget = widgetLibrary.getWidget(newNode.type);\n      nodeDataMap.set('inputType', newNode.type);\n      nodeDataMap.set('widget', newNode.widget);\n\n      if (newNode.dataType === 'array' &&\n        (hasOwn(newNode, 'items') || hasOwn(newNode, 'additionalItems'))\n      ) {\n        const itemRefPointer = removeRecursiveReferences(\n          newNode.dataPointer + '/-', jsf.dataRecursiveRefMap, jsf.arrayMap\n        );\n        if (!jsf.dataMap.has(itemRefPointer)) {\n          jsf.dataMap.set(itemRefPointer, new Map());\n        }\n        jsf.dataMap.get(itemRefPointer).set('inputType', 'section');\n\n        // Fix insufficiently nested array item groups\n        if (newNode.items.length > 1) {\n          const arrayItemGroup = [];\n          for (let i = newNode.items.length - 1; i >= 0; i--) {\n            const subItem = newNode.items[i];\n            if (hasOwn(subItem, 'dataPointer') &&\n              subItem.dataPointer.slice(0, itemRefPointer.length) === itemRefPointer\n            ) {\n              const arrayItem = newNode.items.splice(i, 1)[0];\n              arrayItem.dataPointer = newNode.dataPointer + '/-' +\n                arrayItem.dataPointer.slice(itemRefPointer.length);\n              arrayItemGroup.unshift(arrayItem);\n            } else {\n              subItem.arrayItem = true;\n              // TODO: Check schema to get arrayItemType and removable\n              subItem.arrayItemType = 'list';\n              subItem.removable = newNode.options.removable !== false;\n            }\n          }\n          if (arrayItemGroup.length) {\n            newNode.items.push({\n              _id: uniqueId(),\n              arrayItem: true,\n              arrayItemType: newNode.options.tupleItems > newNode.items.length ?\n                'tuple' : 'list',\n              items: arrayItemGroup,\n              options: { removable: newNode.options.removable !== false, },\n              dataPointer: newNode.dataPointer + '/-',\n              type: 'section',\n              widget: widgetLibrary.getWidget('section'),\n            });\n          }\n        } else {\n          // TODO: Fix to hndle multiple items\n          newNode.items[0].arrayItem = true;\n          if (!newNode.items[0].dataPointer) {\n            newNode.items[0].dataPointer =\n              JsonPointer.toGenericPointer(itemRefPointer, jsf.arrayMap);\n          }\n          if (!JsonPointer.has(newNode, '/items/0/options/removable')) {\n            newNode.items[0].options.removable = true;\n          }\n          if (newNode.options.orderable === false) {\n            newNode.items[0].options.orderable = false;\n          }\n          newNode.items[0].arrayItemType =\n            newNode.options.tupleItems ? 'tuple' : 'list';\n        }\n\n        if (isArray(newNode.items)) {\n          const arrayListItems =\n            newNode.items.filter(item => item.type !== '$ref').length -\n            newNode.options.tupleItems;\n          if (arrayListItems > newNode.options.listItems) {\n            newNode.options.listItems = arrayListItems;\n            nodeDataMap.set('listItems', arrayListItems);\n          }\n        }\n\n        if (!hasOwn(jsf.layoutRefLibrary, itemRefPointer)) {\n          jsf.layoutRefLibrary[itemRefPointer] =\n            cloneDeep(newNode.items[newNode.items.length - 1]);\n          if (recursive) {\n            jsf.layoutRefLibrary[itemRefPointer].recursiveReference = true;\n          }\n          forEach(jsf.layoutRefLibrary[itemRefPointer], (item, key) => {\n            if (hasOwn(item, '_id')) { item._id = null; }\n            if (recursive) {\n              if (hasOwn(item, 'dataPointer')) {\n                item.dataPointer = item.dataPointer.slice(itemRefPointer.length);\n              }\n            }\n          }, 'top-down');\n        }\n\n        // Add any additional default items\n        if (!newNode.recursiveReference || newNode.options.required) {\n          const arrayLength = Math.min(Math.max(\n            newNode.options.tupleItems + newNode.options.listItems,\n            isArray(nodeValue) ? nodeValue.length : 0\n          ), newNode.options.maxItems);\n          for (let i = newNode.items.length; i < arrayLength; i++) {\n            newNode.items.push(getLayoutNode({\n              $ref: itemRefPointer,\n              dataPointer: newNode.dataPointer,\n              recursiveReference: newNode.recursiveReference,\n            }, jsf, widgetLibrary));\n          }\n        }\n\n        // If needed, add button to add items to array\n        if (newNode.options.addable !== false &&\n          newNode.options.minItems < newNode.options.maxItems &&\n          (newNode.items[newNode.items.length - 1] || {}).type !== '$ref'\n        ) {\n          let buttonText = 'Add';\n          if (newNode.options.title) {\n            if (/^add\\b/i.test(newNode.options.title)) {\n              buttonText = newNode.options.title;\n            } else {\n              buttonText += ' ' + newNode.options.title;\n            }\n          } else if (newNode.name && !/^\\d+$/.test(newNode.name)) {\n            if (/^add\\b/i.test(newNode.name)) {\n              buttonText += ' ' + fixTitle(newNode.name);\n            } else {\n              buttonText = fixTitle(newNode.name);\n            }\n\n            // If newNode doesn't have a title, look for title of parent array item\n          } else {\n            const parentSchema =\n              getFromSchema(jsf.schema, newNode.dataPointer, 'parentSchema');\n            if (hasOwn(parentSchema, 'title')) {\n              buttonText += ' to ' + parentSchema.title;\n            } else {\n              const pointerArray = JsonPointer.parse(newNode.dataPointer);\n              buttonText += ' to ' + fixTitle(pointerArray[pointerArray.length - 2]);\n            }\n          }\n          newNode.items.push({\n            _id: uniqueId(),\n            arrayItem: true,\n            arrayItemType: 'list',\n            dataPointer: newNode.dataPointer + '/-',\n            options: {\n              listItems: newNode.options.listItems,\n              maxItems: newNode.options.maxItems,\n              minItems: newNode.options.minItems,\n              removable: false,\n              title: buttonText,\n              tupleItems: newNode.options.tupleItems,\n            },\n            recursiveReference: recursive,\n            type: '$ref',\n            widget: widgetLibrary.getWidget('$ref'),\n            $ref: itemRefPointer,\n          });\n          if (isString(JsonPointer.get(newNode, '/style/add'))) {\n            newNode.items[newNode.items.length - 1].options.fieldStyle =\n              newNode.style.add;\n            delete newNode.style.add;\n            if (isEmpty(newNode.style)) { delete newNode.style; }\n          }\n        }\n      } else {\n        newNode.arrayItem = false;\n      }\n    } else if (hasOwn(newNode, 'type') || hasOwn(newNode, 'items')) {\n      const parentType: string =\n        JsonPointer.get(jsf.layout, layoutPointer, 0, -2).type;\n      if (!hasOwn(newNode, 'type')) {\n        newNode.type =\n          inArray(parentType, ['tabs', 'tabarray']) ? 'tab' : 'array';\n      }\n      newNode.arrayItem = parentType === 'array';\n      newNode.widget = widgetLibrary.getWidget(newNode.type);\n      updateInputOptions(newNode, {}, jsf);\n    }\n    if (newNode.type === 'submit') { hasSubmitButton = true; }\n    return newNode;\n  });\n  if (jsf.hasRootReference) {\n    const fullLayout = cloneDeep(formLayout);\n    if (fullLayout[fullLayout.length - 1].type === 'submit') { fullLayout.pop(); }\n    jsf.layoutRefLibrary[''] = {\n      _id: null,\n      dataPointer: '',\n      dataType: 'object',\n      items: fullLayout,\n      name: '',\n      options: cloneDeep(jsf.formOptions.defautWidgetOptions),\n      recursiveReference: true,\n      required: false,\n      type: 'section',\n      widget: widgetLibrary.getWidget('section'),\n    };\n  }\n  if (!hasSubmitButton) {\n    formLayout.push({\n      _id: uniqueId(),\n      options: { title: 'Submit' },\n      type: 'submit',\n      widget: widgetLibrary.getWidget('submit'),\n    });\n  }\n  return formLayout;\n}\n\n/**\n * 'buildLayoutFromSchema' function\n *\n * //   jsf -\n * //   widgetLibrary -\n * //   nodeValue -\n * //  { string = '' } schemaPointer -\n * //  { string = '' } dataPointer -\n * //  { boolean = false } arrayItem -\n * //  { string = null } arrayItemType -\n * //  { boolean = null } removable -\n * //  { boolean = false } forRefLibrary -\n * //  { string = '' } dataPointerPrefix -\n * //\n */\nexport function buildLayoutFromSchema(\n  jsf, widgetLibrary, nodeValue = null, schemaPointer = '',\n  dataPointer = '', arrayItem = false, arrayItemType: string = null,\n  removable: boolean = null, forRefLibrary = false, dataPointerPrefix = ''\n) {\n  const schema = JsonPointer.get(jsf.schema, schemaPointer);\n  if (!hasOwn(schema, 'type') && !hasOwn(schema, '$ref') &&\n    !hasOwn(schema, 'x-schema-form')\n  ) { return null; }\n  const newNodeType: string = getInputType(schema);\n  if (!isDefined(nodeValue) && (\n    jsf.formOptions.setSchemaDefaults === true ||\n    (jsf.formOptions.setSchemaDefaults === 'auto' && isEmpty(jsf.formValues))\n  )) {\n    nodeValue = JsonPointer.get(jsf.schema, schemaPointer + '/default');\n  }\n  let newNode: any = {\n    _id: forRefLibrary ? null : uniqueId(),\n    arrayItem: arrayItem,\n    dataPointer: JsonPointer.toGenericPointer(dataPointer, jsf.arrayMap),\n    dataType: schema.type || (hasOwn(schema, '$ref') ? '$ref' : null),\n    options: {},\n    required: isInputRequired(jsf.schema, schemaPointer),\n    type: newNodeType,\n    widget: widgetLibrary.getWidget(newNodeType),\n  };\n  const lastDataKey = JsonPointer.toKey(newNode.dataPointer);\n  if (lastDataKey !== '-') { newNode.name = lastDataKey; }\n  if (newNode.arrayItem) {\n    newNode.arrayItemType = arrayItemType;\n    newNode.options.removable = removable !== false;\n  }\n  const shortDataPointer = removeRecursiveReferences(\n    dataPointerPrefix + dataPointer, jsf.dataRecursiveRefMap, jsf.arrayMap\n  );\n  const recursive = !shortDataPointer.length ||\n    shortDataPointer !== dataPointerPrefix + dataPointer;\n  if (!jsf.dataMap.has(shortDataPointer)) {\n    jsf.dataMap.set(shortDataPointer, new Map());\n  }\n  const nodeDataMap = jsf.dataMap.get(shortDataPointer);\n  if (!nodeDataMap.has('inputType')) {\n    nodeDataMap.set('schemaPointer', schemaPointer);\n    nodeDataMap.set('inputType', newNode.type);\n    nodeDataMap.set('widget', newNode.widget);\n    nodeDataMap.set('disabled', !!newNode.options.disabled);\n  }\n  updateInputOptions(newNode, schema, jsf);\n  if (!newNode.options.title && newNode.name && !/^\\d+$/.test(newNode.name)) {\n    newNode.options.title = fixTitle(newNode.name);\n  }\n\n  if (newNode.dataType === 'object') {\n    if (isArray(schema.required) && !nodeDataMap.has('required')) {\n      nodeDataMap.set('required', schema.required);\n    }\n    if (isObject(schema.properties)) {\n      const newSection: any[] = [];\n      const propertyKeys = schema['ui:order'] || Object.keys(schema.properties);\n      if (propertyKeys.includes('*') && !hasOwn(schema.properties, '*')) {\n        const unnamedKeys = Object.keys(schema.properties)\n          .filter(key => !propertyKeys.includes(key));\n        for (let i = propertyKeys.length - 1; i >= 0; i--) {\n          if (propertyKeys[i] === '*') {\n            propertyKeys.splice(i, 1, ...unnamedKeys);\n          }\n        }\n      }\n      propertyKeys\n        .filter(key => hasOwn(schema.properties, key) ||\n          hasOwn(schema, 'additionalProperties')\n        )\n        .forEach(key => {\n          const keySchemaPointer = hasOwn(schema.properties, key) ?\n            '/properties/' + key : '/additionalProperties';\n          const innerItem = buildLayoutFromSchema(\n            jsf, widgetLibrary, isObject(nodeValue) ? nodeValue[key] : null,\n            schemaPointer + keySchemaPointer,\n            dataPointer + '/' + key,\n            false, null, null, forRefLibrary, dataPointerPrefix\n          );\n          if (innerItem) {\n            if (isInputRequired(schema, '/' + key)) {\n              innerItem.options.required = true;\n              jsf.fieldsRequired = true;\n            }\n            newSection.push(innerItem);\n          }\n        });\n      if (dataPointer === '' && !forRefLibrary) {\n        newNode = newSection;\n      } else {\n        newNode.items = newSection;\n      }\n    }\n    // TODO: Add patternProperties and additionalProperties inputs?\n    // ... possibly provide a way to enter both key names and values?\n    // if (isObject(schema.patternProperties)) { }\n    // if (isObject(schema.additionalProperties)) { }\n\n  } else if (newNode.dataType === 'array') {\n    newNode.items = [];\n    newNode.options.maxItems = Math.min(\n      schema.maxItems || 1000, newNode.options.maxItems || 1000\n    );\n    newNode.options.minItems = Math.max(\n      schema.minItems || 0, newNode.options.minItems || 0\n    );\n    if (!newNode.options.minItems && isInputRequired(jsf.schema, schemaPointer)) {\n      newNode.options.minItems = 1;\n    }\n    if (!hasOwn(newNode.options, 'listItems')) { newNode.options.listItems = 1; }\n    newNode.options.tupleItems = isArray(schema.items) ? schema.items.length : 0;\n    if (newNode.options.maxItems <= newNode.options.tupleItems) {\n      newNode.options.tupleItems = newNode.options.maxItems;\n      newNode.options.listItems = 0;\n    } else if (newNode.options.maxItems <\n      newNode.options.tupleItems + newNode.options.listItems\n    ) {\n      newNode.options.listItems = newNode.options.maxItems - newNode.options.tupleItems;\n    } else if (newNode.options.minItems >\n      newNode.options.tupleItems + newNode.options.listItems\n    ) {\n      newNode.options.listItems = newNode.options.minItems - newNode.options.tupleItems;\n    }\n    if (!nodeDataMap.has('maxItems')) {\n      nodeDataMap.set('maxItems', newNode.options.maxItems);\n      nodeDataMap.set('minItems', newNode.options.minItems);\n      nodeDataMap.set('tupleItems', newNode.options.tupleItems);\n      nodeDataMap.set('listItems', newNode.options.listItems);\n    }\n    if (!jsf.arrayMap.has(shortDataPointer)) {\n      jsf.arrayMap.set(shortDataPointer, newNode.options.tupleItems);\n    }\n    removable = newNode.options.removable !== false;\n    let additionalItemsSchemaPointer: string = null;\n\n    // If 'items' is an array = tuple items\n    if (isArray(schema.items)) {\n      newNode.items = [];\n      for (let i = 0; i < newNode.options.tupleItems; i++) {\n        let newItem: any;\n        const itemRefPointer = removeRecursiveReferences(\n          shortDataPointer + '/' + i, jsf.dataRecursiveRefMap, jsf.arrayMap\n        );\n        const itemRecursive = !itemRefPointer.length ||\n          itemRefPointer !== shortDataPointer + '/' + i;\n\n        // If removable, add tuple item layout to layoutRefLibrary\n        if (removable && i >= newNode.options.minItems) {\n          if (!hasOwn(jsf.layoutRefLibrary, itemRefPointer)) {\n            // Set to null first to prevent recursive reference from causing endless loop\n            jsf.layoutRefLibrary[itemRefPointer] = null;\n            jsf.layoutRefLibrary[itemRefPointer] = buildLayoutFromSchema(\n              jsf, widgetLibrary, isArray(nodeValue) ? nodeValue[i] : null,\n              schemaPointer + '/items/' + i,\n              itemRecursive ? '' : dataPointer + '/' + i,\n              true, 'tuple', true, true, itemRecursive ? dataPointer + '/' + i : ''\n            );\n            if (itemRecursive) {\n              jsf.layoutRefLibrary[itemRefPointer].recursiveReference = true;\n            }\n          }\n          newItem = getLayoutNode({\n            $ref: itemRefPointer,\n            dataPointer: dataPointer + '/' + i,\n            recursiveReference: itemRecursive,\n          }, jsf, widgetLibrary, isArray(nodeValue) ? nodeValue[i] : null);\n        } else {\n          newItem = buildLayoutFromSchema(\n            jsf, widgetLibrary, isArray(nodeValue) ? nodeValue[i] : null,\n            schemaPointer + '/items/' + i,\n            dataPointer + '/' + i,\n            true, 'tuple', false, forRefLibrary, dataPointerPrefix\n          );\n        }\n        if (newItem) { newNode.items.push(newItem); }\n      }\n\n      // If 'additionalItems' is an object = additional list items, after tuple items\n      if (isObject(schema.additionalItems)) {\n        additionalItemsSchemaPointer = schemaPointer + '/additionalItems';\n      }\n\n      // If 'items' is an object = list items only (no tuple items)\n    } else if (isObject(schema.items)) {\n      additionalItemsSchemaPointer = schemaPointer + '/items';\n    }\n\n    if (additionalItemsSchemaPointer) {\n      const itemRefPointer = removeRecursiveReferences(\n        shortDataPointer + '/-', jsf.dataRecursiveRefMap, jsf.arrayMap\n      );\n      const itemRecursive = !itemRefPointer.length ||\n        itemRefPointer !== shortDataPointer + '/-';\n      const itemSchemaPointer = removeRecursiveReferences(\n        additionalItemsSchemaPointer, jsf.schemaRecursiveRefMap, jsf.arrayMap\n      );\n      // Add list item layout to layoutRefLibrary\n      if (itemRefPointer.length && !hasOwn(jsf.layoutRefLibrary, itemRefPointer)) {\n        // Set to null first to prevent recursive reference from causing endless loop\n        jsf.layoutRefLibrary[itemRefPointer] = null;\n        jsf.layoutRefLibrary[itemRefPointer] = buildLayoutFromSchema(\n          jsf, widgetLibrary, null,\n          itemSchemaPointer,\n          itemRecursive ? '' : dataPointer + '/-',\n          true, 'list', removable, true, itemRecursive ? dataPointer + '/-' : ''\n        );\n        if (itemRecursive) {\n          jsf.layoutRefLibrary[itemRefPointer].recursiveReference = true;\n        }\n      }\n\n      // Add any additional default items\n      if (!itemRecursive || newNode.options.required) {\n        const arrayLength = Math.min(Math.max(\n          itemRecursive ? 0 :\n            newNode.options.tupleItems + newNode.options.listItems,\n          isArray(nodeValue) ? nodeValue.length : 0\n        ), newNode.options.maxItems);\n        if (newNode.items.length < arrayLength) {\n          for (let i = newNode.items.length; i < arrayLength; i++) {\n            newNode.items.push(getLayoutNode({\n              $ref: itemRefPointer,\n              dataPointer: dataPointer + '/-',\n              recursiveReference: itemRecursive,\n            }, jsf, widgetLibrary, isArray(nodeValue) ? nodeValue[i] : null));\n          }\n        }\n      }\n\n      // If needed, add button to add items to array\n      if (newNode.options.addable !== false &&\n        newNode.options.minItems < newNode.options.maxItems &&\n        (newNode.items[newNode.items.length - 1] || {}).type !== '$ref'\n      ) {\n        let buttonText =\n          ((jsf.layoutRefLibrary[itemRefPointer] || {}).options || {}).title;\n        const prefix = buttonText ? 'Add ' : 'Add to ';\n        if (!buttonText) {\n          buttonText = schema.title || fixTitle(JsonPointer.toKey(dataPointer));\n        }\n        if (!/^add\\b/i.test(buttonText)) { buttonText = prefix + buttonText; }\n        newNode.items.push({\n          _id: uniqueId(),\n          arrayItem: true,\n          arrayItemType: 'list',\n          dataPointer: newNode.dataPointer + '/-',\n          options: {\n            listItems: newNode.options.listItems,\n            maxItems: newNode.options.maxItems,\n            minItems: newNode.options.minItems,\n            removable: false,\n            title: buttonText,\n            tupleItems: newNode.options.tupleItems,\n          },\n          recursiveReference: itemRecursive,\n          type: '$ref',\n          widget: widgetLibrary.getWidget('$ref'),\n          $ref: itemRefPointer,\n        });\n      }\n    }\n\n  } else if (newNode.dataType === '$ref') {\n    const schemaRef = JsonPointer.compile(schema.$ref);\n    const dataRef = JsonPointer.toDataPointer(schemaRef, jsf.schema);\n    let buttonText = '';\n\n    // Get newNode title\n    if (newNode.options.add) {\n      buttonText = newNode.options.add;\n    } else if (newNode.name && !/^\\d+$/.test(newNode.name)) {\n      buttonText =\n        (/^add\\b/i.test(newNode.name) ? '' : 'Add ') + fixTitle(newNode.name);\n\n      // If newNode doesn't have a title, look for title of parent array item\n    } else {\n      const parentSchema =\n        JsonPointer.get(jsf.schema, schemaPointer, 0, -1);\n      if (hasOwn(parentSchema, 'title')) {\n        buttonText = 'Add to ' + parentSchema.title;\n      } else {\n        const pointerArray = JsonPointer.parse(newNode.dataPointer);\n        buttonText = 'Add to ' + fixTitle(pointerArray[pointerArray.length - 2]);\n      }\n    }\n    Object.assign(newNode, {\n      recursiveReference: true,\n      widget: widgetLibrary.getWidget('$ref'),\n      $ref: dataRef,\n    });\n    Object.assign(newNode.options, {\n      removable: false,\n      title: buttonText,\n    });\n    if (isNumber(JsonPointer.get(jsf.schema, schemaPointer, 0, -1).maxItems)) {\n      newNode.options.maxItems =\n        JsonPointer.get(jsf.schema, schemaPointer, 0, -1).maxItems;\n    }\n\n    // Add layout template to layoutRefLibrary\n    if (dataRef.length) {\n      if (!hasOwn(jsf.layoutRefLibrary, dataRef)) {\n        // Set to null first to prevent recursive reference from causing endless loop\n        jsf.layoutRefLibrary[dataRef] = null;\n        const newLayout = buildLayoutFromSchema(\n          jsf, widgetLibrary, null, schemaRef, '',\n          newNode.arrayItem, newNode.arrayItemType, true, true, dataPointer\n        );\n        if (newLayout) {\n          newLayout.recursiveReference = true;\n          jsf.layoutRefLibrary[dataRef] = newLayout;\n        } else {\n          delete jsf.layoutRefLibrary[dataRef];\n        }\n      } else if (!jsf.layoutRefLibrary[dataRef].recursiveReference) {\n        jsf.layoutRefLibrary[dataRef].recursiveReference = true;\n      }\n    }\n  }\n  return newNode;\n}\n\n/**\n * 'mapLayout' function\n *\n * Creates a new layout by running each element in an existing layout through\n * an iteratee. Recursively maps within array elements 'items' and 'tabs'.\n * The iteratee is invoked with four arguments: (value, index, layout, path)\n *\n * The returned layout may be longer (or shorter) then the source layout.\n *\n * If an item from the source layout returns multiple items (as '*' usually will),\n * this function will keep all returned items in-line with the surrounding items.\n *\n * If an item from the source layout causes an error and returns null, it is\n * skipped without error, and the function will still return all non-null items.\n *\n * //   layout - the layout to map\n * //  { (v: any, i?: number, l?: any, p?: string) => any }\n *   function - the funciton to invoke on each element\n * //  { string|string[] = '' } layoutPointer - the layoutPointer to layout, inside rootLayout\n * //  { any[] = layout } rootLayout - the root layout, which conatins layout\n * //\n */\nexport function mapLayout(layout, fn, layoutPointer = '', rootLayout = layout) {\n  let indexPad = 0;\n  let newLayout: any[] = [];\n  forEach(layout, (item, index) => {\n    const realIndex = +index + indexPad;\n    const newLayoutPointer = layoutPointer + '/' + realIndex;\n    let newNode: any = copy(item);\n    let itemsArray: any[] = [];\n    if (isObject(item)) {\n      if (hasOwn(item, 'tabs')) {\n        item.items = item.tabs;\n        delete item.tabs;\n      }\n      if (hasOwn(item, 'items')) {\n        itemsArray = isArray(item.items) ? item.items : [item.items];\n      }\n    }\n    if (itemsArray.length) {\n      newNode.items = mapLayout(itemsArray, fn, newLayoutPointer + '/items', rootLayout);\n    }\n    newNode = fn(newNode, realIndex, newLayoutPointer, rootLayout);\n    if (!isDefined(newNode)) {\n      indexPad--;\n    } else {\n      if (isArray(newNode)) { indexPad += newNode.length - 1; }\n      newLayout = newLayout.concat(newNode);\n    }\n  });\n  return newLayout;\n}\n\n/**\n * 'getLayoutNode' function\n * Copy a new layoutNode from layoutRefLibrary\n *\n * //   refNode -\n * //   layoutRefLibrary -\n * //  { any = null } widgetLibrary -\n * //  { any = null } nodeValue -\n * //  copied layoutNode\n */\nexport function getLayoutNode(\n  refNode, jsf, widgetLibrary: any = null, nodeValue: any = null\n) {\n\n  // If recursive reference and building initial layout, return Add button\n  if (refNode.recursiveReference && widgetLibrary) {\n    const newLayoutNode = cloneDeep(refNode);\n    if (!newLayoutNode.options) { newLayoutNode.options = {}; }\n    Object.assign(newLayoutNode, {\n      recursiveReference: true,\n      widget: widgetLibrary.getWidget('$ref'),\n    });\n    Object.assign(newLayoutNode.options, {\n      removable: false,\n      title: 'Add ' + newLayoutNode.$ref,\n    });\n    return newLayoutNode;\n\n    // Otherwise, return referenced layout\n  } else {\n    let newLayoutNode = jsf.layoutRefLibrary[refNode.$ref];\n    // If value defined, build new node from schema (to set array lengths)\n    if (isDefined(nodeValue)) {\n      newLayoutNode = buildLayoutFromSchema(\n        jsf, widgetLibrary, nodeValue,\n        JsonPointer.toSchemaPointer(refNode.$ref, jsf.schema),\n        refNode.$ref, newLayoutNode.arrayItem,\n        newLayoutNode.arrayItemType, newLayoutNode.options.removable, false\n      );\n    } else {\n      // If value not defined, copy node from layoutRefLibrary\n      newLayoutNode = cloneDeep(newLayoutNode);\n      JsonPointer.forEachDeep(newLayoutNode, (subNode, pointer) => {\n\n        // Reset all _id's in newLayoutNode to unique values\n        if (hasOwn(subNode, '_id')) { subNode._id = uniqueId(); }\n\n        // If adding a recursive item, prefix current dataPointer\n        // to all dataPointers in new layoutNode\n        if (refNode.recursiveReference && hasOwn(subNode, 'dataPointer')) {\n          subNode.dataPointer = refNode.dataPointer + subNode.dataPointer;\n        }\n      });\n    }\n    return newLayoutNode;\n  }\n}\n\n/**\n * 'buildTitleMap' function\n *\n * //   titleMap -\n * //   enumList -\n * //  { boolean = true } fieldRequired -\n * //  { boolean = true } flatList -\n * // { TitleMapItem[] }\n */\nexport function buildTitleMap(\n  titleMap, enumList, fieldRequired = true, flatList = true\n) {\n  let newTitleMap: TitleMapItem[] = [];\n  let hasEmptyValue = false;\n  if (titleMap) {\n    if (isArray(titleMap)) {\n      if (enumList) {\n        for (const i of Object.keys(titleMap)) {\n          if (isObject(titleMap[i])) { // JSON Form style\n            const value = titleMap[i].value;\n            if (enumList.includes(value)) {\n              const name = titleMap[i].name;\n              newTitleMap.push({ name, value });\n              if (value === undefined || value === null) { hasEmptyValue = true; }\n            }\n          } else if (isString(titleMap[i])) { // React Jsonschema Form style\n            if (i < enumList.length) {\n              const name = titleMap[i];\n              const value = enumList[i];\n              newTitleMap.push({ name, value });\n              if (value === undefined || value === null) { hasEmptyValue = true; }\n            }\n          }\n        }\n      } else { // If array titleMap and no enum list, just return the titleMap - Angular Schema Form style\n        newTitleMap = titleMap;\n        if (!fieldRequired) {\n          hasEmptyValue = !!newTitleMap\n            .filter(i => i.value === undefined || i.value === null)\n            .length;\n        }\n      }\n    } else if (enumList) { // Alternate JSON Form style, with enum list\n      for (const i of Object.keys(enumList)) {\n        const value = enumList[i];\n        if (hasOwn(titleMap, value)) {\n          const name = titleMap[value];\n          newTitleMap.push({ name, value });\n          if (value === undefined || value === null) { hasEmptyValue = true; }\n        }\n      }\n    } else { // Alternate JSON Form style, without enum list\n      for (const value of Object.keys(titleMap)) {\n        const name = titleMap[value];\n        newTitleMap.push({ name, value });\n        if (value === undefined || value === null) { hasEmptyValue = true; }\n      }\n    }\n  } else if (enumList) { // Build map from enum list alone\n    for (const i of Object.keys(enumList)) {\n      const name = enumList[i];\n      const value = enumList[i];\n      newTitleMap.push({ name, value });\n      if (value === undefined || value === null) { hasEmptyValue = true; }\n    }\n  } else { // If no titleMap and no enum list, return default map of boolean values\n    newTitleMap = [{ name: 'True', value: true }, { name: 'False', value: false }];\n  }\n\n  // Does titleMap have groups?\n  if (newTitleMap.some(title => hasOwn(title, 'group'))) {\n    hasEmptyValue = false;\n\n    // If flatList = true, flatten items & update name to group: name\n    if (flatList) {\n      newTitleMap = newTitleMap.reduce((groupTitleMap, title) => {\n        if (hasOwn(title, 'group')) {\n          if (isArray(title.items)) {\n            groupTitleMap = [\n              ...groupTitleMap,\n              ...title.items.map(item =>\n                ({ ...item, ...{ name: `${title.group}: ${item.name}` } })\n              )\n            ];\n            if (title.items.some(item => item.value === undefined || item.value === null)) {\n              hasEmptyValue = true;\n            }\n          }\n          if (hasOwn(title, 'name') && hasOwn(title, 'value')) {\n            title.name = `${title.group}: ${title.name}`;\n            delete title.group;\n            groupTitleMap.push(title);\n            if (title.value === undefined || title.value === null) {\n              hasEmptyValue = true;\n            }\n          }\n        } else {\n          groupTitleMap.push(title);\n          if (title.value === undefined || title.value === null) {\n            hasEmptyValue = true;\n          }\n        }\n        return groupTitleMap;\n      }, []);\n\n      // If flatList = false, combine items from matching groups\n    } else {\n      newTitleMap = newTitleMap.reduce((groupTitleMap, title) => {\n        if (hasOwn(title, 'group')) {\n          if (title.group !== (groupTitleMap[groupTitleMap.length - 1] || {}).group) {\n            groupTitleMap.push({ group: title.group, items: title.items || [] });\n          }\n          if (hasOwn(title, 'name') && hasOwn(title, 'value')) {\n            groupTitleMap[groupTitleMap.length - 1].items\n              .push({ name: title.name, value: title.value });\n            if (title.value === undefined || title.value === null) {\n              hasEmptyValue = true;\n            }\n          }\n        } else {\n          groupTitleMap.push(title);\n          if (title.value === undefined || title.value === null) {\n            hasEmptyValue = true;\n          }\n        }\n        return groupTitleMap;\n      }, []);\n    }\n  }\n  if (!fieldRequired && !hasEmptyValue) {\n    newTitleMap.unshift({ name: '<em>None</em>', value: null });\n  }\n  return newTitleMap;\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/merge-schemas.function.ts",
    "content": "import isEqual from 'lodash/isEqual';\n\nimport {\n  isArray, isEmpty, isNumber, isObject, isString\n} from './validator.functions';\nimport { hasOwn, uniqueItems, commonItems } from './utility.functions';\nimport { JsonPointer, Pointer } from './jsonpointer.functions';\n\n/**\n * 'mergeSchemas' function\n *\n * Merges multiple JSON schemas into a single schema with combined rules.\n *\n * If able to logically merge properties from all schemas,\n * returns a single schema object containing all merged properties.\n *\n * Example: ({ a: b, max: 1 }, { c: d, max: 2 }) => { a: b, c: d, max: 1 }\n *\n * If unable to logically merge, returns an allOf schema object containing\n * an array of the original schemas;\n *\n * Example: ({ a: b }, { a: d }) => { allOf: [ { a: b }, { a: d } ] }\n *\n * //   schemas - one or more input schemas\n * //  - merged schema\n */\nexport function mergeSchemas(...schemas) {\n  schemas = schemas.filter(schema => !isEmpty(schema));\n  if (schemas.some(schema => !isObject(schema))) { return null; }\n  const combinedSchema: any = {};\n  for (const schema of schemas) {\n    for (const key of Object.keys(schema)) {\n      const combinedValue = combinedSchema[key];\n      const schemaValue = schema[key];\n      if (!hasOwn(combinedSchema, key) || isEqual(combinedValue, schemaValue)) {\n        combinedSchema[key] = schemaValue;\n      } else {\n        switch (key) {\n          case 'allOf':\n            // Combine all items from both arrays\n            if (isArray(combinedValue) && isArray(schemaValue)) {\n              combinedSchema.allOf = mergeSchemas(...combinedValue, ...schemaValue);\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'additionalItems': case 'additionalProperties':\n          case 'contains': case 'propertyNames':\n            // Merge schema objects\n            if (isObject(combinedValue) && isObject(schemaValue)) {\n              combinedSchema[key] = mergeSchemas(combinedValue, schemaValue);\n            // additionalProperties == false in any schema overrides all other values\n            } else if (\n              key === 'additionalProperties' &&\n              (combinedValue === false || schemaValue === false)\n            ) {\n              combinedSchema.combinedSchema = false;\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'anyOf': case 'oneOf': case 'enum':\n            // Keep only items that appear in both arrays\n            if (isArray(combinedValue) && isArray(schemaValue)) {\n              combinedSchema[key] = combinedValue.filter(item1 =>\n                schemaValue.findIndex(item2 => isEqual(item1, item2)) > -1\n              );\n              if (!combinedSchema[key].length) { return { allOf: [ ...schemas ] }; }\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'definitions':\n            // Combine keys from both objects\n            if (isObject(combinedValue) && isObject(schemaValue)) {\n              const combinedObject = { ...combinedValue };\n              for (const subKey of Object.keys(schemaValue)) {\n                if (!hasOwn(combinedObject, subKey) ||\n                  isEqual(combinedObject[subKey], schemaValue[subKey])\n                ) {\n                  combinedObject[subKey] = schemaValue[subKey];\n                // Don't combine matching keys with different values\n                } else {\n                  return { allOf: [ ...schemas ] };\n                }\n              }\n              combinedSchema.definitions = combinedObject;\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'dependencies':\n            // Combine all keys from both objects\n            // and merge schemas on matching keys,\n            // converting from arrays to objects if necessary\n            if (isObject(combinedValue) && isObject(schemaValue)) {\n              const combinedObject = { ...combinedValue };\n              for (const subKey of Object.keys(schemaValue)) {\n                if (!hasOwn(combinedObject, subKey) ||\n                  isEqual(combinedObject[subKey], schemaValue[subKey])\n                ) {\n                  combinedObject[subKey] = schemaValue[subKey];\n                // If both keys are arrays, include all items from both arrays,\n                // excluding duplicates\n                } else if (\n                  isArray(schemaValue[subKey]) && isArray(combinedObject[subKey])\n                ) {\n                  combinedObject[subKey] =\n                    uniqueItems(...combinedObject[subKey], ...schemaValue[subKey]);\n                // If either key is an object, merge the schemas\n                } else if (\n                  (isArray(schemaValue[subKey]) || isObject(schemaValue[subKey])) &&\n                  (isArray(combinedObject[subKey]) || isObject(combinedObject[subKey]))\n                ) {\n                  // If either key is an array, convert it to an object first\n                  const required = isArray(combinedSchema.required) ?\n                    combinedSchema.required : [];\n                  const combinedDependency = isArray(combinedObject[subKey]) ?\n                    { required: uniqueItems(...required, combinedObject[subKey]) } :\n                    combinedObject[subKey];\n                  const schemaDependency = isArray(schemaValue[subKey]) ?\n                    { required: uniqueItems(...required, schemaValue[subKey]) } :\n                    schemaValue[subKey];\n                  combinedObject[subKey] =\n                    mergeSchemas(combinedDependency, schemaDependency);\n                } else {\n                  return { allOf: [ ...schemas ] };\n                }\n              }\n              combinedSchema.dependencies = combinedObject;\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'items':\n            // If arrays, keep only items that appear in both arrays\n            if (isArray(combinedValue) && isArray(schemaValue)) {\n              combinedSchema.items = combinedValue.filter(item1 =>\n                schemaValue.findIndex(item2 => isEqual(item1, item2)) > -1\n              );\n              if (!combinedSchema.items.length) { return { allOf: [ ...schemas ] }; }\n            // If both keys are objects, merge them\n            } else if (isObject(combinedValue) && isObject(schemaValue)) {\n              combinedSchema.items = mergeSchemas(combinedValue, schemaValue);\n            // If object + array, combine object with each array item\n            } else if (isArray(combinedValue) && isObject(schemaValue)) {\n              combinedSchema.items =\n                combinedValue.map(item => mergeSchemas(item, schemaValue));\n            } else if (isObject(combinedValue) && isArray(schemaValue)) {\n              combinedSchema.items =\n                schemaValue.map(item => mergeSchemas(item, combinedValue));\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'multipleOf':\n            // TODO: Adjust to correctly handle decimal values\n            // If numbers, set to least common multiple\n            if (isNumber(combinedValue) && isNumber(schemaValue)) {\n              const gcd = (x, y) => !y ? x : gcd(y, x % y);\n              const lcm = (x, y) => (x * y) / gcd(x, y);\n              combinedSchema.multipleOf = lcm(combinedValue, schemaValue);\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'maximum': case 'exclusiveMaximum': case 'maxLength':\n          case 'maxItems': case 'maxProperties':\n            // If numbers, set to lowest value\n            if (isNumber(combinedValue) && isNumber(schemaValue)) {\n              combinedSchema[key] = Math.min(combinedValue, schemaValue);\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'minimum': case 'exclusiveMinimum': case 'minLength':\n          case 'minItems': case 'minProperties':\n            // If numbers, set to highest value\n            if (isNumber(combinedValue) && isNumber(schemaValue)) {\n              combinedSchema[key] = Math.max(combinedValue, schemaValue);\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'not':\n            // Combine not values into anyOf array\n            if (isObject(combinedValue) && isObject(schemaValue)) {\n              const notAnyOf = [combinedValue, schemaValue]\n                .reduce((notAnyOfArray, notSchema) =>\n                  isArray(notSchema.anyOf) &&\n                  Object.keys(notSchema).length === 1 ?\n                    [ ...notAnyOfArray, ...notSchema.anyOf ] :\n                    [ ...notAnyOfArray, notSchema ]\n                , []);\n              // TODO: Remove duplicate items from array\n              combinedSchema.not = { anyOf: notAnyOf };\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'patternProperties':\n            // Combine all keys from both objects\n            // and merge schemas on matching keys\n            if (isObject(combinedValue) && isObject(schemaValue)) {\n              const combinedObject = { ...combinedValue };\n              for (const subKey of Object.keys(schemaValue)) {\n                if (!hasOwn(combinedObject, subKey) ||\n                  isEqual(combinedObject[subKey], schemaValue[subKey])\n                ) {\n                  combinedObject[subKey] = schemaValue[subKey];\n                // If both keys are objects, merge them\n                } else if (\n                  isObject(schemaValue[subKey]) && isObject(combinedObject[subKey])\n                ) {\n                  combinedObject[subKey] =\n                    mergeSchemas(combinedObject[subKey], schemaValue[subKey]);\n                } else {\n                  return { allOf: [ ...schemas ] };\n                }\n              }\n              combinedSchema.patternProperties = combinedObject;\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'properties':\n            // Combine all keys from both objects\n            // unless additionalProperties === false\n            // and merge schemas on matching keys\n            if (isObject(combinedValue) && isObject(schemaValue)) {\n              const combinedObject = { ...combinedValue };\n              // If new schema has additionalProperties,\n              // merge or remove non-matching property keys in combined schema\n              if (hasOwn(schemaValue, 'additionalProperties')) {\n                Object.keys(combinedValue)\n                  .filter(combinedKey => !Object.keys(schemaValue).includes(combinedKey))\n                  .forEach(nonMatchingKey => {\n                    if (schemaValue.additionalProperties === false) {\n                      delete combinedObject[nonMatchingKey];\n                    } else if (isObject(schemaValue.additionalProperties)) {\n                      combinedObject[nonMatchingKey] = mergeSchemas(\n                        combinedObject[nonMatchingKey],\n                        schemaValue.additionalProperties\n                      );\n                    }\n                  });\n              }\n              for (const subKey of Object.keys(schemaValue)) {\n                if (isEqual(combinedObject[subKey], schemaValue[subKey]) || (\n                  !hasOwn(combinedObject, subKey) &&\n                  !hasOwn(combinedObject, 'additionalProperties')\n                )) {\n                  combinedObject[subKey] = schemaValue[subKey];\n                // If combined schema has additionalProperties,\n                // merge or ignore non-matching property keys in new schema\n                } else if (\n                  !hasOwn(combinedObject, subKey) &&\n                  hasOwn(combinedObject, 'additionalProperties')\n                ) {\n                  // If combinedObject.additionalProperties === false,\n                  // do nothing (don't set key)\n                  // If additionalProperties is object, merge with new key\n                  if (isObject(combinedObject.additionalProperties)) {\n                    combinedObject[subKey] = mergeSchemas(\n                      combinedObject.additionalProperties, schemaValue[subKey]\n                    );\n                  }\n                // If both keys are objects, merge them\n                } else if (\n                  isObject(schemaValue[subKey]) &&\n                  isObject(combinedObject[subKey])\n                ) {\n                  combinedObject[subKey] =\n                    mergeSchemas(combinedObject[subKey], schemaValue[subKey]);\n                } else {\n                  return { allOf: [ ...schemas ] };\n                }\n              }\n              combinedSchema.properties = combinedObject;\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'required':\n            // If arrays, include all items from both arrays, excluding duplicates\n            if (isArray(combinedValue) && isArray(schemaValue)) {\n              combinedSchema.required = uniqueItems(...combinedValue, ...schemaValue);\n            // If booleans, aet true if either true\n            } else if (\n              typeof schemaValue === 'boolean' &&\n              typeof combinedValue === 'boolean'\n            ) {\n              combinedSchema.required = !!combinedValue || !!schemaValue;\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case '$schema': case '$id': case 'id':\n            // Don't combine these keys\n          break;\n          case 'title': case 'description': case '$comment':\n            // Return the last value, overwriting any previous one\n            // These properties are not used for validation, so conflicts don't matter\n            combinedSchema[key] = schemaValue;\n          break;\n          case 'type':\n            if (\n              (isArray(schemaValue) || isString(schemaValue)) &&\n              (isArray(combinedValue) || isString(combinedValue))\n            ) {\n              const combinedTypes = commonItems(combinedValue, schemaValue);\n              if (!combinedTypes.length) { return { allOf: [ ...schemas ] }; }\n              combinedSchema.type = combinedTypes.length > 1 ? combinedTypes : combinedTypes[0];\n            } else {\n              return { allOf: [ ...schemas ] };\n            }\n          break;\n          case 'uniqueItems':\n            // Set true if either true\n            combinedSchema.uniqueItems = !!combinedValue || !!schemaValue;\n          break;\n          default:\n            return { allOf: [ ...schemas ] };\n        }\n      }\n    }\n  }\n  return combinedSchema;\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/utility.functions.ts",
    "content": "import {hasValue, inArray, isArray, isDefined, isEmpty, isMap, isObject, isSet, isString, PlainObject} from './validator.functions';\n\n/**\n * Utility function library:\n *\n * addClasses, copy, forEach, forEachCopy, hasOwn, mergeFilteredObject,\n * uniqueItems, commonItems, fixTitle, toTitleCase\n*/\n\n/**\n * 'addClasses' function\n *\n * Merges two space-delimited lists of CSS classes and removes duplicates.\n *\n * // {string | string[] | Set<string>} oldClasses\n * // {string | string[] | Set<string>} newClasses\n * // {string | string[] | Set<string>} - Combined classes\n */\nexport function addClasses(\n  oldClasses: string | string[] | Set<string>,\n  newClasses: string | string[] | Set<string>\n): string | string[] | Set<string> {\n  const badType = i => !isSet(i) && !isArray(i) && !isString(i);\n  if (badType(newClasses)) { return oldClasses; }\n  if (badType(oldClasses)) { oldClasses = ''; }\n  const toSet = i => isSet(i) ? i : isArray(i) ? new Set(i) : new Set(i.split(' '));\n  const combinedSet: Set<any> = toSet(oldClasses);\n  const newSet: Set<any> = toSet(newClasses);\n  newSet.forEach(c => combinedSet.add(c));\n  if (isSet(oldClasses)) { return combinedSet; }\n  if (isArray(oldClasses)) { return Array.from(combinedSet); }\n  return Array.from(combinedSet).join(' ');\n}\n\n/**\n * 'copy' function\n *\n * Makes a shallow copy of a JavaScript object, array, Map, or Set.\n * If passed a JavaScript primitive value (string, number, boolean, or null),\n * it returns the value.\n *\n * // {Object|Array|string|number|boolean|null} object - The object to copy\n * // {boolean = false} errors - Show errors?\n * // {Object|Array|string|number|boolean|null} - The copied object\n */\nexport function copy(object: any, errors = false): any {\n  if (typeof object !== 'object' || object === null) { return object; }\n  if (isMap(object))    { return new Map(object); }\n  if (isSet(object))    { return new Set(object); }\n  if (isArray(object))  { return [ ...object ];   }\n  if (isObject(object)) { return { ...object };   }\n  if (errors) {\n    console.error('copy error: Object to copy must be a JavaScript object or value.');\n  }\n  return object;\n}\n\n/**\n * 'forEach' function\n *\n * Iterates over all items in the first level of an object or array\n * and calls an iterator funciton on each item.\n *\n * The iterator function is called with four values:\n * 1. The current item's value\n * 2. The current item's key\n * 3. The parent object, which contains the current item\n * 4. The root object\n *\n * Setting the optional third parameter to 'top-down' or 'bottom-up' will cause\n * it to also recursively iterate over items in sub-objects or sub-arrays in the\n * specified direction.\n *\n * // {Object|Array} object - The object or array to iterate over\n * // {function} fn - the iterator funciton to call on each item\n * // {boolean = false} errors - Show errors?\n * // {void}\n */\nexport function forEach(\n  object: any, fn: (v: any, k?: string | number, c?: any, rc?: any) => any,\n  recurse: boolean | string = false, rootObject: any = object, errors = false\n): void {\n  if (isEmpty(object)) { return; }\n  if ((isObject(object) || isArray(object)) && typeof fn === 'function') {\n    for (const key of Object.keys(object)) {\n      const value = object[key];\n      if (recurse === 'bottom-up' && (isObject(value) || isArray(value))) {\n        forEach(value, fn, recurse, rootObject);\n      }\n      fn(value, key, object, rootObject);\n      if (recurse === 'top-down' && (isObject(value) || isArray(value))) {\n        forEach(value, fn, recurse, rootObject);\n      }\n    }\n  }\n  if (errors) {\n    if (typeof fn !== 'function') {\n      console.error('forEach error: Iterator must be a function.');\n      console.error('function', fn);\n    }\n    if (!isObject(object) && !isArray(object)) {\n      console.error('forEach error: Input object must be an object or array.');\n      console.error('object', object);\n    }\n  }\n}\n\n/**\n * 'forEachCopy' function\n *\n * Iterates over all items in the first level of an object or array\n * and calls an iterator function on each item. Returns a new object or array\n * with the same keys or indexes as the original, and values set to the results\n * of the iterator function.\n *\n * Does NOT recursively iterate over items in sub-objects or sub-arrays.\n *\n * // {Object | Array} object - The object or array to iterate over\n * // {function} fn - The iterator funciton to call on each item\n * // {boolean = false} errors - Show errors?\n * // {Object | Array} - The resulting object or array\n */\nexport function forEachCopy(\n  object: any, fn: (v: any, k?: string | number, o?: any, p?: string) => any,\n  errors = false\n): any {\n  if (!hasValue(object)) { return; }\n  if ((isObject(object) || isArray(object)) && typeof object !== 'function') {\n    const newObject: any = isArray(object) ? [] : {};\n    for (const key of Object.keys(object)) {\n      newObject[key] = fn(object[key], key, object);\n    }\n    return newObject;\n  }\n  if (errors) {\n    if (typeof fn !== 'function') {\n      console.error('forEachCopy error: Iterator must be a function.');\n      console.error('function', fn);\n    }\n    if (!isObject(object) && !isArray(object)) {\n      console.error('forEachCopy error: Input object must be an object or array.');\n      console.error('object', object);\n    }\n  }\n}\n\n/**\n * 'hasOwn' utility function\n *\n * Checks whether an object or array has a particular property.\n *\n * // {any} object - the object to check\n * // {string} property - the property to look for\n * // {boolean} - true if object has property, false if not\n */\nexport function hasOwn(object: any, property: string): boolean {\n  if (!object || !['number', 'string', 'symbol'].includes(typeof property) ||\n    (!isObject(object) && !isArray(object) && !isMap(object) && !isSet(object))\n  ) { return false; }\n  if (isMap(object) || isSet(object)) { return object.has(property); }\n  if (typeof property === 'number') {\n    if (isArray(object)) { return object[<number>property]; }\n    property = property + '';\n  }\n  return object.hasOwnProperty(property);\n}\n\n/**\n * Types of possible expressions which the app is able to evaluate.\n */\nexport enum ExpressionType {\n  EQUALS,\n  NOT_EQUALS,\n  NOT_AN_EXPRESSION\n}\n\n/**\n * Detects the type of expression from the given candidate. `==` for equals,\n * `!=` for not equals. If none of these are contained in the candidate, the candidate\n * is not considered to be an expression at all and thus `NOT_AN_EXPRESSION` is returned.\n * // {expressionCandidate} expressionCandidate - potential expression\n */\nexport function getExpressionType(expressionCandidate: string): ExpressionType {\n  if (expressionCandidate.indexOf('==') !== -1) {\n    return ExpressionType.EQUALS;\n  }\n\n  if (expressionCandidate.toString().indexOf('!=') !== -1) {\n    return ExpressionType.NOT_EQUALS;\n  }\n\n  return ExpressionType.NOT_AN_EXPRESSION;\n}\n\nexport function isEqual(expressionType) {\n  return expressionType as ExpressionType === ExpressionType.EQUALS;\n}\n\nexport function isNotEqual(expressionType) {\n  return expressionType as ExpressionType === ExpressionType.NOT_EQUALS;\n}\n\nexport function isNotExpression(expressionType) {\n  return expressionType as ExpressionType === ExpressionType.NOT_AN_EXPRESSION;\n}\n\n/**\n * Splits the expression key by the expressionType on a pair of values\n * before and after the equals or nor equals sign.\n * // {expressionType} enum of an expression type\n * // {key} the given key from a for loop iver all conditions\n */\nexport function getKeyAndValueByExpressionType(expressionType: ExpressionType, key: string) {\n  if (isEqual(expressionType)) {\n    return key.split('==', 2);\n  }\n\n  if (isNotEqual(expressionType)) {\n    return key.split('!=', 2);\n  }\n\n  return null;\n}\n\nexport function cleanValueOfQuotes(keyAndValue): String {\n  if (keyAndValue.charAt(0) === '\\'' && keyAndValue.charAt(keyAndValue.length - 1) === '\\'') {\n    return keyAndValue.replace('\\'', '').replace('\\'', '');\n  }\n  return keyAndValue;\n}\n\n/**\n * 'mergeFilteredObject' utility function\n *\n * Shallowly merges two objects, setting key and values from source object\n * in target object, excluding specified keys.\n *\n * Optionally, it can also use functions to transform the key names and/or\n * the values of the merging object.\n *\n * // {PlainObject} targetObject - Target object to add keys and values to\n * // {PlainObject} sourceObject - Source object to copy keys and values from\n * // {string[]} excludeKeys - Array of keys to exclude\n * // {(string: string) => string = (k) => k} keyFn - Function to apply to keys\n * // {(any: any) => any = (v) => v} valueFn - Function to apply to values\n * // {PlainObject} - Returns targetObject\n */\nexport function mergeFilteredObject(\n  targetObject: PlainObject,\n  sourceObject: PlainObject,\n  excludeKeys = <string[]>[],\n  keyFn = (key: string): string => key,\n  valFn = (val: any): any => val\n): PlainObject {\n  if (!isObject(sourceObject)) { return targetObject; }\n  if (!isObject(targetObject)) { targetObject = {}; }\n  for (const key of Object.keys(sourceObject)) {\n    if (!inArray(key, excludeKeys) && isDefined(sourceObject[key])) {\n      targetObject[keyFn(key)] = valFn(sourceObject[key]);\n    }\n  }\n  return targetObject;\n}\n\n/**\n * 'uniqueItems' function\n *\n * Accepts any number of string value inputs,\n * and returns an array of all input vaues, excluding duplicates.\n *\n * // {...string} ...items -\n * // {string[]} -\n */\nexport function uniqueItems(...items): string[] {\n  const returnItems = [];\n  for (const item of items) {\n    if (!returnItems.includes(item)) { returnItems.push(item); }\n  }\n  return returnItems;\n}\n\n/**\n * 'commonItems' function\n *\n * Accepts any number of strings or arrays of string values,\n * and returns a single array containing only values present in all inputs.\n *\n * // {...string|string[]} ...arrays -\n * // {string[]} -\n */\nexport function commonItems(...arrays): string[] {\n  let returnItems = null;\n  for (let array of arrays) {\n    if (isString(array)) { array = [array]; }\n    returnItems = returnItems === null ? [ ...array ] :\n      returnItems.filter(item => array.includes(item));\n    if (!returnItems.length) { return []; }\n  }\n  return returnItems;\n}\n\n/**\n * 'fixTitle' function\n *\n *\n * // {string} input -\n * // {string} -\n */\nexport function fixTitle(name: string): string {\n  return name && toTitleCase(name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/_/g, ' '));\n}\n\n/**\n * 'toTitleCase' function\n *\n * Intelligently converts an input string to Title Case.\n *\n * Accepts an optional second parameter with a list of additional\n * words and abbreviations to force into a particular case.\n *\n * This function is built on prior work by John Gruber and David Gouch:\n * http://daringfireball.net/2008/08/title_case_update\n * https://github.com/gouch/to-title-case\n *\n * // {string} input -\n * // {string|string[]} forceWords? -\n * // {string} -\n */\nexport function toTitleCase(input: string, forceWords?: string|string[]): string {\n  if (!isString(input)) { return input; }\n  let forceArray: string[] = ['a', 'an', 'and', 'as', 'at', 'but', 'by', 'en',\n   'for', 'if', 'in', 'nor', 'of', 'on', 'or', 'per', 'the', 'to', 'v', 'v.',\n   'vs', 'vs.', 'via'];\n  if (isString(forceWords)) { forceWords = (<string>forceWords).split('|'); }\n  if (isArray(forceWords)) { forceArray = forceArray.concat(forceWords); }\n  const forceArrayLower: string[] = forceArray.map(w => w.toLowerCase());\n  const noInitialCase: boolean =\n    input === input.toUpperCase() || input === input.toLowerCase();\n  let prevLastChar = '';\n  input = input.trim();\n  return input.replace(/[A-Za-z0-9\\u00C0-\\u00FF]+[^\\s-]*/g, (word, idx) => {\n    if (!noInitialCase && word.slice(1).search(/[A-Z]|\\../) !== -1) {\n      return word;\n    } else {\n      let newWord: string;\n      const forceWord: string =\n        forceArray[forceArrayLower.indexOf(word.toLowerCase())];\n      if (!forceWord) {\n        if (noInitialCase) {\n          if (word.slice(1).search(/\\../) !== -1) {\n            newWord = word.toLowerCase();\n          } else {\n            newWord = word[0].toUpperCase() + word.slice(1).toLowerCase();\n          }\n        } else {\n          newWord = word[0].toUpperCase() + word.slice(1);\n        }\n      } else if (\n        forceWord === forceWord.toLowerCase() && (\n          idx === 0 || idx + word.length === input.length ||\n          prevLastChar === ':' || input[idx - 1].search(/[^\\s-]/) !== -1 ||\n          (input[idx - 1] !== '-' && input[idx + word.length] === '-')\n        )\n      ) {\n        newWord = forceWord[0].toUpperCase() + forceWord.slice(1);\n      } else {\n        newWord = forceWord;\n      }\n      prevLastChar = word.slice(-1);\n      return newWord;\n    }\n  });\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/validator.functions.spec.ts",
    "content": "import {\n  toIsoString,\n  toJavaScriptType,\n  toSchemaType,\n} from \"./validator.functions\";\n\ndescribe(\"Validator functions\", () => {\n  describe(\"toIsoString\", () => {\n    it(\"should work with timezone\", () => {\n      expect(toIsoString(new Date(\"05 October 2011 14:48 UTC\"))).toEqual(\n        \"2011-10-05\"\n      );\n    });\n  });\n  describe(\"toJavaScriptType\", () => {\n    it(\"Converts an input (probably string) value to a JavaScript primitive type 'string', 'number', 'boolean', or 'null' - before storing in a JSON object.\", () => {\n      expect(toJavaScriptType(\"10\", \"number\")).toEqual(10);\n      expect(toJavaScriptType(\"10\", \"integer\")).toEqual(10);\n      expect(toJavaScriptType(10, \"integer\")).toEqual(10);\n      expect(toJavaScriptType(10, \"string\")).toEqual(\"10\");\n      expect(toJavaScriptType(\"10.5\", \"integer\")).toEqual(null);\n      expect(toJavaScriptType(10.5, \"integer\")).toEqual(null);\n    });\n  });\n  describe(\"toSchemaType\", () => {\n    it(\"Number conversion examples\", () => {\n      expect(toSchemaType(10, [\"number\", \"integer\", \"string\"])).toEqual(10);\n      expect(toSchemaType(10, [\"number\", \"string\"])).toEqual(10);\n      expect(toSchemaType(10, [\"string\"])).toEqual(\"10\");\n      expect(toSchemaType(10.5, [\"number\", \"integer\", \"string\"])).toEqual(10.5);\n      expect(toSchemaType(10.5, [\"integer\", \"string\"])).toEqual(\"10.5\");\n      expect(toSchemaType(10.5, [\"integer\"])).toEqual(10);\n    });\n    it(\"Boolean conversion examples\", () => {\n      expect(\n        toSchemaType(\"1\", [\"integer\", \"number\", \"string\", \"boolean\"])\n      ).toEqual(\"1\");\n      expect(toSchemaType(\"1\", [\"string\", \"boolean\"])).toEqual(\"1\");\n      expect(toSchemaType(\"1\", [\"boolean\"])).toEqual(\"1\");\n      expect(toSchemaType(\"true\", [\"number\", \"string\", \"boolean\"])).toEqual(\n        \"true\"\n      );\n      expect(toSchemaType(\"true\", [\"boolean\"])).toEqual(\"true\");\n      expect(toSchemaType(\"true\", [\"number\"])).toEqual(0);\n    });\n    it(\"string conversion examples\", () => {\n      expect(\n        toSchemaType(\"1.58\", [\"boolean\", \"number\", \"integer\", \"string\"])\n      ).toEqual(\"1.58\");\n      expect(toSchemaType(\"1.5\", [\"boolean\", \"number\", \"integer\"])).toEqual(\n        \"1.5\"\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/shared/validator.functions.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { from, Observable } from 'rxjs';\n\n/**\n * Validator utility function library:\n *\n * Validator and error utilities:\n *   _executeValidators, _executeAsyncValidators, _mergeObjects, _mergeErrors\n *\n * Individual value checking:\n *   isDefined, hasValue, isEmpty\n *\n * Individual type checking:\n *   isString, isNumber, isInteger, isBoolean, isFunction, isObject, isArray,\n *   isMap, isSet, isPromise, isObservable\n *\n * Multiple type checking and fixing:\n *   getType, isType, isPrimitive, toJavaScriptType, toSchemaType,\n *   _toPromise, toObservable\n *\n * Utility functions:\n *   inArray, xor\n *\n * Typescript types and interfaces:\n *   SchemaPrimitiveType, SchemaType, JavaScriptPrimitiveType, JavaScriptType,\n *   PrimitiveValue, PlainObject, IValidatorFn, AsyncIValidatorFn\n *\n * Note: 'IValidatorFn' is short for 'invertable validator function',\n *   which is a validator functions that accepts an optional second\n *   argument which, if set to TRUE, causes the validator to perform\n *   the opposite of its original function.\n */\n\nexport type SchemaPrimitiveType =\n  'string' | 'number' | 'integer' | 'boolean' | 'null';\nexport type SchemaType =\n  'string' | 'number' | 'integer' | 'boolean' | 'null' | 'object' | 'array';\nexport type JavaScriptPrimitiveType =\n  'string' | 'number' | 'boolean' | 'null' | 'undefined';\nexport type JavaScriptType =\n  'string' | 'number' | 'boolean' | 'null' | 'undefined' | 'object' | 'array' |\n  'map' | 'set' | 'arguments' | 'date' | 'error' | 'function' | 'json' |\n  'math' | 'regexp'; // Note: this list is incomplete\nexport type PrimitiveValue = string | number | boolean | null | undefined;\nexport interface PlainObject { [k: string]: any; }\n\nexport type IValidatorFn = (c: AbstractControl, i?: boolean) => PlainObject;\nexport type AsyncIValidatorFn = (c: AbstractControl, i?: boolean) => any;\n\n/**\n * '_executeValidators' utility function\n *\n * Validates a control against an array of validators, and returns\n * an array of the same length containing a combination of error messages\n * (from invalid validators) and null values (from valid validators)\n *\n * //  { AbstractControl } control - control to validate\n * //  { IValidatorFn[] } validators - array of validators\n * //  { boolean } invert - invert?\n * // { PlainObject[] } - array of nulls and error message\n */\nexport function _executeValidators(control, validators, invert = false) {\n  return validators.map(validator => validator(control, invert));\n}\n\n/**\n * '_executeAsyncValidators' utility function\n *\n * Validates a control against an array of async validators, and returns\n * an array of observabe results of the same length containing a combination of\n * error messages (from invalid validators) and null values (from valid ones)\n *\n * //  { AbstractControl } control - control to validate\n * //  { AsyncIValidatorFn[] } validators - array of async validators\n * //  { boolean } invert - invert?\n * //  - array of observable nulls and error message\n */\nexport function _executeAsyncValidators(control, validators, invert = false) {\n  return validators.map(validator => validator(control, invert));\n}\n\n/**\n * '_mergeObjects' utility function\n *\n * Recursively Merges one or more objects into a single object with combined keys.\n * Automatically detects and ignores null and undefined inputs.\n * Also detects duplicated boolean 'not' keys and XORs their values.\n *\n * //  { PlainObject[] } objects - one or more objects to merge\n * // { PlainObject } - merged object\n */\nexport function _mergeObjects(...objects) {\n  const mergedObject: PlainObject = { };\n  for (const currentObject of objects) {\n    if (isObject(currentObject)) {\n      for (const key of Object.keys(currentObject)) {\n        const currentValue = currentObject[key];\n        const mergedValue = mergedObject[key];\n        mergedObject[key] = !isDefined(mergedValue) ? currentValue :\n          key === 'not' && isBoolean(mergedValue, 'strict') &&\n            isBoolean(currentValue, 'strict') ? xor(mergedValue, currentValue) :\n          getType(mergedValue) === 'object' && getType(currentValue) === 'object' ?\n            _mergeObjects(mergedValue, currentValue) :\n            currentValue;\n      }\n    }\n  }\n  return mergedObject;\n}\n\n/**\n * '_mergeErrors' utility function\n *\n * Merges an array of objects.\n * Used for combining the validator errors returned from 'executeValidators'\n *\n * //  { PlainObject[] } arrayOfErrors - array of objects\n * // { PlainObject } - merged object, or null if no usable input objectcs\n */\nexport function _mergeErrors(arrayOfErrors) {\n  const mergedErrors = _mergeObjects(...arrayOfErrors);\n  return isEmpty(mergedErrors) ? null : mergedErrors;\n}\n\n/**\n * 'isDefined' utility function\n *\n * Checks if a variable contains a value of any type.\n * Returns true even for otherwise 'falsey' values of 0, '', and false.\n *\n * //   value - the value to check\n * // { boolean } - false if undefined or null, otherwise true\n */\nexport function isDefined(value) {\n  return value !== undefined && value !== null;\n}\n\n/**\n * 'hasValue' utility function\n *\n * Checks if a variable contains a value.\n * Returs false for null, undefined, or a zero-length strng, '',\n * otherwise returns true.\n * (Stricter than 'isDefined' because it also returns false for '',\n * though it stil returns true for otherwise 'falsey' values 0 and false.)\n *\n * //   value - the value to check\n * // { boolean } - false if undefined, null, or '', otherwise true\n */\nexport function hasValue(value) {\n  return value !== undefined && value !== null && value !== '';\n}\n\n/**\n * 'isEmpty' utility function\n *\n * Similar to !hasValue, but also returns true for empty arrays and objects.\n *\n * //   value - the value to check\n * // { boolean } - false if undefined, null, or '', otherwise true\n */\nexport function isEmpty(value) {\n  if (isArray(value)) { return !value.length; }\n  if (isObject(value)) { return !Object.keys(value).length; }\n  return value === undefined || value === null || value === '';\n}\n\n/**\n * 'isString' utility function\n *\n * Checks if a value is a string.\n *\n * //   value - the value to check\n * // { boolean } - true if string, false if not\n */\nexport function isString(value) {\n  return typeof value === 'string';\n}\n\n/**\n * 'isNumber' utility function\n *\n * Checks if a value is a regular number, numeric string, or JavaScript Date.\n *\n * //   value - the value to check\n * //  { any = false } strict - if truthy, also checks JavaScript tyoe\n * // { boolean } - true if number, false if not\n */\nexport function isNumber(value, strict: any = false) {\n  if (strict && typeof value !== 'number') { return false; }\n  return !isNaN(value) && value !== value / 0;\n}\n\n/**\n * 'isInteger' utility function\n *\n * Checks if a value is an integer.\n *\n * //   value - the value to check\n * //  { any = false } strict - if truthy, also checks JavaScript tyoe\n * // {boolean } - true if number, false if not\n */\nexport function isInteger(value, strict: any = false) {\n  if (strict && typeof value !== 'number') { return false; }\n  return !isNaN(value) &&  value !== value / 0 && value % 1 === 0;\n}\n\n/**\n * 'isBoolean' utility function\n *\n * Checks if a value is a boolean.\n *\n * //   value - the value to check\n * //  { any = null } option - if 'strict', also checks JavaScript type\n *                              if TRUE or FALSE, checks only for that value\n * // { boolean } - true if boolean, false if not\n */\nexport function isBoolean(value, option: any = null) {\n  if (option === 'strict') { return value === true || value === false; }\n  if (option === true) {\n    return value === true || value === 1 || value === 'true' || value === '1';\n  }\n  if (option === false) {\n    return value === false || value === 0 || value === 'false' || value === '0';\n  }\n  return value === true || value === 1 || value === 'true' || value === '1' ||\n    value === false || value === 0 || value === 'false' || value === '0';\n}\n\nexport function isFunction(item: any): boolean {\n  return typeof item === 'function';\n}\n\nexport function isObject(item: any): boolean {\n  return item !== null && typeof item === 'object';\n}\n\nexport function isArray(item: any): boolean {\n  return Array.isArray(item);\n}\n\nexport function isDate(item: any): boolean {\n  return !!item && Object.prototype.toString.call(item) === '[object Date]';\n}\n\nexport function isMap(item: any): boolean {\n  return !!item && Object.prototype.toString.call(item) === '[object Map]';\n}\n\nexport function isSet(item: any): boolean {\n  return !!item && Object.prototype.toString.call(item) === '[object Set]';\n}\n\nexport function isSymbol(item: any): boolean {\n  return typeof item === 'symbol';\n}\n\n/**\n * 'getType' function\n *\n * Detects the JSON Schema Type of a value.\n * By default, detects numbers and integers even if formatted as strings.\n * (So all integers are also numbers, and any number may also be a string.)\n * However, it only detects true boolean values (to detect boolean values\n * in non-boolean formats, use isBoolean() instead).\n *\n * If passed a second optional parameter of 'strict', it will only detect\n * numbers and integers if they are formatted as JavaScript numbers.\n *\n * Examples:\n * getType('10.5') = 'number'\n * getType(10.5) = 'number'\n * getType('10') = 'integer'\n * getType(10) = 'integer'\n * getType('true') = 'string'\n * getType(true) = 'boolean'\n * getType(null) = 'null'\n * getType({ }) = 'object'\n * getType([]) = 'array'\n *\n * getType('10.5', 'strict') = 'string'\n * getType(10.5, 'strict') = 'number'\n * getType('10', 'strict') = 'string'\n * getType(10, 'strict') = 'integer'\n * getType('true', 'strict') = 'string'\n * getType(true, 'strict') = 'boolean'\n *\n * //   value - value to check\n * //  { any = false } strict - if truthy, also checks JavaScript tyoe\n * // { SchemaType }\n */\nexport function getType(value, strict: any = false) {\n  if (!isDefined(value)) { return 'null'; }\n  if (isArray(value)) { return 'array'; }\n  if (isObject(value)) { return 'object'; }\n  if (isBoolean(value, 'strict')) { return 'boolean'; }\n  if (isInteger(value, strict)) { return 'integer'; }\n  if (isNumber(value, strict)) { return 'number'; }\n  if (isString(value) || (!strict && isDate(value))) { return 'string'; }\n  return null;\n}\n\n/**\n * 'isType' function\n *\n * Checks wether an input (probably string) value contains data of\n * a specified JSON Schema type\n *\n * //  { PrimitiveValue } value - value to check\n * //  { SchemaPrimitiveType } type - type to check\n * // { boolean }\n */\nexport function isType(value, type) {\n  switch (type) {\n    case 'string':\n      return isString(value) || isDate(value);\n    case 'number':\n      return isNumber(value);\n    case 'integer':\n      return isInteger(value);\n    case 'boolean':\n      return isBoolean(value);\n    case 'null':\n      return !hasValue(value);\n    default:\n      console.error(`isType error: \"${type}\" is not a recognized type.`);\n      return null;\n  }\n}\n\n/**\n * 'isPrimitive' function\n *\n * Checks wether an input value is a JavaScript primitive type:\n * string, number, boolean, or null.\n *\n * //   value - value to check\n * // { boolean }\n */\nexport function isPrimitive(value) {\n  return (isString(value) || isNumber(value) ||\n    isBoolean(value, 'strict') || value === null);\n}\n\n/**\n * \n * @param date \n * @returns {string}\n * exmaple:\n * toDateString('2018-01-01') = '2018-01-01'\n * toDateString('2018-01-30T00:00:00.000Z') = '2018-01-30'\n */\nexport const toIsoString = (date: Date) => {\n  const day = date.getDate();\n  const month = date.getMonth() + 1;\n  const year = date.getFullYear();\n  return `${year}-${month < 10 ? '0' + month : month}-${day < 10 ? '0' + day : day}`;\n}\n\n/**\n * 'toJavaScriptType' function\n *\n * Converts an input (probably string) value to a JavaScript primitive type -\n * 'string', 'number', 'boolean', or 'null' - before storing in a JSON object.\n *\n * Does not coerce values (other than null), and only converts the types\n * of values that would otherwise be valid.\n *\n * If the optional third parameter 'strictIntegers' is TRUE, and the\n * JSON Schema type 'integer' is specified, it also verifies the input value\n * is an integer and, if it is, returns it as a JaveScript number.\n * If 'strictIntegers' is FALSE (or not set) the type 'integer' is treated\n * exactly the same as 'number', and allows decimals.\n *\n * Valid Examples:\n * toJavaScriptType('10',   'number' ) = 10   // '10'   is a number\n * toJavaScriptType('10',   'integer') = 10   // '10'   is also an integer\n * toJavaScriptType( 10,    'integer') = 10   //  10    is still an integer\n * toJavaScriptType( 10,    'string' ) = '10' //  10    can be made into a string\n * toJavaScriptType('10.5', 'number' ) = 10.5 // '10.5' is a number\n *\n * Invalid Examples:\n * toJavaScriptType('10.5', 'integer') = null // '10.5' is not an integer\n * toJavaScriptType( 10.5,  'integer') = null //  10.5  is still not an integer\n *\n * //  { PrimitiveValue } value - value to convert\n * //  { SchemaPrimitiveType | SchemaPrimitiveType[] } types - types to convert to\n * //  { boolean = false } strictIntegers - if FALSE, treat integers as numbers\n * // { PrimitiveValue }\n */\nexport function toJavaScriptType(value, types, strictIntegers = true)  {\n  if (!isDefined(value)) { return null; }\n  if (isString(types)) { types = [types]; }\n  if (strictIntegers && inArray('integer', types)) {\n    if (isInteger(value, 'strict')) { return value; }\n    if (isInteger(value)) { return parseInt(value, 10); }\n  }\n  if (inArray('number', types) || (!strictIntegers && inArray('integer', types))) {\n    if (isNumber(value, 'strict')) { return value; }\n    if (isNumber(value)) { return parseFloat(value); }\n  }\n  if (inArray('string', types)) {\n    if (isString(value)) { return value; }\n    // If value is a date, and types includes 'string',\n    // convert the date to a string\n    if (isDate(value)) { return toIsoString(value); }\n    if (isNumber(value)) { return value.toString(); }\n  }\n  // If value is a date, and types includes 'integer' or 'number',\n  // but not 'string', convert the date to a number\n  if (isDate(value) && (inArray('integer', types) || inArray('number', types))) {\n    return value.getTime();\n  }\n  if (inArray('boolean', types)) {\n    if (isBoolean(value, true)) { return true; }\n    if (isBoolean(value, false)) { return false; }\n  }\n  return null;\n}\n\n/**\n * 'toSchemaType' function\n *\n * Converts an input (probably string) value to the \"best\" JavaScript\n * equivalent available from an allowed list of JSON Schema types, which may\n * contain 'string', 'number', 'integer', 'boolean', and/or 'null'.\n * If necssary, it does progressively agressive type coersion.\n * It will not return null unless null is in the list of allowed types.\n *\n * Number conversion examples:\n * toSchemaType('10', ['number','integer','string']) = 10 // integer\n * toSchemaType('10', ['number','string']) = 10 // number\n * toSchemaType('10', ['string']) = '10' // string\n * toSchemaType('10.5', ['number','integer','string']) = 10.5 // number\n * toSchemaType('10.5', ['integer','string']) = '10.5' // string\n * toSchemaType('10.5', ['integer']) = 10 // integer\n * toSchemaType(10.5, ['null','boolean','string']) = '10.5' // string\n * toSchemaType(10.5, ['null','boolean']) = true // boolean\n *\n * String conversion examples:\n * toSchemaType('1.5x', ['boolean','number','integer','string']) = '1.5x' // string\n * toSchemaType('1.5x', ['boolean','number','integer']) = '1.5' // number\n * toSchemaType('1.5x', ['boolean','integer']) = '1' // integer\n * toSchemaType('1.5x', ['boolean']) = true // boolean\n * toSchemaType('xyz', ['number','integer','boolean','null']) = true // boolean\n * toSchemaType('xyz', ['number','integer','null']) = null // null\n * toSchemaType('xyz', ['number','integer']) = 0 // number\n *\n * Boolean conversion examples:\n * toSchemaType('1', ['integer','number','string','boolean']) = 1 // integer\n * toSchemaType('1', ['number','string','boolean']) = 1 // number\n * toSchemaType('1', ['string','boolean']) = '1' // string\n * toSchemaType('1', ['boolean']) = true // boolean\n * toSchemaType('true', ['number','string','boolean']) = 'true' // string\n * toSchemaType('true', ['boolean']) = true // boolean\n * toSchemaType('true', ['number']) = 0 // number\n * toSchemaType(true, ['number','string','boolean']) = true // boolean\n * toSchemaType(true, ['number','string']) = 'true' // string\n * toSchemaType(true, ['number']) = 1 // number\n *\n * //  { PrimitiveValue } value - value to convert\n * //  { SchemaPrimitiveType | SchemaPrimitiveType[] } types - allowed types to convert to\n * // { PrimitiveValue }\n */\nexport function toSchemaType(value, types) {\n  if (!isArray(<SchemaPrimitiveType>types)) {\n    types = <SchemaPrimitiveType[]>[types];\n  }\n  if ((<SchemaPrimitiveType[]>types).includes('null') && !hasValue(value)) {\n    return null;\n  }\n  if ((<SchemaPrimitiveType[]>types).includes('boolean') && !isBoolean(value, 'strict')) {\n    return value;\n  }\n  if ((<SchemaPrimitiveType[]>types).includes('integer')) {\n    const testValue = toJavaScriptType(value, 'integer');\n    if (testValue !== null) { return +testValue; }\n  }\n  if ((<SchemaPrimitiveType[]>types).includes('number')) {\n    const testValue = toJavaScriptType(value, 'number');\n    if (testValue !== null) { return +testValue; }\n  }\n  if (\n    (isString(value) || isNumber(value, 'strict')) &&\n    (<SchemaPrimitiveType[]>types).includes('string')\n  ) { // Convert number to string\n    return toJavaScriptType(value, 'string');\n  }\n  if ((<SchemaPrimitiveType[]>types).includes('boolean') && isBoolean(value)) {\n    return toJavaScriptType(value, 'boolean');\n  }\n  if ((<SchemaPrimitiveType[]>types).includes('string')) { // Convert null & boolean to string\n    if (value === null) { return ''; }\n    const testValue = toJavaScriptType(value, 'string');\n    if (testValue !== null) { return testValue; }\n  }\n  if ((\n    (<SchemaPrimitiveType[]>types).includes('number') ||\n    (<SchemaPrimitiveType[]>types).includes('integer'))\n  ) {\n    if (value === true) { return 1; } // Convert boolean & null to number\n    if (value === false || value === null || value === '') { return 0; }\n  }\n  if ((<SchemaPrimitiveType[]>types).includes('number')) { // Convert mixed string to number\n    const testValue = parseFloat(<string>value);\n    if (!!testValue) { return testValue; }\n  }\n  if ((<SchemaPrimitiveType[]>types).includes('integer')) { // Convert string or number to integer\n    const testValue = parseInt(<string>value, 10);\n    if (!!testValue) { return testValue; }\n  }\n  if ((<SchemaPrimitiveType[]>types).includes('boolean')) { // Convert anything to boolean\n    return !!value;\n  }\n  if ((\n      (<SchemaPrimitiveType[]>types).includes('number') ||\n      (<SchemaPrimitiveType[]>types).includes('integer')\n    ) && !(<SchemaPrimitiveType[]>types).includes('null')\n  ) {\n    return 0; // If null not allowed, return 0 for non-convertable values\n  }\n}\n\n/**\n * 'isPromise' function\n *\n * //   object\n * // { boolean }\n */\nexport function isPromise(object): object is Promise<any> {\n  return !!object && typeof object.then === 'function';\n}\n\n/**\n * 'isObservable' function\n *\n * //   object\n * // { boolean }\n */\nexport function isObservable(object): object is Observable<any> {\n  return !!object && typeof object.subscribe === 'function';\n}\n\n/**\n * '_toPromise' function\n *\n * //  { object } object\n * // { Promise<any> }\n */\nexport function _toPromise(object): Promise<any> {\n  return isPromise(object) ? object : object.toPromise();\n}\n\n/**\n * 'toObservable' function\n *\n * //  { object } object\n * // { Observable<any> }\n */\nexport function toObservable(object): Observable<any> {\n  const observable = isPromise(object) ? from(object) : object;\n  if (isObservable(observable)) { return observable; }\n  console.error('toObservable error: Expected validator to return Promise or Observable.');\n  return new Observable();\n}\n\n/**\n * 'inArray' function\n *\n * Searches an array for an item, or one of a list of items, and returns true\n * as soon as a match is found, or false if no match.\n *\n * If the optional third parameter allIn is set to TRUE, and the item to find\n * is an array, then the function returns true only if all elements from item\n * are found in the array list, and false if any element is not found. If the\n * item to find is not an array, setting allIn to TRUE has no effect.\n *\n * //  { any|any[] } item - the item to search for\n * //   array - the array to search\n * //  { boolean = false } allIn - if TRUE, all items must be in array\n * // { boolean } - true if item(s) in array, false otherwise\n */\nexport function inArray(item, array, allIn = false) {\n  if (!isDefined(item) || !isArray(array)) { return false; }\n  return isArray(item) ?\n    item[allIn ? 'every' : 'some'](subItem => array.includes(subItem)) :\n    array.includes(item);\n}\n\n/**\n * 'xor' utility function - exclusive or\n *\n * Returns true if exactly one of two values is truthy.\n *\n * //   value1 - first value to check\n * //   value2 - second value to check\n * // { boolean } - true if exactly one input value is truthy, false if not\n */\nexport function xor(value1, value2) {\n  return (!!value1 && !value2) || (!value1 && !!value2);\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/add-reference.component.ts",
    "content": "import {\n  ChangeDetectionStrategy,\n  Component,\n  Input,\n  OnInit\n  } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'add-reference-widget',\n  template: `\n    <button *ngIf=\"showAddButton\"\n      [class]=\"options?.fieldHtmlClass || ''\"\n      [disabled]=\"options?.readonly\"\n      (click)=\"addItem($event)\">\n      <span *ngIf=\"options?.icon\" [class]=\"options?.icon\"></span>\n      <span *ngIf=\"options?.title\" [innerHTML]=\"buttonText\"></span>\n    </button>`,\n    changeDetection: ChangeDetectionStrategy.Default,\n})\nexport class AddReferenceComponent implements OnInit {\n  options: any;\n  itemCount: number;\n  previousLayoutIndex: number[];\n  previousDataIndex: number[];\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n  }\n\n  get showAddButton(): boolean {\n    return !this.layoutNode.arrayItem ||\n      this.layoutIndex[this.layoutIndex.length - 1] < this.options.maxItems;\n  }\n\n  addItem(event) {\n    event.preventDefault();\n    this.jsf.addItem(this);\n  }\n\n  get buttonText(): string {\n    const parent: any = {\n      dataIndex: this.dataIndex.slice(0, -1),\n      layoutIndex: this.layoutIndex.slice(0, -1),\n      layoutNode: this.jsf.getParentNode(this)\n    };\n    return parent.layoutNode.add ||\n      this.jsf.setArrayItemTitle(parent, this.layoutNode, this.itemCount);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/button.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'button-widget',\n  template: `\n    <div\n      [class]=\"options?.htmlClass || ''\">\n      <button\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [disabled]=\"controlDisabled\"\n        [name]=\"controlName\"\n        [type]=\"layoutNode?.type\"\n        [value]=\"controlValue\"\n        (click)=\"updateValue($event)\">\n        <span *ngIf=\"options?.icon || options?.title\"\n          [class]=\"options?.icon\"\n          [innerHTML]=\"options?.title\"></span>\n      </button>\n    </div>`,\n})\nexport class ButtonComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    if (typeof this.options.onClick === 'function') {\n      this.options.onClick(event);\n    } else {\n      this.jsf.updateValue(this, event.target.value);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/checkbox.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'checkbox-widget',\n  template: `\n    <label\n      [attr.for]=\"'control' + layoutNode?._id\"\n      [class]=\"options?.itemLabelHtmlClass || ''\">\n      <input *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [class]=\"(options?.fieldHtmlClass || '') + (isChecked ?\n          (' ' + (options?.activeClass || '') + ' ' + (options?.style?.selected || '')) :\n          (' ' + (options?.style?.unselected || '')))\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        type=\"checkbox\">\n      <input *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [checked]=\"isChecked ? 'checked' : null\"\n        [class]=\"(options?.fieldHtmlClass || '') + (isChecked ?\n          (' ' + (options?.activeClass || '') + ' ' + (options?.style?.selected || '')) :\n          (' ' + (options?.style?.unselected || '')))\"\n        [disabled]=\"controlDisabled\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [value]=\"controlValue\"\n        type=\"checkbox\"\n        (change)=\"updateValue($event)\">\n      <span *ngIf=\"options?.title\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></span>\n    </label>`,\n})\nexport class CheckboxComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  trueValue: any = true;\n  falseValue: any = false;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n    if (this.controlValue === null || this.controlValue === undefined) {\n      this.controlValue = this.options.title;\n    }\n  }\n\n  updateValue(event) {\n    event.preventDefault();\n    this.jsf.updateValue(this, event.target.checked ? this.trueValue : this.falseValue);\n  }\n\n  get isChecked() {\n    return this.jsf.getFormControlValue(this) === this.trueValue;\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/checkboxes.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { buildTitleMap } from '../shared';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService, TitleMapItem } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'checkboxes-widget',\n  template: `\n    <label *ngIf=\"options?.title\"\n      [class]=\"options?.labelHtmlClass || ''\"\n      [style.display]=\"options?.notitle ? 'none' : ''\"\n      [innerHTML]=\"options?.title\"></label>\n\n    <!-- 'horizontal' = checkboxes-inline or checkboxbuttons -->\n    <div *ngIf=\"layoutOrientation === 'horizontal'\" [class]=\"options?.htmlClass || ''\">\n      <label *ngFor=\"let checkboxItem of checkboxList\"\n        [attr.for]=\"'control' + layoutNode?._id + '/' + checkboxItem.value\"\n        [class]=\"(options?.itemLabelHtmlClass || '') + (checkboxItem.checked ?\n          (' ' + (options?.activeClass || '') + ' ' + (options?.style?.selected || '')) :\n          (' ' + (options?.style?.unselected || '')))\">\n        <input type=\"checkbox\"\n          [attr.required]=\"options?.required\"\n          [checked]=\"checkboxItem.checked\"\n          [class]=\"options?.fieldHtmlClass || ''\"\n          [disabled]=\"controlDisabled\"\n          [id]=\"'control' + layoutNode?._id + '/' + checkboxItem.value\"\n          [name]=\"checkboxItem?.name\"\n          [readonly]=\"options?.readonly ? 'readonly' : null\"\n          [value]=\"checkboxItem.value\"\n          (change)=\"updateValue($event)\">\n        <span [innerHTML]=\"checkboxItem.name\"></span>\n      </label>\n    </div>\n\n    <!-- 'vertical' = regular checkboxes -->\n    <div *ngIf=\"layoutOrientation === 'vertical'\">\n      <div *ngFor=\"let checkboxItem of checkboxList\" [class]=\"options?.htmlClass || ''\">\n        <label\n          [attr.for]=\"'control' + layoutNode?._id + '/' + checkboxItem.value\"\n          [class]=\"(options?.itemLabelHtmlClass || '') + (checkboxItem.checked ?\n            (' ' + (options?.activeClass || '') + ' ' + (options?.style?.selected || '')) :\n            (' ' + (options?.style?.unselected || '')))\">\n          <input type=\"checkbox\"\n            [attr.required]=\"options?.required\"\n            [checked]=\"checkboxItem.checked\"\n            [class]=\"options?.fieldHtmlClass || ''\"\n            [disabled]=\"controlDisabled\"\n            [id]=\"options?.name + '/' + checkboxItem.value\"\n            [name]=\"checkboxItem?.name\"\n            [readonly]=\"options?.readonly ? 'readonly' : null\"\n            [value]=\"checkboxItem.value\"\n            (change)=\"updateValue($event)\">\n          <span [innerHTML]=\"checkboxItem?.name\"></span>\n        </label>\n      </div>\n    </div>`,\n})\nexport class CheckboxesComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  layoutOrientation: string;\n  formArray: AbstractControl;\n  checkboxList: TitleMapItem[] = [];\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.layoutOrientation = (this.layoutNode.type === 'checkboxes-inline' ||\n      this.layoutNode.type === 'checkboxbuttons') ? 'horizontal' : 'vertical';\n    this.jsf.initializeControl(this);\n    this.checkboxList = buildTitleMap(\n      this.options.titleMap || this.options.enumNames, this.options.enum, true\n    );\n    if (this.boundControl) {\n      const formArray = this.jsf.getFormControl(this);\n      this.checkboxList.forEach(checkboxItem =>\n        checkboxItem.checked = formArray.value.includes(checkboxItem.value)\n      );\n    }\n  }\n\n  updateValue(event) {\n    for (const checkboxItem of this.checkboxList) {\n      if (event.target.value === checkboxItem.value) {\n        checkboxItem.checked = event.target.checked;\n      }\n    }\n    if (this.boundControl) {\n      this.jsf.updateArrayCheckboxList(this, this.checkboxList);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/file.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n// TODO: Add this control\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'file-widget',\n  template: ``,\n})\nexport class FileComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/hidden.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'hidden-widget',\n  template: `\n    <input *ngIf=\"boundControl\"\n      [formControl]=\"formControl\"\n      [id]=\"'control' + layoutNode?._id\"\n      [name]=\"controlName\"\n      type=\"hidden\">\n    <input *ngIf=\"!boundControl\"\n      [disabled]=\"controlDisabled\"\n      [name]=\"controlName\"\n      [id]=\"'control' + layoutNode?._id\"\n      type=\"hidden\"\n      [value]=\"controlValue\">`,\n})\nexport class HiddenComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.jsf.initializeControl(this);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/index.ts",
    "content": "import { AddReferenceComponent } from './add-reference.component';\nimport { ButtonComponent } from './button.component';\nimport { CheckboxComponent } from './checkbox.component';\nimport { CheckboxesComponent } from './checkboxes.component';\nimport { FileComponent } from './file.component';\nimport { HiddenComponent } from './hidden.component';\nimport { InputComponent } from './input.component';\nimport { MessageComponent } from './message.component';\nimport { NoneComponent } from './none.component';\nimport { NumberComponent } from './number.component';\nimport { OneOfComponent } from './one-of.component';\nimport { RadiosComponent } from './radios.component';\nimport { RootComponent } from './root.component';\nimport { SectionComponent } from './section.component';\nimport { SelectComponent } from './select.component';\nimport { SelectFrameworkComponent } from './select-framework.component';\nimport { SelectWidgetComponent } from './select-widget.component';\nimport { SubmitComponent } from './submit.component';\nimport { TabComponent } from './tab.component';\nimport { TabsComponent } from './tabs.component';\nimport { TemplateComponent } from './template.component';\nimport { TextareaComponent } from './textarea.component';\n\nexport const BASIC_WIDGETS = [\n  AddReferenceComponent, OneOfComponent, ButtonComponent, CheckboxComponent,\n  CheckboxesComponent, FileComponent, HiddenComponent, InputComponent,\n  MessageComponent, NoneComponent, NumberComponent, RadiosComponent,\n  RootComponent, SectionComponent, SelectComponent, SelectFrameworkComponent,\n  SelectWidgetComponent, SubmitComponent, TabComponent, TabsComponent,\n  TemplateComponent, TextareaComponent\n];\n\nexport { AddReferenceComponent } from './add-reference.component';\nexport { OneOfComponent } from './one-of.component';\nexport { ButtonComponent } from './button.component';\nexport { CheckboxComponent } from './checkbox.component';\nexport { CheckboxesComponent } from './checkboxes.component';\nexport { FileComponent } from './file.component';\nexport { HiddenComponent } from './hidden.component';\nexport { InputComponent } from './input.component';\nexport { MessageComponent } from './message.component';\nexport { NoneComponent } from './none.component';\nexport { NumberComponent } from './number.component';\nexport { OrderableDirective } from './orderable.directive';\nexport { RadiosComponent } from './radios.component';\nexport { RootComponent } from './root.component';\nexport { SectionComponent } from './section.component';\nexport { SelectComponent } from './select.component';\nexport { SelectFrameworkComponent } from './select-framework.component';\nexport { SelectWidgetComponent } from './select-widget.component';\nexport { SubmitComponent } from './submit.component';\nexport { TabComponent } from './tab.component';\nexport { TabsComponent } from './tabs.component';\nexport { TemplateComponent } from './template.component';\nexport { TextareaComponent } from './textarea.component';\nexport { WidgetLibraryService } from './widget-library.service';\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/input.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'input-widget',\n  template: `\n    <div [class]=\"options?.htmlClass || ''\">\n      <label *ngIf=\"options?.title\"\n        [attr.for]=\"'control' + layoutNode?._id\"\n        [class]=\"options?.labelHtmlClass || ''\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></label>\n      <input *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.list]=\"'control' + layoutNode?._id + 'Autocomplete'\"\n        [attr.maxlength]=\"options?.maxLength\"\n        [attr.minlength]=\"options?.minLength\"\n        [attr.pattern]=\"options?.pattern\"\n        [attr.placeholder]=\"options?.placeholder\"\n        [attr.required]=\"options?.required\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [type]=\"layoutNode?.type\">\n      <input *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.list]=\"'control' + layoutNode?._id + 'Autocomplete'\"\n        [attr.maxlength]=\"options?.maxLength\"\n        [attr.minlength]=\"options?.minLength\"\n        [attr.pattern]=\"options?.pattern\"\n        [attr.placeholder]=\"options?.placeholder\"\n        [attr.required]=\"options?.required\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [disabled]=\"controlDisabled\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [type]=\"layoutNode?.type\"\n        [value]=\"controlValue\"\n        (input)=\"updateValue($event)\">\n        <datalist *ngIf=\"options?.typeahead?.source\"\n          [id]=\"'control' + layoutNode?._id + 'Autocomplete'\">\n          <option *ngFor=\"let word of options?.typeahead?.source\" [value]=\"word\">\n        </datalist>\n    </div>`,\n})\nexport class InputComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: string;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  autoCompleteList: string[] = [];\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/message.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'message-widget',\n  template: `\n    <span *ngIf=\"message\"\n      [class]=\"options?.labelHtmlClass || ''\"\n      [innerHTML]=\"message\"></span>`,\n})\nexport class MessageComponent implements OnInit {\n  options: any;\n  message: string = null;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.message = this.options.help || this.options.helpvalue ||\n      this.options.msg || this.options.message;\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/none.component.ts",
    "content": "import { Component, Input } from '@angular/core';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'none-widget',\n  template: ``,\n})\nexport class NoneComponent {\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/number.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { AbstractControl } from '@angular/forms';\n\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'number-widget',\n  template: `\n    <div [class]=\"options?.htmlClass || ''\">\n      <label *ngIf=\"options?.title\"\n        [attr.for]=\"'control' + layoutNode?._id\"\n        [class]=\"options?.labelHtmlClass || ''\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></label>\n      <input *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.max]=\"options?.maximum\"\n        [attr.min]=\"options?.minimum\"\n        [attr.placeholder]=\"options?.placeholder\"\n        [attr.required]=\"options?.required\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.step]=\"options?.multipleOf || options?.step || 'any'\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [title]=\"lastValidNumber\"\n        [type]=\"layoutNode?.type === 'range' ? 'range' : 'number'\">\n      <input *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.max]=\"options?.maximum\"\n        [attr.min]=\"options?.minimum\"\n        [attr.placeholder]=\"options?.placeholder\"\n        [attr.required]=\"options?.required\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.step]=\"options?.multipleOf || options?.step || 'any'\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [disabled]=\"controlDisabled\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [title]=\"lastValidNumber\"\n        [type]=\"layoutNode?.type === 'range' ? 'range' : 'number'\"\n        [value]=\"controlValue\"\n        (input)=\"updateValue($event)\">\n      <span *ngIf=\"layoutNode?.type === 'range'\" [innerHTML]=\"controlValue\"></span>\n    </div>`,\n})\nexport class NumberComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  allowNegative = true;\n  allowDecimal = true;\n  allowExponents = false;\n  lastValidNumber = '';\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n    if (this.layoutNode.dataType === 'integer') { this.allowDecimal = false; }\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/one-of.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { AbstractControl } from '@angular/forms';\n\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n// TODO: Add this control\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'one-of-widget',\n  template: ``,\n})\nexport class OneOfComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/orderable.directive.ts",
    "content": "import {\n  Directive,\n  ElementRef,\n  Input,\n  NgZone,\n  OnInit\n  } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n/**\n * OrderableDirective\n *\n * Enables array elements to be reordered by dragging and dropping.\n *\n * Only works for arrays that have at least two elements.\n *\n * Also detects arrays-within-arrays, and correctly moves either\n * the child array element or the parent array element,\n * depending on the drop targert.\n *\n * Listeners for movable element being dragged:\n * - dragstart: add 'dragging' class to element, set effectAllowed = 'move'\n * - dragover: set dropEffect = 'move'\n * - dragend: remove 'dragging' class from element\n *\n * Listeners for stationary items being dragged over:\n * - dragenter: add 'drag-target-...' classes to element\n * - dragleave: remove 'drag-target-...' classes from element\n * - drop: remove 'drag-target-...' classes from element, move dropped array item\n */\n@Directive({\n  // tslint:disable-next-line:directive-selector\n  selector: '[orderable]',\n})\nexport class OrderableDirective implements OnInit {\n  arrayLayoutIndex: string;\n  element: any;\n  overParentElement = false;\n  overChildElement = false;\n  @Input() orderable: boolean;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private elementRef: ElementRef,\n    private jsf: JsonSchemaFormService,\n    private ngZone: NgZone\n  ) { }\n\n  ngOnInit() {\n    if (this.orderable && this.layoutNode && this.layoutIndex && this.dataIndex) {\n      this.element = this.elementRef.nativeElement;\n      this.element.draggable = true;\n      this.arrayLayoutIndex = 'move:' + this.layoutIndex.slice(0, -1).toString();\n\n      this.ngZone.runOutsideAngular(() => {\n\n        // Listeners for movable element being dragged:\n\n        this.element.addEventListener('dragstart', (event) => {\n          event.dataTransfer.effectAllowed = 'move';\n          event.dataTransfer.setData('text', '');\n          // Hack to bypass stupid HTML drag-and-drop dataTransfer protection\n          // so drag source info will be available on dragenter\n          const sourceArrayIndex = this.dataIndex[this.dataIndex.length - 1];\n          sessionStorage.setItem(this.arrayLayoutIndex, sourceArrayIndex + '');\n        });\n\n        this.element.addEventListener('dragover', (event) => {\n          if (event.preventDefault) { event.preventDefault(); }\n          event.dataTransfer.dropEffect = 'move';\n          return false;\n        });\n\n        // Listeners for stationary items being dragged over:\n\n        this.element.addEventListener('dragenter', (event) => {\n          // Part 1 of a hack, inspired by Dragster, to simulate mouseover and mouseout\n          // behavior while dragging items - http://bensmithett.github.io/dragster/\n          if (this.overParentElement) {\n            return this.overChildElement = true;\n          } else {\n            this.overParentElement = true;\n          }\n\n          const sourceArrayIndex = sessionStorage.getItem(this.arrayLayoutIndex);\n          if (sourceArrayIndex !== null) {\n            if (this.dataIndex[this.dataIndex.length - 1] < +sourceArrayIndex) {\n              this.element.classList.add('drag-target-top');\n            } else if (this.dataIndex[this.dataIndex.length - 1] > +sourceArrayIndex) {\n              this.element.classList.add('drag-target-bottom');\n            }\n          }\n        });\n\n        this.element.addEventListener('dragleave', (event) => {\n          // Part 2 of the Dragster hack\n          if (this.overChildElement) {\n            this.overChildElement = false;\n          } else if (this.overParentElement) {\n            this.overParentElement = false;\n          }\n\n          const sourceArrayIndex = sessionStorage.getItem(this.arrayLayoutIndex);\n          if (!this.overParentElement && !this.overChildElement && sourceArrayIndex !== null) {\n            this.element.classList.remove('drag-target-top');\n            this.element.classList.remove('drag-target-bottom');\n          }\n        });\n\n        this.element.addEventListener('drop', (event) => {\n          this.element.classList.remove('drag-target-top');\n          this.element.classList.remove('drag-target-bottom');\n          // Confirm that drop target is another item in the same array as source item\n          const sourceArrayIndex = sessionStorage.getItem(this.arrayLayoutIndex);\n          const destArrayIndex = this.dataIndex[this.dataIndex.length - 1];\n          if (sourceArrayIndex !== null && +sourceArrayIndex !== destArrayIndex) {\n            // Move array item\n            this.jsf.moveArrayItem(this, +sourceArrayIndex, destArrayIndex);\n          }\n          sessionStorage.removeItem(this.arrayLayoutIndex);\n          return false;\n        });\n\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/radios.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { buildTitleMap } from '../shared';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'radios-widget',\n  template: `\n    <label *ngIf=\"options?.title\"\n      [attr.for]=\"'control' + layoutNode?._id\"\n      [class]=\"options?.labelHtmlClass || ''\"\n      [style.display]=\"options?.notitle ? 'none' : ''\"\n      [innerHTML]=\"options?.title\"></label>\n\n    <!-- 'horizontal' = radios-inline or radiobuttons -->\n    <div *ngIf=\"layoutOrientation === 'horizontal'\"\n      [class]=\"options?.htmlClass || ''\">\n      <label *ngFor=\"let radioItem of radiosList\"\n        [attr.for]=\"'control' + layoutNode?._id + '/' + radioItem?.value\"\n        [class]=\"(options?.itemLabelHtmlClass || '') +\n          ((controlValue + '' === radioItem?.value + '') ?\n          (' ' + (options?.activeClass || '') + ' ' + (options?.style?.selected || '')) :\n          (' ' + (options?.style?.unselected || '')))\">\n        <input type=\"radio\"\n          [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n          [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n          [attr.required]=\"options?.required\"\n          [checked]=\"radioItem?.value === controlValue\"\n          [class]=\"options?.fieldHtmlClass || ''\"\n          [disabled]=\"controlDisabled\"\n          [id]=\"'control' + layoutNode?._id + '/' + radioItem?.value\"\n          [name]=\"controlName\"\n          [value]=\"radioItem?.value\"\n          (change)=\"updateValue($event)\">\n        <span [innerHTML]=\"radioItem?.name\"></span>\n      </label>\n    </div>\n\n    <!-- 'vertical' = regular radios -->\n    <div *ngIf=\"layoutOrientation !== 'horizontal'\">\n      <div *ngFor=\"let radioItem of radiosList\"\n        [class]=\"options?.htmlClass || ''\">\n        <label\n          [attr.for]=\"'control' + layoutNode?._id + '/' + radioItem?.value\"\n          [class]=\"(options?.itemLabelHtmlClass || '') +\n            ((controlValue + '' === radioItem?.value + '') ?\n            (' ' + (options?.activeClass || '') + ' ' + (options?.style?.selected || '')) :\n            (' ' + (options?.style?.unselected || '')))\">\n          <input type=\"radio\"\n            [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n            [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n            [attr.required]=\"options?.required\"\n            [checked]=\"radioItem?.value === controlValue\"\n            [class]=\"options?.fieldHtmlClass || ''\"\n            [disabled]=\"controlDisabled\"\n            [id]=\"'control' + layoutNode?._id + '/' + radioItem?.value\"\n            [name]=\"controlName\"\n            [value]=\"radioItem?.value\"\n            (change)=\"updateValue($event)\">\n          <span [innerHTML]=\"radioItem?.name\"></span>\n        </label>\n      </div>\n    </div>`,\n})\nexport class RadiosComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  layoutOrientation = 'vertical';\n  radiosList: any[] = [];\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    if (this.layoutNode.type === 'radios-inline' ||\n      this.layoutNode.type === 'radiobuttons'\n    ) {\n      this.layoutOrientation = 'horizontal';\n    }\n    this.radiosList = buildTitleMap(\n      this.options.titleMap || this.options.enumNames,\n      this.options.enum, true\n    );\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/root.component.ts",
    "content": "import { Component, Input } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'root-widget',\n  template: `\n    <div *ngFor=\"let layoutItem of layout; let i = index\"\n      [class.form-flex-item]=\"isFlexItem\"\n      [style.align-self]=\"(layoutItem.options || {})['align-self']\"\n      [style.flex-basis]=\"getFlexAttribute(layoutItem, 'flex-basis')\"\n      [style.flex-grow]=\"getFlexAttribute(layoutItem, 'flex-grow')\"\n      [style.flex-shrink]=\"getFlexAttribute(layoutItem, 'flex-shrink')\"\n      [style.order]=\"(layoutItem.options || {}).order\">\n      <div\n        [dataIndex]=\"layoutItem?.arrayItem ? (dataIndex || []).concat(i) : (dataIndex || [])\"\n        [layoutIndex]=\"(layoutIndex || []).concat(i)\"\n        [layoutNode]=\"layoutItem\"\n        [orderable]=\"isDraggable(layoutItem)\">\n        <select-framework-widget *ngIf=\"showWidget(layoutItem)\"\n          [dataIndex]=\"layoutItem?.arrayItem ? (dataIndex || []).concat(i) : (dataIndex || [])\"\n          [layoutIndex]=\"(layoutIndex || []).concat(i)\"\n          [layoutNode]=\"layoutItem\"></select-framework-widget>\n      </div>\n    </div>`,\n  styles: [`\n    [draggable=true] {\n      transition: all 150ms cubic-bezier(.4, 0, .2, 1);\n    }\n    [draggable=true]:hover {\n      cursor: move;\n      box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);\n      position: relative; z-index: 10;\n      margin-top: -1px;\n      margin-left: -1px;\n      margin-right: 1px;\n      margin-bottom: 1px;\n    }\n    [draggable=true].drag-target-top {\n      box-shadow: 0 -2px 0 #000;\n      position: relative; z-index: 20;\n    }\n    [draggable=true].drag-target-bottom {\n      box-shadow: 0 2px 0 #000;\n      position: relative; z-index: 20;\n    }\n  `],\n})\nexport class RootComponent {\n  options: any;\n  @Input() dataIndex: number[];\n  @Input() layoutIndex: number[];\n  @Input() layout: any[];\n  @Input() isOrderable: boolean;\n  @Input() isFlexItem = false;\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  isDraggable(node: any): boolean {\n    return node.arrayItem && node.type !== '$ref' &&\n      node.arrayItemType === 'list' && this.isOrderable !== false;\n  }\n\n  // Set attributes for flexbox child\n  // (container attributes are set in section.component)\n  getFlexAttribute(node: any, attribute: string) {\n    const index = ['flex-grow', 'flex-shrink', 'flex-basis'].indexOf(attribute);\n    return ((node.options || {}).flex || '').split(/\\s+/)[index] ||\n      (node.options || {})[attribute] || ['1', '1', 'auto'][index];\n  }\n\n  showWidget(layoutNode: any): boolean {\n    return this.jsf.evaluateCondition(layoutNode, this.dataIndex);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/section.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'section-widget',\n  template: `\n    <div *ngIf=\"containerType === 'div'\"\n      [class]=\"options?.htmlClass || ''\"\n      [class.expandable]=\"options?.expandable && !expanded\"\n      [class.expanded]=\"options?.expandable && expanded\">\n      <label *ngIf=\"sectionTitle\"\n        class=\"legend\"\n        [class]=\"options?.labelHtmlClass || ''\"\n        [innerHTML]=\"sectionTitle\"\n        (click)=\"toggleExpanded()\"></label>\n      <root-widget *ngIf=\"expanded\"\n        [dataIndex]=\"dataIndex\"\n        [layout]=\"layoutNode.items\"\n        [layoutIndex]=\"layoutIndex\"\n        [isFlexItem]=\"getFlexAttribute('is-flex')\"\n        [isOrderable]=\"options?.orderable\"\n        [class.form-flex-column]=\"getFlexAttribute('flex-direction') === 'column'\"\n        [class.form-flex-row]=\"getFlexAttribute('flex-direction') === 'row'\"\n        [style.align-content]=\"getFlexAttribute('align-content')\"\n        [style.align-items]=\"getFlexAttribute('align-items')\"\n        [style.display]=\"getFlexAttribute('display')\"\n        [style.flex-direction]=\"getFlexAttribute('flex-direction')\"\n        [style.flex-wrap]=\"getFlexAttribute('flex-wrap')\"\n        [style.justify-content]=\"getFlexAttribute('justify-content')\"></root-widget>\n    </div>\n    <fieldset *ngIf=\"containerType === 'fieldset'\"\n      [class]=\"options?.htmlClass || ''\"\n      [class.expandable]=\"options?.expandable && !expanded\"\n      [class.expanded]=\"options?.expandable && expanded\"\n      [disabled]=\"options?.readonly\">\n      <legend *ngIf=\"sectionTitle\"\n        class=\"legend\"\n        [class]=\"options?.labelHtmlClass || ''\"\n        [innerHTML]=\"sectionTitle\"\n        (click)=\"toggleExpanded()\"></legend>\n      <div *ngIf=\"options?.messageLocation !== 'bottom'\">\n        <p *ngIf=\"options?.description\"\n        class=\"help-block\"\n        [class]=\"options?.labelHelpBlockClass || ''\"\n        [innerHTML]=\"options?.description\"></p>\n      </div>\n      <root-widget *ngIf=\"expanded\"\n        [dataIndex]=\"dataIndex\"\n        [layout]=\"layoutNode.items\"\n        [layoutIndex]=\"layoutIndex\"\n        [isFlexItem]=\"getFlexAttribute('is-flex')\"\n        [isOrderable]=\"options?.orderable\"\n        [class.form-flex-column]=\"getFlexAttribute('flex-direction') === 'column'\"\n        [class.form-flex-row]=\"getFlexAttribute('flex-direction') === 'row'\"\n        [style.align-content]=\"getFlexAttribute('align-content')\"\n        [style.align-items]=\"getFlexAttribute('align-items')\"\n        [style.display]=\"getFlexAttribute('display')\"\n        [style.flex-direction]=\"getFlexAttribute('flex-direction')\"\n        [style.flex-wrap]=\"getFlexAttribute('flex-wrap')\"\n        [style.justify-content]=\"getFlexAttribute('justify-content')\"></root-widget>\n      <div *ngIf=\"options?.messageLocation === 'bottom'\">\n        <p *ngIf=\"options?.description\"\n        class=\"help-block\"\n        [class]=\"options?.labelHelpBlockClass || ''\"\n        [innerHTML]=\"options?.description\"></p>\n      </div>\n    </fieldset>`,\n  styles: [`\n    .legend { font-weight: bold; }\n    .expandable > legend:before, .expandable > label:before  { content: '▶'; padding-right: .3em; }\n    .expanded > legend:before, .expanded > label:before  { content: '▼'; padding-right: .2em; }\n  `],\n})\nexport class SectionComponent implements OnInit {\n  options: any;\n  expanded = true;\n  containerType: string;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  get sectionTitle() {\n    return this.options.notitle ? null : this.jsf.setItemTitle(this);\n  }\n\n  ngOnInit() {\n    this.jsf.initializeControl(this);\n    this.options = this.layoutNode.options || {};\n    this.expanded = typeof this.options.expanded === 'boolean' ?\n      this.options.expanded : !this.options.expandable;\n    switch (this.layoutNode.type) {\n      case 'fieldset': case 'array': case 'tab': case 'advancedfieldset':\n      case 'authfieldset': case 'optionfieldset': case 'selectfieldset':\n        this.containerType = 'fieldset';\n      break;\n      default: // 'div', 'flex', 'section', 'conditional', 'actions', 'tagsinput'\n        this.containerType = 'div';\n      break;\n    }\n  }\n\n  toggleExpanded() {\n    if (this.options.expandable) { this.expanded = !this.expanded; }\n  }\n\n  // Set attributes for flexbox container\n  // (child attributes are set in root.component)\n  getFlexAttribute(attribute: string) {\n    const flexActive: boolean =\n      this.layoutNode.type === 'flex' ||\n      !!this.options.displayFlex ||\n      this.options.display === 'flex';\n    if (attribute !== 'flex' && !flexActive) { return null; }\n    switch (attribute) {\n      case 'is-flex':\n        return flexActive;\n      case 'display':\n        return flexActive ? 'flex' : 'initial';\n      case 'flex-direction': case 'flex-wrap':\n        const index = ['flex-direction', 'flex-wrap'].indexOf(attribute);\n        return (this.options['flex-flow'] || '').split(/\\s+/)[index] ||\n          this.options[attribute] || ['column', 'nowrap'][index];\n      case 'justify-content': case 'align-items': case 'align-content':\n        return this.options[attribute];\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/select-framework.component.ts",
    "content": "import {\n  Component, ComponentFactoryResolver, ComponentRef, Input,\n  OnChanges, OnInit, ViewChild, ViewContainerRef\n} from '@angular/core';\n\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'select-framework-widget',\n  template: `<div #widgetContainer></div>`,\n})\nexport class SelectFrameworkComponent implements OnChanges, OnInit {\n  newComponent: ComponentRef<any> = null;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n  @ViewChild('widgetContainer', {\n      read: ViewContainerRef,\n      static: true })\n    widgetContainer: ViewContainerRef;\n\n  constructor(\n    private componentFactory: ComponentFactoryResolver,\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.updateComponent();\n  }\n\n  ngOnChanges() {\n    this.updateComponent();\n  }\n\n  updateComponent() {\n    if (this.widgetContainer && !this.newComponent && this.jsf.framework) {\n      this.newComponent = this.widgetContainer.createComponent(\n        this.componentFactory.resolveComponentFactory(this.jsf.framework)\n      );\n    }\n    if (this.newComponent) {\n      for (const input of ['layoutNode', 'layoutIndex', 'dataIndex']) {\n        this.newComponent.instance[input] = this[input];\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/select-widget.component.ts",
    "content": "import {\n  Component, ComponentFactoryResolver, ComponentRef, Input,\n  OnChanges, OnInit, ViewChild, ViewContainerRef\n} from '@angular/core';\n\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'select-widget-widget',\n  template: `<div #widgetContainer></div>`,\n})\nexport class SelectWidgetComponent implements OnChanges, OnInit {\n  newComponent: ComponentRef<any> = null;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n  @ViewChild('widgetContainer', { read: ViewContainerRef, static: true })\n    widgetContainer: ViewContainerRef;\n\n  constructor(\n    private componentFactory: ComponentFactoryResolver,\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.updateComponent();\n  }\n\n  ngOnChanges() {\n    this.updateComponent();\n  }\n\n  updateComponent() {\n    if (this.widgetContainer && !this.newComponent && (this.layoutNode || {}).widget) {\n      this.newComponent = this.widgetContainer.createComponent(\n        this.componentFactory.resolveComponentFactory(this.layoutNode.widget)\n      );\n    }\n    if (this.newComponent) {\n      for (const input of ['layoutNode', 'layoutIndex', 'dataIndex']) {\n        this.newComponent.instance[input] = this[input];\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/select.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { buildTitleMap, isArray } from '../shared';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'select-widget',\n  template: `\n    <div\n      [class]=\"options?.htmlClass || ''\">\n      <label *ngIf=\"options?.title\"\n        [attr.for]=\"'control' + layoutNode?._id\"\n        [class]=\"options?.labelHtmlClass || ''\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></label>\n      <select *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.required]=\"options?.required\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\">\n        <ng-template ngFor let-selectItem [ngForOf]=\"selectList\">\n          <option *ngIf=\"!isArray(selectItem?.items)\"\n            [value]=\"selectItem?.value\">\n            <span [innerHTML]=\"selectItem?.name\"></span>\n          </option>\n          <optgroup *ngIf=\"isArray(selectItem?.items)\"\n            [label]=\"selectItem?.group\">\n            <option *ngFor=\"let subItem of selectItem.items\"\n              [value]=\"subItem?.value\">\n              <span [innerHTML]=\"subItem?.name\"></span>\n            </option>\n          </optgroup>\n        </ng-template>\n      </select>\n      <select *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.required]=\"options?.required\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [disabled]=\"controlDisabled\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        (change)=\"updateValue($event)\">\n        <ng-template ngFor let-selectItem [ngForOf]=\"selectList\">\n          <option *ngIf=\"!isArray(selectItem?.items)\"\n            [selected]=\"selectItem?.value === controlValue\"\n            [value]=\"selectItem?.value\">\n            <span [innerHTML]=\"selectItem?.name\"></span>\n          </option>\n          <optgroup *ngIf=\"isArray(selectItem?.items)\"\n            [label]=\"selectItem?.group\">\n            <option *ngFor=\"let subItem of selectItem.items\"\n              [attr.selected]=\"subItem?.value === controlValue\"\n              [value]=\"subItem?.value\">\n              <span [innerHTML]=\"subItem?.name\"></span>\n            </option>\n          </optgroup>\n        </ng-template>\n      </select>\n    </div>`,\n})\nexport class SelectComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  selectList: any[] = [];\n  isArray = isArray;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.selectList = buildTitleMap(\n      this.options.titleMap || this.options.enumNames,\n      this.options.enum, !!this.options.required, !!this.options.flatList\n    );\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/submit.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { hasOwn } from '../shared/utility.functions';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'submit-widget',\n  template: `\n    <div\n      [class]=\"options?.htmlClass || ''\">\n      <input\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.required]=\"options?.required\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [disabled]=\"controlDisabled\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [type]=\"layoutNode?.type\"\n        [value]=\"controlValue\"\n        (click)=\"updateValue($event)\">\n    </div>`,\n})\nexport class SubmitComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n    if (hasOwn(this.options, 'disabled')) {\n      this.controlDisabled = this.options.disabled;\n    } else if (this.jsf.formOptions.disableInvalidSubmit) {\n      this.controlDisabled = !this.jsf.isValid;\n      this.jsf.isValidChanges.subscribe(isValid => this.controlDisabled = !isValid);\n    }\n    if (this.controlValue === null || this.controlValue === undefined) {\n      this.controlValue = this.options.title;\n    }\n  }\n\n  updateValue(event) {\n    if (typeof this.options.onClick === 'function') {\n      this.options.onClick(event);\n    } else {\n      this.jsf.updateValue(this, event.target.value);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/tab.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'tab-widget',\n  template: `\n    <div [class]=\"options?.htmlClass || ''\">\n      <root-widget\n        [dataIndex]=\"dataIndex\"\n        [layoutIndex]=\"layoutIndex\"\n        [layout]=\"layoutNode.items\"></root-widget>\n    </div>`,\n})\nexport class TabComponent implements OnInit {\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/tabs.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'tabs-widget',\n  template: `\n    <ul\n      [class]=\"options?.labelHtmlClass || ''\">\n      <li *ngFor=\"let item of layoutNode?.items; let i = index\"\n        [class]=\"(options?.itemLabelHtmlClass || '') + (selectedItem === i ?\n          (' ' + (options?.activeClass || '') + ' ' + (options?.style?.selected || '')) :\n          (' ' + options?.style?.unselected))\"\n        role=\"presentation\"\n        data-tabs>\n        <a *ngIf=\"showAddTab || item.type !== '$ref'\"\n           [class]=\"'nav-link' + (selectedItem === i ? (' ' + options?.activeClass + ' ' + options?.style?.selected) :\n            (' ' + options?.style?.unselected))\"\n          [innerHTML]=\"setTabTitle(item, i)\"\n          (click)=\"select(i)\"></a>\n      </li>\n    </ul>\n\n    <div *ngFor=\"let layoutItem of layoutNode?.items; let i = index\"\n      [class]=\"options?.htmlClass || ''\">\n\n      <select-framework-widget *ngIf=\"selectedItem === i\"\n        [class]=\"(options?.fieldHtmlClass || '') +\n          ' ' + (options?.activeClass || '') +\n          ' ' + (options?.style?.selected || '')\"\n        [dataIndex]=\"layoutNode?.dataType === 'array' ? (dataIndex || []).concat(i) : dataIndex\"\n        [layoutIndex]=\"(layoutIndex || []).concat(i)\"\n        [layoutNode]=\"layoutItem\"></select-framework-widget>\n\n    </div>`,\n  styles: [` a { cursor: pointer; } `],\n})\nexport class TabsComponent implements OnInit {\n  options: any;\n  itemCount: number;\n  selectedItem = 0;\n  showAddTab = true;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.itemCount = this.layoutNode.items.length - 1;\n    this.updateControl();\n  }\n\n  select(index) {\n    if (this.layoutNode.items[index].type === '$ref') {\n      this.itemCount = this.layoutNode.items.length;\n      this.jsf.addItem({\n        layoutNode: this.layoutNode.items[index],\n        layoutIndex: this.layoutIndex.concat(index),\n        dataIndex: this.dataIndex.concat(index)\n      });\n      this.updateControl();\n    }\n    this.selectedItem = index;\n  }\n\n  updateControl() {\n    const lastItem = this.layoutNode.items[this.layoutNode.items.length - 1];\n    if (lastItem.type === '$ref' &&\n      this.itemCount >= (lastItem.options.maxItems || 1000)\n    ) {\n      this.showAddTab = false;\n    }\n  }\n\n  setTabTitle(item: any, index: number): string {\n    return this.jsf.setArrayItemTitle(this, item, index);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/template.component.ts",
    "content": "import {\n  Component,\n  ComponentFactoryResolver,\n  ComponentRef,\n  Input,\n  OnChanges,\n  OnInit,\n  ViewChild,\n  ViewContainerRef\n  } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'template-widget',\n  template: `<div #widgetContainer></div>`,\n})\nexport class TemplateComponent implements OnInit, OnChanges {\n  newComponent: ComponentRef<any> = null;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n  @ViewChild('widgetContainer', { read: ViewContainerRef , static: true})\n    widgetContainer: ViewContainerRef;\n\n  constructor(\n    private componentFactory: ComponentFactoryResolver,\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.updateComponent();\n  }\n\n  ngOnChanges() {\n    this.updateComponent();\n  }\n\n  updateComponent() {\n    if (this.widgetContainer && !this.newComponent && this.layoutNode.options.template) {\n      this.newComponent = this.widgetContainer.createComponent(\n        this.componentFactory.resolveComponentFactory(this.layoutNode.options.template)\n      );\n    }\n    if (this.newComponent) {\n      for (const input of ['layoutNode', 'layoutIndex', 'dataIndex']) {\n        this.newComponent.instance[input] = this[input];\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/textarea.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '../json-schema-form.service';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'textarea-widget',\n  template: `\n    <div\n      [class]=\"options?.htmlClass || ''\">\n      <label *ngIf=\"options?.title\"\n        [attr.for]=\"'control' + layoutNode?._id\"\n        [class]=\"options?.labelHtmlClass || ''\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></label>\n      <textarea *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.maxlength]=\"options?.maxLength\"\n        [attr.minlength]=\"options?.minLength\"\n        [attr.pattern]=\"options?.pattern\"\n        [attr.placeholder]=\"options?.placeholder\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.required]=\"options?.required\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"></textarea>\n      <textarea *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.maxlength]=\"options?.maxLength\"\n        [attr.minlength]=\"options?.minLength\"\n        [attr.pattern]=\"options?.pattern\"\n        [attr.placeholder]=\"options?.placeholder\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.required]=\"options?.required\"\n        [class]=\"options?.fieldHtmlClass || ''\"\n        [disabled]=\"controlDisabled\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [value]=\"controlValue\"\n        (input)=\"updateValue($event)\">{{controlValue}}</textarea>\n    </div>`,\n})\nexport class TextareaComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/widget-library.module.ts",
    "content": "import { BASIC_WIDGETS } from './index';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { NgModule } from '@angular/core';\nimport { OrderableDirective } from './orderable.directive';\n\n@NgModule({\n    imports: [CommonModule, FormsModule, ReactiveFormsModule],\n    declarations: [...BASIC_WIDGETS, OrderableDirective],\n    exports: [...BASIC_WIDGETS, OrderableDirective]\n})\nexport class WidgetLibraryModule {\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/lib/widget-library/widget-library.service.ts",
    "content": "import { AddReferenceComponent } from './add-reference.component';\nimport { ButtonComponent } from './button.component';\nimport { CheckboxComponent } from './checkbox.component';\nimport { CheckboxesComponent } from './checkboxes.component';\nimport { FileComponent } from './file.component';\nimport { hasOwn } from '../shared/utility.functions';\nimport { Injectable } from '@angular/core';\nimport { InputComponent } from './input.component';\nimport { MessageComponent } from './message.component';\nimport { NoneComponent } from './none.component';\nimport { NumberComponent } from './number.component';\nimport { OneOfComponent } from './one-of.component';\nimport { RadiosComponent } from './radios.component';\nimport { RootComponent } from './root.component';\nimport { SectionComponent } from './section.component';\nimport { SelectComponent } from './select.component';\nimport { SelectFrameworkComponent } from './select-framework.component';\nimport { SelectWidgetComponent } from './select-widget.component';\nimport { SubmitComponent } from './submit.component';\nimport { TabsComponent } from './tabs.component';\nimport { TemplateComponent } from './template.component';\nimport { TextareaComponent } from './textarea.component';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class WidgetLibraryService {\n\n  defaultWidget = 'text';\n  widgetLibrary: any = {\n\n  // Angular JSON Schema Form administrative widgets\n    'none': NoneComponent, // Placeholder, for development - displays nothing\n    'root': RootComponent, // Form root, renders a complete layout\n    'select-framework': SelectFrameworkComponent, // Applies the selected framework to a specified widget\n    'select-widget': SelectWidgetComponent, // Displays a specified widget\n    '$ref': AddReferenceComponent, // Button to add a new array item or $ref element\n\n  // Free-form text HTML 'input' form control widgets <input type=\"...\">\n    'email': 'text',\n    'integer': 'number', // Note: 'integer' is not a recognized HTML input type\n    'number': NumberComponent,\n    'password': 'text',\n    'search': 'text',\n    'tel': 'text',\n    'text': InputComponent,\n    'url': 'text',\n\n  // Controlled text HTML 'input' form control widgets <input type=\"...\">\n    'color': 'text',\n    'date': 'text',\n    'datetime': 'text',\n    'datetime-local': 'text',\n    'month': 'text',\n    'range': 'number',\n    'time': 'text',\n    'week': 'text',\n\n  // Non-text HTML 'input' form control widgets <input type=\"...\">\n    // 'button': <input type=\"button\"> not used, use <button> instead\n    'checkbox': CheckboxComponent, // TODO: Set ternary = true for 3-state ??\n    'file': FileComponent, // TODO: Finish 'file' widget\n    'hidden': 'text',\n    'image': 'text', // TODO: Figure out how to handle these\n    'radio': 'radios',\n    'reset': 'submit', // TODO: Figure out how to handle these\n    'submit': SubmitComponent,\n\n  // Other (non-'input') HTML form control widgets\n    'button': ButtonComponent,\n    'select': SelectComponent,\n    // 'option': automatically generated by select widgets\n    // 'optgroup': automatically generated by select widgets\n    'textarea': TextareaComponent,\n\n  // HTML form control widget sets\n    'checkboxes': CheckboxesComponent, // Grouped list of checkboxes\n    'checkboxes-inline': 'checkboxes', // Checkboxes in one line\n    'checkboxbuttons': 'checkboxes', // Checkboxes as html buttons\n    'radios': RadiosComponent, // Grouped list of radio buttons\n    'radios-inline': 'radios', // Radio controls in one line\n    'radiobuttons': 'radios', // Radio controls as html buttons\n\n  // HTML Layout widgets\n    // 'label': automatically added to data widgets\n    // 'legend': automatically added to fieldsets\n    'section': SectionComponent, // Just a div <div>\n    'div': 'section', // Still just a div <div>\n    'fieldset': 'section', // A fieldset, with an optional legend <fieldset>\n    'flex': 'section', // A flexbox container <div style=\"display: flex\">\n\n  // Non-HTML layout widgets\n    'one-of': OneOfComponent, // A select box that changes another input\n                              // TODO: Finish 'one-of' widget\n    'array': 'section', // A list you can add, remove and reorder <fieldset>\n    'tabarray': 'tabs', // A tabbed version of array\n    'tab': 'section', // A tab group, similar to a fieldset or section <fieldset>\n    'tabs': TabsComponent, // A tabbed set of panels with different controls\n    'message': MessageComponent, // Insert arbitrary html\n    'help': 'message', // Insert arbitrary html\n    'msg': 'message', // Insert arbitrary html\n    'html': 'message', // Insert arbitrary html\n    'template': TemplateComponent, // Insert a custom Angular component\n\n  // Widgets included for compatibility with JSON Form API\n    'advancedfieldset': 'section', // Adds 'Advanced settings' title <fieldset>\n    'authfieldset': 'section', // Adds 'Authentication settings' title <fieldset>\n    'optionfieldset': 'one-of', // Option control, displays selected sub-item <fieldset>\n    'selectfieldset': 'one-of', // Select control, displays selected sub-item <fieldset>\n    'conditional': 'section', // Identical to 'section' (depeciated) <div>\n    'actions': 'section', // Horizontal button list, can only submit, uses buttons as items <div>\n    'tagsinput': 'section', // For entering short text tags <div>\n    // See: http://ulion.github.io/jsonform/playground/?example=fields-checkboxbuttons\n\n  // Widgets included for compatibility with React JSON Schema Form API\n    'updown': 'number',\n    'date-time': 'datetime-local',\n    'alt-datetime': 'datetime-local',\n    'alt-date': 'date',\n\n  // Widgets included for compatibility with Angular Schema Form API\n    'wizard': 'section', // TODO: Sequential panels with \"Next\" and \"Previous\" buttons\n\n  // Widgets included for compatibility with other libraries\n    'textline': 'text',\n\n  // Recommended 3rd-party add-on widgets (TODO: create wrappers for these...)\n    // 'ng2-select': Select control replacement - http://valor-software.com/ng2-select/\n    // 'flatpickr': Flatpickr date picker - https://github.com/chmln/flatpickr\n    // 'pikaday': Pikaday date picker - https://github.com/dbushell/Pikaday\n    // 'spectrum': Spectrum color picker - http://bgrins.github.io/spectrum\n    // 'bootstrap-slider': Bootstrap Slider range control - https://github.com/seiyria/bootstrap-slider\n    // 'ace': ACE code editor - https://ace.c9.io\n    // 'ckeditor': CKEditor HTML / rich text editor - http://ckeditor.com\n    // 'tinymce': TinyMCE HTML / rich text editor - https://www.tinymce.com\n    // 'imageselect': Bootstrap drop-down image selector - http://silviomoreto.github.io/bootstrap-select\n    // 'wysihtml5': HTML editor - http://jhollingworth.github.io/bootstrap-wysihtml5\n    // 'quill': Quill HTML / rich text editor (?) - https://quilljs.com\n  };\n  registeredWidgets: any = { };\n  frameworkWidgets: any = { };\n  activeWidgets: any = { };\n\n  constructor() {\n    this.setActiveWidgets();\n  }\n\n  setActiveWidgets(): boolean {\n    this.activeWidgets = Object.assign(\n      { }, this.widgetLibrary, this.frameworkWidgets, this.registeredWidgets\n    );\n    for (const widgetName of Object.keys(this.activeWidgets)) {\n      let widget: any = this.activeWidgets[widgetName];\n      // Resolve aliases\n      if (typeof widget === 'string') {\n        const usedAliases: string[] = [];\n        while (typeof widget === 'string' && !usedAliases.includes(widget)) {\n          usedAliases.push(widget);\n          widget = this.activeWidgets[widget];\n        }\n        if (typeof widget !== 'string') {\n          this.activeWidgets[widgetName] = widget;\n        }\n      }\n    }\n    return true;\n  }\n\n  setDefaultWidget(type: string): boolean {\n    if (!this.hasWidget(type)) { return false; }\n    this.defaultWidget = type;\n    return true;\n  }\n\n  hasWidget(type: string, widgetSet = 'activeWidgets'): boolean {\n    if (!type || typeof type !== 'string') { return false; }\n    return hasOwn(this[widgetSet], type);\n  }\n\n  hasDefaultWidget(type: string): boolean {\n    return this.hasWidget(type, 'widgetLibrary');\n  }\n\n  registerWidget(type: string, widget: any): boolean {\n    if (!type || !widget || typeof type !== 'string') { return false; }\n    this.registeredWidgets[type] = widget;\n    return this.setActiveWidgets();\n  }\n\n  unRegisterWidget(type: string): boolean {\n    if (!hasOwn(this.registeredWidgets, type)) { return false; }\n    delete this.registeredWidgets[type];\n    return this.setActiveWidgets();\n  }\n\n  unRegisterAllWidgets(unRegisterFrameworkWidgets = true): boolean {\n    this.registeredWidgets = { };\n    if (unRegisterFrameworkWidgets) { this.frameworkWidgets = { }; }\n    return this.setActiveWidgets();\n  }\n\n  registerFrameworkWidgets(widgets: any): boolean {\n    if (widgets === null || typeof widgets !== 'object') { widgets = { }; }\n    this.frameworkWidgets = widgets;\n    return this.setActiveWidgets();\n  }\n\n  unRegisterFrameworkWidgets(): boolean {\n    if (Object.keys(this.frameworkWidgets).length) {\n      this.frameworkWidgets = { };\n      return this.setActiveWidgets();\n    }\n    return false;\n  }\n\n  getWidget(type?: string, widgetSet = 'activeWidgets'): any {\n    if (this.hasWidget(type, widgetSet)) {\n      return this[widgetSet][type];\n    } else if (this.hasWidget(this.defaultWidget, widgetSet)) {\n      return this[widgetSet][this.defaultWidget];\n    } else {\n      return null;\n    }\n  }\n\n  getAllWidgets(): any {\n    return {\n      widgetLibrary: this.widgetLibrary,\n      registeredWidgets: this.registeredWidgets,\n      frameworkWidgets: this.frameworkWidgets,\n      activeWidgets: this.activeWidgets,\n    };\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-core/src/public_api.ts",
    "content": "/*\n * Public API Surface of json-schema-form\n */\n\nexport { JsonSchemaFormModule } from './lib/json-schema-form.module';\nexport { TitleMapItem, ErrorMessages, JsonSchemaFormService } from './lib/json-schema-form.service';\nexport { JsonSchemaFormComponent } from './lib/json-schema-form.component';\nexport { Framework } from './lib/framework-library/framework';\nexport { FrameworkLibraryService } from './lib/framework-library/framework-library.service';\nexport {\n    deValidationMessages,\n    enValidationMessages,\n    esValidationMessages,\n    frValidationMessages,\n    itValidationMessages,\n    ptValidationMessages,\n    zhValidationMessages\n  } from './lib/locale';\nexport * from './lib/widget-library';\nexport * from './lib/widget-library/widget-library.module';\nexport * from './lib/shared';\n"
  },
  {
    "path": "projects/ajsf-core/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'core-js/es/reflect';\nimport 'zone.js';\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(), {\n    teardown: { destroyAfterEach: false }\n}\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "projects/ajsf-core/tsconfig.lib.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/lib\",\n    \"declarationMap\": true,\n    \"target\": \"es2020\",\n    \"declaration\": true,\n    \"inlineSources\": true,\n    \"types\": [],\n    \"lib\": [\n      \"dom\",\n      \"es2018\"\n    ]\n  },\n  \"angularCompilerOptions\": {\n    \"skipTemplateCodegen\": true,\n    \"strictMetadataEmit\": true,\n    \"fullTemplateTypeCheck\": true,\n    \"strictInjectionParameters\": true,\n    \"enableResourceInlining\": true\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-core/tsconfig.lib.prod.json",
    "content": "{\n  \"extends\": \"./tsconfig.lib.json\",\n  \"compilerOptions\": {\n    \"declarationMap\": false\n  },\n  \"angularCompilerOptions\": {\n    \"compilationMode\": \"partial\"\n  }\n}"
  },
  {
    "path": "projects/ajsf-core/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-core/tslint.json",
    "content": "{\n    \"extends\": \"../../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"lib\",\n            \"camelCase\"\n        ]\n    }\n}\n"
  },
  {
    "path": "projects/ajsf-material/README.md",
    "content": "# @ajsf/material\n\n## Getting started\n\n```shell\nnpm install @ajsf/material@latest\n```\n\nWith YARN, run the following:\n\n```shell\nyarn add @ajsf/material@latest\n```\n\nThen import `MaterialDesignFrameworkModule` in your main application module if you want to use `material-angular` UI, like this:\n\n```javascript\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\n\nimport { MaterialDesignFrameworkModule } from '@ajsf/material';\n\nimport { AppComponent } from './app.component';\n\n@NgModule({\n  declarations: [ AppComponent ],\n  imports: [\n    MaterialDesignFrameworkModule\n  ],\n  providers: [],\n  bootstrap: [ AppComponent ]\n})\nexport class AppModule { }\n```\n\nFor basic use, after loading JsonSchemaFormModule as described above, to display a form in your Angular component, simply add the following to your component's template:\n\n```html\n<json-schema-form\n  loadExternalAssets=\"true\"\n  [schema]=\"yourJsonSchema\"\n  framework=\"material-design\"\n  (onSubmit)=\"yourOnSubmitFn($event)\">\n</json-schema-form>\n```\n\nWhere `schema` is a valid JSON schema object, and `onSubmit` calls a function to process the submitted JSON form data. If you don't already have your own schemas, you can find a bunch of samples to test with in the `demo/assets/example-schemas` folder, as described above.\n\n`framework` is for the template you want to use, the default value is `no-framwork`. The possible values are:\n\n* `material-design` for  Material Design.\n* `bootstrap-3` for Bootstrap 3.\n* `bootstrap-4` for 'Bootstrap 4.\n* `no-framework` for (plain HTML).\n\n## Code scaffolding\n\nRun `ng generate component component-name --project @ajsf/material` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project @ajsf/material`.\n> Note: Don't forget to add `--project @ajsf/material` or else it will be added to the default project in your `angular.json` file.\n\n## Build\n\nRun `ng build @ajsf/material` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test @ajsf/material` to execute the unit tests via [Karma](https://karma-runner.github.io).\n"
  },
  {
    "path": "projects/ajsf-material/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma'),\n    ],\n    client: {\n      clearContext: false, // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, '../../coverage/ajsf-material'),\n      reporters: [\n        {\n          type: 'html',\n        },\n        {\n          type: 'lcov',\n        },\n        {\n          type: 'text-summary',\n        },\n      ],\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    singleRun: false,\n    browsers: ['Chrome', 'ChromeHeadlessCI'],\n    customLaunchers: {\n      ChromeHeadlessCI: {\n        base: 'ChromeHeadless',\n        flags: ['--no-sandbox'],\n      },\n    },\n  });\n};\n"
  },
  {
    "path": "projects/ajsf-material/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/@ajsf/material\",\n  \"lib\": {\n    \"entryFile\": \"src/public_api.ts\"\n  },\n  \"allowedNonPeerDependencies\": [\n    \"lodash-es\",\n    \"@ajsf/core\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-material/package.json",
    "content": "{\n  \"name\": \"@ajsf/material\",\n  \"version\": \"0.8.0\",\n  \"description\": \"Angular JSON Schema Form builder using Angular Material UI\",\n  \"author\": \"https://github.com/hamzahamidi/ajsf/graphs/contributors\",\n  \"keywords\": [\n    \"Angular\",\n    \"ng\",\n    \"Angular14\",\n    \"Angular 14\",\n    \"ng14\",\n    \"JSON Schema\",\n    \"form\",\n    \"forms\",\n    \"form builder\",\n    \"material\",\n    \"angular material\",\n    \"ajsf\",\n    \"angular json schema form\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/hamzahamidi/ajsf\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/hamzahamidi/ajsf/issues\"\n  },\n  \"private\": false,\n  \"dependencies\": {\n    \"lodash-es\": \"~4.17.21\",\n    \"@ajsf/core\": \"~0.8.0\",\n    \"tslib\": \"^2.0.0\"\n  },\n  \"peerDependencies\": {\n    \"@angular/flex-layout\": \">=14.0.0-beta.40\",\n    \"@angular/material\": \">=14.0.0\",\n    \"@angular/cdk\": \">=14.0.0\"\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/angular-flex-monkey-patch.ts",
    "content": "import { MediaMarshaller } from '@angular/flex-layout/core';\n\nexport function fixAngularFlex() {\n  // monkey patch based on errors in console  - https://github.com/angular/flex-layout/issues/1011\n  const MediaMarshallerUpdateElement = MediaMarshaller.prototype.updateElement;\n\n  MediaMarshaller.prototype.updateElement = function (element: HTMLElement, key: string, value: any) {\n    if (key === 'layout-gap' && (value === null || value === undefined)) {\n      value = '0px';\n    }\n    MediaMarshallerUpdateElement.apply(this, [element, key, value]);\n  };\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/material-design-framework.component.html",
    "content": "<div\n  [class.array-item]=\"widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref'\"\n  [orderable]=\"isOrderable\"\n  [dataIndex]=\"dataIndex\"\n  [layoutIndex]=\"layoutIndex\"\n  [layoutNode]=\"widgetLayoutNode\">\n  <svg *ngIf=\"showRemoveButton\"\n       xmlns=\"http://www.w3.org/2000/svg\"\n       height=\"18\" width=\"18\" viewBox=\"0 0 24 24\"\n       class=\"close-button\"\n       (click)=\"removeItem()\">\n    <path\n      d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z\"/>\n  </svg>\n  <select-widget-widget\n    [dataIndex]=\"dataIndex\"\n    [layoutIndex]=\"layoutIndex\"\n    [layoutNode]=\"widgetLayoutNode\"></select-widget-widget>\n</div>\n<div class=\"spacer\" *ngIf=\"widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref'\"></div>\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/material-design-framework.component.scss",
    "content": ".array-item {\n  border-radius: 2px;\n  box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .2),\n  0 2px 2px 0 rgba(0, 0, 0, .14),\n  0 1px 5px 0 rgba(0, 0, 0, .12);\n  padding: 6px;\n  position: relative;\n  transition: all 280ms cubic-bezier(.4, 0, .2, 1);\n}\n\n.close-button {\n  cursor: pointer;\n  position: absolute;\n  top: 6px;\n  right: 6px;\n  fill: rgba(0, 0, 0, .4);\n  visibility: hidden;\n  z-index: 500;\n}\n\n.close-button:hover {\n  fill: rgba(0, 0, 0, .8);\n}\n\n.array-item:hover > .close-button {\n  visibility: visible;\n}\n\n.spacer {\n  margin: 6px 0;\n}\n\n[draggable=true]:hover {\n  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, .2),\n  0 8px 10px 1px rgba(0, 0, 0, .14),\n  0 3px 14px 2px rgba(0, 0, 0, .12);\n  cursor: move;\n  z-index: 10;\n}\n\n[draggable=true].drag-target-top {\n  box-shadow: 0 -2px 0 #000;\n  position: relative;\n  z-index: 20;\n}\n\n[draggable=true].drag-target-bottom {\n  box-shadow: 0 2px 0 #000;\n  position: relative;\n  z-index: 20;\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/material-design-framework.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\nimport { CommonModule } from '@angular/common';\nimport {\n  JsonSchemaFormModule,\n  JsonSchemaFormService,\n  WidgetLibraryModule\n} from '@ajsf/core';\nimport { MaterialDesignFrameworkComponent } from './material-design-framework.component';\n\ndescribe('FwBootstrap4Component', () => {\n  let component: MaterialDesignFrameworkComponent;\n  let fixture: ComponentFixture<MaterialDesignFrameworkComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        JsonSchemaFormModule,\n        CommonModule,\n        WidgetLibraryModule,\n      ],\n      declarations: [MaterialDesignFrameworkComponent],\n      providers: [JsonSchemaFormService]\n    })\n      .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(MaterialDesignFrameworkComponent);\n    component = fixture.componentInstance;\n    component.layoutNode = { options: {} };\n    component.layoutIndex = [];\n    component.dataIndex = [];\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/material-design-framework.component.ts",
    "content": "import {ChangeDetectorRef, Component, Input, OnChanges, OnInit} from '@angular/core';\nimport {isDefined, JsonSchemaFormService} from '@ajsf/core';\nimport cloneDeep from 'lodash/cloneDeep';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-design-framework',\n  templateUrl: './material-design-framework.component.html',\n  styleUrls: ['./material-design-framework.component.scss'],\n})\nexport class MaterialDesignFrameworkComponent implements OnInit, OnChanges {\n  frameworkInitialized = false;\n  inputType: string;\n  options: any; // Options used in this framework\n  widgetLayoutNode: any; // layoutNode passed to child widget\n  widgetOptions: any; // Options passed to child widget\n  formControl: any = null;\n  parentArray: any = null;\n  isOrderable = false;\n  dynamicTitle: string = null;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private changeDetector: ChangeDetectorRef,\n    private jsf: JsonSchemaFormService\n  ) {\n  }\n\n  get showRemoveButton(): boolean {\n    if (!this.layoutNode || !this.widgetOptions.removable ||\n      this.widgetOptions.readonly || this.layoutNode.type === '$ref'\n    ) {\n      return false;\n    }\n    if (this.layoutNode.recursiveReference) {\n      return true;\n    }\n    if (!this.layoutNode.arrayItem || !this.parentArray) {\n      return false;\n    }\n    // If array length <= minItems, don't allow removing any items\n    return this.parentArray.items.length - 1 <= this.parentArray.options.minItems ? false :\n      // For removable list items, allow removing any item\n      this.layoutNode.arrayItemType === 'list' ? true :\n        // For removable tuple items, only allow removing last item in list\n        this.layoutIndex[this.layoutIndex.length - 1] === this.parentArray.items.length - 2;\n  }\n\n  ngOnInit() {\n    this.initializeFramework();\n  }\n\n  ngOnChanges() {\n    if (!this.frameworkInitialized) {\n      this.initializeFramework();\n    }\n    if (this.dynamicTitle) {\n      this.updateTitle();\n    }\n  }\n\n  initializeFramework() {\n    if (this.layoutNode) {\n      this.options = cloneDeep(this.layoutNode.options || {});\n      this.widgetLayoutNode = {\n        ...this.layoutNode,\n        options: cloneDeep(this.layoutNode.options || {})\n      };\n      this.widgetOptions = this.widgetLayoutNode.options;\n      this.formControl = this.jsf.getFormControl(this);\n\n      if (\n        isDefined(this.widgetOptions.minimum) &&\n        isDefined(this.widgetOptions.maximum) &&\n        this.widgetOptions.multipleOf >= 1\n      ) {\n        this.layoutNode.type = 'range';\n      }\n\n      if (\n        !['$ref', 'advancedfieldset', 'authfieldset', 'button', 'card',\n          'checkbox', 'expansion-panel', 'help', 'message', 'msg', 'section',\n          'submit', 'tabarray', 'tabs'].includes(this.layoutNode.type) &&\n        /{{.+?}}/.test(this.widgetOptions.title || '')\n      ) {\n        this.dynamicTitle = this.widgetOptions.title;\n        this.updateTitle();\n      }\n\n      if (this.layoutNode.arrayItem && this.layoutNode.type !== '$ref') {\n        this.parentArray = this.jsf.getParentNode(this);\n        if (this.parentArray) {\n          this.isOrderable =\n            this.parentArray.type.slice(0, 3) !== 'tab' &&\n            this.layoutNode.arrayItemType === 'list' &&\n            !this.widgetOptions.readonly &&\n            this.parentArray.options.orderable;\n        }\n      }\n\n      this.frameworkInitialized = true;\n    } else {\n      this.options = {};\n    }\n  }\n\n  updateTitle() {\n    this.widgetLayoutNode.options.title = this.jsf.parseText(\n      this.dynamicTitle,\n      this.jsf.getFormControlValue(this),\n      this.jsf.getFormControlGroup(this).value,\n      this.dataIndex[this.dataIndex.length - 1]\n    );\n  }\n\n  removeItem() {\n    this.jsf.removeItem(this);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/material-design-framework.module.ts",
    "content": "import {\n  Framework,\n  FrameworkLibraryService,\n  JsonSchemaFormModule,\n  JsonSchemaFormService,\n  WidgetLibraryModule, WidgetLibraryService\n} from '@ajsf/core';\nimport {NgModule} from '@angular/core';\nimport {CommonModule} from '@angular/common';\nimport {FormsModule, ReactiveFormsModule} from '@angular/forms';\nimport {FlexLayoutModule} from '@angular/flex-layout';\nimport {MatAutocompleteModule} from '@angular/material/autocomplete';\nimport {MatButtonModule} from '@angular/material/button';\nimport {MatButtonToggleModule} from '@angular/material/button-toggle';\nimport {MatCardModule} from '@angular/material/card';\nimport {MatCheckboxModule} from '@angular/material/checkbox';\nimport {MatChipsModule} from '@angular/material/chips';\nimport {MatNativeDateModule} from '@angular/material/core';\nimport {MatDatepickerModule} from '@angular/material/datepicker';\nimport {MatExpansionModule} from '@angular/material/expansion';\nimport {MatFormFieldModule} from '@angular/material/form-field';\nimport {MatIconModule} from '@angular/material/icon';\nimport {MatInputModule} from '@angular/material/input';\nimport {MatRadioModule} from '@angular/material/radio';\nimport {MatSelectModule} from '@angular/material/select';\nimport {MatSlideToggleModule} from '@angular/material/slide-toggle';\nimport {MatSliderModule} from '@angular/material/slider';\nimport {MatStepperModule} from '@angular/material/stepper';\nimport {MatTabsModule} from '@angular/material/tabs';\nimport {MatTooltipModule} from '@angular/material/tooltip';\nimport {MatMenuModule} from '@angular/material/menu';\nimport {MatToolbarModule} from '@angular/material/toolbar';\nimport {MaterialDesignFramework} from './material-design.framework';\nimport {MATERIAL_FRAMEWORK_COMPONENTS} from './widgets/public_api';\nimport {fixAngularFlex} from './angular-flex-monkey-patch';\n\n/**\n * unused @angular/material modules:\n * MatDialogModule, MatGridListModule, MatListModule, MatMenuModule,\n * MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule,\n * MatSidenavModule, MatSnackBarModule, MatSortModule, MatTableModule,\n * ,\n */\nexport const ANGULAR_MATERIAL_MODULES = [\n  MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule,\n  MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatExpansionModule,\n  MatFormFieldModule, MatIconModule, MatInputModule, MatNativeDateModule,\n  MatRadioModule, MatSelectModule, MatSliderModule, MatSlideToggleModule,\n  MatStepperModule, MatTabsModule, MatTooltipModule,\n  MatToolbarModule, MatMenuModule, MatToolbarModule,\n];\n\n@NgModule({\n    imports: [\n        CommonModule,\n        FormsModule,\n        ReactiveFormsModule,\n        FlexLayoutModule,\n        ...ANGULAR_MATERIAL_MODULES,\n        WidgetLibraryModule,\n        JsonSchemaFormModule,\n    ],\n    declarations: [\n        ...MATERIAL_FRAMEWORK_COMPONENTS,\n    ],\n    exports: [\n        JsonSchemaFormModule,\n        ...MATERIAL_FRAMEWORK_COMPONENTS,\n    ],\n    providers: [\n        JsonSchemaFormService,\n        FrameworkLibraryService,\n        WidgetLibraryService,\n        { provide: Framework, useClass: MaterialDesignFramework, multi: true },\n    ]\n})\nexport class MaterialDesignFrameworkModule {\n  constructor() {\n    fixAngularFlex();\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/material-design.framework.ts",
    "content": "import {Injectable} from '@angular/core';\nimport {Framework} from '@ajsf/core';\nimport {\n  FlexLayoutRootComponent,\n  FlexLayoutSectionComponent,\n  MaterialAddReferenceComponent,\n  MaterialButtonComponent,\n  MaterialButtonGroupComponent,\n  MaterialCheckboxComponent,\n  MaterialCheckboxesComponent,\n  MaterialChipListComponent,\n  MaterialDatepickerComponent,\n  MaterialDesignFrameworkComponent,\n  MaterialFileComponent,\n  MaterialInputComponent,\n  MaterialNumberComponent,\n  MaterialOneOfComponent,\n  MaterialRadiosComponent,\n  MaterialSelectComponent,\n  MaterialSliderComponent,\n  MaterialStepperComponent,\n  MaterialTabsComponent,\n  MaterialTextareaComponent\n} from './widgets/public_api';\n\n\n// Material Design Framework\n// https://github.com/angular/material2\n\n@Injectable()\nexport class MaterialDesignFramework extends Framework {\n  name = 'material-design';\n\n  framework = MaterialDesignFrameworkComponent;\n\n  stylesheets = [\n    '//fonts.googleapis.com/icon?family=Material+Icons',\n    '//fonts.googleapis.com/css?family=Roboto:300,400,500,700',\n  ];\n\n  widgets = {\n    'root': FlexLayoutRootComponent,\n    'section': FlexLayoutSectionComponent,\n    '$ref': MaterialAddReferenceComponent,\n    'button': MaterialButtonComponent,\n    'button-group': MaterialButtonGroupComponent,\n    'checkbox': MaterialCheckboxComponent,\n    'checkboxes': MaterialCheckboxesComponent,\n    'chip-list': MaterialChipListComponent,\n    'date': MaterialDatepickerComponent,\n    'file': MaterialFileComponent,\n    'number': MaterialNumberComponent,\n    'one-of': MaterialOneOfComponent,\n    'radios': MaterialRadiosComponent,\n    'select': MaterialSelectComponent,\n    'slider': MaterialSliderComponent,\n    'stepper': MaterialStepperComponent,\n    'tabs': MaterialTabsComponent,\n    'text': MaterialInputComponent,\n    'textarea': MaterialTextareaComponent,\n    'alt-date': 'date',\n    'any-of': 'one-of',\n    'card': 'section',\n    'color': 'text',\n    'expansion-panel': 'section',\n    'hidden': 'none',\n    'image': 'none',\n    'integer': 'number',\n    'radiobuttons': 'button-group',\n    'range': 'slider',\n    'submit': 'button',\n    'tagsinput': 'chip-list',\n    'wizard': 'stepper',\n  };\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/flex-layout-root.component.html",
    "content": "<div *ngFor=\"let layoutNode of layout; let i = index\" [class.form-flex-item]=\"isFlexItem\"\n  [style.flex-grow]=\"getFlexAttribute(layoutNode, 'flex-grow')\"\n  [style.flex-shrink]=\"getFlexAttribute(layoutNode, 'flex-shrink')\"\n  [style.flex-basis]=\"getFlexAttribute(layoutNode, 'flex-basis')\"\n  [style.align-self]=\"(layoutNode?.options || {})['align-self']\" [style.order]=\"layoutNode?.options?.order\"\n  [fxFlex]=\"layoutNode?.options?.fxFlex\" [fxFlexOrder]=\"layoutNode?.options?.fxFlexOrder\"\n  [fxFlexOffset]=\"layoutNode?.options?.fxFlexOffset\" [fxFlexAlign]=\"layoutNode?.options?.fxFlexAlign\">\n  <select-framework-widget *ngIf=\"showWidget(layoutNode)\"\n    [dataIndex]=\"layoutNode?.arrayItem ? (dataIndex || []).concat(i) : (dataIndex || [])\"\n    [layoutIndex]=\"(layoutIndex || []).concat(i)\" [layoutNode]=\"layoutNode\"></select-framework-widget>\n  <div>"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/flex-layout-root.component.ts",
    "content": "import { ChangeDetectionStrategy, Component, Input } from '@angular/core';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'flex-layout-root-widget',\n  template: `\n    <div *ngFor=\"let layoutNode of layout; let i = index\"\n      [class.form-flex-item]=\"isFlexItem\"\n      [style.flex-grow]=\"getFlexAttribute(layoutNode, 'flex-grow')\"\n      [style.flex-shrink]=\"getFlexAttribute(layoutNode, 'flex-shrink')\"\n      [style.flex-basis]=\"getFlexAttribute(layoutNode, 'flex-basis')\"\n      [style.align-self]=\"(layoutNode?.options || {})['align-self']\"\n      [style.order]=\"layoutNode?.options?.order\"\n      [fxFlex]=\"layoutNode?.options?.fxFlex\"\n      [fxFlexOrder]=\"layoutNode?.options?.fxFlexOrder\"\n      [fxFlexOffset]=\"layoutNode?.options?.fxFlexOffset\"\n      [fxFlexAlign]=\"layoutNode?.options?.fxFlexAlign\">\n      <select-framework-widget *ngIf=\"showWidget(layoutNode)\"\n        [dataIndex]=\"layoutNode?.arrayItem ? (dataIndex || []).concat(i) : (dataIndex || [])\"\n        [layoutIndex]=\"(layoutIndex || []).concat(i)\"\n        [layoutNode]=\"layoutNode\"></select-framework-widget>\n    <div>`,\n  changeDetection: ChangeDetectionStrategy.Default,\n})\nexport class FlexLayoutRootComponent {\n  @Input() dataIndex: number[];\n  @Input() layoutIndex: number[];\n  @Input() layout: any[];\n  @Input() isFlexItem = false;\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  removeItem(item) {\n    this.jsf.removeItem(item);\n  }\n\n  // Set attributes for flexbox child\n  // (container attributes are set in flex-layout-section.component)\n  getFlexAttribute(node: any, attribute: string) {\n    const index = ['flex-grow', 'flex-shrink', 'flex-basis'].indexOf(attribute);\n    return ((node.options || {}).flex || '').split(/\\s+/)[index] ||\n      (node.options || {})[attribute] || ['1', '1', 'auto'][index];\n  }\n\n  showWidget(layoutNode: any): boolean {\n    return this.jsf.evaluateCondition(layoutNode, this.dataIndex);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/flex-layout-section.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'flex-layout-section-widget',\n  template: `\n    <div *ngIf=\"containerType === 'div'\"\n      [class]=\"options?.htmlClass || ''\"\n      [class.expandable]=\"options?.expandable && !expanded\"\n      [class.expanded]=\"options?.expandable && expanded\">\n      <label *ngIf=\"sectionTitle\"\n        [class]=\"'legend ' + (options?.labelHtmlClass || '')\"\n        [innerHTML]=\"sectionTitle\"\n        (click)=\"toggleExpanded()\"></label>\n      <flex-layout-root-widget *ngIf=\"expanded\"\n        [layout]=\"layoutNode.items\"\n        [dataIndex]=\"dataIndex\"\n        [layoutIndex]=\"layoutIndex\"\n        [isFlexItem]=\"getFlexAttribute('is-flex')\"\n        [class.form-flex-column]=\"getFlexAttribute('flex-direction') === 'column'\"\n        [class.form-flex-row]=\"getFlexAttribute('flex-direction') === 'row'\"\n        [style.display]=\"getFlexAttribute('display')\"\n        [style.flex-direction]=\"getFlexAttribute('flex-direction')\"\n        [style.flex-wrap]=\"getFlexAttribute('flex-wrap')\"\n        [style.justify-content]=\"getFlexAttribute('justify-content')\"\n        [style.align-items]=\"getFlexAttribute('align-items')\"\n        [style.align-content]=\"getFlexAttribute('align-content')\"\n        [fxLayout]=\"getFlexAttribute('layout')\"\n        [fxLayoutGap]=\"options?.fxLayoutGap\"\n        [fxLayoutAlign]=\"options?.fxLayoutAlign\"\n        [attr.fxFlexFill]=\"options?.fxLayoutAlign\"></flex-layout-root-widget>\n      <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n        [innerHTML]=\"options?.errorMessage\"></mat-error>\n    </div>\n\n    <fieldset *ngIf=\"containerType === 'fieldset'\"\n      [class]=\"options?.htmlClass || ''\"\n      [class.expandable]=\"options?.expandable && !expanded\"\n      [class.expanded]=\"options?.expandable && expanded\"\n      [disabled]=\"options?.readonly\">\n      <legend *ngIf=\"sectionTitle\"\n        [class]=\"'legend ' + (options?.labelHtmlClass || '')\"\n        [innerHTML]=\"sectionTitle\"\n        (click)=\"toggleExpanded()\"></legend>\n      <flex-layout-root-widget *ngIf=\"expanded\"\n        [layout]=\"layoutNode.items\"\n        [dataIndex]=\"dataIndex\"\n        [layoutIndex]=\"layoutIndex\"\n        [isFlexItem]=\"getFlexAttribute('is-flex')\"\n        [class.form-flex-column]=\"getFlexAttribute('flex-direction') === 'column'\"\n        [class.form-flex-row]=\"getFlexAttribute('flex-direction') === 'row'\"\n        [style.display]=\"getFlexAttribute('display')\"\n        [style.flex-direction]=\"getFlexAttribute('flex-direction')\"\n        [style.flex-wrap]=\"getFlexAttribute('flex-wrap')\"\n        [style.justify-content]=\"getFlexAttribute('justify-content')\"\n        [style.align-items]=\"getFlexAttribute('align-items')\"\n        [style.align-content]=\"getFlexAttribute('align-content')\"\n        [fxLayout]=\"getFlexAttribute('layout')\"\n        [fxLayoutGap]=\"options?.fxLayoutGap\"\n        [fxLayoutAlign]=\"options?.fxLayoutAlign\"\n        [attr.fxFlexFill]=\"options?.fxLayoutAlign\"></flex-layout-root-widget>\n      <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n        [innerHTML]=\"options?.errorMessage\"></mat-error>\n    </fieldset>\n\n    <mat-card *ngIf=\"containerType === 'card'\"\n      [ngClass]=\"options?.htmlClass || ''\"\n      [class.expandable]=\"options?.expandable && !expanded\"\n      [class.expanded]=\"options?.expandable && expanded\">\n      <mat-card-header *ngIf=\"sectionTitle\">\n        <legend\n          [class]=\"'legend ' + (options?.labelHtmlClass || '')\"\n          [innerHTML]=\"sectionTitle\"\n          (click)=\"toggleExpanded()\"></legend>\n      </mat-card-header>\n      <mat-card-content *ngIf=\"expanded\">\n        <fieldset [disabled]=\"options?.readonly\">\n          <flex-layout-root-widget *ngIf=\"expanded\"\n            [layout]=\"layoutNode.items\"\n            [dataIndex]=\"dataIndex\"\n            [layoutIndex]=\"layoutIndex\"\n            [isFlexItem]=\"getFlexAttribute('is-flex')\"\n            [class.form-flex-column]=\"getFlexAttribute('flex-direction') === 'column'\"\n            [class.form-flex-row]=\"getFlexAttribute('flex-direction') === 'row'\"\n            [style.display]=\"getFlexAttribute('display')\"\n            [style.flex-direction]=\"getFlexAttribute('flex-direction')\"\n            [style.flex-wrap]=\"getFlexAttribute('flex-wrap')\"\n            [style.justify-content]=\"getFlexAttribute('justify-content')\"\n            [style.align-items]=\"getFlexAttribute('align-items')\"\n            [style.align-content]=\"getFlexAttribute('align-content')\"\n            [fxLayout]=\"getFlexAttribute('layout')\"\n            [fxLayoutGap]=\"options?.fxLayoutGap\"\n            [fxLayoutAlign]=\"options?.fxLayoutAlign\"\n            [attr.fxFlexFill]=\"options?.fxLayoutAlign\"></flex-layout-root-widget>\n          </fieldset>\n      </mat-card-content>\n      <mat-card-footer>\n        <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n          [innerHTML]=\"options?.errorMessage\"></mat-error>\n      </mat-card-footer>\n    </mat-card>\n\n    <mat-expansion-panel *ngIf=\"containerType === 'expansion-panel'\"\n      [expanded]=\"expanded\"\n      [hideToggle]=\"!options?.expandable\">\n      <mat-expansion-panel-header>\n        <mat-panel-title>\n          <legend *ngIf=\"sectionTitle\"\n            [class]=\"options?.labelHtmlClass\"\n            [innerHTML]=\"sectionTitle\"\n            (click)=\"toggleExpanded()\"></legend>\n        </mat-panel-title>\n      </mat-expansion-panel-header>\n      <fieldset [disabled]=\"options?.readonly\">\n        <flex-layout-root-widget *ngIf=\"expanded\"\n          [layout]=\"layoutNode.items\"\n          [dataIndex]=\"dataIndex\"\n          [layoutIndex]=\"layoutIndex\"\n          [isFlexItem]=\"getFlexAttribute('is-flex')\"\n          [class.form-flex-column]=\"getFlexAttribute('flex-direction') === 'column'\"\n          [class.form-flex-row]=\"getFlexAttribute('flex-direction') === 'row'\"\n          [style.display]=\"getFlexAttribute('display')\"\n          [style.flex-direction]=\"getFlexAttribute('flex-direction')\"\n          [style.flex-wrap]=\"getFlexAttribute('flex-wrap')\"\n          [style.justify-content]=\"getFlexAttribute('justify-content')\"\n          [style.align-items]=\"getFlexAttribute('align-items')\"\n          [style.align-content]=\"getFlexAttribute('align-content')\"\n          [fxLayout]=\"getFlexAttribute('layout')\"\n          [fxLayoutGap]=\"options?.fxLayoutGap\"\n          [fxLayoutAlign]=\"options?.fxLayoutAlign\"\n          [attr.fxFlexFill]=\"options?.fxLayoutAlign\"></flex-layout-root-widget>\n      </fieldset>\n      <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n        [innerHTML]=\"options?.errorMessage\"></mat-error>\n    </mat-expansion-panel>`,\n  styles: [`\n    fieldset { border: 0; margin: 0; padding: 0; }\n    .legend { font-weight: bold; }\n    .expandable > .legend:before { content: '▶'; padding-right: .3em; }\n    .expanded > .legend:before { content: '▼'; padding-right: .2em; }\n  `],\n})\nexport class FlexLayoutSectionComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  expanded = true;\n  containerType = 'div';\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  get sectionTitle() {\n    return this.options.notitle ? null : this.jsf.setItemTitle(this);\n  }\n\n  ngOnInit() {\n    this.jsf.initializeControl(this);\n    this.options = this.layoutNode.options || {};\n    this.expanded = typeof this.options.expanded === 'boolean' ?\n      this.options.expanded : !this.options.expandable;\n    switch (this.layoutNode.type) {\n      case 'section': case 'array': case 'fieldset': case 'advancedfieldset':\n      case 'authfieldset': case 'optionfieldset': case 'selectfieldset':\n        this.containerType = 'fieldset';\n        break;\n      case 'card':\n        this.containerType = 'card';\n        break;\n      case 'expansion-panel':\n        this.containerType = 'expansion-panel';\n        break;\n      default: // 'div', 'flex', 'tab', 'conditional', 'actions'\n        this.containerType = 'div';\n    }\n  }\n\n  toggleExpanded() {\n    if (this.options.expandable) { this.expanded = !this.expanded; }\n  }\n\n  // Set attributes for flexbox container\n  // (child attributes are set in flex-layout-root.component)\n  getFlexAttribute(attribute: string) {\n    const flexActive: boolean =\n      this.layoutNode.type === 'flex' ||\n      !!this.options.displayFlex ||\n      this.options.display === 'flex';\n    // if (attribute !== 'flex' && !flexActive) { return null; }\n    switch (attribute) {\n      case 'is-flex':\n        return flexActive;\n      case 'display':\n        return flexActive ? 'flex' : 'initial';\n      case 'flex-direction': case 'flex-wrap':\n        const index = ['flex-direction', 'flex-wrap'].indexOf(attribute);\n        return (this.options['flex-flow'] || '').split(/\\s+/)[index] ||\n          this.options[attribute] || ['column', 'nowrap'][index];\n      case 'justify-content': case 'align-items': case 'align-content':\n        return this.options[attribute];\n      case 'layout':\n        return (this.options.fxLayout || 'row') +\n          this.options.fxLayoutWrap ? ' ' + this.options.fxLayoutWrap : '';\n\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-add-reference.component.ts",
    "content": "import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-add-reference-widget',\n  template: `\n    <section [class]=\"options?.htmlClass || ''\" align=\"end\">\n      <button mat-raised-button *ngIf=\"showAddButton\"\n        [color]=\"options?.color || 'accent'\"\n        [disabled]=\"options?.readonly\"\n        (click)=\"addItem($event)\">\n        <span *ngIf=\"options?.icon\" [class]=\"options?.icon\"></span>\n        <span *ngIf=\"options?.title\" [innerHTML]=\"buttonText\"></span>\n      </button>\n    </section>`,\n  changeDetection: ChangeDetectionStrategy.Default,\n})\nexport class MaterialAddReferenceComponent implements OnInit {\n  options: any;\n  itemCount: number;\n  previousLayoutIndex: number[];\n  previousDataIndex: number[];\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n  }\n\n  get showAddButton(): boolean {\n    return !this.layoutNode.arrayItem ||\n      this.layoutIndex[this.layoutIndex.length - 1] < this.options.maxItems;\n  }\n\n  addItem(event) {\n    event.preventDefault();\n    this.jsf.addItem(this);\n  }\n\n  get buttonText(): string {\n    const parent: any = {\n      dataIndex: this.dataIndex.slice(0, -1),\n      layoutIndex: this.layoutIndex.slice(0, -1),\n      layoutNode: this.jsf.getParentNode(this),\n    };\n    return parent.layoutNode.add ||\n      this.jsf.setArrayItemTitle(parent, this.layoutNode, this.itemCount);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-button-group.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService, buildTitleMap } from '@ajsf/core';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-button-group-widget',\n  template: `\n    <div>\n      <div *ngIf=\"options?.title\">\n        <label\n          [attr.for]=\"'control' + layoutNode?._id\"\n          [class]=\"options?.labelHtmlClass || ''\"\n          [style.display]=\"options?.notitle ? 'none' : ''\"\n          [innerHTML]=\"options?.title\"></label>\n      </div>\n      <mat-button-toggle-group\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.required]=\"options?.required\"\n        [disabled]=\"controlDisabled || options?.readonly\"\n        [name]=\"controlName\"\n        [value]=\"controlValue\"\n        [vertical]=\"!!options.vertical\">\n        <mat-button-toggle *ngFor=\"let radioItem of radiosList\"\n          [id]=\"'control' + layoutNode?._id + '/' + radioItem?.name\"\n          [value]=\"radioItem?.value\"\n          (click)=\"updateValue(radioItem?.value)\">\n          <span [innerHTML]=\"radioItem?.name\"></span>\n        </mat-button-toggle>\n      </mat-button-toggle-group>\n      <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n        [innerHTML]=\"options?.errorMessage\"></mat-error>\n    </div>`,\n    styles: [` mat-error { font-size: 75%; } `],\n})\nexport class MaterialButtonGroupComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  radiosList: any[] = [];\n  vertical = false;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.radiosList = buildTitleMap(\n      this.options.titleMap || this.options.enumNames,\n      this.options.enum, true\n    );\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(value) {\n    this.options.showErrors = true;\n    this.jsf.updateValue(this, value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-button.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService, hasOwn } from '@ajsf/core';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-button-widget',\n  template: `\n    <div class=\"button-row\" [class]=\"options?.htmlClass || ''\">\n      <button mat-raised-button\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [color]=\"options?.color || 'primary'\"\n        [disabled]=\"controlDisabled || options?.readonly\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [type]=\"layoutNode?.type\"\n        [value]=\"controlValue\"\n        (click)=\"updateValue($event)\">\n        <mat-icon *ngIf=\"options?.icon\" class=\"mat-24\">{{options?.icon}}</mat-icon>\n        <span *ngIf=\"options?.title\" [innerHTML]=\"options?.title\"></span>\n      </button>\n    </div>`,\n    styles: [` button { margin-top: 10px; } `],\n})\nexport class MaterialButtonComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n    if (hasOwn(this.options, 'disabled')) {\n      this.controlDisabled = this.options.disabled;\n    } else if (this.jsf.formOptions.disableInvalidSubmit) {\n      this.controlDisabled = !this.jsf.isValid;\n      this.jsf.isValidChanges.subscribe(isValid => this.controlDisabled = !isValid);\n    }\n  }\n\n  updateValue(event) {\n    if (typeof this.options.onClick === 'function') {\n      this.options.onClick(event);\n    } else {\n      this.jsf.updateValue(this, event.target.value);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-checkbox.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { AbstractControl } from '@angular/forms';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-checkbox-widget',\n  template: `\n    <mat-checkbox *ngIf=\"boundControl && !showSlideToggle\"\n      [formControl]=\"formControl\"\n      align=\"left\"\n      [color]=\"options?.color || 'primary'\"\n      [id]=\"'control' + layoutNode?._id\"\n      labelPosition=\"after\"\n      [name]=\"controlName\"\n      (blur)=\"options.showErrors = true\">\n      <span *ngIf=\"options?.title\"\n        class=\"checkbox-name\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></span>\n    </mat-checkbox>\n    <mat-checkbox *ngIf=\"!boundControl && !showSlideToggle\"\n      align=\"left\"\n      [color]=\"options?.color || 'primary'\"\n      [disabled]=\"controlDisabled || options?.readonly\"\n      [id]=\"'control' + layoutNode?._id\"\n      labelPosition=\"after\"\n      [name]=\"controlName\"\n      [checked]=\"isChecked\"\n      (blur)=\"options.showErrors = true\"\n      (change)=\"updateValue($event)\">\n      <span *ngIf=\"options?.title\"\n        class=\"checkbox-name\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></span>\n    </mat-checkbox>\n    <mat-slide-toggle *ngIf=\"boundControl && showSlideToggle\"\n      [formControl]=\"formControl\"\n      align=\"left\"\n      [color]=\"options?.color || 'primary'\"\n      [id]=\"'control' + layoutNode?._id\"\n      labelPosition=\"after\"\n      [name]=\"controlName\"\n      (blur)=\"options.showErrors = true\">\n      <span *ngIf=\"options?.title\"\n        class=\"checkbox-name\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></span>\n    </mat-slide-toggle>\n    <mat-slide-toggle *ngIf=\"!boundControl && showSlideToggle\"\n      align=\"left\"\n      [color]=\"options?.color || 'primary'\"\n      [disabled]=\"controlDisabled || options?.readonly\"\n      [id]=\"'control' + layoutNode?._id\"\n      labelPosition=\"after\"\n      [name]=\"controlName\"\n      [checked]=\"isChecked\"\n      (blur)=\"options.showErrors = true\"\n      (change)=\"updateValue($event)\">\n      <span *ngIf=\"options?.title\"\n        class=\"checkbox-name\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></span>\n    </mat-slide-toggle>\n    <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n      [innerHTML]=\"options?.errorMessage\"></mat-error>`,\n  styles: [`\n    .checkbox-name { white-space: nowrap; }\n    mat-error { font-size: 75%; }\n  `],\n})\nexport class MaterialCheckboxComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  trueValue: any = true;\n  falseValue: any = false;\n  showSlideToggle = false;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this, !this.options.readonly);\n    if (this.controlValue === null || this.controlValue === undefined) {\n      this.controlValue = false;\n      this.jsf.updateValue(this, this.falseValue);\n    }\n    if (this.layoutNode.type === 'slide-toggle' ||\n      this.layoutNode.format === 'slide-toggle'\n    ) {\n      this.showSlideToggle = true;\n    }\n  }\n\n  updateValue(event) {\n    this.options.showErrors = true;\n    this.jsf.updateValue(this, event.checked ? this.trueValue : this.falseValue);\n  }\n\n  get isChecked() {\n    return this.jsf.getFormControlValue(this) === this.trueValue;\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-checkboxes.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { buildTitleMap } from '@ajsf/core';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService, TitleMapItem } from '@ajsf/core';\n\n// TODO: Change this to use a Selection List instead?\n// https://material.angular.io/components/list/overview\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-checkboxes-widget',\n  template: `\n    <div>\n      <mat-checkbox type=\"checkbox\"\n        [checked]=\"allChecked\"\n        [color]=\"options?.color || 'primary'\"\n        [disabled]=\"controlDisabled || options?.readonly\"\n        [indeterminate]=\"someChecked\"\n        [name]=\"options?.name\"\n        (blur)=\"options.showErrors = true\"\n        (change)=\"updateAllValues($event)\">\n        <span class=\"checkbox-name\" [innerHTML]=\"options?.name\"></span>\n      </mat-checkbox>\n      <label *ngIf=\"options?.title\"\n        class=\"title\"\n        [class]=\"options?.labelHtmlClass || ''\"\n        [style.display]=\"options?.notitle ? 'none' : ''\"\n        [innerHTML]=\"options?.title\"></label>\n      <ul class=\"checkbox-list\" [class.horizontal-list]=\"horizontalList\">\n        <li *ngFor=\"let checkboxItem of checkboxList\"\n          [class]=\"options?.htmlClass || ''\">\n          <mat-checkbox type=\"checkbox\"\n            [(ngModel)]=\"checkboxItem.checked\"\n            [color]=\"options?.color || 'primary'\"\n            [disabled]=\"controlDisabled || options?.readonly\"\n            [name]=\"checkboxItem?.name\"\n            (blur)=\"options.showErrors = true\"\n            (change)=\"updateValue()\">\n            <span class=\"checkbox-name\" [innerHTML]=\"checkboxItem?.name\"></span>\n          </mat-checkbox>\n        </li>\n      </ul>\n      <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n        [innerHTML]=\"options?.errorMessage\"></mat-error>\n    </div>`,\n  styles: [`\n    .title { font-weight: bold; }\n    .checkbox-list { list-style-type: none; }\n    .horizontal-list > li { display: inline-block; margin-right: 10px; zoom: 1; }\n    .checkbox-name { white-space: nowrap; }\n    mat-error { font-size: 75%; }\n  `],\n})\nexport class MaterialCheckboxesComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  horizontalList = false;\n  formArray: AbstractControl;\n  checkboxList: TitleMapItem[] = [];\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.horizontalList = this.layoutNode.type === 'checkboxes-inline' ||\n      this.layoutNode.type === 'checkboxbuttons';\n    this.jsf.initializeControl(this);\n    this.checkboxList = buildTitleMap(\n      this.options.titleMap || this.options.enumNames, this.options.enum, true\n    );\n    if (this.boundControl) {\n      const formArray = this.jsf.getFormControl(this);\n      for (const checkboxItem of this.checkboxList) {\n        checkboxItem.checked = formArray.value.includes(checkboxItem.value);\n      }\n    }\n  }\n\n  get allChecked(): boolean {\n    return this.checkboxList.filter(t => t.checked).length === this.checkboxList.length;\n  }\n\n  get someChecked(): boolean {\n    const checkedItems = this.checkboxList.filter(t => t.checked).length;\n    return checkedItems > 0 && checkedItems < this.checkboxList.length;\n  }\n\n  updateValue() {\n    this.options.showErrors = true;\n    if (this.boundControl) {\n      this.jsf.updateArrayCheckboxList(this, this.checkboxList);\n    }\n  }\n\n  updateAllValues(event: any) {\n    this.options.showErrors = true;\n    this.checkboxList.forEach(t => t.checked = event.checked);\n    this.updateValue();\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-chip-list.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { AbstractControl } from '@angular/forms';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n// TODO: Add this control\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-chip-list-widget',\n  template: ``,\n})\nexport class MaterialChipListComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-datepicker.component.ts",
    "content": "import { Component, Inject, Input, OnInit, Optional } from '@angular/core';\nimport { AbstractControl } from '@angular/forms';\nimport { JsonSchemaFormService } from '@ajsf/core';\nimport { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-datepicker-widget',\n  template: `\n    <mat-form-field [appearance]=\"options?.appearance || matFormFieldDefaultOptions?.appearance || 'standard'\"\n                    [class]=\"options?.htmlClass || ''\"\n                    [floatLabel]=\"options?.floatLabel || matFormFieldDefaultOptions?.floatLabel || (options?.notitle ? 'never' : 'auto')\"\n                    [hideRequiredMarker]=\"options?.hideRequired ? 'true' : 'false'\"\n                    [style.width]=\"'100%'\">\n      <mat-label *ngIf=\"!options?.notitle\">{{options?.title}}</mat-label>\n      <span matPrefix *ngIf=\"options?.prefix || options?.fieldAddonLeft\"\n        [innerHTML]=\"options?.prefix || options?.fieldAddonLeft\"></span>\n      <input matInput *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.list]=\"'control' + layoutNode?._id + 'Autocomplete'\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [id]=\"'control' + layoutNode?._id\"\n        [max]=\"options?.maximum\"\n        [matDatepicker]=\"picker\"\n        [min]=\"options?.minimum\"\n        [name]=\"controlName\"\n        [placeholder]=\"options?.title\"\n        [readonly]=\"options?.readonly\"\n        [required]=\"options?.required\"\n        [style.width]=\"'100%'\"\n        (blur)=\"options.showErrors = true\"\n        >\n      <input matInput *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.list]=\"'control' + layoutNode?._id + 'Autocomplete'\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [disabled]=\"controlDisabled || options?.readonly\"\n        [id]=\"'control' + layoutNode?._id\"\n        [max]=\"options?.maximum\"\n        [matDatepicker]=\"picker\"\n        [min]=\"options?.minimum\"\n        [name]=\"controlName\"\n        [placeholder]=\"options?.title\"\n        [required]=\"options?.required\"\n        [style.width]=\"'100%'\"\n        [readonly]=\"options?.readonly\"\n        (blur)=\"options.showErrors = true\"\n        >\n      <span matSuffix *ngIf=\"options?.suffix || options?.fieldAddonRight\"\n        [innerHTML]=\"options?.suffix || options?.fieldAddonRight\"></span>\n      <mat-hint *ngIf=\"options?.description && (!options?.showErrors || !options?.errorMessage)\"\n        align=\"end\" [innerHTML]=\"options?.description\"></mat-hint>\n      <mat-datepicker-toggle matSuffix [for]=\"picker\"></mat-datepicker-toggle>\n    </mat-form-field>\n    <mat-datepicker #picker ></mat-datepicker>\n    <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n      [innerHTML]=\"options?.errorMessage\"></mat-error>`,\n  styles: [`\n    mat-error { font-size: 75%; margin-top: -1rem; margin-bottom: 0.5rem; }\n    ::ng-deep json-schema-form mat-form-field .mat-form-field-wrapper .mat-form-field-flex\n      .mat-form-field-infix { width: initial; }\n  `],\n})\nexport class MaterialDatepickerComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  dateValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  autoCompleteList: string[] = [];\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) @Optional() public matFormFieldDefaultOptions,\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this, !this.options.readonly);\n    if (!this.options.notitle && !this.options.description && this.options.placeholder) {\n      this.options.description = this.options.placeholder;\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-file.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n// TODO: Add this control\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-file-widget',\n  template: ``,\n})\nexport class MaterialFileComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-input.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport {Component, Inject, Input, OnInit, Optional} from '@angular/core';\nimport { JsonSchemaFormService } from '@ajsf/core';\nimport { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-input-widget',\n  template: `\n    <mat-form-field [appearance]=\"options?.appearance || matFormFieldDefaultOptions?.appearance || 'standard'\"\n      [class]=\"options?.htmlClass || ''\"\n      [floatLabel]=\"options?.floatLabel || matFormFieldDefaultOptions?.floatLabel || (options?.notitle ? 'never' : 'auto')\"\n      [hideRequiredMarker]=\"options?.hideRequired ? 'true' : 'false'\"\n      [style.width]=\"'100%'\">\n      <mat-label *ngIf=\"!options?.notitle\">{{options?.title}}</mat-label>\n      <span matPrefix *ngIf=\"options?.prefix || options?.fieldAddonLeft\"\n        [innerHTML]=\"options?.prefix || options?.fieldAddonLeft\"></span>\n      <input matInput *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.list]=\"'control' + layoutNode?._id + 'Autocomplete'\"\n        [attr.maxlength]=\"options?.maxLength\"\n        [attr.minlength]=\"options?.minLength\"\n        [attr.pattern]=\"options?.pattern\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [placeholder]=\"options?.notitle ? options?.placeholder : options?.title\"\n        [required]=\"options?.required\"\n        [style.width]=\"'100%'\"\n        [type]=\"layoutNode?.type\"\n        (blur)=\"options.showErrors = true\">\n      <input matInput *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.list]=\"'control' + layoutNode?._id + 'Autocomplete'\"\n        [attr.maxlength]=\"options?.maxLength\"\n        [attr.minlength]=\"options?.minLength\"\n        [attr.pattern]=\"options?.pattern\"\n        [disabled]=\"controlDisabled\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [placeholder]=\"options?.notitle ? options?.placeholder : options?.title\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [required]=\"options?.required\"\n        [style.width]=\"'100%'\"\n        [type]=\"layoutNode?.type\"\n        [value]=\"controlValue\"\n        (input)=\"updateValue($event)\"\n        (blur)=\"options.showErrors = true\">\n      <span matSuffix *ngIf=\"options?.suffix || options?.fieldAddonRight\"\n        [innerHTML]=\"options?.suffix || options?.fieldAddonRight\"></span>\n      <mat-hint *ngIf=\"options?.description && (!options?.showErrors || !options?.errorMessage)\"\n        align=\"end\" [innerHTML]=\"options?.description\"></mat-hint>\n      <mat-autocomplete *ngIf=\"options?.typeahead?.source\">\n        <mat-option *ngFor=\"let word of options?.typeahead?.source\"\n          [value]=\"word\">{{word}}</mat-option>\n      </mat-autocomplete>\n    </mat-form-field>\n    <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n      [innerHTML]=\"options?.errorMessage\"></mat-error>`,\n  styles: [`\n    mat-error { font-size: 75%; margin-top: -1rem; margin-bottom: 0.5rem; }\n    ::ng-deep json-schema-form mat-form-field .mat-form-field-wrapper .mat-form-field-flex\n      .mat-form-field-infix { width: initial; }\n  `],\n})\nexport class MaterialInputComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: string;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  autoCompleteList: string[] = [];\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n\n  constructor(\n    @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) @Optional() public matFormFieldDefaultOptions,\n    private jsf: JsonSchemaFormService\n  ) {\n  }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n    if (!this.options.notitle && !this.options.description && this.options.placeholder) {\n      this.options.description = this.options.placeholder;\n    }\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-number.component.ts",
    "content": "import {Component, Inject, Input, OnInit, Optional} from '@angular/core';\nimport { AbstractControl } from '@angular/forms';\nimport { JsonSchemaFormService } from '@ajsf/core';\nimport { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-number-widget',\n  template: `\n    <mat-form-field [appearance]=\"options?.appearance || matFormFieldDefaultOptions?.appearance || 'standard'\"\n    [class]=\"options?.htmlClass || ''\"\n    [floatLabel]=\"options?.floatLabel || matFormFieldDefaultOptions?.floatLabel || (options?.notitle ? 'never' : 'auto')\"\n    [hideRequiredMarker]=\"options?.hideRequired ? 'true' : 'false'\"\n    [style.width]=\"'100%'\">\n    <mat-label *ngIf=\"!options?.notitle\">{{options?.title}}</mat-label>\n      <span matPrefix *ngIf=\"options?.prefix || options?.fieldAddonLeft\"\n        [innerHTML]=\"options?.prefix || options?.fieldAddonLeft\"></span>\n      <input matInput *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.max]=\"options?.maximum\"\n        [attr.min]=\"options?.minimum\"\n        [attr.step]=\"options?.multipleOf || options?.step || 'any'\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [placeholder]=\"options?.notitle ? options?.placeholder : options?.title\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [required]=\"options?.required\"\n        [style.width]=\"'100%'\"\n        [type]=\"'number'\"\n        (blur)=\"options.showErrors = true\">\n      <input matInput *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.max]=\"options?.maximum\"\n        [attr.min]=\"options?.minimum\"\n        [attr.step]=\"options?.multipleOf || options?.step || 'any'\"\n        [disabled]=\"controlDisabled\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [placeholder]=\"options?.notitle ? options?.placeholder : options?.title\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [required]=\"options?.required\"\n        [style.width]=\"'100%'\"\n        [type]=\"'number'\"\n        [value]=\"controlValue\"\n        (input)=\"updateValue($event)\"\n        (blur)=\"options.showErrors = true\">\n      <span matSuffix *ngIf=\"options?.suffix || options?.fieldAddonRight\"\n        [innerHTML]=\"options?.suffix || options?.fieldAddonRight\"></span>\n      <mat-hint *ngIf=\"layoutNode?.type === 'range'\" align=\"start\"\n        [innerHTML]=\"controlValue\"></mat-hint>\n      <mat-hint *ngIf=\"options?.description && (!options?.showErrors || !options?.errorMessage)\"\n        align=\"end\" [innerHTML]=\"options?.description\"></mat-hint>\n    </mat-form-field>\n    <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n      [innerHTML]=\"options?.errorMessage\"></mat-error>`,\n  styles: [`\n    mat-error { font-size: 75%; margin-top: -1rem; margin-bottom: 0.5rem; }\n    ::ng-deep json-schema-form mat-form-field .mat-form-field-wrapper .mat-form-field-flex\n      .mat-form-field-infix { width: initial; }\n  `],\n})\nexport class MaterialNumberComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  allowNegative = true;\n  allowDecimal = true;\n  allowExponents = false;\n  lastValidNumber = '';\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) @Optional() public matFormFieldDefaultOptions,\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n    if (this.layoutNode.dataType === 'integer') { this.allowDecimal = false; }\n    if (!this.options.notitle && !this.options.description && this.options.placeholder) {\n      this.options.description = this.options.placeholder;\n    }\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-one-of.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { AbstractControl } from '@angular/forms';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n// TODO: Add this control\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-one-of-widget',\n  template: ``,\n})\nexport class MaterialOneOfComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-radios.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService, buildTitleMap } from '@ajsf/core';\n\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-radios-widget',\n  template: `\n    <div>\n      <div *ngIf=\"options?.title\">\n        <label\n          [attr.for]=\"'control' + layoutNode?._id\"\n          [class]=\"options?.labelHtmlClass || ''\"\n          [style.display]=\"options?.notitle ? 'none' : ''\"\n          [innerHTML]=\"options?.title\"></label>\n      </div>\n      <mat-radio-group *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.required]=\"options?.required\"\n        [style.flex-direction]=\"flexDirection\"\n        [name]=\"controlName\"\n        (blur)=\"options.showErrors = true\">\n        <mat-radio-button *ngFor=\"let radioItem of radiosList\"\n          [id]=\"'control' + layoutNode?._id + '/' + radioItem?.name\"\n          [value]=\"radioItem?.value\">\n          <span [innerHTML]=\"radioItem?.name\"></span>\n        </mat-radio-button>\n      </mat-radio-group>\n      <mat-radio-group *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.readonly]=\"options?.readonly ? 'readonly' : null\"\n        [attr.required]=\"options?.required\"\n        [style.flex-direction]=\"flexDirection\"\n        [disabled]=\"controlDisabled || options?.readonly\"\n        [name]=\"controlName\"\n        [value]=\"controlValue\">\n        <mat-radio-button *ngFor=\"let radioItem of radiosList\"\n          [id]=\"'control' + layoutNode?._id + '/' + radioItem?.name\"\n          [value]=\"radioItem?.value\"\n          (click)=\"updateValue(radioItem?.value)\">\n          <span [innerHTML]=\"radioItem?.name\"></span>\n        </mat-radio-button>\n      </mat-radio-group>\n      <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n        [innerHTML]=\"options?.errorMessage\"></mat-error>\n    </div>`,\n  styles: [`\n    mat-radio-group { display: inline-flex; }\n    mat-radio-button { margin: 2px; }\n    mat-error { font-size: 75%; }\n  `]\n})\nexport class MaterialRadiosComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  flexDirection = 'column';\n  radiosList: any[] = [];\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    if (this.layoutNode.type === 'radios-inline') {\n      this.flexDirection = 'row';\n    }\n    this.radiosList = buildTitleMap(\n      this.options.titleMap || this.options.enumNames,\n      this.options.enum, true\n    );\n    this.jsf.initializeControl(this, !this.options.readonly);\n  }\n\n  updateValue(value) {\n    this.options.showErrors = true;\n    this.jsf.updateValue(this, value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-select.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport {Component, Inject, Input, OnInit, Optional} from '@angular/core';\nimport { JsonSchemaFormService, buildTitleMap, isArray } from '@ajsf/core';\nimport { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-select-widget',\n  template: `\n    <mat-form-field\n      [appearance]=\"options?.appearance || matFormFieldDefaultOptions?.appearance || 'standard'\"\n      [class]=\"options?.htmlClass || ''\"\n      [floatLabel]=\"options?.floatLabel || matFormFieldDefaultOptions?.floatLabel || (options?.notitle ? 'never' : 'auto')\"\n      [hideRequiredMarker]=\"options?.hideRequired ? 'true' : 'false'\"\n      [style.width]=\"'100%'\">\n      <mat-label *ngIf=\"!options?.notitle\">{{options?.title}}</mat-label>\n      <span matPrefix *ngIf=\"options?.prefix || options?.fieldAddonLeft\"\n        [innerHTML]=\"options?.prefix || options?.fieldAddonLeft\"></span>\n      <mat-select *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.name]=\"controlName\"\n        [id]=\"'control' + layoutNode?._id\"\n        [multiple]=\"options?.multiple\"\n        [placeholder]=\"options?.notitle ? options?.placeholder : options?.title\"\n        [required]=\"options?.required\"\n        [style.width]=\"'100%'\"\n        (blur)=\"options.showErrors = true\">\n        <ng-template ngFor let-selectItem [ngForOf]=\"selectList\">\n          <mat-option *ngIf=\"!isArray(selectItem?.items)\"\n            [value]=\"selectItem?.value\">\n            <span [innerHTML]=\"selectItem?.name\"></span>\n          </mat-option>\n          <mat-optgroup *ngIf=\"isArray(selectItem?.items)\"\n            [label]=\"selectItem?.group\">\n            <mat-option *ngFor=\"let subItem of selectItem.items\"\n              [value]=\"subItem?.value\">\n              <span [innerHTML]=\"subItem?.name\"></span>\n            </mat-option>\n          </mat-optgroup>\n        </ng-template>\n      </mat-select>\n      <mat-select *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.name]=\"controlName\"\n        [disabled]=\"controlDisabled || options?.readonly\"\n        [id]=\"'control' + layoutNode?._id\"\n        [multiple]=\"options?.multiple\"\n        [placeholder]=\"options?.notitle ? options?.placeholder : options?.title\"\n        [required]=\"options?.required\"\n        [style.width]=\"'100%'\"\n        [value]=\"controlValue\"\n        (blur)=\"options.showErrors = true\"\n        (change)=\"updateValue($event)\">\n        <ng-template ngFor let-selectItem [ngForOf]=\"selectList\">\n          <mat-option *ngIf=\"!isArray(selectItem?.items)\"\n            [attr.selected]=\"selectItem?.value === controlValue\"\n            [value]=\"selectItem?.value\">\n            <span [innerHTML]=\"selectItem?.name\"></span>\n          </mat-option>\n          <mat-optgroup *ngIf=\"isArray(selectItem?.items)\"\n            [label]=\"selectItem?.group\">\n            <mat-option *ngFor=\"let subItem of selectItem.items\"\n              [attr.selected]=\"subItem?.value === controlValue\"\n              [value]=\"subItem?.value\">\n              <span [innerHTML]=\"subItem?.name\"></span>\n            </mat-option>\n          </mat-optgroup>\n        </ng-template>\n      </mat-select>\n      <span matSuffix *ngIf=\"options?.suffix || options?.fieldAddonRight\"\n        [innerHTML]=\"options?.suffix || options?.fieldAddonRight\"></span>\n      <mat-hint *ngIf=\"options?.description && (!options?.showErrors || !options?.errorMessage)\"\n        align=\"end\" [innerHTML]=\"options?.description\"></mat-hint>\n    </mat-form-field>\n    <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n      [innerHTML]=\"options?.errorMessage\"></mat-error>`,\n  styles: [`\n    mat-error { font-size: 75%; margin-top: -1rem; margin-bottom: 0.5rem; }\n    ::ng-deep json-schema-form mat-form-field .mat-form-field-wrapper .mat-form-field-flex\n      .mat-form-field-infix { width: initial; }\n  `],\n})\nexport class MaterialSelectComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  selectList: any[] = [];\n  isArray = isArray;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) @Optional() public matFormFieldDefaultOptions,\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.selectList = buildTitleMap(\n      this.options.titleMap || this.options.enumNames,\n      this.options.enum, !!this.options.required, !!this.options.flatList\n    );\n    this.jsf.initializeControl(this, !this.options.readonly);\n    if (!this.options.notitle && !this.options.description && this.options.placeholder) {\n      this.options.description = this.options.placeholder;\n    }\n  }\n\n  updateValue(event) {\n    this.options.showErrors = true;\n    this.jsf.updateValue(this, event.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-slider.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-slider-widget',\n  template: `\n    <mat-slider thumbLabel *ngIf=\"boundControl\"\n      [formControl]=\"formControl\"\n      [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n      [id]=\"'control' + layoutNode?._id\"\n      [max]=\"options?.maximum\"\n      [min]=\"options?.minimum\"\n      [step]=\"options?.multipleOf || options?.step || 'any'\"\n      [style.width]=\"'100%'\"\n      (blur)=\"options.showErrors = true\"></mat-slider>\n    <mat-slider thumbLabel *ngIf=\"!boundControl\"\n      [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n      [disabled]=\"controlDisabled || options?.readonly\"\n      [id]=\"'control' + layoutNode?._id\"\n      [max]=\"options?.maximum\"\n      [min]=\"options?.minimum\"\n      [step]=\"options?.multipleOf || options?.step || 'any'\"\n      [style.width]=\"'100%'\"\n      [value]=\"controlValue\"\n      (blur)=\"options.showErrors = true\"\n      (change)=\"updateValue($event)\"></mat-slider>\n    <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n      [innerHTML]=\"options?.errorMessage\"></mat-error>`,\n    styles: [` mat-error { font-size: 75%; } `],\n})\nexport class MaterialSliderComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  allowNegative = true;\n  allowDecimal = true;\n  allowExponents = false;\n  lastValidNumber = '';\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this, !this.options.readonly);\n  }\n\n  updateValue(event) {\n    this.options.showErrors = true;\n    this.jsf.updateValue(this, event.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-stepper.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n// TODO: Add this control\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-stepper-widget',\n  template: ``,\n})\nexport class MaterialStepperComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-tabs.component.ts",
    "content": "import { Component, Input, OnInit } from '@angular/core';\nimport { JsonSchemaFormService } from '@ajsf/core';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-tabs-widget',\n  template: `\n    <nav mat-tab-nav-bar\n      [attr.aria-label]=\"options?.label || options?.title || ''\"\n      [style.width]=\"'100%'\">\n        <a mat-tab-link *ngFor=\"let item of layoutNode?.items; let i = index\"\n          [active]=\"selectedItem === i\"\n          (click)=\"select(i)\">\n          <span *ngIf=\"showAddTab || item.type !== '$ref'\"\n            [innerHTML]=\"setTabTitle(item, i)\"></span>\n        </a>\n    </nav>\n    <div *ngFor=\"let layoutItem of layoutNode?.items; let i = index\"\n      [class]=\"options?.htmlClass || ''\">\n      <select-framework-widget *ngIf=\"selectedItem === i\"\n        [class]=\"(options?.fieldHtmlClass || '') + ' ' + (options?.activeClass || '') + ' ' + (options?.style?.selected || '')\"\n        [dataIndex]=\"layoutNode?.dataType === 'array' ? (dataIndex || []).concat(i) : dataIndex\"\n        [layoutIndex]=\"(layoutIndex || []).concat(i)\"\n        [layoutNode]=\"layoutItem\"></select-framework-widget>\n    </div>`,\n  styles: [` a { cursor: pointer; } `],\n})\nexport class MaterialTabsComponent implements OnInit {\n  options: any;\n  itemCount: number;\n  selectedItem = 0;\n  showAddTab = true;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.itemCount = this.layoutNode.items.length - 1;\n    this.updateControl();\n  }\n\n  select(index) {\n    if (this.layoutNode.items[index].type === '$ref') {\n      this.jsf.addItem({\n        layoutNode: this.layoutNode.items[index],\n        layoutIndex: this.layoutIndex.concat(index),\n        dataIndex: this.dataIndex.concat(index)\n      });\n      this.updateControl();\n    }\n    this.selectedItem = index;\n  }\n\n  updateControl() {\n    this.itemCount = this.layoutNode.items.length - 1;\n    const lastItem = this.layoutNode.items[this.layoutNode.items.length - 1];\n    this.showAddTab = lastItem.type === '$ref' &&\n      this.itemCount < (lastItem.options.maxItems || 1000);\n  }\n\n  setTabTitle(item: any, index: number): string {\n    return this.jsf.setArrayItemTitle(this, item, index);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/material-textarea.component.ts",
    "content": "import { AbstractControl } from '@angular/forms';\nimport {Component, Inject, Input, OnInit, Optional} from '@angular/core';\nimport { JsonSchemaFormService } from '@ajsf/core';\nimport { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';\n\n@Component({\n  // tslint:disable-next-line:component-selector\n  selector: 'material-textarea-widget',\n  template: `\n    <mat-form-field [appearance]=\"options?.appearance || matFormFieldDefaultOptions?.appearance || 'standard'\"\n      [class]=\"options?.htmlClass || ''\"\n      [floatLabel]=\"options?.floatLabel || matFormFieldDefaultOptions?.floatLabel || (options?.notitle ? 'never' : 'auto')\"\n      [hideRequiredMarker]=\"options?.hideRequired ? 'true' : 'false'\"\n      [style.width]=\"'100%'\">\n      <mat-label *ngIf=\"!options?.notitle\">{{options?.title}}</mat-label>\n      <span matPrefix *ngIf=\"options?.prefix || options?.fieldAddonLeft\"\n        [innerHTML]=\"options?.prefix || options?.fieldAddonLeft\"></span>\n      <textarea matInput *ngIf=\"boundControl\"\n        [formControl]=\"formControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.list]=\"'control' + layoutNode?._id + 'Autocomplete'\"\n        [attr.maxlength]=\"options?.maxLength\"\n        [attr.minlength]=\"options?.minLength\"\n        [attr.pattern]=\"options?.pattern\"\n        [required]=\"options?.required\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [placeholder]=\"options?.notitle ? options?.placeholder : options?.title\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [style.width]=\"'100%'\"\n        (blur)=\"options.showErrors = true\"></textarea>\n      <textarea matInput *ngIf=\"!boundControl\"\n        [attr.aria-describedby]=\"'control' + layoutNode?._id + 'Status'\"\n        [attr.list]=\"'control' + layoutNode?._id + 'Autocomplete'\"\n        [attr.maxlength]=\"options?.maxLength\"\n        [attr.minlength]=\"options?.minLength\"\n        [attr.pattern]=\"options?.pattern\"\n        [required]=\"options?.required\"\n        [disabled]=\"controlDisabled\"\n        [id]=\"'control' + layoutNode?._id\"\n        [name]=\"controlName\"\n        [placeholder]=\"options?.notitle ? options?.placeholder : options?.title\"\n        [readonly]=\"options?.readonly ? 'readonly' : null\"\n        [style.width]=\"'100%'\"\n        [value]=\"controlValue\"\n        (input)=\"updateValue($event)\"\n        (blur)=\"options.showErrors = true\"></textarea>\n      <span matSuffix *ngIf=\"options?.suffix || options?.fieldAddonRight\"\n        [innerHTML]=\"options?.suffix || options?.fieldAddonRight\"></span>\n      <mat-hint *ngIf=\"options?.description && (!options?.showErrors || !options?.errorMessage)\"\n        align=\"end\" [innerHTML]=\"options?.description\"></mat-hint>\n    </mat-form-field>\n    <mat-error *ngIf=\"options?.showErrors && options?.errorMessage\"\n      [innerHTML]=\"options?.errorMessage\"></mat-error>`,\n  styles: [`\n    mat-error { font-size: 75%; margin-top: -1rem; margin-bottom: 0.5rem; }\n    ::ng-deep json-schema-form mat-form-field .mat-form-field-wrapper .mat-form-field-flex\n      .mat-form-field-infix { width: initial; }\n  `],\n})\nexport class MaterialTextareaComponent implements OnInit {\n  formControl: AbstractControl;\n  controlName: string;\n  controlValue: any;\n  controlDisabled = false;\n  boundControl = false;\n  options: any;\n  @Input() layoutNode: any;\n  @Input() layoutIndex: number[];\n  @Input() dataIndex: number[];\n\n  constructor(\n    @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) @Optional() public matFormFieldDefaultOptions,\n    private jsf: JsonSchemaFormService\n  ) { }\n\n  ngOnInit() {\n    this.options = this.layoutNode.options || {};\n    this.jsf.initializeControl(this);\n    if (!this.options.notitle && !this.options.description && this.options.placeholder) {\n      this.options.description = this.options.placeholder;\n    }\n  }\n\n  updateValue(event) {\n    this.jsf.updateValue(this, event.target.value);\n  }\n}\n"
  },
  {
    "path": "projects/ajsf-material/src/lib/widgets/public_api.ts",
    "content": "import { FlexLayoutRootComponent } from './flex-layout-root.component';\nimport { FlexLayoutSectionComponent } from './flex-layout-section.component';\nimport { MaterialAddReferenceComponent } from './material-add-reference.component';\nimport { MaterialButtonComponent } from './material-button.component';\nimport { MaterialButtonGroupComponent } from './material-button-group.component';\nimport { MaterialCheckboxComponent } from './material-checkbox.component';\nimport { MaterialCheckboxesComponent } from './material-checkboxes.component';\nimport { MaterialChipListComponent } from './material-chip-list.component';\nimport { MaterialDatepickerComponent } from './material-datepicker.component';\nimport { MaterialDesignFrameworkComponent } from '../material-design-framework.component';\nimport { MaterialFileComponent } from './material-file.component';\nimport { MaterialInputComponent } from './material-input.component';\nimport { MaterialNumberComponent } from './material-number.component';\nimport { MaterialOneOfComponent } from './material-one-of.component';\nimport { MaterialRadiosComponent } from './material-radios.component';\nimport { MaterialSelectComponent } from './material-select.component';\nimport { MaterialSliderComponent } from './material-slider.component';\nimport { MaterialStepperComponent } from './material-stepper.component';\nimport { MaterialTabsComponent } from './material-tabs.component';\nimport { MaterialTextareaComponent } from './material-textarea.component';\n\n\nexport const MATERIAL_FRAMEWORK_COMPONENTS = [\n  FlexLayoutRootComponent, FlexLayoutSectionComponent,\n  MaterialAddReferenceComponent, MaterialOneOfComponent,\n  MaterialButtonComponent, MaterialButtonGroupComponent,\n  MaterialCheckboxComponent, MaterialCheckboxesComponent,\n  MaterialChipListComponent, MaterialDatepickerComponent,\n  MaterialFileComponent, MaterialInputComponent, MaterialNumberComponent,\n  MaterialRadiosComponent, MaterialSelectComponent, MaterialSliderComponent,\n  MaterialStepperComponent, MaterialTabsComponent, MaterialTextareaComponent,\n  MaterialDesignFrameworkComponent\n];\n\nexport { FlexLayoutRootComponent } from './flex-layout-root.component';\nexport { FlexLayoutSectionComponent } from './flex-layout-section.component';\nexport { MaterialAddReferenceComponent } from './material-add-reference.component';\nexport { MaterialOneOfComponent } from './material-one-of.component';\nexport { MaterialButtonComponent } from './material-button.component';\nexport { MaterialButtonGroupComponent } from './material-button-group.component';\nexport { MaterialCheckboxComponent } from './material-checkbox.component';\nexport { MaterialCheckboxesComponent } from './material-checkboxes.component';\nexport { MaterialChipListComponent } from './material-chip-list.component';\nexport { MaterialDatepickerComponent } from './material-datepicker.component';\nexport { MaterialFileComponent } from './material-file.component';\nexport { MaterialInputComponent } from './material-input.component';\nexport { MaterialNumberComponent } from './material-number.component';\nexport { MaterialRadiosComponent } from './material-radios.component';\nexport { MaterialSelectComponent } from './material-select.component';\nexport { MaterialSliderComponent } from './material-slider.component';\nexport { MaterialStepperComponent } from './material-stepper.component';\nexport { MaterialTabsComponent } from './material-tabs.component';\nexport { MaterialTextareaComponent } from './material-textarea.component';\nexport { MaterialDesignFrameworkComponent } from '../material-design-framework.component';\n"
  },
  {
    "path": "projects/ajsf-material/src/public_api.ts",
    "content": "/*\n * Public API Surface of @ajsf/material-framework\n */\n\nexport * from './lib/material-design.framework';\nexport * from './lib/material-design-framework.module';\nexport * from './lib/material-design-framework.component';\nexport * from './lib/widgets/public_api';\n"
  },
  {
    "path": "projects/ajsf-material/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js';\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(), {\n    teardown: { destroyAfterEach: false }\n}\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "projects/ajsf-material/tsconfig.lib.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/lib\",\n    \"declarationMap\": true,\n    \"target\": \"es2020\",\n    \"declaration\": true,\n    \"inlineSources\": true,\n    \"types\": [],\n    \"lib\": [\n      \"dom\",\n      \"es2018\"\n    ]\n  },\n  \"angularCompilerOptions\": {\n    \"skipTemplateCodegen\": true,\n    \"strictMetadataEmit\": true,\n    \"fullTemplateTypeCheck\": true,\n    \"strictInjectionParameters\": true,\n    \"enableResourceInlining\": true\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-material/tsconfig.lib.prod.json",
    "content": "{\n  \"extends\": \"./tsconfig.lib.json\",\n  \"compilerOptions\": {\n    \"declarationMap\": false\n  },\n  \"angularCompilerOptions\": {\n    \"compilationMode\": \"partial\"\n  }\n}"
  },
  {
    "path": "projects/ajsf-material/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ajsf-material/tslint.json",
    "content": "{\n  \"extends\": \"../../tslint.json\",\n  \"rules\": {\n    \"directive-selector\": [\n      true,\n      \"attribute\",\n      \"lib\",\n      \"camelCase\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"es2020\",\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"allowSyntheticDefaultImports\": true,\n    \"resolveJsonModule\": true,\n    \"typeRoots\": [\n      \"node_modules/@types\",\n      \"manual_typings\"\n    ],\n    \"lib\": [\n      \"es2018\",\n      \"dom\"\n    ],\n    \"paths\": {\n      \"@angular/*\": [\n        \"./node_modules/@angular/*\"\n      ],\n      \"lodash/*\": [\n        \"node_modules/@types/lodash-es/*\"\n      ],\n      \"@ajsf/core\": [\n        \"dist/@ajsf/core\"\n      ],\n      \"@ajsf/core/*\": [\n        \"dist/@ajsf/core/*\"\n      ],\n      \"@ajsf/bootstrap3\": [\n        \"dist/@ajsf/bootstrap3\"\n      ],\n      \"@ajsf/bootstrap3/*\": [\n        \"dist/@ajsf/bootstrap3/*\"\n      ],\n      \"@ajsf/bootstrap4\": [\n        \"dist/@ajsf/bootstrap4\"\n      ],\n      \"@ajsf/bootstrap4/*\": [\n        \"dist/@ajsf/bootstrap4/*\"\n      ],\n      \"@ajsf/material\": [\n        \"dist/@ajsf/material\"\n      ],\n      \"@ajsf/material/*\": [\n        \"dist/@ajsf/material/*\"\n      ]\n    }\n  },\n  \"angularCompilerOptions\": {\n    \"fullTemplateTypeCheck\": true,\n    \"strictInjectionParameters\": true\n  }\n}"
  },
  {
    "path": "tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\",\n      \"lodash\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      100\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}"
  }
]