Repository: Choices-js/Choices Branch: main Commit: 59feffe87448 Files: 199 Total size: 2.2 MB Directory structure: gitextract_040cemen/ ├── .browserslistrc ├── .codebeatignore ├── .codecov.yml ├── .editorconfig ├── .eslintrc.json ├── .git-blame-ignore-revs ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── actions-scripts/ │ │ └── polyfills-sync.cjs │ ├── release-drafter.yml │ └── workflows/ │ ├── browsers.yml │ ├── bundlesize.yml │ ├── deploy-pages.yml │ ├── deployment.yml │ ├── lint.yml │ ├── polyfills-sync.yml │ ├── release-drafter.yml │ └── unit-tests.yml ├── .gitignore ├── .nvmrc ├── .prettierrc.json ├── .stylelintrc.json ├── .vscode/ │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.json ├── jsconfig.json ├── package.json ├── playwright.config.ts ├── public/ │ ├── assets/ │ │ ├── images/ │ │ │ ├── browserconfig.xml │ │ │ └── manifest.json │ │ ├── scripts/ │ │ │ ├── choices.js │ │ │ ├── choices.mjs │ │ │ ├── choices.search-basic.js │ │ │ ├── choices.search-basic.mjs │ │ │ ├── choices.search-kmp.js │ │ │ ├── choices.search-kmp.mjs │ │ │ ├── choices.search-prefix.js │ │ │ └── choices.search-prefix.mjs │ │ └── styles/ │ │ ├── base.css │ │ └── choices.css │ ├── index.html │ ├── robots.txt │ ├── test/ │ │ ├── data.json │ │ ├── disabled-data.json │ │ ├── select-multiple/ │ │ │ ├── index-performance.html │ │ │ └── index.html │ │ ├── select-one/ │ │ │ └── index.html │ │ └── text/ │ │ └── index.html │ └── types/ │ └── src/ │ ├── index.d.ts │ └── scripts/ │ ├── actions/ │ │ ├── choices.d.ts │ │ ├── groups.d.ts │ │ └── items.d.ts │ ├── choices.d.ts │ ├── components/ │ │ ├── container.d.ts │ │ ├── dropdown.d.ts │ │ ├── index.d.ts │ │ ├── input.d.ts │ │ ├── list.d.ts │ │ ├── wrapped-element.d.ts │ │ ├── wrapped-input.d.ts │ │ └── wrapped-select.d.ts │ ├── constants.d.ts │ ├── defaults.d.ts │ ├── interfaces/ │ │ ├── action-type.d.ts │ │ ├── build-flags.d.ts │ │ ├── choice-full.d.ts │ │ ├── class-names.d.ts │ │ ├── event-choice.d.ts │ │ ├── event-type.d.ts │ │ ├── group-full.d.ts │ │ ├── index.d.ts │ │ ├── input-choice.d.ts │ │ ├── input-group.d.ts │ │ ├── item.d.ts │ │ ├── keycode-map.d.ts │ │ ├── options.d.ts │ │ ├── passed-element-type.d.ts │ │ ├── passed-element.d.ts │ │ ├── position-options-type.d.ts │ │ ├── search.d.ts │ │ ├── state.d.ts │ │ ├── store.d.ts │ │ ├── string-pre-escaped.d.ts │ │ ├── string-untrusted.d.ts │ │ ├── templates.d.ts │ │ └── types.d.ts │ ├── lib/ │ │ ├── choice-input.d.ts │ │ ├── html-guard-statements.d.ts │ │ └── utils.d.ts │ ├── reducers/ │ │ ├── choices.d.ts │ │ ├── groups.d.ts │ │ └── items.d.ts │ ├── search/ │ │ ├── fuse.d.ts │ │ ├── index.d.ts │ │ ├── kmp.d.ts │ │ └── prefix-filter.d.ts │ ├── store/ │ │ └── store.d.ts │ └── templates.d.ts ├── scripts/ │ ├── lint-staged.config.js │ ├── rollup.config.mjs │ └── server.mjs ├── src/ │ ├── entry.js │ ├── index.ts │ ├── scripts/ │ │ ├── actions/ │ │ │ ├── choices.ts │ │ │ ├── groups.ts │ │ │ └── items.ts │ │ ├── choices.ts │ │ ├── components/ │ │ │ ├── container.ts │ │ │ ├── dropdown.ts │ │ │ ├── index.ts │ │ │ ├── input.ts │ │ │ ├── list.ts │ │ │ ├── wrapped-element.ts │ │ │ ├── wrapped-input.ts │ │ │ └── wrapped-select.ts │ │ ├── constants.ts │ │ ├── defaults.ts │ │ ├── interfaces/ │ │ │ ├── action-type.ts │ │ │ ├── build-flags.ts │ │ │ ├── choice-full.ts │ │ │ ├── class-names.ts │ │ │ ├── event-choice.ts │ │ │ ├── event-type.ts │ │ │ ├── group-full.ts │ │ │ ├── index.ts │ │ │ ├── input-choice.ts │ │ │ ├── input-group.ts │ │ │ ├── item.ts │ │ │ ├── keycode-map.ts │ │ │ ├── options.ts │ │ │ ├── passed-element-type.ts │ │ │ ├── passed-element.ts │ │ │ ├── position-options-type.ts │ │ │ ├── search.ts │ │ │ ├── state.ts │ │ │ ├── store.ts │ │ │ ├── string-pre-escaped.ts │ │ │ ├── string-untrusted.ts │ │ │ ├── templates.ts │ │ │ └── types.ts │ │ ├── lib/ │ │ │ ├── choice-input.ts │ │ │ ├── html-guard-statements.ts │ │ │ └── utils.ts │ │ ├── reducers/ │ │ │ ├── choices.ts │ │ │ ├── groups.ts │ │ │ └── items.ts │ │ ├── search/ │ │ │ ├── fuse.ts │ │ │ ├── index.ts │ │ │ ├── kmp.ts │ │ │ └── prefix-filter.ts │ │ ├── store/ │ │ │ └── store.ts │ │ └── templates.ts │ ├── styles/ │ │ ├── base.scss │ │ └── choices.scss │ └── tsconfig.json ├── test/ │ ├── scripts/ │ │ ├── actions/ │ │ │ ├── choices.test.ts │ │ │ ├── groups.test.ts │ │ │ └── items.test.ts │ │ ├── choices.test.ts │ │ ├── components/ │ │ │ ├── container.test.ts │ │ │ ├── dropdown.test.ts │ │ │ ├── input.test.ts │ │ │ ├── list.test.ts │ │ │ ├── wrapped-element.test.ts │ │ │ ├── wrapped-input.test.ts │ │ │ └── wrapped-select.test.ts │ │ ├── lib/ │ │ │ └── utils.test.ts │ │ ├── reducers/ │ │ │ ├── choices.test.ts │ │ │ ├── groups.test.ts │ │ │ └── items.test.ts │ │ ├── search/ │ │ │ └── index.test.ts │ │ ├── store/ │ │ │ └── store.test.ts │ │ └── templates.test.ts │ ├── setupFiles/ │ │ └── window-matchMedia.ts │ └── tsconfig.json ├── test-e2e/ │ ├── bundle-test.ts │ ├── hars/ │ │ ├── 0432285dab6a62ab5e6efaf4cb1272720e0c3f1b.json │ │ ├── 69c6136c671f1173ee6d6596d125e821dea44a4f.json │ │ ├── a8763b95c9f6c98156a9100e8bed82cb17b93ecd.json │ │ └── discogs.har │ ├── select-test-suit.ts │ ├── test-suit.ts │ ├── tests/ │ │ ├── demo-page.spec.ts │ │ ├── select-multiple-performance.spec.ts │ │ ├── select-multiple.spec.ts │ │ ├── select-one.spec.ts │ │ └── text.spec.ts │ ├── text-test-suit.ts │ └── tsconfig.json └── vitest.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .browserslistrc ================================================ > 1% ================================================ FILE: .codebeatignore ================================================ public/** webpack.config.*.js *.js ================================================ FILE: .codecov.yml ================================================ coverage: parsers: javascript: enable_partials: yes ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true ================================================ FILE: .eslintrc.json ================================================ { "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint", "prettier", "sort-class-members"], "extends": [ "airbnb-base", "airbnb-typescript", "plugin:prettier/recommended", "plugin:compat/recommended", "plugin:@typescript-eslint/recommended" ], "env": { "es6": true, "node": true, "browser": true }, "parserOptions": { "sourceType": "module", "project": true }, "rules": { "no-param-reassign": ["error", { "props": false }], "@typescript-eslint/explicit-function-return-type": "error", "import/no-named-as-default": "off", "import/prefer-default-export": "off", "import/no-extraneous-dependencies": [ "error", { "devDependencies": true } ], "no-console": [ "warn", { "allow": ["warn", "error"] } ], "no-plusplus": "off", "no-unused-expressions": "off", "no-underscore-dangle": "off", "consistent-return": "off", "import/no-useless-path-segments": "warn", "prefer-destructuring": [ "warn", { "array": false, "object": true } ], "curly": ["error", "all"], "newline-before-return": "error", "sort-class-members/sort-class-members": [ 2, { "order": [ "[static-properties]", "[static-methods]", "[properties]", "[conventional-private-properties]", "constructor", "[methods]", "[conventional-private-methods]" ], "accessorPairPositioning": "getThenSet" } ], "lines-between-class-members": "off", "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-namespace": "off", "react/jsx-filename-extension": [0], "import/extensions": [ "error", "ignorePackages", { "js": "never", "mjs": "never", "jsx": "never", "ts": "never", "tsx": "never" } ] }, "overrides": [ { "files": ["*.test.ts", "*.spec.ts"], "rules": { "no-await-in-loop": "off", "@typescript-eslint/explicit-function-return-type": "off", "no-restricted-syntax": "off", "compat/compat": "off", "no-new": "off", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/naming-convention": [ "error", { "selector": "default", "format": ["camelCase", "PascalCase", "UPPER_CASE"], "leadingUnderscore": "allow" } ] } } ], "settings": { "polyfills": [ "Array.from", "Array.prototype.find", "Array.prototype.includes", "Symbol", "Symbol.iterator", "DOMTokenList", "Object.assign", "CustomEvent", "Element.prototype.classList", "Element.prototype.closest", "Element.prototype.dataset", "Element.prototype.replaceChildren" ], "import/resolver": { "node": { "extensions": [".js", ".ts"] } } }, "ignorePatterns": ["node_modules/*", "public/*"] } ================================================ FILE: .git-blame-ignore-revs ================================================ # byte shaving (hoist semi-commonly variables, remove some low level low-usage functions) 157a47a44a01e3ce4b54ad211b1756ff59985bef 5bee41d7ff08e05442b232e3e552dcb6c703568d e9382df0ae63edfc7540f82f74cf969342c759c0 # prettier config change 00433d200d8cccc8b544fbc8f05d5e96bf8ccff7 # misc linting cleanup 00009d2effa8b41a6ce27ef8b06a35a04215aea6 62b786d1f13d0934137a62909d3a37db0a3e927e 5ad61841143508c9f91f0edd57f81f8b11066e0a 84a61cad1ddab1e851c98efa619a2cd35af434c1 33f573247e8badc9ee10defe326f13985342e09b b0199538a82d49de429f35546e412d14fc8bfeb9 ================================================ FILE: .gitattributes ================================================ ## GITATTRIBUTES FOR WEB PROJECTS # # These settings are for any web project. # # Details per file setting: # text These files should be normalized (i.e. convert CRLF to LF). # binary These files are binary and should be left untouched. # # Note that binary is a macro for -text -diff. ###################################################################### # Auto detect ## Handle line endings automatically for files detected as ## text and leave all files detected as binary untouched. ## This will handle all files NOT defined below. * text eol=lf # Source code *.css text eol=lf *.html text diff=html eol=lf *.js text eol=lf *.json text eol=lf *.scss text diff=css eol=lf *.ts text eol=lf # Documentation *.md text eol=lf *.txt text eol=lf AUTHORS text eol=lf CHANGELOG text eol=lf CHANGES text eol=lf CONTRIBUTING text eol=lf COPYING text eol=lf copyright text eol=lf *COPYRIGHT* text eol=lf INSTALL text eol=lf license text eol=lf LICENSE text eol=lf NEWS text eol=lf readme text eol=lf *README* text eol=lf TODO text eol=lf # Linters .eslintrc text eol=lf .stylelintrc text eol=lf # Configs .babelrc text eol=lf .browserslistrc text eol=lf .editorconfig text eol=lf .env text eol=lf .gitattributes text eol=lf .gitconfig text eol=lf package-lock.json text -diff eol=lf *.npmignore text eol=lf *.yaml text eol=lf *.yml text eol=lf browserslist text eol=lf # Graphics # SVG treated as an asset (binary) by default. *.svg text eol=lf *.png binary ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Using https://jsfiddle.net/ to create a minimal reproducible example. Otherwise Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Choices version and bundle** - Version: [e.g. v11.0.0 choices.min.js] **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: feature request assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Description ## Screenshots (if appropriate) ## Types of changes - [ ] Chore (tooling change or documentation change) - [ ] Refactor (non-breaking change which maintains existing functionality) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) ## Checklist - [ ] My code follows the code style of this project. - [ ] I have added new tests for the bug I fixed/the new feature I added. - [ ] I have modified existing tests for the bug I fixed/the new feature I added. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. ================================================ FILE: .github/actions-scripts/polyfills-sync.cjs ================================================ const { readFileSync } = require('fs'); const path = require('path'); const assert = require('assert'); const readme = readFileSync(path.resolve(__dirname, '../../README.md'), 'utf8'); const polyfillsFromDocs = /^```polyfills\s*\n([^`]+)\n^```/m .exec(readme)[1] .split('\n') .map(v => v.trim()) .sort(); // @ts-ignore const polyfillsFromSettings = require('../../.eslintrc.json').settings.polyfills.sort(); assert.deepStrictEqual(polyfillsFromDocs, polyfillsFromSettings); ================================================ FILE: .github/release-drafter.yml ================================================ name-template: 'Draft (next release)' tag-template: 'v$NEXT_PATCH_VERSION' sort-direction: descending exclude-labels: - 'skip-changelog' - 'release' categories: - title: '🚨 Breaking changes' labels: - 'breaking change' - title: '🚀 Features' labels: - 'feature' - 'enhancement' - title: '🐛 Bug Fixes' labels: - 'bugfix' - title: '🔧 Maintenance' labels: - 'chore' - 'housekeeping' - 'refactor' - 'documentation' change-template: '- $TITLE @$AUTHOR (#$NUMBER)' template: | # Changes $CHANGES # Contributors $CONTRIBUTORS ================================================ FILE: .github/workflows/browsers.yml ================================================ name: End-to-end tests (playwright) on: push: branches: [ main ] paths: - 'src/**' - 'test-e2e/**' - 'package-lock.json' - '.browserslistrc' - 'babel.config.json' - 'public/index.html' - 'public/**/index.html' - '.github/workflows/browsers.yml' - 'playwright.config.ts' pull_request: paths: - 'src/**' - 'test-e2e/**' - 'package-lock.json' - '.browserslistrc' - 'babel.config.json' - 'public/index.html' - 'public/**/index.html' - '.github/workflows/browsers.yml' - 'playwright.config.ts' jobs: test-e2e-playwright: timeout-minutes: 60 strategy: fail-fast: false matrix: os: [windows-latest, macos-latest, ubuntu-latest] browser: [chromium, firefox, webkit] exclude: - os: windows-latest browser: webkit - os: windows-latest browser: firefox - os: macos-latest browser: firefox runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - uses: actions/setup-node@v4 with: node-version: 24 cache: 'npm' - name: Install dependencies run: npm ci --no-audit - name: Install Playwright Browsers run: npx playwright install --with-deps - run: npx playwright install-deps - name: Run Playwright tests run: npx playwright test --project=${{ matrix.browser }} - uses: actions/upload-artifact@v4 name: Upload screenshots to GitHub Actions Artifacts if: failure() with: name: screenshot-${{ matrix.os }}-${{ matrix.browser }} path: test-results/**/*.png - uses: actions/upload-artifact@v4 name: Upload blob report to GitHub Actions Artifacts if: ${{ !cancelled() }} with: name: playwright-report-${{ matrix.os }}-${{ matrix.browser }} path: playwright-report/ retention-days: 30 ================================================ FILE: .github/workflows/bundlesize.yml ================================================ name: Bundle size checks on: push: branches: [ main ] paths: - '.github/workflows/bundlesize.yml' - 'src/scripts/**' - 'src/styles/**' - 'package-lock.json' - '.browserslistrc' pull_request: paths: - '.github/workflows/bundlesize.yml' - 'src/scripts/**' - 'src/styles/**' - 'package-lock.json' - '.browserslistrc' jobs: measure: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - uses: actions/setup-node@v4 with: node-version: 24 cache: 'npm' - name: Install dependencies run: npm ci --no-audit - run: npm run build # we don't need to build here, as even minized assets expected to be commited - run: npm run bundlesize env: # token has expired, don't block the test #CI: true #BUNDLESIZE_GITHUB_TOKEN: ${{secrets.BUNDLESIZE_GITHUB_TOKEN}} CI_REPO_NAME: ${{ github.event.repository.name }} CI_REPO_OWNER: ${{ github.event.organization.login }} CI_COMMIT_SHA: ${{ github.event.after }} GIT_COMMIT: ${{ github.event.after }} CI_BRANCH: ${{ github.head_ref }} FORCE_COLOR: 2 ================================================ FILE: .github/workflows/deploy-pages.yml ================================================ name: Deploy Pages on: release: types: [published] workflow_dispatch: jobs: deploy-gh-pages: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - uses: actions/setup-node@v4 with: node-version: 24 - name: Build run: | npm ci npm run build rm -rf public/test - name: Deploy uses: peaceiris/actions-gh-pages@v4 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PUBLISH_BRANCH: gh-pages PUBLISH_DIR: ./public ================================================ FILE: .github/workflows/deployment.yml ================================================ name: Publish to npm on: release: types: [published] permissions: id-token: write # Required for OIDC contents: read jobs: publish-npm: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - uses: actions/setup-node@v4 with: node-version: 24 - run: npm ci - run: npm publish --provenance --access public ================================================ FILE: .github/workflows/lint.yml ================================================ name: Code linting on: push: branches: [ main ] paths: - '.github/workflows/lint.yml' - 'src/scripts/**' - 'src/*.ts' - 'src/styles/**' - 'test/**' - 'test-e2e/**' - 'package-lock.json' - '.browserslistrc' - '.eslintrc.json' - '.editorconfig' - '.prettierrc.json' - '.stylelintrc.json' pull_request: paths: - '.github/workflows/lint.yml' - 'src/scripts/**' - 'src/*.ts' - 'src/styles/**' - 'test/**' - 'test-e2e/**' - 'package-lock.json' - '.browserslistrc' - '.eslintrc.json' - '.editorconfig' - '.prettierrc.json' - '.stylelintrc.json' jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - uses: actions/setup-node@v4 with: node-version: 24 cache: 'npm' - name: Install dependencies run: npm ci --no-audit - name: run eslint run: npm run lint:js ## Can't use same eslint config for TypeScript and JavaScript ## TypeScript rules cause rule definition not found errors ## Can be re-enabled if this is resolved: https://github.com/eslint/eslint/issues/14851 # - name: Lint JS bundle # run: | # npm run js:build # npx eslint --no-ignore ./public/assets/scripts/*.js - name: run stylelint run: npm run lint:scss ================================================ FILE: .github/workflows/polyfills-sync.yml ================================================ name: Polyfills documentation on: pull_request: paths: - 'README.md' - '.browserslistrc' - '.eslintrc.json' jobs: sync: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - uses: actions/setup-node@v4 with: node-version: 24 - name: Check Polyfills documentation and settings sync run: node .github/actions-scripts/polyfills-sync.cjs ================================================ FILE: .github/workflows/release-drafter.yml ================================================ name: Release drafter on: push: branches: [ main ] jobs: update-draft-release: runs-on: ubuntu-latest steps: - uses: release-drafter/release-drafter@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/unit-tests.yml ================================================ name: Unit tests on: push: branches: [ main ] paths: - '.github/workflows/unit-tests.yml' - 'src/scripts/**' - 'src/*.ts' - 'test/**' - 'package-lock.json' - '.browserslistrc' - 'babel.config.json' - 'vitest.config.ts' pull_request: paths: - '.github/workflows/unit-tests.yml' - 'src/scripts/**' - 'src/*.ts' - 'test/**' - 'package-lock.json' - '.browserslistrc' - 'babel.config.json' - 'vitest.config.ts' jobs: test-unit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - uses: actions/setup-node@v4 with: node-version: 24 cache: 'npm' - name: Install dependencies run: npm ci --no-audit - run: npm run build - run: npm run test:unit:coverage env: FORCE_COLOR: 2 - name: Upload coverage to Codecov run: bash <(curl -s https://codecov.io/bash) -f ./coverage/lcov.info -B ${{ github.head_ref }} -C ${{ github.sha }} -Z || echo 'Codecov upload failed' env: CI: true GITLAB_CI: true # pretend we are GitLab CI, while Codecov adding support for Github Actions CODECOV_ENV: github-action CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} ================================================ FILE: .gitignore ================================================ node_modules npm-debug.log .DS_Store .idea .rollup.cache tsconfig.tsbuildinfo .npmrc .run # Test tests/reports tests/results .nyc_output coverage /test-results/ /playwright-report/ /blob-report/ /playwright/.cache/ ================================================ FILE: .nvmrc ================================================ v22.17.0 ================================================ FILE: .prettierrc.json ================================================ { "printWidth": 120, "singleQuote": true, "trailingComma": "all", "endOfLine": "lf", "overrides": [ { "files": ["*.svg"], "options": { "parser": "html", "htmlWhitespaceSensitivity": "ignore" } }, { "files": ["public/*.html"], "options": { "trailingComma": "es5" } } ] } ================================================ FILE: .stylelintrc.json ================================================ { "extends": "stylelint-config-standard-scss", "rules": { "media-feature-range-notation": null, "declaration-block-no-redundant-longhand-properties": null } } ================================================ FILE: .vscode/extensions.json ================================================ { // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ // we enforce ESLint rules, so, recommend extension "dbaeumer.vscode-eslint", // we use prettier, so, recommend extension "esbenp.prettier-vscode", // we are on GitHub, so, recommend extension "github.vscode-pull-request-github", // needed for our configured debug configuration with Chrome "msjsdiag.debugger-for-chrome" ] } ================================================ FILE: .vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome", "preLaunchTask": "buildAndWatch", "url": "http://localhost:3001", "webRoot": "${workspaceFolder}", "sourceMapPathOverrides": { "webpack://Choices/*": "${workspaceFolder}/*" } }, ] } ================================================ FILE: .vscode/settings.json ================================================ { "eslint.enable": true, // prevent watch task failures on lint errors "eslint.autoFixOnSave": true, // switch off default VSCode formatting rules "javascript.format.enable": false, // Javascript prettier runs via ESLint "prettier.disableLanguages": ["javascript"], "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[scss]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[javascript]": { "editor.formatOnSave": false }, "search.exclude": { "**/node_modules": true, "public/assets": true, "**/coverage": true }, // for Windows collaborators "files.eol": "\n", "files.encoding": "utf8", // associations for some files this project is using "files.associations": { ".browserslistrc": "gitignore", ".npmrc": "ini" }, // We use NPM as package manager "npm.packageManager": "npm", "npm.autoDetect": "on", "npm.fetchOnlinePackageInfo": true, "eslint.packageManager": "npm", "json.schemas": [ // Prettier config { "fileMatch": [".prettierrc.json"], "url": "http://json.schemastore.org/prettierrc" } ], "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "stylelint.validate": [ "css", "less", "postcss", "scss" ] } ================================================ FILE: .vscode/tasks.json ================================================ { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "type": "npm", "label": "buildAndWatch", "script": "js:watch", "group": { "kind": "build", "isDefault": true }, "isBackground": true, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "dedicated", "showReuseMessage": true, "clear": false }, "problemMatcher": [ "$eslint-stylish", { "owner": "webpack", "fileLocation": "absolute", "pattern": [ { "regexp": "^Module build failed \\(from (\\.+)\\)", "file": 1, "line": 2, "column": 3 }, { "regexp": "\\s*TS\\d+:\\s*(.*)", "message": 1 } ], "severity": "error", "source": "webpack", "background": { "activeOnStart": true, "beginsPattern": "^Listening at", "endsPattern": "Compiled successfully\\." } } ] }, { "type": "npm", "script": "css:build", "group": "build", "problemMatcher": ["$node-sass"] }, { "type": "npm", "script": "lint", "problemMatcher": ["$eslint-stylish"] }, { "type": "npm", "script": "build", "group": "build" }, { "type": "npm", "script": "test", "group": "test" }, { "type": "npm", "script": "test:e2e", "group": "test" }, { "type": "npm", "script": "test:unit", "group": "test" }, ] } ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [11.2.0] (2026-01-05) ### Features - Add `searchRenderSelectedChoices` configuration option to control whether selected choices appear in search results for select-multiple inputs. Defaults to `true` (backward compatible behavior). Set to `false` to hide selected choices from search results. - Add support for `required` html attribute [#1332](https://github.com/Choices-js/Choices/pull/1332) - Note; This feature requires updating any css targeting the `.choices [hidden]` selector - Improve UX on the select dropdown [#1361](https://github.com/Choices-js/Choices/pull/1361) - Add `searchDisabledChoices` configuration option to allow disabled choices to appear in search results [#1357](https://github.com/Choices-js/Choices/pull/1357) - Add additional SCSS variables [#1304](https://github.com/Choices-js/Choices/pull/1304) - Add CSS custom properties support (+ dark mode for the intro page) (#1335](https://github.com/Choices-js/Choices/pull/1335) - Soften constraints on remove buttons [#1338](https://github.com/Choices-js/Choices/pull/1338) ### Bugfixes - Fix data-label-description from source html was not treated as trusted [#1365](https://github.com/Choices-js/Choices/pull/1365) - Fix kmp search not returning results as expected [#1364](https://github.com/Choices-js/Choices/pull/1364) - Fix selected choice was not reliably highlighted when opening the dropdown [#1339](https://github.com/Choices-js/Choices/pull/1339) - Define `[aria-selected]` for selectable choices per WAI-ARIA 1.2 spec, and avoid triple state with aria-selected [#1330](https://github.com/Choices-js/Choices/pull/1330) - Fix `appendGroupInSearch` option was non-functional [#1324](https://github.com/Choices-js/Choices/pull/1324) - When resolving the remove item/label/icon, add a 3rd argument item argument. Update default remove item label to use this (Fixes #1296) [#1323](https://github.com/Choices-js/Choices/pull/1323) - Fix `searchResultLimit` could not be set to `-1` when `renderChoiceLimit` was set [#1322](https://github.com/Choices-js/Choices/pull/1322) - Fix dropdown would stick closed when a search loses focus [#1308](https://github.com/Choices-js/Choices/pull/1308) - Fix `searchEnabled` being disabled for `select-multiple` did not work [#1366](https://github.com/Choices-js/Choices/pull/1366) ### Chore - Update callback argument documentation - Update development dependencies to fix npm install warning ## [11.1.0] (2025-03-14) ### Features - Support `` label attribute [#1289](https://github.com/Choices-js/Choices/pull/1289) - Add KMP search algorithm (gated by build flag) [#1229](https://github.com/Choices-js/Choices/issue/1229) [#1277](https://github.com/Choices-js/Choices/pull/1277) ### Bug Fixes - Remove `role="textbox"` from search input, per a11y practices. [#941](https://github.com/Choices-js/Choices/issues/941) @mlinnetz ([#1285](https://github.com/Choices-js/Choices/issues/1285)) ## [11.0.6] (2025-02-27) ### Breaking changes - Changes to `setChoices` & `clearChoices` adjust how the selection and new choices combine when using `replaceChoices: true` is used to better match v10 and v11.0.3 behavior. - To remove duplication, consider `duplicateItemsAllowed: false` to be set, or use the new 6th argument `replaceItems:true` ### Bug Fixes - Fix `setChoices` & `clearChoices` related regressions @Xon ([#1278](https://github.com/Choices-js/Choices/issues/1278])) [#1283](https://github.com/Choices-js/Choices/issues/1283) - Revert "Do not preventDefault on item to support dragging" [#1266](https://github.com/Choices-js/Choices/issues/1266) @Xon ([#1282](https://github.com/Choices-js/Choices/issues/1282)) ### Chore - Add e2e test for dropdown behavior on item mouse down/click - Add e2e test for serveral `setChoices`/`clearChoices` actions ## [11.0.5] (2025-02-26) ### Bug Fixes - Fix regression when calling setChoices [#1278](https://github.com/Choices-js/Choices/issues/1278) ## [11.0.4] (2025-02-23) ### Features - Do not preventDefault on item to support dragging [#417](https://github.com/Choices-js/Choices/issues/417) [#1094](https://github.com/Choices-js/Choices/issues/1094) [#920](https://github.com/Choices-js/Choices/issues/920) ### Bug Fixes (from 11.0.0) - Fix performance regression when calling setChoices [#1275](https://github.com/Choices-js/Choices/issues/1275) * Fix `renderSelectedChoices` option when all choices are selected [#1274](https://github.com/Choices-js/Choices/issues/1274) * Fix v11 regression for disabled placeholder option handling [#1203](https://github.com/Choices-js/Choices/issues/1203) * Fix v11 regression where `clearChoices` (and `setChoices` with `replaceChoices:true`) did not remove selected items when preserving placeholders [#1261](https://github.com/Choices-js/Choices/issues/1261) * Fix v11 regression where `duplicateItemsAllowed` option did not work with `select-one`/`select-multiple` [#1271](https://github.com/Choices-js/Choices/issues/1271) * Fix: Reached maximum item limit notice is not cleared after removing selections [#1249](https://github.com/Choices-js/Choices/issues/1249) * Fix: Disabled options are not visible [#1257](https://github.com/Choices-js/Choices/issues/1257) [#1269](https://github.com/Choices-js/Choices/issues/1257) * Fix: Clear button reverses items order [#1251](https://github.com/Choices-js/Choices/issues/1251) * Fix `tab` => direction keys handling with disabled search [#1260](https://github.com/Choices-js/Choices/issues/1260) * Improve cjs compatibility by removing pinned "module" type in package.json [#1250](https://github.com/Choices-js/Choices/issues/1250) ## [11.0.3] (2024-12-22) ### Bug Fixes (from 11.0.0) * Fix input text - method setValue didn't work [#1207](https://github.com/Choices-js/Choices/issues/1207) * Fix `tab` and `esc` keys handling [#1234](https://github.com/Choices-js/Choices/issues/1234) [#1209](https://github.com/Choices-js/Choices/issues/1209) * Fix Notice for max item limit is removed permanently if you keep typing [#1201](https://github.com/Choices-js/Choices/issues/1201) * Fix search was not stopped when leaving focus with esc key [#1240](https://github.com/Choices-js/Choices/issues/1240) * Fix single-select mode disabling search when `tab` => arrow keys are pressed [#1230](https://github.com/Choices-js/Choices/issues/1230) * Fix HTML comments were copied from backing `` and were rendered as text [#1231](https://github.com/Choices-js/Choices/issues/1231) ## [11.0.2] (2024-09-05) ### Features (from 11.0.0) * Pass `getClassNames` as the 3rd argument to `callbackOnCreateTemplates` callback * `duplicateItemsAllowed` option is now respected by `setChoices()` method [#855](https://github.com/Choices-js/Choices/issues/855) ### Bug Fixes (from 11.0.0) * Fix choice disable state wasn't considered when showing the "no choices to choose from" notice * Fix regression where webpack doesn't permit importing scss/css @tagliala [#1193](https://github.com/Choices-js/Choices/issues/1193) * Fix regression "no choices to choose from"/"no results found" notice did not reliably trigger. [#1185](https://github.com/Choices-js/Choices/issues/1185) [#1191](https://github.com/Choices-js/Choices/issues/1191) * Fix regression of `UnhighlightItem` event not firing [#1173](https://github.com/Choices-js/Choices/issues/1173) * Fix `clearChoices()` would remove items, and clear the search flag. * Fixes for opt-group handling/rendering * Fix `removeChoice()` did not properly remove a choice which was part of a group ### Chore * Add e2e tests for "no choices" behavior to match v10 ## [11.0.1] (2024-08-30) ### Bug Fixes (from 11.0.0) * Fix the rendered item list was not cleared when `clearStore` was called. This impacted the on-form-reset and `refresh` features. ### Chore * Add e2e test for 'form reset' and 'on paste & search'. * Cleanup adding classes to generated elements. ## [11.0.0] (2024-08-28) ### ⚠ BREAKING CHANGES * Update polyfills to include `Element.prototype.replaceChildren` * Number of internal APIs have changed ### Bug Fixes (from 10.2.0) * Reduce work done for `unhighlightAll` during on-click handler (batching in v11.0.0-rc8 would also have helped) [#522](https://github.com/Choices-js/Choices/issues/522) [#599](https://github.com/Choices-js/Choices/issues/599) * Improve performance when rendering very large number of items and choices. Stuttering when stopping searching or selecting an item still happens depending on device and number of choices. ## [11.0.0-rc8] (2024-08-23) ### ⚠ BREAKING CHANGES * Trigger a search event (with empty value and 0 resultCount) when search stops ### Features * `searchResultLimit` can be set to `-1` for no limit of search results to display. ### Bug Fixes (from 10.2.0) * Fix edge case where aria-label could be added twice * Fix the page scrolls when you press 'space' on a single select input #1103 * Update typescript definition for `removeActiveItems` to explicitly mark `excludedId` as optional #1116 ### Chore * Reduce the number of loops over choices when rendering search results, results in more compact code. * Byte shave bundle sizes down ## [11.0.0-rc7] (2024-08-19) ### ⚠ BREAKING CHANGES * Improve consistency of the `choice` event firing. `choice` event now occurs after the `addItem` event * `enter` key now consistently opens/closes the dropdown instead of the behavior varying depending on backing element or internal state of the highlighted choice ### Features * Add `closeDropdownOnSelect` option, controls how the dropdown is close after selection is made. [#636](https://github.com/Choices-js/Choices/issues/636) [#973](https://github.com/Choices-js/Choices/issues/873) [#1012](https://github.com/Choices-js/Choices/issues/1012) * Allow choices.js to be imported on nodejs, useful for tests and also server side rendering. As windows.document is by default not defined, the default template rendering will not function. The `callbackOnCreateTemplates` callback must be used. [#861](https://github.com/Choices-js/Choices/issues/861) ### Bug Fixes (from 10.2.0) * Improve various `[aria-*]` attribute handling for better lighthouse accessibility scores [#1169](https://github.com/Choices-js/Choices/issues/1169) * Improve contrast on default CSS by darkening primary item selection color [#924](https://github.com/Choices-js/Choices/issues/924) ### Bug Fixes (from 11.0.0RC6) * Fix destroy&init of `choices.js` would lost track of data from the backing ``/`` * Update e2e tests ### Bug Fixes (from 11.0.0RC1) * Fix various `select-one` bugs related to how `` initializes and selected values do not match the configured `choices.js` * Fix legacy `placeholder` attribute support for `select-one` * Fix `data-value` attribute on choices may not be correctly rendered into html ### Chore * Switch e2e tests from `puppeteer`/`selenium`/`cypress` to `playwright` * Restructure end-to-end tests so html/script blocks are co-located to improve debugability * Enable `@typescript-eslint/explicit-function-return-type` eslint rule ## [11.0.0-rc6] (2024-08-12) ### ⚠ BREAKING CHANGES * Mutation APIs `setChoiceByValue`/`setChoices`/`setValue` now throw an error if the Choices instance was not initialized or multiple choices instances where initialized on the same element. Prevents bad internal states from triggering unexpected errors [#1129](https://github.com/Choices-js/Choices/issues/1129) ### Features * Improve performance of search/filtering with large number of choices. ### Bug Fixes (from 10.2.0) * Fix Choices does not accept an element from an iframe [#1057](https://github.com/Choices-js/Choices/issues/1057) * Fix Choices was not disable in a `` [#1132](https://github.com/Choices-js/Choices/issues/1132) * Fix `silent` option does not silence warnings about unknown options [#1119](https://github.com/Choices-js/Choices/issues/1119) * Fix documentation that suggests duplicateItemsAllowed works with select-multiple, when it only works for text. [#1123](https://github.com/Choices-js/Choices/issues/1123) * Fix quadratic algorithm complexity (aka O(N^2) ) when filtering/search choices. * Fix search results could be unexpectedly unstable, and that `fuseOptions.sortFn` was effectively ignored [#1106](https://github.com/Choices-js/Choices/issues/1106) ### Bug Fixes (from 11.0.0RC1) * Fix possible empty `aria-label` generation on remove item button * Fix `clearChoices()` did not remove the actual selection options ## [11.0.0-rc5] (2024-08-08) ### ⚠ BREAKING CHANGES * Update to using Fuse.js v7.0.0 * Update choices.js package to be an ES module, and use '[subpath exports](https://nodejs.org/api/packages.html#subpath-exports)' to expose multiple versions (UMD, CJS or MTS bundles). * Provide "fuse full" (default `choices.js`, ~20.36KB), or "fuse basic" (`choices.search-basic.js` ~19.31KB) or "prefix filter" (`choices.search-filter.js` ~15.27KB) based on how much Fuse.js is included. ### Bug Fixes (from 10.2.0) * Fix `select-one` placeholder could ignore the non-option placeholder configuration * Remove typescript types for tests from distribution ### Chore * Reduce bundle size from ~24KB to ~20.36KB * Switch bundler from `webpack` to `rollup` * Switch test framework from `mocha` to `vitest` ### Bug Fixes (from 11.0.0RC4) * Fix `aria-describedby` was being assigned when it shouldn't be * Fix check to ensure search was fully enabled for multiple select mode, as this functionality is hard-coded enabled elsewhere in the code base. ## [11.0.0 RC3] (2024-08-04) ### ⚠ BREAKING CHANGES * For `select-one` and `select-multiple`, the placeholder value is pulled from `config.placeholderValue="..."` or `` before attempting to extract a placeholder from the options list. [#912](https://github.com/Choices-js/Choices/issues/912) [#567](https://github.com/Choices-js/Choices/issues/567) [#843](https://github.com/Choices-js/Choices/issues/843) ### Bug Fixes (from 10.2.0) * Fix search did not trigger to copy&paste events [#860](https://github.com/Choices-js/Choices/issues/860) [#174](https://github.com/Choices-js/Choices/issues/174) ### Chore * Update defaults for classnames to be arrays instead of strings ### Bug Fixes (from 11.0.0 RC1) * Fix `noResults`/`noChoices` classes could not be set to a list of classes * Fix failing to add an item would close the dropdown * Fix invalid css selectors being generated for configurable css class-names with multiple css classes for an element * Fix "Press Enter to add..." would not render if the dropdown had partially matching search results * Fix render limit would allow `select-one` to select multiple items ## [11.0.0 RC2] (2024-08-03) ### Bug Fixes (from 10.2.0) * Avoid pushing a search to fuse.js which is just additional whitespace to the existing search term ### Bug Fixes (from 11.0.0 RC1) * Fix error when using backspace key after adding an item and then removing it * Fix adding items for select boxes would not give the max item messages reliably * Fix `destroy()`/`init()` would not load choices from the underlying `` as expected * Fix adding user provided choices for `select-one` would not remove the existing item and result in a select-one with multiple items set. ### Chore * Remove unused code * Use constant enum instead of repeating strings and type information * For test html pages, prevent a failing `fetch()` from breaking the rest of the examples * Tweak `_render()` loop to avoid duplicating has-changed checks ## [11.0.0 RC1] (2024-08-02) ### ⚠ BREAKING CHANGES * `allowHtml` now defaults to false. * HTML escaping of choice/item labels should no longer double escape depending on allowHTML mode. * Templates/text functions now escape `'` characters for display. * `addItemText`/`uniqueItemText`/`customAddItemText` are now called with the `value` argument already escaped. * Typescript classes for input data vs internal working data have been adjusted resulting in the `Choice`/`Group`/`Item` typescript classes have been renamed, and aliases left as required. ### Features * `config.classNames` now accept arrays to support multiple classes. [#1121](https://github.com/Choices-js/Choices/issues/1121) [#1074](https://github.com/Choices-js/Choices/issues/1074) [#907](https://github.com/Choices-js/Choices/issues/907) [#832](https://github.com/Choices-js/Choices/issues/832) * The original option list for the select is not destroyed, and all loaded choices are serialised to HTML for better compatibility with external javascript. [#1053](https://github.com/Choices-js/Choices/issues/1053) [#1023](https://github.com/Choices-js/Choices/issues/1023) * New `singleModeForMultiSelect` feature to treat a `select-single` as if it was a `select-multiple` with a max item count of `1`, and still auto-close the dropdown and swap the active item on selection. [#1136](https://github.com/Choices-js/Choices/issues/1136) [#904](https://github.com/Choices-js/Choices/issues/904) * `Remove item text` can be localized. * Allow user-created choices for selects. [#1117](https://github.com/Choices-js/Choices/issues/1117) [#1114](https://github.com/Choices-js/Choices/issues/1114) * User input is escaped by default. At the risk of XSS attacks this can be disabled by `allowHtmlUserInput`. * Render options without a group even if groups are present. [#615](https://github.com/Choices-js/Choices/issues/615) [#1110](https://github.com/Choices-js/Choices/issues/1110) * Read `data-label-class`/`data-label-description` from `` HTML to drive adding a per-choice CSS label and description text when `allowHtml: false`. * Add `removeItemButtonAlignLeft` option, to control if the remove item button is at the start or the end of the item. * Add `removeChoice` method. Removes the choice from the `choices.js` object and any backing `` HTML element * Add `refresh` method. Reloads choices from the backing ``s options. * Improve rendering performance by batching changes. * `escapeForTemplate` function is passed to the 2nd method of the `callbackOnCreateTemplates` callback. * When `allowHtml` is false, default templates now render escaped html to `innerHtml` writing to `innerText`. * This provides consistent rendering performance as `innerText` is quirky and slower than escaped html into `innerHtml` * Shadow DOM support [#938](https://github.com/Choices-js/Choices/pull/938) ### Bug Fixes * Replace malicious polyfill with cdnjs. [#1161](https://github.com/Choices-js/Choices/issues/1161) * Maintain groups in search mode. [#1152](https://github.com/Choices-js/Choices/issues/1152) * Fix various "first press" bugs on single select dropdowns. [#1104](https://github.com/Choices-js/Choices/issues/1104) * Fix 'esc' would close the dropdown and also apply to the container resulting in an overlay/modal unexpectedly closing. [#1039](https://github.com/Choices-js/Choices/issues/1039) * Fix form reset would clear the choices list, but not clear the search bar. [#1023](https://github.com/Choices-js/Choices/issues/1023) * Fix options would be disabled when choices.js was intialized on a disabled `` element. [#1025](https://github.com/Choices-js/Choices/issues/1025) * Fix a `search_term` element to appear in form submit data. [#1049](https://github.com/Choices-js/Choices/issues/1049) * Fix 'remove item' button would trigger the change event twice due to placeholder value being used (match html single-select). [#892](https://github.com/Choices-js/Choices/issues/892) * Fix optgroups are not preserved when Choices is destroyed [#1055](https://github.com/Choices-js/Choices/issues/1055) * Fix placeholder config option would be ignored for select boxes which have blank entries. * Fix `data-custom-properties` attribute did not serialize to created elements as a json blob as expected. [#840](https://github.com/Choices-js/Choices/issues/840) [#1155](https://github.com/Choices-js/Choices/issues/1155) [#543](https://github.com/Choices-js/Choices/issues/543) * Fix multi-select did not correctly resizing when a select option is selected on choices.js initialization. * Fix clearInput function did not clear the last search. * Fix `addItemFilter` would allow empty strings as input to be added for items. * Fix various issues with double escaping when displaying items/choices depending on allowHTML mode. * Fix `aria-label` for placeholders was set to the string `null` * Fix `searchEnable` flag was not respected for `select-multiple` [#1042](https://github.com/Choices-js/Choices/issues/1042) * Fix poor error message when Choices is passed a string selector which fails to find the element for Choices to attach to. ### Chore * Remove `deepMerge` dependency. ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at josh@joshuajohnson.co.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributions In lieu of a formal styleguide, take care to maintain the existing coding style ensuring there are no linting errors. Add unit tests for any new or changed functionality. Lint and test your code using the npm scripts below: ### Minified code For compatibility, `new` and `get` must be pure (side effect free). ### NPM tasks | Task | Usage | |---------------------------|--------------------------------------------------------------| | `npm run start` | Fire up local server for development | | `npm run test:unit` | Run sequence of tests once | | `npm run test:unit:watch` | Fire up test server and re-test on file change | | `npm run test:e2e` | Run sequence of e2e tests (with local server) | | `npm run test` | Run both unit and e2e tests | | `npm run playwright:gui` | Run Playwright e2e tests (GUI) | | `npm run playwright:cli` | Run Playwright e2e tests (CLI) | | `npm run js:build` | Compile Choices to an uglified JavaScript file | | `npm run css:watch` | Watch SCSS files for changes. On a change, run build process | | `npm run css:build` | Compile, minify and prefix SCSS files to CSS | ## Passing environmental arguments to rollup Use `--` followed by normal rollup `--environment` arguments. The last one overrides any previous ones with the same name An example of changing what js:watch will bind to: ``` npm run js:watch -- --environment WATCH_HOST:0.0.0.0 ``` ## Build flags The following build flags are supported via environment variables: ### CHOICES_SEARCH_FUSE **Values:**: **"full" / "basic" / "null" ** **Usage:** The level of integration with fuse. `full` is the entire fuse.js build, `basic` is fuse.js with just standard fuzzy searching. `null` is a basic prefix string search with no fuse.js **Example**: ``` npm run js:watch -- --environment CHOICES_SEARCH_FUSE:basic ``` ### CHOICES_SEARCH_KMP **Values:**: **"1" / "0" ** **Usage:** High performance `indexOf`-like search algorithm. **Example**: ``` npm run js:watch -- --environment CHOICES_SEARCH_KMP:1 ``` ### CHOICES_CAN_USE_DOM **Values:**: **"1" / "0" ** **Usage:** Indicates if DOM methods are supported in the global namespace. Useful if importing into DOM or the e2e tests without a DOM implementation available. **Example**: ``` npm run js:watch -- --environment CHOICES_CAN_USE_DOM:1 ``` ## Pull requests When submitting a pull request that resolves a bug, feel free to use the following template: ```md ## This is the problem: ## Steps to reproduce: ## This is my solution: ``` ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Josh Johnson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Choices.js [](https://github.com/Choices-js/Choices/actions) [](https://github.com/Choices-js/Choices/actions) [](https://www.npmjs.com/package/choices.js) A vanilla, lightweight (~20kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency. [Demo](https://choices-js.github.io/Choices/) ## TL;DR - Lightweight - No jQuery dependency - Configurable sorting - Flexible styling - Fast search/filtering - Clean API - Right-to-left support - Custom templates --- ### Interested in writing your own ES6 JavaScript plugins? Check out [ES6.io](https://ES6.io/friend/JOHNSON) for great tutorials! 💪🏼 ### Sponsored by: --- ## Table of Contents - [Installation](#installation) - [Setup](#setup) - [Terminology](#terminology) - [Input Types](#input-types) - [Configuration Options](#configuration-options) - [Callbacks](#callbacks) - [Events](#events) - [Methods](#methods) - [CSS custom properties](#css-custom-properties) - [Development](#development) - [License](#license) ## Installation With [NPM](https://www.npmjs.com/package/choices.js): ```zsh npm install choices.js ``` With [Yarn](https://yarnpkg.com/): ```zsh yarn add choices.js ``` From a [CDN](https://www.jsdelivr.com/package/npm/choices.js): **Notes:** * There is sometimes a delay before the latest version of Choices is reflected on the CDN. * Examples below pin a version (v11.1.0). Check [latest release](https://www.jsdelivr.com/package/npm/choices.js) and update v11.1.0 to the latest tag before using. ```html ``` Or include Choices directly: ```html ``` ### CSS/SCSS The use of `import` of css/scss is supported from webpack. In .scss: ```scss @import "choices.js/src/styles/choices"; ``` In .js/.ts: ```javascript import "choices.js/public/assets/styles/choices.css"; ``` ## Setup **Note:** If you pass a selector which targets multiple elements, the first matching element will be used. Versions prior to 8.x.x would return multiple Choices instances. ```js // Pass single element const element = document.querySelector('.js-choice'); const choices = new Choices(element); // Pass reference const choices = new Choices('[data-trigger]'); const choices = new Choices('.js-choice'); // Pass jQuery element const choices = new Choices($('.js-choice')[0]); // Passing options (with default options) const choices = new Choices(element, { silent: false, items: [], choices: [], renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: (value) => !!value && value !== '', removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: (a, b) => sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: (value, rawValue) => { return `Press Enter to add "${value}"`; }, removeItemIconText: () => `Remove item`, removeItemLabelText: (value, rawValue) => `Remove item: ${value}`, maxItemText: (maxItemCount) => { return `Only ${maxItemCount} values can be added`; }, valueComparer: (value1, value2) => { return value1 === value2; }, classNames: { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], }, // Choices uses the great Fuse library for searching. You // can find more options here: https://fusejs.io/api/options.html fuseOptions: { includeScore: true }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, appendGroupInSearch: false, }); ``` ## Terminology | Word | Definition | | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Choice | A choice is a value a user can select. A choice would be equivalent to the `` element within a select input. | | Group | A group is a collection of choices. A group should be seen as equivalent to a `` element within a select input. | | Item | An item is an inputted value (text input) or a selected choice (select element). In the context of a select element, an item is equivalent to a selected option element: `` whereas in the context of a text input an item is equivalent to `` | ## Input Types Choices works with the following input types, referenced in the documentation as noted. | HTML Element | Documentation "Input Type" | | -------------------------------------------------------------------------------------------------------| -------------------------- | | [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) | `text` | | [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) | `select-one` | | [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-multiple) | `select-multiple` | ## Configuration Options ### silent **Type:** `Boolean` **Default:** `false` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Optionally suppress console errors and warnings. ### items **Type:** `Array` **Default:** `[]` **Input types affected:** `text` **Usage:** Add pre-selected items (see terminology) to text input. Pass an array of strings: `['value 1', 'value 2', 'value 3']` Pass an array of objects: ``` [{ value: 'Value 1', label: 'Label 1', id: 1 }, { value: 'Value 2', label: 'Label 2', id: 2, customProperties: { random: 'I am a custom property' } }] ``` ### choices **Type:** `Array` **Default:** `[]` **Input types affected:** `select-one`, `select-multiple` **Usage:** Add choices (see terminology) to select input. Pass an array of objects: ``` [{ value: 'Option 1', label: 'Option 1', selected: true, disabled: false, }, { value: 'Option 2', label: 'Option 2', selected: false, disabled: true, customProperties: { description: 'Custom description about Option 2', random: 'Another random custom property' }, }, { label: 'Group 1', choices: [{ value: 'Option 3', label: 'Option 4', selected: true, disabled: false, }, { value: 'Option 2', label: 'Option 2', selected: false, disabled: true, customProperties: { description: 'Custom description about Option 2', random: 'Another random custom property' } }] }] ``` ### renderChoiceLimit **Type:** `Number` **Default:** `-1` **Input types affected:** `select-one`, `select-multiple` **Usage:** The amount of choices to be rendered within the dropdown list ("-1" indicates no limit). This is useful if you have a lot of choices where it is easier for a user to use the search area to find a choice. ### maxItemCount **Type:** `Number` **Default:** `-1` **Input types affected:** `text`, `select-multiple` **Usage:** The amount of items a user can input/select ("-1" indicates no limit). ### closeDropdownOnSelect **Type:** `Boolean` | 'auto' **Default:** `auto` **Input types affected:** select-one, select-multiple **Usage:** Control how the dropdown closes after making a selection for select-one or select-multiple. - 'auto' defaults based on backing-element type: - select-one: true - select-multiple: false ### singleModeForMultiSelect **Type:** `Boolean` **Default:** `false` **Input types affected:** select-one, select-multiple **Usage:** Make select-multiple with a max item count of 1 work similar to select-one does. Selecting an item will auto-close the dropdown and swap any existing item for the just selected choice. If applied to a select-one, it functions as above and not the standard select-one. ### addChoices **Type**: `Boolean` **Default:** `false` **Input types affected:** `select-multiple`, `select-one` **Usage:** Whether a user can add choices dynamically. **Note:** `addItems` must also be `true` ### addItems **Type:** `Boolean` **Default:** `true` **Input types affected:** `text` **Usage:** Whether a user can add items. ### removeItems **Type:** `Boolean` **Default:** `true` **Input types affected:** `text`, `select-multiple` **Usage:** Whether a user can remove items. ### removeItemButton **Type:** `Boolean` **Default:** `false` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Whether each item should have a remove button. ### removeItemButtonAlignLeft **Type:** `Boolean` **Default:** `false` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Align item remove button left vs right ### editItems **Type:** `Boolean` **Default:** `false` **Input types affected:** `text` **Usage:** Whether a user can edit items. An item's value can be edited by pressing the backspace. ### allowHTML **Type:** `Boolean` **Default:** `false` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Whether HTML should be rendered in all Choices elements. If `false`, all elements (placeholder, items, etc.) will be treated as plain text. If `true`, this can be used to perform XSS scripting attacks if you load choices from a remote source. ### allowHtmlUserInput **Type:** `Boolean` **Default:** `false` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Whether HTML should be escaped on input when `addItems` or `addChoices` is true. If `false`, user input will be treated as plain text. If `true`, this can be used to perform XSS scripting attacks if you load choices from a remote source. ### duplicateItemsAllowed **Type:** `Boolean` **Default:** `true` **Input types affected:** `text`, `select-multiple`, `select-one` **Usage:** Whether duplicate inputted/chosen items are allowed ### delimiter **Type:** `String` **Default:** `,` **Input types affected:** `text` **Usage:** What divides each value. The default delimiter separates each value with a comma: `"Value 1, Value 2, Value 3"`. ### paste **Type:** `Boolean` **Default:** `true` **Input types affected:** `text`, `select-multiple` **Usage:** Whether a user can paste into the input. ### searchEnabled **Type:** `Boolean` **Default:** `true` **Input types affected:** `select-one`, `select-multiple` **Usage:** Whether a search area should be shown. ### searchChoices **Type:** `Boolean` **Default:** `true` **Input types affected:** `select-one` **Usage:** Whether choices should be filtered by input or not. If `false`, the search event will still emit, but choices will not be filtered. ### searchDisabledChoices **Type:** `Boolean` **Default:** `false` **Input types affected:** `select-one`, `select-multiple` **Usage:** Whether disabled choices should be included in search results. If `true`, disabled choices will appear in search results but still cannot be selected. This is useful when you want users to see what options exist but are currently unavailable. Placeholders are always excluded from search results regardless of this setting. ### searchFields **Type:** `Array/String` **Default:** `['label', 'value']` **Input types affected:**`select-one`, `select-multiple` **Usage:** Specify which fields should be used when a user is searching. If you have added custom properties to your choices, you can add these values thus: `['label', 'value', 'customProperties.example']`. ### searchFloor **Type:** `Number` **Default:** `1` **Input types affected:** `select-one`, `select-multiple` **Usage:** The minimum length a search value should be before choices are searched. ### searchResultLimit: 4, **Type:** `Number` **Default:** `4` **Input types affected:** `select-one`, `select-multiple` **Usage:** The maximum amount of search results to show ("-1" indicates no limit). ### shadowRoot **Type:** Document Element **Default:** null **Input types affected:** `select-one`, `select-multiple` **Usage:** You can pass along the shadowRoot from your application like so. ```js var shadowRoot = document .getElementById('wrapper') .attachShadow({ mode: 'open' }); ... var el = shadowRoot.querySelector(...); var choices = new Choices(el, { shadowRoot: shadowRoot, }); ``` ### position **Type:** `String` **Default:** `auto` **Input types affected:** `select-one`, `select-multiple` **Usage:** Whether the dropdown should appear above (`top`) or below (`bottom`) the input. By default, if there is not enough space within the window the dropdown will appear above the input, otherwise below it. ### resetScrollPosition **Type:** `Boolean` **Default:** `true` **Input types affected:** `select-multiple` **Usage:** Whether the scroll position should reset after adding an item. ### addItemFilter **Type:** `string | RegExp | Function` **Default:** `null` **Input types affected:** `text` **Usage:** A RegExp or string (will be passed to RegExp constructor internally) or filter function that will need to return `true` for a user to successfully add an item. **Example:** ```js // Only adds items matching the text test new Choices(element, { addItemFilter: (value) => { return ['orange', 'apple', 'banana'].includes(value); }; }); // only items ending to `-red` new Choices(element, { addItemFilter: '-red$'; }); ``` ### shouldSort **Type:** `Boolean` **Default:** `true` **Input types affected:** `select-one`, `select-multiple` **Usage:** Whether choices and groups should be sorted. If false, choices/groups will appear in the order they were given. ### shouldSortItems **Type:** `Boolean` **Default:** `false` **Input types affected:** `text`, `select-multiple` **Usage:** Whether items should be sorted. If false, items will appear in the order they were selected. ### sorter **Type:** `Function` **Default:** sortByAlpha **Input types affected:** `select-one`, `select-multiple` **Usage:** The function that will sort choices and items before they are displayed (unless a user is searching). By default choices and items are sorted by alphabetical order. **Example:** ```js // Sorting via length of label from largest to smallest const example = new Choices(element, { sorter: function(a, b) { return b.label.length - a.label.length; }, }); ``` ### placeholder **Type:** `Boolean` **Default:** `true` **Input types affected:** `text` **Usage:** Whether the input should show a placeholder. Used in conjunction with `placeholderValue`. If `placeholder` is set to true and no value is passed to `placeholderValue`, the passed input's placeholder attribute will be used as the placeholder value. **Note:** For select boxes, the recommended way of adding a placeholder is as follows: ```html ... ... ... ``` For backward compatibility, `This is a placeholder` and `This is a placeholder` are also supported. ### placeholderValue **Type:** `String` **Default:** `null` **Input types affected:** `text` **Usage:** The value of the inputs placeholder. ### searchPlaceholderValue **Type:** `String` **Default:** `null` **Input types affected:** `select-one` **Usage:** The value of the search inputs placeholder. ### prependValue **Type:** `String` **Default:** `null` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Prepend a value to each item added/selected. ### appendValue **Type:** `String` **Default:** `null` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Append a value to each item added/selected. ### renderSelectedChoices **Type:** `String` **Default:** `auto` **Input types affected:** `select-multiple` **Usage:** Whether selected choices should be removed from the list. By default choices are removed when they are selected in multiple select box. To always render choices pass `always`. ### searchRenderSelectedChoices **Type:** `Boolean` **Default:** `true'` **Input types affected:** `select-multiple` **Usage:** Whether selected choices should be removed from the list during search. **Example:** ```js // Hide selected choices from search results const example = new Choices(element, { searchRenderSelectedChoices: false, }); ``` ### loadingText **Type:** `String` **Default:** `Loading...` **Input types affected:** `select-one`, `select-multiple` **Usage:** The text that is shown whilst choices are being populated via AJAX. ### noResultsText **Type:** `String/Function` **Default:** `No results found` **Input types affected:** `select-one`, `select-multiple` **Usage:** The text that is shown when a user's search has returned no results. Optionally pass a function returning a string. ### noChoicesText **Type:** `String/Function` **Default:** `No choices to choose from` **Input types affected:** `select-multiple`, `select-one` **Usage:** The text that is shown when a user has selected all possible choices, or no choices exist. Optionally pass a function returning a string. ### itemSelectText **Type:** `String` **Default:** `Press to select` **Input types affected:** `select-multiple`, `select-one` **Usage:** The text that is shown when a user hovers over a selectable choice. Set to empty to not reserve space for this text. ### addItemText **Type:** `String/Function` **Default:** `Press Enter to add "${value}"` **Arguments:** `value`, `valueRaw` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** The text that is shown when a user has inputted a new item but has not pressed the enter key. To access the current input value, pass a function with a `value` argument (see the [default config](https://github.com/Choices-js/Choices#setup) for an example), otherwise pass a string. Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) ### removeItemIconText **Type:** `String/Function` **Default:** `Remove item"` **Arguments:** `value`, `valueRaw`, `item` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** The text/icon for the remove button. To access the item's value, pass a function with a `value` argument (see the **default config** [https://github.com/Choices-js/Choices#setup] for an example), otherwise pass a string. To access the item's label, use the 3rd argument. *Note*; this label is not escaped. Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) ### removeItemLabelText **Type:** `String/Function` **Default:** `Remove item: ${value}"` **Arguments:** `value`, `valueRaw`, `item` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** The text for the remove button's aria label. To access the item's value, pass a function with a `value` argument (see the **default config** [https://github.com/Choices-js/Choices#setup] for an example), otherwise pass a string. To access the item's label, use the 3rd argument. *Note*; this label is not escaped. Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) ### maxItemText **Type:** `String/Function` **Default:** `Only ${maxItemCount} values can be added` **Arguments:** `maxItemCount` **Input types affected:** `text` **Usage:** The text that is shown when a user has focus on the input but has already reached the [max item count](https://github.com/Choices-js/Choices#maxitemcount). To access the max item count, pass a function with a `maxItemCount` argument (see the [default config](https://github.com/Choices-js/Choices#setup) for an example), otherwise pass a string. ### valueComparer **Type:** `Function` **Default:** `strict equality` **Arguments:** `value1`, `value2` **Input types affected:** `select-one`, `select-multiple` **Usage:** A custom compare function used when finding choices by value (using `setChoiceByValue`). **Example:** ```js const example = new Choices(element, { valueComparer: (a, b) => value.trim() === b.trim(), }); ``` ### labelId **Type:** `String` **Default:** `` **Input types affected:** `select-one`, `select-multiple` **Usage:** The labelId improves accessibility. If set, it will add aria-labelledby to the choices element. ### classNames **Type:** `Object` **Default:** ``` classNames: { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], } ``` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Classes added to HTML generated by Choices. By default classnames follow the [BEM](http://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/) notation. ## Callbacks **Note:** For each callback, `this` refers to the current instance of Choices. This can be useful if you need access to methods (`this.disable()`) or the config object (`this.config`). ### callbackOnInit **Type:** `Function` **Default:** `null` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Function to run once Choices initialises. ### callbackOnCreateTemplates(strToEl: (str: string) => HTMLElement, escapeForTemplate: (allowHTML: boolean, s: StringUntrusted | StringPreEscaped | string) => string, getClassNames: (s: Array | string) => string) **Type:** `Function` **Default:** `null` **Arguments:** `strToEl`, `escapeForTemplate`, `getClassNames` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Function to run on template creation. Through this callback it is possible to provide custom templates for the various components of Choices (see terminology). For Choices to work with custom templates, it is important you maintain the various data attributes defined [here](https://github.com/Choices-js/Choices/blob/main/src/scripts/templates.ts). If you want just extend a little original template then you may use `Choices.defaults.templates` to get access to original template function. Templates receive the full Choices config as the first argument to any template, which allows you to conditionally display things based on the options specified. @note For each callback, `this` refers to the current instance of Choices. This can be useful if you need access to methods `(this.disable())`. **Example:** ```js const example = new Choices(element, { callbackOnCreateTemplates: (strToEl, escapeForTemplate, getClassNames) => ({ input: (...args) => Object.assign(Choices.defaults.templates.input.call(this, ...args), { type: 'email', }), }), }); ``` or more complex: ```js // StrToEl = (str: string) => HTMLElement | HTMLInputElement | HTMLOptionElement; // EscapeForTemplateFn = (allowHTML: boolean, s: StringUntrusted | StringPreEscaped | string) => string; // GetClassNamesFn = (s: string | Array) => string; const example = new Choices(element, { callbackOnCreateTemplates: function(strToEl /*:StrToEl*/, escapeForTemplate /*:EscapeForTemplateFn*/, getClassNames /*:GetClassNamesFn*/) { return { item: ({ classNames }, data) => { return strToEl(` ★ ${escapeForTemplate(true, data.label)} `); }, choice: ({ classNames }, data) => { return strToEl(` 0 ? 'role="treeitem"' : 'role="option"' }> ★ ${escapeForTemplate(true, data.label)} `); }, }; }, }); ``` ## Events **Note:** Events fired by Choices behave the same as standard events. Each event is triggered on the element passed to Choices (accessible via `this.passedElement`. Arguments are accessible within the `event.detail` object. **Example:** ```js const element = document.getElementById('example'); const example = new Choices(element); element.addEventListener( 'addItem', function(event) { // do something creative here... console.log(event.detail.id); console.log(event.detail.value); console.log(event.detail.label); console.log(event.detail.customProperties); console.log(event.detail.groupValue); }, false, ); // or const example = new Choices(document.getElementById('example')); example.passedElement.element.addEventListener( 'addItem', function(event) { // do something creative here... console.log(event.detail.id); console.log(event.detail.value); console.log(event.detail.label); console.log(event.detail.customProperties); console.log(event.detail.groupValue); }, false, ); ``` ### addItem **Payload:** `id, highlighted, labelClass, labelDescription, customProperties, disabled, active, label, placeholder, value, groupValue, element, keyCode` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Triggered each time an item is added (programmatically or by the user). ### removeItem **Payload:** `id, highlighted, labelClass, labelDescription, customProperties, disabled, active, label, placeholder, value, groupValue, element, keyCode` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Triggered each time an item is removed (programmatically or by the user). ### highlightItem **Payload:** `id, highlighted, labelClass, labelDescription, customProperties, disabled, active, label, placeholder, value, groupValue, element, keyCode` **Input types affected:** `text`, `select-multiple` **Usage:** Triggered each time an item is highlighted. ### unhighlightItem **Payload:** `id, highlighted, labelClass, labelDescription, customProperties, disabled, active, label, placeholder, value, groupValue, element, keyCode` **Input types affected:** `text`, `select-multiple` **Usage:** Triggered each time an item is unhighlighted. ### choice **Payload:** `id, highlighted, labelClass, labelDescription, customProperties, disabled, active, label, placeholder, value, groupValue, element, keyCode` **Input types affected:** `select-one`, `select-multiple` **Usage:** Triggered each time a choice is selected **by a user**, regardless if it changes the value of the input. `choice` is a Choice object here (see terminology or typings file) ### change **Payload:** `value: string` **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Triggered each time an item is added/removed **by a user**. ### search **Payload:** `value: string`, `resultCount: number` **Input types affected:** `select-one`, `select-multiple` **Usage:** Triggered when a user types into an input to search choices. When a search is ended, a search event with an empty value with no resultCount is triggered. ### showDropdown **Payload:** - **Input types affected:** `select-one`, `select-multiple` **Usage:** Triggered when the dropdown is shown. ### hideDropdown **Payload:** - **Input types affected:** `select-one`, `select-multiple` **Usage:** Triggered when the dropdown is hidden. ### highlightChoice **Payload:** `el` **Input types affected:** `select-one`, `select-multiple` **Usage:** Triggered when a choice from the dropdown is highlighted. The `el` argument is choices.passedElement object that was affected. ## Methods Methods can be called either directly or by chaining: ```js // Calling a method by chaining const choices = new Choices(element, { addItems: false, removeItems: false, }) .setValue(['Set value 1', 'Set value 2']) .disable(); // Calling a method directly const choices = new Choices(element, { addItems: false, removeItems: false, }); choices.setValue(['Set value 1', 'Set value 2']); choices.disable(); ``` ### destroy(); **Input types affected:** `text`, `select-multiple`, `select-one` **Usage:** Kills the instance of Choices, removes all event listeners and returns passed input to its initial state. ### init(); **Input types affected:** `text`, `select-multiple`, `select-one` **Usage:** Creates a new instance of Choices, adds event listeners, creates templates and renders a Choices element to the DOM. **Note:** This is called implicitly when a new instance of Choices is created. This would be used after a Choices instance had already been destroyed (using `destroy()`). ### refresh(withEvents: boolean = false, selectFirstOption: boolean = false); **Input types affected:** `select-multiple`, `select-one` **Usage:** Reads options from backing `` element, and recreates choices. Existing items are preserved. When `withEvents` is truthy, only `addItem` events are generated. ### highlightAll(); **Input types affected:** `text`, `select-multiple` **Usage:** Highlight each chosen item (selected items can be removed). ### unhighlightAll(); **Input types affected:** `text`, `select-multiple` **Usage:** Un-highlight each chosen item. ### removeActiveItemsByValue(value: string); **Input types affected:** `text`, `select-multiple` **Usage:** Remove each item by a given value. ### removeActiveItems(excludedId?: number); **Input types affected:** `text`, `select-multiple` **Usage:** Remove each selectable item. ## removeChoice(value: string); **Input types affected:** `text`, `select-multiple`, `select-one` **Usage:** Remove an option/item by value ### removeHighlightedItems(runEvent?: boolean); **Input types affected:** `text`, `select-multiple` **Usage:** Remove each item the user has selected. ### showDropdown(preventInputFocus?: boolean); **Input types affected:** `select-one`, `select-multiple` **Usage:** Show choices list dropdown. ### hideDropdown(preventInputFocus?: boolean); **Input types affected:** ``select-one`, `select-multiple` **Usage:** Hide choices list dropdown. ### setChoices(choicesArrayOrFetcher?: (InputChoice | InputGroup)[] | ((instance: Choices) => (InputChoice | InputGroup)[] | Promise<(InputChoice | InputGroup)[]>), value?: string | null, label?: string, replaceChoices?: boolean = false, clearSearchFlag?: boolean = false, replaceItems?: boolean = false): this | Promise; **Input types affected:** `select-one`, `select-multiple` **Usage:** Set choices of select input via an array of objects (or function that returns array of object or promise of it), a value field name and a label field name. This behaves the similar as passing items via the `choices` option but can be called after initialising Choices. This can also be used to add groups of choices (see example 3); Optionally pass a true `replaceChoices` value to remove any existing choices. Optionally pass a true `replaceItems` value to remove any items, if false choices for selected items are preserved. Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). Passing an empty array as the first parameter, and a true `replaceChoices` is the same as calling `clearChoices` (see below). **Example 1:** ```js const example = new Choices(element); example.setChoices( [ { value: 'One', label: 'Label One', disabled: true }, { value: 'Two', label: 'Label Two', selected: true }, { value: 'Three', label: 'Label Three' }, ], 'value', 'label', false, ); ``` **Example 2:** ```js const example = new Choices(element); // Passing a function that returns Promise of choices example.setChoices(async () => { try { const items = await fetch('/items'); return items.json(); } catch (err) { console.error(err); } }); ``` **Example 3:** ```js const example = new Choices(element); example.setChoices( [ { label: 'Group one', disabled: false, choices: [ { value: 'Child One', label: 'Child One', selected: true }, { value: 'Child Two', label: 'Child Two', disabled: true }, { value: 'Child Three', label: 'Child Three' }, ], }, { label: 'Group two', disabled: false, choices: [ { value: 'Child Four', label: 'Child Four', disabled: true }, { value: 'Child Five', label: 'Child Five' }, { value: 'Child Six', label: 'Child Six', customProperties: { description: 'Custom description about child six', random: 'Another random custom property', }, }, ], }, ], 'value', 'label', false, ); ``` ### clearChoices(clearOptions: boolean = true, clearItems: boolean = false); **Input types affected:** `select-one`, `select-multiple` **Usage:** Clear all choices from select including any selected items. Does **not** reset the search state. - `clearOptions` If true, clears the backing options from the `` element - `clearItems` If false, preserves selected items instead of clearing them ### getValue(valueOnly?: boolean): string[] | EventChoice[] | EventChoice | string; **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Get value(s) of input (i.e. inputted items (text) or selected choices (select)). Optionally pass an argument of `true` to only return values rather than value objects. **Example:** ```js const example = new Choices(element); const values = example.getValue(true); // returns ['value 1', 'value 2']; const valueArray = example.getValue(); // returns [{ active: true, choiceId: 1, highlighted: false, id: 1, label: 'Label 1', value: 'Value 1'}, { active: true, choiceId: 2, highlighted: false, id: 2, label: 'Label 2', value: 'Value 2'}]; ``` ### setValue(items: string[] | InputChoice[]): this; **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Set value of input based on an array of objects or strings. This behaves exactly the same as passing items via the `items` option but can be called after initialising Choices. **Example:** ```js const example = new Choices(element); // via an array of objects example.setValue([ { value: 'One', label: 'Label One' }, { value: 'Two', label: 'Label Two' }, { value: 'Three', label: 'Label Three' }, ]); // or via an array of strings example.setValue(['Four', 'Five', 'Six']); ``` ### setChoiceByValue(value: string | string[]); **Input types affected:** `select-one`, `select-multiple` **Usage:** Set value of input based on existing Choice. `value` can be either a single string or an array of strings **Example:** ```js const example = new Choices(element, { choices: [ { value: 'One', label: 'Label One' }, { value: 'Two', label: 'Label Two', disabled: true }, { value: 'Three', label: 'Label Three' }, ], }); example.setChoiceByValue('Two'); // Choice with value of 'Two' has now been selected. ``` ### clearStore(); **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Removes all items, choices and groups. Resets the search state. Use with caution. ### clearInput(); **Input types affected:** `text` **Usage:** Clear input of any user inputted text. ### disable(); **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Disables input from accepting new value/selecting further choices. ### enable(); **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Enables input to accept new values/select further choices. ## Browser compatibility Choices is compiled using [Babel](https://babeljs.io/) targeting browsers [with more than 1% of global usage](https://github.com/Choices-js/Choices/blob/main/.browserslistrc) and expecting that features [listed below](https://github.com/Choices-js/Choices/blob/main/.eslintrc.json#L62) are available or polyfilled in browser. You may see exact list of target browsers by running `npm exec browserslist` within this repository folder. If you need to support a browser that does not have one of the features listed below, I suggest including a polyfill from [cdnjs.cloudflare.com/polyfill](https://cdnjs.cloudflare.com/polyfill): **Polyfill example used for the demo:** ```html ``` **Features used in Choices:** ```polyfills Array.from Array.prototype.find Array.prototype.includes Symbol Symbol.iterator DOMTokenList Object.assign CustomEvent Element.prototype.classList Element.prototype.closest Element.prototype.dataset Element.prototype.replaceChildren ``` ## CSS custom properties Since version `11.2`, you are able to customize the behavior and CSS of Choices.js using the following [custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties). | Property | Default | Description | |-----------------------------------|-------------------------------------------|-----------------------------------------------------------------------------| | `--choices-darken` | `black` | Darken color used within the color-mix | | `--choices-lighten` | `white` | Ligten color used within the color-mix | | `--choices-bg-color` | `#f9f9f9` | Background color of the choices element | | `--choices-bg-color-disabled` | `#eaeaea` | Background color of a disabled choices element | | `--choices-bg-color-dropdown` | `#fff` | Background color of the dropdown | | `--choices-text-color` | `#333` | Text color of choices | | `--choices-keyline-color` | `#ddd` | Border-colors within choices | | `--choices-primary-color` | `#005F75` | Primary color | | `--choices-disabled-color` | `#eaeaea` | Background color of disabled items | | `--choices-item-disabled-color` | `#fff` | Text color of disabled items | | `--choices-invalid-color` | `#d33141` | Border color of the invalid state | | `--choices-highlighted-color` | `#f2f2f2` | Highlight background of the choices items | | `--choices-highlight-color` | `#005F75` | Focus color of the choices button | | `--choices-font-size-lg` | `16px` | Basic font size for choices | | `--choices-font-size-md` | `14px` | Font size for medium choices items, e.g. the input field | | `--choices-font-size-sm` | `12px` | Font size for the small choices items, e.g. select multiple or explanations | | `--choices-guttering` | `24px` | Margin-Bottom of the choices wrapper | | `--choices-border-radius` | `2.5px` | Border-radius of the choices element | | `--choices-border-radius-item` | `20px` | Border-radius of the choices items | | `--choices-z-index` | `1` | z-index of the active choices dropdown | | `--choices-input-height` | `44px` | Height of the choices inner element | | `--choices-width` | `100%` | Width of the choices inner element | | `--choices-base-border` | `1px solid var( --choices-keyline-color)` | Bottom-border of the choices inner element | | `--choices-multiple-item-margin` | `3.75px` | Margin of the dropdown items (multiple mode) | | `--choices-multiple-item-padding` | `4px 10px` | Padding of the dropdown items (multiple mode) | | `--choices-dropdown-item-padding` | `10px` | Padding of the choices dropdown items | | `--choices-list-single-padding` | `4px 16px 4px 4px` | Padding of the listbox description | | `--choices-input-margin-bottom` | `5px` | Margin-bottom of the choices input (text inputs) | | `--choices-input-padding` | `4px 0 4px 2px` | Padding of the choices input | | `--choices-inner-padding` | `7.5px 7.5px 3.75px` | Padding of the choices inner element | | `--choices-inner-one-padding` | `7.5px` | Padding of the choices inner element (Single select input) | | `--choices-arrow-size` | `5px` | Size of the choices dropdown symbol | | `--choices-arrow-margin-top` | `-2.5px` | Top offset of the dropdown symbol | | `--choices-arrow-margin-top-open` | `-7.5px` | Top offset of the active dropdown symbol | | `--choices-arrow-right` | `11.5px` | Right offset of the dropdown symbol | | `--choices-icon-cross` | `url("...")` | Button image | | `--choices-icon-cross-inverse` | `url("...")` | Button image (inversed color) | | `--choices-button-offset` | `8px` | Button offset | | `--choices-button-dimension` | `8px` | Button background size | | `--choices-button-line-height` | `1` | Button line height | | `--choices-button-border-radius` | `0` | Button border-radius | | `--choices-button-opacity` | `0.75` | Button opacity | | `--choices-button-opacity-hover` | `1` | Button opacity on hover | | `--choices-placeholder-opacity` | `0.5` | Placeholder opacity | ### Dark mode example The current demo page uses the following variables for its dark mode ```css @media (prefers-color-scheme: dark) { :root { --choices-primary-color: #38daff; --choices-item-color: black; --choices-bg-color: #101010; --choices-bg-color-dropdown: #101010; --choices-keyline-color: #3b3e40; --choices-bg-color-disabled: #181a1b; --choices-item-disabled-color: #eee; --choices-disabled-color: #2d2d2d; --choices-highlighted-color: #16292d; --choices-icon-cross: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg=="); --choices-icon-cross-inverse: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg=="); } } ``` ## Development To setup a local environment: clone this repo, navigate into its directory in a terminal window and run the following command: `npm install` ### playwright e2e (End-to-end) tests are implemented using playwright, which requires installing likely with OS support. `npx playwright install` `npx playwright install-deps ` For JetBrain IDE's the `Test automation` plugin is recommended: https://plugins.jetbrains.com/plugin/20175-test-automation For usage see: https://www.jetbrains.com/help/phpstorm/playwright.html ### NPM tasks | Task | Usage | |---------------------------|--------------------------------------------------------------| | `npm run start` | Fire up local server for development | | `npm run test:unit` | Run sequence of tests once | | `npm run test:unit:watch` | Fire up test server and re-test on file change | | `npm run test:e2e` | Run sequence of e2e tests (with local server) | | `npm run test` | Run both unit and e2e tests | | `npm run playwright:gui` | Run Playwright e2e tests (GUI) | | `npm run playwright:cli` | Run Playwright e2e tests (CLI) | | `npm run js:build` | Compile Choices to an uglified JavaScript file | | `npm run css:watch` | Watch SCSS files for changes. On a change, run build process | | `npm run css:build` | Compile, minify and prefix SCSS files to CSS | ### Build flags Choices supports various environment variables as build-flags to enable/disable features. The pre-built bundles these features set, and tree shaking uses the non-used parts. #### CHOICES_SEARCH_FUSE **Values:** `full` / `basic` / `null` **Default:** `full` Fuse.js support a `full`/`basic` profile. `full` adds additional logic operations, which aren't used by default with Choices. The `null` option drops Fuse.js as a dependency and instead uses a simple prefix only search feature. #### CHOICES_SEARCH_KMP **Values:** `1` / `0` **Default:** `0` If `CHOICES_SEARCH_FUSE` is `null`, this enables an `indexOf`-like [Knuth–Morris–Pratt algorithm](https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm). Useful for very large data sets, without fuzzy searching. #### CHOICES_CAN_USE_DOM **Values:** `1` / `0` **Default:** `1` Allows loading Choices into a non-browser environment. ### Interested in contributing? We're always interested in having more active maintainers. Please get in touch if you're interested 👍 ## License MIT License ## Web component Want to use Choices as a web component? You're in luck. Adidas have built one for their design system which can be found [here](https://github.com/adidas/choicesjs-stencil). ## Misc Thanks to [@mikefrancis](https://github.com/mikefrancis/) for [sending me on a hunt](https://twitter.com/_mikefrancis/status/701797835826667520) for a non-jQuery solution for select boxes that eventually led to this being built! ================================================ FILE: babel.config.json ================================================ { "presets": ["@babel/preset-env", "@babel/preset-typescript"], "plugins": [ "@babel/plugin-transform-object-rest-spread" ] } ================================================ FILE: jsconfig.json ================================================ { "compilerOptions": { "checkJs": true, "target": "es2020", "lib": ["esnext", "dom"], "types": [], "strict": true, "moduleResolution": "node", /* Additional Checks */ "noImplicitAny": false, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "strictNullChecks": false } } ================================================ FILE: package.json ================================================ { "name": "choices.js", "version": "11.2.1", "description": "A vanilla JS customisable text input/select box plugin", "main": "./public/assets/scripts/choices.js", "module": "./public/assets/scripts/choices.mjs", "unpkg": "./public/assets/scripts/choices.js", "jsdelivr": "./public/assets/scripts/choices.js", "types": "./public/types/src/index.d.ts", "exports": { ".": { "types": "./public/types/src/index.d.ts", "import": "./public/assets/scripts/choices.mjs", "require": "./public/assets/scripts/choices.js", "style": "./public/assets/styles/choices.css", "sass": "./src/styles/choices.scss" }, "./search-basic": { "types": "./public/types/src/index.d.ts", "import": "./public/assets/scripts/choices.search-basic.mjs", "require": "./public/assets/scripts/choices.search-basic.min.js" }, "./search-prefix": { "types": "./public/types/src/index.d.ts", "import": "./public/assets/scripts/choices.search-prefix.mjs", "require": "./public/assets/scripts/choices.search-prefix.min.js" }, "./search-kmp": { "types": "./public/types/src/index.d.ts", "import": "./public/assets/scripts/choices.search-kmp.mjs", "require": "./public/assets/scripts/choices.search-kmp.min.js" }, "./search-none": { "types": "./public/types/src/index.d.ts", "import": "./public/assets/scripts/choices.search-none.mjs", "require": "./public/assets/scripts/choices.search-none.min.js" }, "./public/assets/styles/*.css": "./public/assets/styles/*.css", "./src/styles/*.scss": "./src/styles/*.scss", "./src/styles/*": "./src/styles/*.scss" }, "sideEffects": [ "*.scss", "*.css" ], "scripts": { "start": "run-p js:watch css:watch", "build": "run-p js:build css:build", "lint": "run-p lint:js lint:scss", "lint:js": "eslint src/scripts test/scripts test-e2e", "lint:scss": "stylelint src/**/*.scss", "bundlesize": "bundlesize", "playwright:cli": "playwright test --project=chromium", "playwright:gui": "playwright test --ui --project=chromium", "test": "run-s test:unit test:e2e:all", "test:unit": "vitest run", "test:unit:watch": "npm run test:unit -- --watch --inspect=5556", "test:unit:coverage": "vitest run --coverage", "test:e2e": "run-s playwright:cli", "test:e2e:all": "playwright test", "js:watch": "rollup -w --bundleConfigAsCjs -c scripts/rollup.config.mjs --environment TARGET:. --environment OUTPUT_TYPES:umd --environment WATCH_HOST:localhost", "js:build": "rollup --bundleConfigAsCjs -c scripts/rollup.config.mjs --environment WITH_D_TS_FILES:1 && mv public/assets/scripts/src public/types/", "js:build-dev": "rollup --bundleConfigAsCjs -c scripts/rollup.config.mjs --environment TARGET:. --environment OUTPUT_TYPES:umd", "js:build-dev:esm": "rollup --bundleConfigAsCjs -c scripts/rollup.config.mjs --environment TARGET:. --environment OUTPUT_TYPES:mjs", "css:watch": "nodemon -e scss -x \"npm run css:build\"", "css:build": "run-s css:sass css:prefix css:min", "css:sass": "sass -I scss src/styles/base.scss public/assets/styles/base.css && sass -I scss src/styles/choices.scss public/assets/styles/choices.css", "css:prefix": "postcss public/assets/styles/*.css --use autoprefixer --no-map --env prod --dir public/assets/styles", "css:min": "csso public/assets/styles/base.css --output public/assets/styles/base.min.css && csso public/assets/styles/choices.css --output public/assets/styles/choices.min.css", "prepublishOnly": "npm run build" }, "repository": { "type": "git", "url": "https://github.com/Choices-js/Choices.git" }, "author": "Josh Johnson", "license": "MIT", "files": [ "public/assets/scripts", "public/assets/styles", "public/types", "src" ], "bugs": { "url": "https://github.com/Choices-js/Choices/issues" }, "homepage": "https://github.com/Choices-js/Choices#readme", "keywords": [ "customisable", "input", "select", "vanilla", "plugin", "js" ], "devDependencies": { "@babel/cli": "^7.24.8", "@babel/core": "^7.25.2", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/preset-env": "^7.25.3", "@babel/preset-typescript": "^7.24.7", "@playwright/test": "^1.46.0", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-terser": "^1.0.0", "@rollup/plugin-typescript": "^11.1.6", "@types/chai": "^4.3.17", "@types/node": "^22.12.0", "@types/sinon": "^17.0.3", "@types/sinon-chai": "^3.2.12", "@vitest/coverage-v8": "^3.2.4", "autoprefixer": "^10.4.20", "bundlesize2": "^0.0.35", "chai": "^5.1.1", "cross-process-lock": "^2.1.1", "csso-cli": "^4.0.2", "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-compat": "6.0.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-sort-class-members": "^1.20.0", "eslint-plugin-tree-shaking": "^1.12.2", "jsdom": "^24.1.1", "nodemon": "^3.1.4", "npm-run-all": "^4.1.5", "postcss": "^8.4.41", "postcss-cli": "^11.0.0", "prettier": "^3.3.3", "rollup": "^4.20.0", "rollup-plugin-dev": "^2.0.5", "sass": "^1.77.8", "sinon": "^18.0.0", "sinon-chai": "^4.0.0", "stylelint": "^16.7.0", "stylelint-config-standard": "^36.0.1", "stylelint-config-standard-scss": "^13.1.0", "tslib": "^2.6.3", "typescript": "5.5.x", "typescript-eslint": "^8.0.1", "vitest": "^3.2.4" }, "dependencies": { "fuse.js": "^7.0.0" }, "npmName": "choices.js", "npmFileMap": [ { "files": [ "public/assets/scripts/*", "public/assets/styles/*", "public/types/*", "src/icons/*" ] } ], "bundlesize": [ { "path": "public/assets/scripts/choices*.min.js", "maxSize": "25 kB" }, { "path": "public/assets/styles/choices.min.css", "maxSize": "2.5 kB" } ] } ================================================ FILE: playwright.config.ts ================================================ import { defineConfig, devices } from '@playwright/test'; import { PlaywrightTestConfig } from 'playwright/types/test'; import { BundleTest } from './test-e2e/bundle-test'; /** * Read environment variables from file. * https://github.com/motdotla/dotenv */ // import dotenv from 'dotenv'; // dotenv.config({ path: path.resolve(__dirname, '.env') }); /** * See https://playwright.dev/docs/test-configuration. */ const config: PlaywrightTestConfig = { testDir: './test-e2e', snapshotPathTemplate: '{testDir}/__screenshots__/{projectName}-{platform}{ext}', /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ retries: 0, /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'line', timeout: process.env.CI ? 5000 : 1000, expect : { timeout: process.env.CI ? 1000 : 500, }, /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ baseURL: 'http://localhost:3001/', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', testIdAttribute: 'data-test-hook', }, /* Configure projects for major browsers */ projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'], contextOptions: { // chromium-specific permissions permissions: ['clipboard-read', 'clipboard-write'], }, }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, /* Test against mobile viewports. */ // { // name: 'Mobile Chrome', // use: { ...devices['Pixel 5'] }, // }, // { // name: 'Mobile Safari', // use: { ...devices['iPhone 12'] }, // }, /* Test against branded browsers. */ // { // name: 'Microsoft Edge', // use: { ...devices['Desktop Edge'], channel: 'msedge' }, // }, // { // name: 'Google Chrome', // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, // }, ], /* Run your local dev server before starting the tests */ webServer: { command: 'npm run start', url: 'http://localhost:3001', reuseExistingServer: !process.env.CI, }, }; const bundles = [ { name: '', bundle: process.env.CI ? '/assets/scripts/choices.min.js' : '/assets/scripts/choices.js', enabled: true, }, ]; const projects = config.projects; if (config.use.baseURL) { config.projects = []; projects.forEach((project) => { bundles.forEach(({ name, bundle, enabled }) => { if (!enabled) { return; } const projectBundle = { ...project, name: project.name + name, use: { ...project.use, bundle: config.use.baseURL + bundle, } }; config.projects.push(projectBundle); }); }); } export default defineConfig(config); ================================================ FILE: public/assets/images/browserconfig.xml ================================================ #ffffff ================================================ FILE: public/assets/images/manifest.json ================================================ { "name": "Choices.js", "icons": [ { "src": "\/assets\/images\/android-chrome-192x192.png", "sizes": "192x192", "type": "image\/png" } ], "theme_color": "#ffffff", "display": "standalone" } ================================================ FILE: public/assets/scripts/choices.js ================================================ /*! choices.js v11.2.1 | © 2026 Josh Johnson | https://github.com/Choices-js/Choices#readme */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Choices = factory()); })(this, (function () { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var ActionType = { ADD_CHOICE: 'ADD_CHOICE', REMOVE_CHOICE: 'REMOVE_CHOICE', FILTER_CHOICES: 'FILTER_CHOICES', ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', CLEAR_CHOICES: 'CLEAR_CHOICES', ADD_GROUP: 'ADD_GROUP', ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', }; var EventType = { showDropdown: 'showDropdown', hideDropdown: 'hideDropdown', change: 'change', choice: 'choice', search: 'search', addItem: 'addItem', removeItem: 'removeItem', highlightItem: 'highlightItem', highlightChoice: 'highlightChoice', unhighlightItem: 'unhighlightItem', }; var KeyCodeMap = { TAB_KEY: 9, SHIFT_KEY: 16, BACK_KEY: 46, DELETE_KEY: 8, ENTER_KEY: 13, A_KEY: 65, ESC_KEY: 27, UP_KEY: 38, DOWN_KEY: 40, PAGE_UP_KEY: 33, PAGE_DOWN_KEY: 34, }; var ObjectsInConfig = ['fuseOptions', 'classNames']; var PassedElementTypes = { Text: 'text', SelectOne: 'select-one', SelectMultiple: 'select-multiple', }; var addChoice = function (choice) { return ({ type: ActionType.ADD_CHOICE, choice: choice, }); }; var removeChoice = function (choice) { return ({ type: ActionType.REMOVE_CHOICE, choice: choice, }); }; var filterChoices = function (results) { return ({ type: ActionType.FILTER_CHOICES, results: results, }); }; var activateChoices = function (active) { return ({ type: ActionType.ACTIVATE_CHOICES, active: active, }); }; var addGroup = function (group) { return ({ type: ActionType.ADD_GROUP, group: group, }); }; var addItem = function (item) { return ({ type: ActionType.ADD_ITEM, item: item, }); }; var removeItem$1 = function (item) { return ({ type: ActionType.REMOVE_ITEM, item: item, }); }; var highlightItem = function (item, highlighted) { return ({ type: ActionType.HIGHLIGHT_ITEM, item: item, highlighted: highlighted, }); }; var getRandomNumber = function (min, max) { return Math.floor(Math.random() * (max - min) + min); }; var generateChars = function (length) { return Array.from({ length: length }, function () { return getRandomNumber(0, 36).toString(36); }).join(''); }; var generateId = function (element, prefix) { var id = element.id || (element.name && "".concat(element.name, "-").concat(generateChars(2))) || generateChars(4); id = id.replace(/(:|\.|\[|\]|,)/g, ''); id = "".concat(prefix, "-").concat(id); return id; }; var getAdjacentEl = function (startEl, selector, direction) { if (direction === void 0) { direction = 1; } var prop = "".concat(direction > 0 ? 'next' : 'previous', "ElementSibling"); var sibling = startEl[prop]; while (sibling) { if (sibling.matches(selector)) { return sibling; } sibling = sibling[prop]; } return null; }; var isScrolledIntoView = function (element, parent, direction) { if (direction === void 0) { direction = 1; } var isVisible; if (direction > 0) { // In view from bottom isVisible = parent.scrollTop + parent.offsetHeight >= element.offsetTop + element.offsetHeight; } else { // In view from top isVisible = element.offsetTop >= parent.scrollTop; } return isVisible; }; var sanitise = function (value) { if (typeof value !== 'string') { if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { if ('raw' in value) { return sanitise(value.raw); } if ('trusted' in value) { return value.trusted; } } return value; } return value .replace(/&/g, '&') .replace(/>/g, '>') .replace(/= 0 && !window.matchMedia("(min-height: ".concat(dropdownPos + 1, "px)")).matches; } else if (this.position === 'top') { shouldFlip = true; } return shouldFlip; }; Container.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Container.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Container.prototype.open = function (dropdownPos, dropdownHeight) { addClassesToElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; if (this.shouldFlip(dropdownPos, dropdownHeight)) { addClassesToElement(this.element, this.classNames.flippedState); this.isFlipped = true; } }; Container.prototype.close = function () { removeClassesFromElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'false'); this.removeActiveDescendant(); this.isOpen = false; // A dropdown flips if it does not have space within the page if (this.isFlipped) { removeClassesFromElement(this.element, this.classNames.flippedState); this.isFlipped = false; } }; Container.prototype.addFocusState = function () { addClassesToElement(this.element, this.classNames.focusState); }; Container.prototype.removeFocusState = function () { removeClassesFromElement(this.element, this.classNames.focusState); }; Container.prototype.addInvalidState = function () { addClassesToElement(this.element, this.classNames.invalidState); }; Container.prototype.removeInvalidState = function () { removeClassesFromElement(this.element, this.classNames.invalidState); }; Container.prototype.enable = function () { removeClassesFromElement(this.element, this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; }; Container.prototype.disable = function () { addClassesToElement(this.element, this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; }; Container.prototype.wrap = function (element) { var el = this.element; var parentNode = element.parentNode; if (parentNode) { if (element.nextSibling) { parentNode.insertBefore(el, element.nextSibling); } else { parentNode.appendChild(el); } } el.appendChild(element); }; Container.prototype.unwrap = function (element) { var el = this.element; var parentNode = el.parentNode; if (parentNode) { // Move passed element outside this element parentNode.insertBefore(element, el); // Remove this element parentNode.removeChild(el); } }; Container.prototype.addLoadingState = function () { addClassesToElement(this.element, this.classNames.loadingState); this.element.setAttribute('aria-busy', 'true'); this.isLoading = true; }; Container.prototype.removeLoadingState = function () { removeClassesFromElement(this.element, this.classNames.loadingState); this.element.removeAttribute('aria-busy'); this.isLoading = false; }; return Container; }()); var Input = /** @class */ (function () { function Input(_a) { var element = _a.element, type = _a.type, classNames = _a.classNames, preventPaste = _a.preventPaste; this.element = element; this.type = type; this.classNames = classNames; this.preventPaste = preventPaste; this.isFocussed = this.element.isEqualNode(document.activeElement); this.isDisabled = element.disabled; this._onPaste = this._onPaste.bind(this); this._onInput = this._onInput.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } Object.defineProperty(Input.prototype, "placeholder", { set: function (placeholder) { this.element.placeholder = placeholder; }, enumerable: false, configurable: true }); Object.defineProperty(Input.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.value = value; }, enumerable: false, configurable: true }); Input.prototype.addEventListeners = function () { var el = this.element; el.addEventListener('paste', this._onPaste); el.addEventListener('input', this._onInput, { passive: true, }); el.addEventListener('focus', this._onFocus, { passive: true, }); el.addEventListener('blur', this._onBlur, { passive: true, }); }; Input.prototype.removeEventListeners = function () { var el = this.element; el.removeEventListener('input', this._onInput); el.removeEventListener('paste', this._onPaste); el.removeEventListener('focus', this._onFocus); el.removeEventListener('blur', this._onBlur); }; Input.prototype.enable = function () { var el = this.element; el.removeAttribute('disabled'); this.isDisabled = false; }; Input.prototype.disable = function () { var el = this.element; el.setAttribute('disabled', ''); this.isDisabled = true; }; Input.prototype.focus = function () { if (!this.isFocussed) { this.element.focus(); } }; Input.prototype.blur = function () { if (this.isFocussed) { this.element.blur(); } }; Input.prototype.clear = function (setWidth) { if (setWidth === void 0) { setWidth = true; } this.element.value = ''; if (setWidth) { this.setWidth(); } return this; }; /** * Set the correct input width based on placeholder * value or input value */ Input.prototype.setWidth = function () { // Resize input to contents or placeholder var element = this.element; element.style.minWidth = "".concat(element.placeholder.length + 1, "ch"); element.style.width = "".concat(element.value.length + 1, "ch"); }; Input.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Input.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Input.prototype._onInput = function () { if (this.type !== PassedElementTypes.SelectOne) { this.setWidth(); } }; Input.prototype._onPaste = function (event) { if (this.preventPaste) { event.preventDefault(); } }; Input.prototype._onFocus = function () { this.isFocussed = true; }; Input.prototype._onBlur = function () { this.isFocussed = false; }; return Input; }()); var SCROLLING_SPEED = 4; var List = /** @class */ (function () { function List(_a) { var element = _a.element; this.element = element; this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; } List.prototype.prepend = function (node) { var child = this.element.firstElementChild; if (child) { this.element.insertBefore(node, child); } else { this.element.append(node); } }; List.prototype.scrollToTop = function () { this.element.scrollTop = 0; }; List.prototype.scrollToChildElement = function (element, direction) { var _this = this; if (!element) { return; } var listHeight = this.element.offsetHeight; // Scroll position of dropdown var listScrollPosition = this.element.scrollTop + listHeight; var elementHeight = element.offsetHeight; // Distance from bottom of element to top of parent var elementPos = element.offsetTop + elementHeight; // Difference between the element and scroll position var destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop; requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); }; List.prototype._scrollDown = function (scrollPos, strength, destination) { var easing = (destination - scrollPos) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos + distance; }; List.prototype._scrollUp = function (scrollPos, strength, destination) { var easing = (scrollPos - destination) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos - distance; }; List.prototype._animateScroll = function (destination, direction) { var _this = this; var strength = SCROLLING_SPEED; var choiceListScrollTop = this.element.scrollTop; var continueAnimation = false; if (direction > 0) { this._scrollDown(choiceListScrollTop, strength, destination); if (choiceListScrollTop < destination) { continueAnimation = true; } } else { this._scrollUp(choiceListScrollTop, strength, destination); if (choiceListScrollTop > destination) { continueAnimation = true; } } if (continueAnimation) { requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); } }; return List; }()); var WrappedElement = /** @class */ (function () { function WrappedElement(_a) { var element = _a.element, classNames = _a.classNames; this.element = element; this.classNames = classNames; this.isDisabled = false; } Object.defineProperty(WrappedElement.prototype, "isActive", { get: function () { return this.element.dataset.choice === 'active'; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "dir", { get: function () { return this.element.dir; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.setAttribute('value', value); this.element.value = value; }, enumerable: false, configurable: true }); WrappedElement.prototype.conceal = function () { var el = this.element; // Hide passed input addClassesToElement(el, this.classNames.input); el.hidden = true; // Remove element from tab index el.tabIndex = -1; // Backup original styles if any var origStyle = el.getAttribute('style'); if (origStyle) { el.setAttribute('data-choice-orig-style', origStyle); } el.setAttribute('data-choice', 'active'); }; WrappedElement.prototype.reveal = function () { var el = this.element; // Reinstate passed element removeClassesFromElement(el, this.classNames.input); el.hidden = false; el.removeAttribute('tabindex'); // Recover original styles if any var origStyle = el.getAttribute('data-choice-orig-style'); if (origStyle) { el.removeAttribute('data-choice-orig-style'); el.setAttribute('style', origStyle); } else { el.removeAttribute('style'); } el.removeAttribute('data-choice'); }; WrappedElement.prototype.enable = function () { this.element.removeAttribute('disabled'); this.element.disabled = false; this.isDisabled = false; }; WrappedElement.prototype.disable = function () { this.element.setAttribute('disabled', ''); this.element.disabled = true; this.isDisabled = true; }; WrappedElement.prototype.triggerEvent = function (eventType, data) { dispatchEvent(this.element, eventType, data || {}); }; return WrappedElement; }()); var WrappedInput = /** @class */ (function (_super) { __extends(WrappedInput, _super); function WrappedInput() { return _super !== null && _super.apply(this, arguments) || this; } return WrappedInput; }(WrappedElement)); var coerceBool = function (arg, defaultValue) { if (defaultValue === void 0) { defaultValue = true; } return typeof arg === 'undefined' ? defaultValue : !!arg; }; var stringToHtmlClass = function (input) { if (typeof input === 'string') { // eslint-disable-next-line no-param-reassign input = input.split(' ').filter(function (s) { return s.length; }); } if (Array.isArray(input) && input.length) { return input; } return undefined; }; var mapInputToChoice = function (value, allowGroup, allowRawString) { if (allowRawString === void 0) { allowRawString = true; } if (typeof value === 'string') { var sanitisedValue = sanitise(value); var userValue = allowRawString || sanitisedValue === value ? value : { escaped: sanitisedValue, raw: value }; var result_1 = mapInputToChoice({ value: value, label: userValue, selected: true, }, false); return result_1; } var groupOrChoice = value; if ('choices' in groupOrChoice) { if (!allowGroup) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup throw new TypeError("optGroup is not allowed"); } var group = groupOrChoice; var choices = group.choices.map(function (e) { return mapInputToChoice(e, false); }); var result_2 = { id: 0, // actual ID will be assigned during _addGroup label: unwrapStringForRaw(group.label) || group.value, active: !!choices.length, disabled: !!group.disabled, choices: choices, }; return result_2; } var choice = groupOrChoice; var result = { id: 0, // actual ID will be assigned during _addChoice group: null, // actual group will be assigned during _addGroup but before _addChoice score: 0, // used in search rank: 0, // used in search, stable sort order value: choice.value, label: choice.label || choice.value, active: coerceBool(choice.active), selected: coerceBool(choice.selected, false), disabled: coerceBool(choice.disabled, false), placeholder: coerceBool(choice.placeholder, false), highlighted: false, labelClass: stringToHtmlClass(choice.labelClass), labelDescription: choice.labelDescription, customProperties: choice.customProperties, }; return result; }; var isHtmlInputElement = function (e) { return e.tagName === 'INPUT'; }; var isHtmlSelectElement = function (e) { return e.tagName === 'SELECT'; }; var isHtmlOption = function (e) { return e.tagName === 'OPTION'; }; var isHtmlOptgroup = function (e) { return e.tagName === 'OPTGROUP'; }; var WrappedSelect = /** @class */ (function (_super) { __extends(WrappedSelect, _super); function WrappedSelect(_a) { var element = _a.element, classNames = _a.classNames, template = _a.template, extractPlaceholder = _a.extractPlaceholder; var _this = _super.call(this, { element: element, classNames: classNames }) || this; _this.template = template; _this.extractPlaceholder = extractPlaceholder; return _this; } Object.defineProperty(WrappedSelect.prototype, "placeholderOption", { get: function () { return (this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]')); }, enumerable: false, configurable: true }); WrappedSelect.prototype.addOptions = function (choices) { var _this = this; var fragment = document.createDocumentFragment(); choices.forEach(function (obj) { var choice = obj; if (choice.element) { return; } var option = _this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); }; WrappedSelect.prototype.optionsAsChoices = function () { var _this = this; var choices = []; this.element.querySelectorAll(':scope > option, :scope > optgroup').forEach(function (e) { if (isHtmlOption(e)) { choices.push(_this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(_this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; }; // eslint-disable-next-line class-methods-use-this WrappedSelect.prototype._optionToChoice = function (option) { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? { trusted: option.dataset.labelDescription } : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; }; WrappedSelect.prototype._optgroupToChoice = function (optgroup) { var _this = this; var options = optgroup.querySelectorAll('option'); var choices = Array.from(options).map(function (option) { return _this._optionToChoice(option); }); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices: choices, }; }; return WrappedSelect; }(WrappedElement)); var DEFAULT_CLASSNAMES = { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], }; var DEFAULT_CONFIG = { items: [], choices: [], silent: false, renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: function (value) { return !!value && value !== ''; }, removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: function (value) { return "Press Enter to add \"".concat(value, "\""); }, removeItemIconText: function () { return "Remove item"; }, removeItemLabelText: function (value, _valueRaw, i) { return "Remove item: ".concat(i ? sanitise(i.label) : value); }, maxItemText: function (maxItemCount) { return "Only ".concat(maxItemCount, " values can be added"); }, valueComparer: function (value1, value2) { return value1 === value2; }, fuseOptions: { includeScore: true, }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, appendGroupInSearch: false, }; var removeItem = function (item) { var itemEl = item.itemEl; if (itemEl) { itemEl.remove(); item.itemEl = undefined; } }; function items(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_ITEM: { action.item.selected = true; var el = action.item.element; if (el) { el.selected = true; el.setAttribute('selected', ''); } state.push(action.item); break; } case ActionType.REMOVE_ITEM: { action.item.selected = false; var el = action.item.element; if (el) { el.selected = false; el.removeAttribute('selected'); // For a select-one, if all options are deselected, the first item is selected. To set a black value, select.value needs to be set var select = el.parentElement; if (select && isHtmlSelectElement(select) && select.type === PassedElementTypes.SelectOne) { select.value = ''; } } // this is mixing concerns, but this is *so much faster* removeItem(action.item); state = state.filter(function (choice) { return choice.id !== action.item.id; }); break; } case ActionType.REMOVE_CHOICE: { removeItem(action.choice); state = state.filter(function (item) { return item.id !== action.choice.id; }); break; } case ActionType.HIGHLIGHT_ITEM: { var highlighted = action.highlighted; var item = state.find(function (obj) { return obj.id === action.item.id; }); if (item && item.highlighted !== highlighted) { item.highlighted = highlighted; if (context) { updateClassList(item, highlighted ? context.classNames.highlightedState : context.classNames.selectedState, highlighted ? context.classNames.selectedState : context.classNames.highlightedState); } } break; } default: { update = false; break; } } return { state: state, update: update }; } function groups(s, action) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_GROUP: { state.push(action.group); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } /* eslint-disable */ function choices(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_CHOICE: { state.push(action.choice); break; } case ActionType.REMOVE_CHOICE: { action.choice.choiceEl = undefined; if (action.choice.group) { action.choice.group.choices = action.choice.group.choices.filter(function (obj) { return obj.id !== action.choice.id; }); } state = state.filter(function (obj) { return obj.id !== action.choice.id; }); break; } case ActionType.ADD_ITEM: case ActionType.REMOVE_ITEM: { action.item.choiceEl = undefined; break; } case ActionType.FILTER_CHOICES: { // avoid O(n^2) algorithm complexity when searching/filtering choices var scoreLookup_1 = []; action.results.forEach(function (result) { scoreLookup_1[result.item.id] = result; }); state.forEach(function (choice) { var result = scoreLookup_1[choice.id]; if (result !== undefined) { choice.score = result.score; choice.rank = result.rank; choice.active = true; } else { choice.score = 0; choice.rank = 0; choice.active = false; } if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.ACTIVATE_CHOICES: { state.forEach(function (choice) { choice.active = action.active; if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } var reducers = { groups: groups, items: items, choices: choices, }; var Store = /** @class */ (function () { function Store(context) { this._state = this.defaultState; this._listeners = []; this._txn = 0; this._context = context; } Object.defineProperty(Store.prototype, "defaultState", { // eslint-disable-next-line class-methods-use-this get: function () { return { groups: [], items: [], choices: [], }; }, enumerable: false, configurable: true }); // eslint-disable-next-line class-methods-use-this Store.prototype.changeSet = function (init) { return { groups: init, items: init, choices: init, }; }; Store.prototype.reset = function () { this._state = this.defaultState; var changes = this.changeSet(true); if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } }; Store.prototype.subscribe = function (onChange) { this._listeners.push(onChange); return this; }; Store.prototype.dispatch = function (action) { var _this = this; var state = this._state; var hasChanges = false; var changes = this._changeSet || this.changeSet(false); Object.keys(reducers).forEach(function (key) { var stateUpdate = reducers[key](state[key], action, _this._context); if (stateUpdate.update) { hasChanges = true; changes[key] = true; state[key] = stateUpdate.state; } }); if (hasChanges) { if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } } }; Store.prototype.withTxn = function (func) { this._txn++; try { func(); } finally { this._txn = Math.max(0, this._txn - 1); if (!this._txn) { var changeSet_1 = this._changeSet; if (changeSet_1) { this._changeSet = undefined; this._listeners.forEach(function (l) { return l(changeSet_1); }); } } } }; Object.defineProperty(Store.prototype, "state", { /** * Get store object */ get: function () { return this._state; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "items", { /** * Get items from store */ get: function () { return this.state.items; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "highlightedActiveItems", { /** * Get highlighted items from store */ get: function () { return this.items.filter(function (item) { return item.active && item.highlighted; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "choices", { /** * Get choices from store */ get: function () { return this.state.choices; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeChoices", { /** * Get active choices from store */ get: function () { return this.choices.filter(function (choice) { return choice.active; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "searchableChoices", { /** * Get choices that can be searched (excluding placeholders or disabled choices) */ get: function () { var context = this._context; return this.choices.filter(function (choice) { return !choice.placeholder && (context.searchDisabledChoices || !choice.disabled); }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "groups", { /** * Get groups from store */ get: function () { return this.state.groups; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeGroups", { /** * Get active groups from store */ get: function () { var _this = this; return this.state.groups.filter(function (group) { var isActive = group.active && !group.disabled; var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; }); return isActive && hasActiveOptions; }, []); }, enumerable: false, configurable: true }); Store.prototype.inTxn = function () { return this._txn > 0; }; /** * Get single choice by it's ID */ Store.prototype.getChoiceById = function (id) { return this.activeChoices.find(function (choice) { return choice.id === id; }); }; /** * Get group by group id */ Store.prototype.getGroupById = function (id) { return this.groups.find(function (group) { return group.id === id; }); }; return Store; }()); var NoticeTypes = { noChoices: 'no-choices', noResults: 'no-results', addChoice: 'add-choice', generic: '', }; function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } /** * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io) * * Copyright (c) 2023 Kiro Risk (http://kiro.me) * All Rights Reserved. Apache Software License 2.0 * * http://www.apache.org/licenses/LICENSE-2.0 */ function isArray(value) { return !Array.isArray ? getTag(value) === '[object Array]' : Array.isArray(value); } function baseToString(value) { // Exit early for strings to avoid a performance hit in some environments. if (typeof value == 'string') { return value; } let result = value + ''; return result == '0' && 1 / value == -Infinity ? '-0' : result; } function toString(value) { return value == null ? '' : baseToString(value); } function isString(value) { return typeof value === 'string'; } function isNumber(value) { return typeof value === 'number'; } // Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js function isBoolean(value) { return value === true || value === false || isObjectLike(value) && getTag(value) == '[object Boolean]'; } function isObject(value) { return typeof value === 'object'; } // Checks if `value` is object-like. function isObjectLike(value) { return isObject(value) && value !== null; } function isDefined(value) { return value !== undefined && value !== null; } function isBlank(value) { return !value.trim().length; } // Gets the `toStringTag` of `value`. // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js function getTag(value) { return value == null ? value === undefined ? '[object Undefined]' : '[object Null]' : Object.prototype.toString.call(value); } const INCORRECT_INDEX_TYPE = "Incorrect 'index' type"; const LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = key => `Invalid value for key ${key}`; const PATTERN_LENGTH_TOO_LARGE = max => `Pattern length exceeds max of ${max}.`; const MISSING_KEY_PROPERTY = name => `Missing ${name} property in key`; const INVALID_KEY_WEIGHT_VALUE = key => `Property 'weight' in key '${key}' must be a positive integer`; const hasOwn = Object.prototype.hasOwnProperty; class KeyStore { constructor(keys) { this._keys = []; this._keyMap = {}; let totalWeight = 0; keys.forEach(key => { let obj = createKey(key); this._keys.push(obj); this._keyMap[obj.id] = obj; totalWeight += obj.weight; }); // Normalize weights so that their sum is equal to 1 this._keys.forEach(key => { key.weight /= totalWeight; }); } get(keyId) { return this._keyMap[keyId]; } keys() { return this._keys; } toJSON() { return JSON.stringify(this._keys); } } function createKey(key) { let path = null; let id = null; let src = null; let weight = 1; let getFn = null; if (isString(key) || isArray(key)) { src = key; path = createKeyPath(key); id = createKeyId(key); } else { if (!hasOwn.call(key, 'name')) { throw new Error(MISSING_KEY_PROPERTY('name')); } const name = key.name; src = name; if (hasOwn.call(key, 'weight')) { weight = key.weight; if (weight <= 0) { throw new Error(INVALID_KEY_WEIGHT_VALUE(name)); } } path = createKeyPath(name); id = createKeyId(name); getFn = key.getFn; } return { path, id, weight, src, getFn }; } function createKeyPath(key) { return isArray(key) ? key : key.split('.'); } function createKeyId(key) { return isArray(key) ? key.join('.') : key; } function get(obj, path) { let list = []; let arr = false; const deepGet = (obj, path, index) => { if (!isDefined(obj)) { return; } if (!path[index]) { // If there's no path left, we've arrived at the object we care about. list.push(obj); } else { let key = path[index]; const value = obj[key]; if (!isDefined(value)) { return; } // If we're at the last value in the path, and if it's a string/number/bool, // add it to the list if (index === path.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) { list.push(toString(value)); } else if (isArray(value)) { arr = true; // Search each item in the array. for (let i = 0, len = value.length; i < len; i += 1) { deepGet(value[i], path, index + 1); } } else if (path.length) { // An object. Recurse further. deepGet(value, path, index + 1); } } }; // Backwards compatibility (since path used to be a string) deepGet(obj, isString(path) ? path.split('.') : path, 0); return arr ? list : list[0]; } const MatchOptions = { // Whether the matches should be included in the result set. When `true`, each record in the result // set will include the indices of the matched characters. // These can consequently be used for highlighting purposes. includeMatches: false, // When `true`, the matching function will continue to the end of a search pattern even if // a perfect match has already been located in the string. findAllMatches: false, // Minimum number of characters that must be matched before a result is considered a match minMatchCharLength: 1 }; const BasicOptions = { // When `true`, the algorithm continues searching to the end of the input even if a perfect // match is found before the end of the same input. isCaseSensitive: false, // When true, the matching function will continue to the end of a search pattern even if includeScore: false, // List of properties that will be searched. This also supports nested properties. keys: [], // Whether to sort the result list, by score shouldSort: true, // Default sort function: sort by ascending score, ascending index sortFn: (a, b) => a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1 }; const FuzzyOptions = { // Approximately where in the text is the pattern expected to be found? location: 0, // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match // (of both letters and location), a threshold of '1.0' would match anything. threshold: 0.6, // Determines how close the match must be to the fuzzy location (specified above). // An exact letter match which is 'distance' characters away from the fuzzy location // would score as a complete mismatch. A distance of '0' requires the match be at // the exact location specified, a threshold of '1000' would require a perfect match // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. distance: 100 }; const AdvancedOptions = { // When `true`, it enables the use of unix-like search commands useExtendedSearch: false, // The get function to use when fetching an object's properties. // The default will search nested paths *ie foo.bar.baz* getFn: get, // When `true`, search will ignore `location` and `distance`, so it won't matter // where in the string the pattern appears. // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score ignoreLocation: false, // When `true`, the calculation for the relevance score (used for sorting) will // ignore the field-length norm. // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm ignoreFieldNorm: false, // The weight to determine how much field length norm effects scoring. fieldNormWeight: 1 }; var Config = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, BasicOptions), MatchOptions), FuzzyOptions), AdvancedOptions); const SPACE = /[^ ]+/g; // Field-length norm: the shorter the field, the higher the weight. // Set to 3 decimals to reduce index size. function norm(weight = 1, mantissa = 3) { const cache = new Map(); const m = Math.pow(10, mantissa); return { get(value) { const numTokens = value.match(SPACE).length; if (cache.has(numTokens)) { return cache.get(numTokens); } // Default function is 1/sqrt(x), weight makes that variable const norm = 1 / Math.pow(numTokens, 0.5 * weight); // In place of `toFixed(mantissa)`, for faster computation const n = parseFloat(Math.round(norm * m) / m); cache.set(numTokens, n); return n; }, clear() { cache.clear(); } }; } class FuseIndex { constructor({ getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { this.norm = norm(fieldNormWeight, 3); this.getFn = getFn; this.isCreated = false; this.setIndexRecords(); } setSources(docs = []) { this.docs = docs; } setIndexRecords(records = []) { this.records = records; } setKeys(keys = []) { this.keys = keys; this._keysMap = {}; keys.forEach((key, idx) => { this._keysMap[key.id] = idx; }); } create() { if (this.isCreated || !this.docs.length) { return; } this.isCreated = true; // List is Array if (isString(this.docs[0])) { this.docs.forEach((doc, docIndex) => { this._addString(doc, docIndex); }); } else { // List is Array this.docs.forEach((doc, docIndex) => { this._addObject(doc, docIndex); }); } this.norm.clear(); } // Adds a doc to the end of the index add(doc) { const idx = this.size(); if (isString(doc)) { this._addString(doc, idx); } else { this._addObject(doc, idx); } } // Removes the doc at the specified index of the index removeAt(idx) { this.records.splice(idx, 1); // Change ref index of every subsquent doc for (let i = idx, len = this.size(); i < len; i += 1) { this.records[i].i -= 1; } } getValueForItemAtKeyId(item, keyId) { return item[this._keysMap[keyId]]; } size() { return this.records.length; } _addString(doc, docIndex) { if (!isDefined(doc) || isBlank(doc)) { return; } let record = { v: doc, i: docIndex, n: this.norm.get(doc) }; this.records.push(record); } _addObject(doc, docIndex) { let record = { i: docIndex, $: {} }; // Iterate over every key (i.e, path), and fetch the value at that key this.keys.forEach((key, keyIndex) => { let value = key.getFn ? key.getFn(doc) : this.getFn(doc, key.path); if (!isDefined(value)) { return; } if (isArray(value)) { let subRecords = []; const stack = [{ nestedArrIndex: -1, value }]; while (stack.length) { const { nestedArrIndex, value } = stack.pop(); if (!isDefined(value)) { continue; } if (isString(value) && !isBlank(value)) { let subRecord = { v: value, i: nestedArrIndex, n: this.norm.get(value) }; subRecords.push(subRecord); } else if (isArray(value)) { value.forEach((item, k) => { stack.push({ nestedArrIndex: k, value: item }); }); } else ; } record.$[keyIndex] = subRecords; } else if (isString(value) && !isBlank(value)) { let subRecord = { v: value, n: this.norm.get(value) }; record.$[keyIndex] = subRecord; } }); this.records.push(record); } toJSON() { return { keys: this.keys, records: this.records }; } } function createIndex(keys, docs, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { const myIndex = new FuseIndex({ getFn, fieldNormWeight }); myIndex.setKeys(keys.map(createKey)); myIndex.setSources(docs); myIndex.create(); return myIndex; } function parseIndex(data, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { const { keys, records } = data; const myIndex = new FuseIndex({ getFn, fieldNormWeight }); myIndex.setKeys(keys); myIndex.setIndexRecords(records); return myIndex; } function computeScore$1(pattern, { errors = 0, currentLocation = 0, expectedLocation = 0, distance = Config.distance, ignoreLocation = Config.ignoreLocation } = {}) { const accuracy = errors / pattern.length; if (ignoreLocation) { return accuracy; } const proximity = Math.abs(expectedLocation - currentLocation); if (!distance) { // Dodge divide by zero error. return proximity ? 1.0 : accuracy; } return accuracy + proximity / distance; } function convertMaskToIndices(matchmask = [], minMatchCharLength = Config.minMatchCharLength) { let indices = []; let start = -1; let end = -1; let i = 0; for (let len = matchmask.length; i < len; i += 1) { let match = matchmask[i]; if (match && start === -1) { start = i; } else if (!match && start !== -1) { end = i - 1; if (end - start + 1 >= minMatchCharLength) { indices.push([start, end]); } start = -1; } } // (i-1 - start) + 1 => i - start if (matchmask[i - 1] && i - start >= minMatchCharLength) { indices.push([start, i - 1]); } return indices; } // Machine word size const MAX_BITS = 32; function search(text, pattern, patternAlphabet, { location = Config.location, distance = Config.distance, threshold = Config.threshold, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, includeMatches = Config.includeMatches, ignoreLocation = Config.ignoreLocation } = {}) { if (pattern.length > MAX_BITS) { throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS)); } const patternLen = pattern.length; // Set starting location at beginning text and initialize the alphabet. const textLen = text.length; // Handle the case when location > text.length const expectedLocation = Math.max(0, Math.min(location, textLen)); // Highest score beyond which we give up. let currentThreshold = threshold; // Is there a nearby exact match? (speedup) let bestLocation = expectedLocation; // Performance: only computer matches when the minMatchCharLength > 1 // OR if `includeMatches` is true. const computeMatches = minMatchCharLength > 1 || includeMatches; // A mask of the matches, used for building the indices const matchMask = computeMatches ? Array(textLen) : []; let index; // Get all exact matches, here for speed up while ((index = text.indexOf(pattern, bestLocation)) > -1) { let score = computeScore$1(pattern, { currentLocation: index, expectedLocation, distance, ignoreLocation }); currentThreshold = Math.min(score, currentThreshold); bestLocation = index + patternLen; if (computeMatches) { let i = 0; while (i < patternLen) { matchMask[index + i] = 1; i += 1; } } } // Reset the best location bestLocation = -1; let lastBitArr = []; let finalScore = 1; let binMax = patternLen + textLen; const mask = 1 << patternLen - 1; for (let i = 0; i < patternLen; i += 1) { // Scan for the best match; each iteration allows for one more error. // Run a binary search to determine how far from the match location we can stray // at this error level. let binMin = 0; let binMid = binMax; while (binMin < binMid) { const score = computeScore$1(pattern, { errors: i, currentLocation: expectedLocation + binMid, expectedLocation, distance, ignoreLocation }); if (score <= currentThreshold) { binMin = binMid; } else { binMax = binMid; } binMid = Math.floor((binMax - binMin) / 2 + binMin); } // Use the result from this iteration as the maximum for the next. binMax = binMid; let start = Math.max(1, expectedLocation - binMid + 1); let finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen; // Initialize the bit array let bitArr = Array(finish + 2); bitArr[finish + 1] = (1 << i) - 1; for (let j = finish; j >= start; j -= 1) { let currentLocation = j - 1; let charMatch = patternAlphabet[text.charAt(currentLocation)]; if (computeMatches) { // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`) matchMask[currentLocation] = +!!charMatch; } // First pass: exact match bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch; // Subsequent passes: fuzzy match if (i) { bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1]; } if (bitArr[j] & mask) { finalScore = computeScore$1(pattern, { errors: i, currentLocation, expectedLocation, distance, ignoreLocation }); // This match will almost certainly be better than any existing match. // But check anyway. if (finalScore <= currentThreshold) { // Indeed it is currentThreshold = finalScore; bestLocation = currentLocation; // Already passed `loc`, downhill from here on in. if (bestLocation <= expectedLocation) { break; } // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`. start = Math.max(1, 2 * expectedLocation - bestLocation); } } } // No hope for a (better) match at greater error levels. const score = computeScore$1(pattern, { errors: i + 1, currentLocation: expectedLocation, expectedLocation, distance, ignoreLocation }); if (score > currentThreshold) { break; } lastBitArr = bitArr; } const result = { isMatch: bestLocation >= 0, // Count exact matches (those with a score of 0) to be "almost" exact score: Math.max(0.001, finalScore) }; if (computeMatches) { const indices = convertMaskToIndices(matchMask, minMatchCharLength); if (!indices.length) { result.isMatch = false; } else if (includeMatches) { result.indices = indices; } } return result; } function createPatternAlphabet(pattern) { let mask = {}; for (let i = 0, len = pattern.length; i < len; i += 1) { const char = pattern.charAt(i); mask[char] = (mask[char] || 0) | 1 << len - i - 1; } return mask; } class BitapSearch { constructor(pattern, { location = Config.location, threshold = Config.threshold, distance = Config.distance, includeMatches = Config.includeMatches, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, isCaseSensitive = Config.isCaseSensitive, ignoreLocation = Config.ignoreLocation } = {}) { this.options = { location, threshold, distance, includeMatches, findAllMatches, minMatchCharLength, isCaseSensitive, ignoreLocation }; this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); this.chunks = []; if (!this.pattern.length) { return; } const addChunk = (pattern, startIndex) => { this.chunks.push({ pattern, alphabet: createPatternAlphabet(pattern), startIndex }); }; const len = this.pattern.length; if (len > MAX_BITS) { let i = 0; const remainder = len % MAX_BITS; const end = len - remainder; while (i < end) { addChunk(this.pattern.substr(i, MAX_BITS), i); i += MAX_BITS; } if (remainder) { const startIndex = len - MAX_BITS; addChunk(this.pattern.substr(startIndex), startIndex); } } else { addChunk(this.pattern, 0); } } searchIn(text) { const { isCaseSensitive, includeMatches } = this.options; if (!isCaseSensitive) { text = text.toLowerCase(); } // Exact match if (this.pattern === text) { let result = { isMatch: true, score: 0 }; if (includeMatches) { result.indices = [[0, text.length - 1]]; } return result; } // Otherwise, use Bitap algorithm const { location, distance, threshold, findAllMatches, minMatchCharLength, ignoreLocation } = this.options; let allIndices = []; let totalScore = 0; let hasMatches = false; this.chunks.forEach(({ pattern, alphabet, startIndex }) => { const { isMatch, score, indices } = search(text, pattern, alphabet, { location: location + startIndex, distance, threshold, findAllMatches, minMatchCharLength, includeMatches, ignoreLocation }); if (isMatch) { hasMatches = true; } totalScore += score; if (isMatch && indices) { allIndices = [...allIndices, ...indices]; } }); let result = { isMatch: hasMatches, score: hasMatches ? totalScore / this.chunks.length : 1 }; if (hasMatches && includeMatches) { result.indices = allIndices; } return result; } } class BaseMatch { constructor(pattern) { this.pattern = pattern; } static isMultiMatch(pattern) { return getMatch(pattern, this.multiRegex); } static isSingleMatch(pattern) { return getMatch(pattern, this.singleRegex); } search( /*text*/) {} } function getMatch(pattern, exp) { const matches = pattern.match(exp); return matches ? matches[1] : null; } // Token: 'file class ExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'exact'; } static get multiRegex() { return /^="(.*)"$/; } static get singleRegex() { return /^=(.*)$/; } search(text) { const isMatch = text === this.pattern; return { isMatch, score: isMatch ? 0 : 1, indices: [0, this.pattern.length - 1] }; } } // Token: !fire class InverseExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'inverse-exact'; } static get multiRegex() { return /^!"(.*)"$/; } static get singleRegex() { return /^!(.*)$/; } search(text) { const index = text.indexOf(this.pattern); const isMatch = index === -1; return { isMatch, score: isMatch ? 0 : 1, indices: [0, text.length - 1] }; } } // Token: ^file class PrefixExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'prefix-exact'; } static get multiRegex() { return /^\^"(.*)"$/; } static get singleRegex() { return /^\^(.*)$/; } search(text) { const isMatch = text.startsWith(this.pattern); return { isMatch, score: isMatch ? 0 : 1, indices: [0, this.pattern.length - 1] }; } } // Token: !^fire class InversePrefixExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'inverse-prefix-exact'; } static get multiRegex() { return /^!\^"(.*)"$/; } static get singleRegex() { return /^!\^(.*)$/; } search(text) { const isMatch = !text.startsWith(this.pattern); return { isMatch, score: isMatch ? 0 : 1, indices: [0, text.length - 1] }; } } // Token: .file$ class SuffixExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'suffix-exact'; } static get multiRegex() { return /^"(.*)"\$$/; } static get singleRegex() { return /^(.*)\$$/; } search(text) { const isMatch = text.endsWith(this.pattern); return { isMatch, score: isMatch ? 0 : 1, indices: [text.length - this.pattern.length, text.length - 1] }; } } // Token: !.file$ class InverseSuffixExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'inverse-suffix-exact'; } static get multiRegex() { return /^!"(.*)"\$$/; } static get singleRegex() { return /^!(.*)\$$/; } search(text) { const isMatch = !text.endsWith(this.pattern); return { isMatch, score: isMatch ? 0 : 1, indices: [0, text.length - 1] }; } } class FuzzyMatch extends BaseMatch { constructor(pattern, { location = Config.location, threshold = Config.threshold, distance = Config.distance, includeMatches = Config.includeMatches, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, isCaseSensitive = Config.isCaseSensitive, ignoreLocation = Config.ignoreLocation } = {}) { super(pattern); this._bitapSearch = new BitapSearch(pattern, { location, threshold, distance, includeMatches, findAllMatches, minMatchCharLength, isCaseSensitive, ignoreLocation }); } static get type() { return 'fuzzy'; } static get multiRegex() { return /^"(.*)"$/; } static get singleRegex() { return /^(.*)$/; } search(text) { return this._bitapSearch.searchIn(text); } } // Token: 'file class IncludeMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'include'; } static get multiRegex() { return /^'"(.*)"$/; } static get singleRegex() { return /^'(.*)$/; } search(text) { let location = 0; let index; const indices = []; const patternLen = this.pattern.length; // Get all exact matches while ((index = text.indexOf(this.pattern, location)) > -1) { location = index + patternLen; indices.push([index, location - 1]); } const isMatch = !!indices.length; return { isMatch, score: isMatch ? 0 : 1, indices }; } } // ❗Order is important. DO NOT CHANGE. const searchers = [ExactMatch, IncludeMatch, PrefixExactMatch, InversePrefixExactMatch, InverseSuffixExactMatch, SuffixExactMatch, InverseExactMatch, FuzzyMatch]; const searchersLen = searchers.length; // Regex to split by spaces, but keep anything in quotes together const SPACE_RE = / +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/; const OR_TOKEN = '|'; // Return a 2D array representation of the query, for simpler parsing. // Example: // "^core go$ | rb$ | py$ xy$" => [["^core", "go$"], ["rb$"], ["py$", "xy$"]] function parseQuery(pattern, options = {}) { return pattern.split(OR_TOKEN).map(item => { let query = item.trim().split(SPACE_RE).filter(item => item && !!item.trim()); let results = []; for (let i = 0, len = query.length; i < len; i += 1) { const queryItem = query[i]; // 1. Handle multiple query match (i.e, once that are quoted, like `"hello world"`) let found = false; let idx = -1; while (!found && ++idx < searchersLen) { const searcher = searchers[idx]; let token = searcher.isMultiMatch(queryItem); if (token) { results.push(new searcher(token, options)); found = true; } } if (found) { continue; } // 2. Handle single query matches (i.e, once that are *not* quoted) idx = -1; while (++idx < searchersLen) { const searcher = searchers[idx]; let token = searcher.isSingleMatch(queryItem); if (token) { results.push(new searcher(token, options)); break; } } } return results; }); } // These extended matchers can return an array of matches, as opposed // to a singl match const MultiMatchSet = new Set([FuzzyMatch.type, IncludeMatch.type]); /** * Command-like searching * ====================== * * Given multiple search terms delimited by spaces.e.g. `^jscript .python$ ruby !java`, * search in a given text. * * Search syntax: * * | Token | Match type | Description | * | ----------- | -------------------------- | -------------------------------------- | * | `jscript` | fuzzy-match | Items that fuzzy match `jscript` | * | `=scheme` | exact-match | Items that are `scheme` | * | `'python` | include-match | Items that include `python` | * | `!ruby` | inverse-exact-match | Items that do not include `ruby` | * | `^java` | prefix-exact-match | Items that start with `java` | * | `!^earlang` | inverse-prefix-exact-match | Items that do not start with `earlang` | * | `.js$` | suffix-exact-match | Items that end with `.js` | * | `!.go$` | inverse-suffix-exact-match | Items that do not end with `.go` | * * A single pipe character acts as an OR operator. For example, the following * query matches entries that start with `core` and end with either`go`, `rb`, * or`py`. * * ``` * ^core go$ | rb$ | py$ * ``` */ class ExtendedSearch { constructor(pattern, { isCaseSensitive = Config.isCaseSensitive, includeMatches = Config.includeMatches, minMatchCharLength = Config.minMatchCharLength, ignoreLocation = Config.ignoreLocation, findAllMatches = Config.findAllMatches, location = Config.location, threshold = Config.threshold, distance = Config.distance } = {}) { this.query = null; this.options = { isCaseSensitive, includeMatches, minMatchCharLength, findAllMatches, ignoreLocation, location, threshold, distance }; this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); this.query = parseQuery(this.pattern, this.options); } static condition(_, options) { return options.useExtendedSearch; } searchIn(text) { const query = this.query; if (!query) { return { isMatch: false, score: 1 }; } const { includeMatches, isCaseSensitive } = this.options; text = isCaseSensitive ? text : text.toLowerCase(); let numMatches = 0; let allIndices = []; let totalScore = 0; // ORs for (let i = 0, qLen = query.length; i < qLen; i += 1) { const searchers = query[i]; // Reset indices allIndices.length = 0; numMatches = 0; // ANDs for (let j = 0, pLen = searchers.length; j < pLen; j += 1) { const searcher = searchers[j]; const { isMatch, indices, score } = searcher.search(text); if (isMatch) { numMatches += 1; totalScore += score; if (includeMatches) { const type = searcher.constructor.type; if (MultiMatchSet.has(type)) { allIndices = [...allIndices, ...indices]; } else { allIndices.push(indices); } } } else { totalScore = 0; numMatches = 0; allIndices.length = 0; break; } } // OR condition, so if TRUE, return if (numMatches) { let result = { isMatch: true, score: totalScore / numMatches }; if (includeMatches) { result.indices = allIndices; } return result; } } // Nothing was matched return { isMatch: false, score: 1 }; } } const registeredSearchers = []; function register(...args) { registeredSearchers.push(...args); } function createSearcher(pattern, options) { for (let i = 0, len = registeredSearchers.length; i < len; i += 1) { let searcherClass = registeredSearchers[i]; if (searcherClass.condition(pattern, options)) { return new searcherClass(pattern, options); } } return new BitapSearch(pattern, options); } const LogicalOperator = { AND: '$and', OR: '$or' }; const KeyType = { PATH: '$path', PATTERN: '$val' }; const isExpression = query => !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]); const isPath = query => !!query[KeyType.PATH]; const isLeaf = query => !isArray(query) && isObject(query) && !isExpression(query); const convertToExplicit = query => ({ [LogicalOperator.AND]: Object.keys(query).map(key => ({ [key]: query[key] })) }); // When `auto` is `true`, the parse function will infer and initialize and add // the appropriate `Searcher` instance function parse(query, options, { auto = true } = {}) { const next = query => { let keys = Object.keys(query); const isQueryPath = isPath(query); if (!isQueryPath && keys.length > 1 && !isExpression(query)) { return next(convertToExplicit(query)); } if (isLeaf(query)) { const key = isQueryPath ? query[KeyType.PATH] : keys[0]; const pattern = isQueryPath ? query[KeyType.PATTERN] : query[key]; if (!isString(pattern)) { throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key)); } const obj = { keyId: createKeyId(key), pattern }; if (auto) { obj.searcher = createSearcher(pattern, options); } return obj; } let node = { children: [], operator: keys[0] }; keys.forEach(key => { const value = query[key]; if (isArray(value)) { value.forEach(item => { node.children.push(next(item)); }); } }); return node; }; if (!isExpression(query)) { query = convertToExplicit(query); } return next(query); } // Practical scoring function function computeScore(results, { ignoreFieldNorm = Config.ignoreFieldNorm }) { results.forEach(result => { let totalScore = 1; result.matches.forEach(({ key, norm, score }) => { const weight = key ? key.weight : null; totalScore *= Math.pow(score === 0 && weight ? Number.EPSILON : score, (weight || 1) * (ignoreFieldNorm ? 1 : norm)); }); result.score = totalScore; }); } function transformMatches(result, data) { const matches = result.matches; data.matches = []; if (!isDefined(matches)) { return; } matches.forEach(match => { if (!isDefined(match.indices) || !match.indices.length) { return; } const { indices, value } = match; let obj = { indices, value }; if (match.key) { obj.key = match.key.src; } if (match.idx > -1) { obj.refIndex = match.idx; } data.matches.push(obj); }); } function transformScore(result, data) { data.score = result.score; } function format(results, docs, { includeMatches = Config.includeMatches, includeScore = Config.includeScore } = {}) { const transformers = []; if (includeMatches) transformers.push(transformMatches); if (includeScore) transformers.push(transformScore); return results.map(result => { const { idx } = result; const data = { item: docs[idx], refIndex: idx }; if (transformers.length) { transformers.forEach(transformer => { transformer(result, data); }); } return data; }); } class Fuse { constructor(docs, options = {}, index) { this.options = _objectSpread2(_objectSpread2({}, Config), options); if (this.options.useExtendedSearch && false) ; this._keyStore = new KeyStore(this.options.keys); this.setCollection(docs, index); } setCollection(docs, index) { this._docs = docs; if (index && !(index instanceof FuseIndex)) { throw new Error(INCORRECT_INDEX_TYPE); } this._myIndex = index || createIndex(this.options.keys, this._docs, { getFn: this.options.getFn, fieldNormWeight: this.options.fieldNormWeight }); } add(doc) { if (!isDefined(doc)) { return; } this._docs.push(doc); this._myIndex.add(doc); } remove(predicate = ( /* doc, idx */) => false) { const results = []; for (let i = 0, len = this._docs.length; i < len; i += 1) { const doc = this._docs[i]; if (predicate(doc, i)) { this.removeAt(i); i -= 1; len -= 1; results.push(doc); } } return results; } removeAt(idx) { this._docs.splice(idx, 1); this._myIndex.removeAt(idx); } getIndex() { return this._myIndex; } search(query, { limit = -1 } = {}) { const { includeMatches, includeScore, shouldSort, sortFn, ignoreFieldNorm } = this.options; let results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query); computeScore(results, { ignoreFieldNorm }); if (shouldSort) { results.sort(sortFn); } if (isNumber(limit) && limit > -1) { results = results.slice(0, limit); } return format(results, this._docs, { includeMatches, includeScore }); } _searchStringList(query) { const searcher = createSearcher(query, this.options); const { records } = this._myIndex; const results = []; // Iterate over every string in the index records.forEach(({ v: text, i: idx, n: norm }) => { if (!isDefined(text)) { return; } const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { results.push({ item: text, idx, matches: [{ score, value: text, norm, indices }] }); } }); return results; } _searchLogical(query) { const expression = parse(query, this.options); const evaluate = (node, item, idx) => { if (!node.children) { const { keyId, searcher } = node; const matches = this._findMatches({ key: this._keyStore.get(keyId), value: this._myIndex.getValueForItemAtKeyId(item, keyId), searcher }); if (matches && matches.length) { return [{ idx, item, matches }]; } return []; } const res = []; for (let i = 0, len = node.children.length; i < len; i += 1) { const child = node.children[i]; const result = evaluate(child, item, idx); if (result.length) { res.push(...result); } else if (node.operator === LogicalOperator.AND) { return []; } } return res; }; const records = this._myIndex.records; const resultMap = {}; const results = []; records.forEach(({ $: item, i: idx }) => { if (isDefined(item)) { let expResults = evaluate(expression, item, idx); if (expResults.length) { // Dedupe when adding if (!resultMap[idx]) { resultMap[idx] = { idx, item, matches: [] }; results.push(resultMap[idx]); } expResults.forEach(({ matches }) => { resultMap[idx].matches.push(...matches); }); } } }); return results; } _searchObjectList(query) { const searcher = createSearcher(query, this.options); const { keys, records } = this._myIndex; const results = []; // List is Array records.forEach(({ $: item, i: idx }) => { if (!isDefined(item)) { return; } let matches = []; // Iterate over every key (i.e, path), and fetch the value at that key keys.forEach((key, keyIndex) => { matches.push(...this._findMatches({ key, value: item[keyIndex], searcher })); }); if (matches.length) { results.push({ idx, item, matches }); } }); return results; } _findMatches({ key, value, searcher }) { if (!isDefined(value)) { return []; } let matches = []; if (isArray(value)) { value.forEach(({ v: text, i: idx, n: norm }) => { if (!isDefined(text)) { return; } const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { matches.push({ score, key, value: text, idx, norm, indices }); } }); } else { const { v: text, n: norm } = value; const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { matches.push({ score, key, value: text, norm, indices }); } } return matches; } } Fuse.version = '7.0.0'; Fuse.createIndex = createIndex; Fuse.parseIndex = parseIndex; Fuse.config = Config; { Fuse.parseQuery = parse; } { register(ExtendedSearch); } var SearchByFuse = /** @class */ (function () { function SearchByFuse(config) { this._haystack = []; this._fuseOptions = __assign(__assign({}, config.fuseOptions), { keys: __spreadArray([], config.searchFields, true), includeMatches: true }); } SearchByFuse.prototype.index = function (data) { this._haystack = data; if (this._fuse) { this._fuse.setCollection(data); } }; SearchByFuse.prototype.reset = function () { this._haystack = []; this._fuse = undefined; }; SearchByFuse.prototype.isEmptyIndex = function () { return !this._haystack.length; }; SearchByFuse.prototype.search = function (needle) { if (!this._fuse) { { this._fuse = new Fuse(this._haystack, this._fuseOptions); } } var results = this._fuse.search(needle); return results.map(function (value, i) { return { item: value.item, score: value.score || 0, rank: i + 1, // If value.score is used for sorting, this can create non-stable sorts! }; }); }; return SearchByFuse; }()); function getSearcher(config) { { return new SearchByFuse(config); } } /** * Helpers to create HTML elements used by Choices * Can be overridden by providing `callbackOnCreateTemplates` option. * `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates` */ var isEmptyObject = function (obj) { // eslint-disable-next-line no-restricted-syntax for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { return false; } } return true; }; var assignCustomProperties = function (el, choice, withCustomProperties) { var dataset = el.dataset; var customProperties = choice.customProperties, labelClass = choice.labelClass, labelDescription = choice.labelDescription; if (labelClass) { dataset.labelClass = getClassNames(labelClass).join(' '); } if (labelDescription) { dataset.labelDescription = unwrapStringForRaw(labelDescription); } if (withCustomProperties && customProperties) { if (typeof customProperties === 'string') { dataset.customProperties = customProperties; } else if (typeof customProperties === 'object' && !isEmptyObject(customProperties)) { dataset.customProperties = JSON.stringify(customProperties); } } }; var addAriaLabel = function (docRoot, id, element) { var label = id && docRoot.querySelector("label[for='".concat(id, "']")); var text = label && label.innerText; if (text) { element.setAttribute('aria-label', text); } }; var templates = { containerOuter: function (_a, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType, labelId) { var containerOuter = _a.classNames.containerOuter; var div = document.createElement('div'); addClassesToElement(div, containerOuter); div.dataset.type = passedElementType; if (dir) { div.dir = dir; } if (isSelectOneElement) { div.tabIndex = 0; } if (isSelectElement) { div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox'); if (searchEnabled) { div.setAttribute('aria-autocomplete', 'list'); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, div); } div.setAttribute('aria-haspopup', 'true'); div.setAttribute('aria-expanded', 'false'); } if (labelId) { div.setAttribute('aria-labelledby', labelId); } return div; }, containerInner: function (_a) { var containerInner = _a.classNames.containerInner; var div = document.createElement('div'); addClassesToElement(div, containerInner); return div; }, itemList: function (_a, isSelectOneElement) { var searchEnabled = _a.searchEnabled, _b = _a.classNames, list = _b.list, listSingle = _b.listSingle, listItems = _b.listItems; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, isSelectOneElement ? listSingle : listItems); if (this._isSelectElement && searchEnabled) { div.setAttribute('role', 'listbox'); } return div; }, placeholder: function (_a, value) { var allowHTML = _a.allowHTML, placeholder = _a.classNames.placeholder; var div = document.createElement('div'); addClassesToElement(div, placeholder); setElementHtml(div, allowHTML, value); return div; }, item: function (_a, choice, removeItemButton) { var allowHTML = _a.allowHTML, removeItemButtonAlignLeft = _a.removeItemButtonAlignLeft, removeItemIconText = _a.removeItemIconText, removeItemLabelText = _a.removeItemLabelText, _b = _a.classNames, item = _b.item, button = _b.button, highlightedState = _b.highlightedState, itemSelectable = _b.itemSelectable, placeholder = _b.placeholder; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); addClassesToElement(div, item); if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, choice.label); addClassesToElement(spanLabel, choice.labelClass); div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, choice.label); } div.dataset.item = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; assignCustomProperties(div, choice, true); if (choice.disabled || this.containerOuter.isDisabled) { div.setAttribute('aria-disabled', 'true'); } if (this._isSelectElement) { div.setAttribute('aria-selected', 'true'); div.setAttribute('role', 'option'); } if (choice.placeholder) { addClassesToElement(div, placeholder); div.dataset.placeholder = ''; } addClassesToElement(div, choice.highlighted ? highlightedState : itemSelectable); if (removeItemButton) { if (choice.disabled) { removeClassesFromElement(div, itemSelectable); } div.dataset.deletable = ''; var removeButton = document.createElement('button'); removeButton.type = 'button'; addClassesToElement(removeButton, button); var eventChoice = getChoiceForOutput(choice); setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value, eventChoice)); var REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value, eventChoice); if (REMOVE_ITEM_LABEL) { removeButton.setAttribute('aria-label', REMOVE_ITEM_LABEL); } removeButton.dataset.button = ''; if (removeItemButtonAlignLeft) { div.insertAdjacentElement('afterbegin', removeButton); } else { div.appendChild(removeButton); } } return div; }, choiceList: function (_a, isSelectOneElement) { var list = _a.classNames.list; var div = document.createElement('div'); addClassesToElement(div, list); if (!isSelectOneElement) { div.setAttribute('aria-multiselectable', 'true'); } div.setAttribute('role', 'listbox'); return div; }, choiceGroup: function (_a, _b) { var allowHTML = _a.allowHTML, _c = _a.classNames, group = _c.group, groupHeading = _c.groupHeading, itemDisabled = _c.itemDisabled; var id = _b.id, label = _b.label, disabled = _b.disabled; var rawLabel = unwrapStringForRaw(label); var div = document.createElement('div'); addClassesToElement(div, group); if (disabled) { addClassesToElement(div, itemDisabled); } div.setAttribute('role', 'group'); div.dataset.group = ''; div.dataset.id = id; div.dataset.value = rawLabel; if (disabled) { div.setAttribute('aria-disabled', 'true'); } var heading = document.createElement('div'); addClassesToElement(heading, groupHeading); setElementHtml(heading, allowHTML, label || ''); div.appendChild(heading); return div; }, choice: function (_a, choice, selectText, groupName) { var allowHTML = _a.allowHTML, _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, itemSelectable = _b.itemSelectable, selectedState = _b.selectedState, itemDisabled = _b.itemDisabled, description = _b.description, placeholder = _b.placeholder; // eslint-disable-next-line prefer-destructuring var label = choice.label; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); div.id = choice.elementId; addClassesToElement(div, item); addClassesToElement(div, itemChoice); if (groupName && typeof label === 'string') { label = escapeForTemplate(allowHTML, label); label += " (".concat(groupName, ")"); label = { trusted: label }; } var describedBy = div; if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, label); addClassesToElement(spanLabel, choice.labelClass); describedBy = spanLabel; div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, label); } if (choice.labelDescription) { var descId = "".concat(choice.elementId, "-description"); describedBy.setAttribute('aria-describedby', descId); var spanDesc = document.createElement('span'); setElementHtml(spanDesc, allowHTML, choice.labelDescription); spanDesc.id = descId; addClassesToElement(spanDesc, description); div.appendChild(spanDesc); } if (choice.selected) { addClassesToElement(div, selectedState); } if (choice.placeholder) { addClassesToElement(div, placeholder); } div.setAttribute('role', choice.group ? 'treeitem' : 'option'); div.dataset.choice = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; if (selectText) { div.dataset.selectText = selectText; } if (choice.group) { div.dataset.groupId = "".concat(choice.group.id); } assignCustomProperties(div, choice, false); if (choice.disabled) { addClassesToElement(div, itemDisabled); div.dataset.choiceDisabled = ''; div.setAttribute('aria-disabled', 'true'); } else { addClassesToElement(div, itemSelectable); div.dataset.choiceSelectable = ''; div.setAttribute('aria-selected', choice.selected ? 'true' : 'false'); } return div; }, input: function (_a, placeholderValue) { var _b = _a.classNames, input = _b.input, inputCloned = _b.inputCloned, labelId = _a.labelId; var inp = document.createElement('input'); inp.type = 'search'; addClassesToElement(inp, input); addClassesToElement(inp, inputCloned); inp.autocomplete = 'off'; inp.autocapitalize = 'off'; inp.spellcheck = false; inp.setAttribute('aria-autocomplete', 'list'); if (placeholderValue) { inp.setAttribute('aria-label', placeholderValue); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, inp); } return inp; }, dropdown: function (_a) { var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, listDropdown); div.setAttribute('aria-expanded', 'false'); return div; }, notice: function (_a, innerHTML, type) { var _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, addChoice = _b.addChoice, noResults = _b.noResults, noChoices = _b.noChoices, noticeItem = _b.notice; if (type === void 0) { type = NoticeTypes.generic; } var notice = document.createElement('div'); setElementHtml(notice, true, innerHTML); addClassesToElement(notice, item); addClassesToElement(notice, itemChoice); addClassesToElement(notice, noticeItem); // eslint-disable-next-line default-case switch (type) { case NoticeTypes.addChoice: addClassesToElement(notice, addChoice); break; case NoticeTypes.noResults: addClassesToElement(notice, noResults); break; case NoticeTypes.noChoices: addClassesToElement(notice, noChoices); break; } if (type === NoticeTypes.addChoice) { notice.dataset.choiceSelectable = ''; notice.dataset.choice = ''; } return notice; }, option: function (choice) { // HtmlOptionElement's label value does not support HTML, so the avoid double escaping unwrap the untrusted string. var labelValue = unwrapStringForRaw(choice.label); var opt = new Option(labelValue, choice.value, false, choice.selected); assignCustomProperties(opt, choice, true); opt.disabled = choice.disabled; if (choice.selected) { opt.setAttribute('selected', ''); } return opt; }, }; /** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ var IS_IE11 = '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style; var USER_DEFAULTS = {}; var parseDataSetId = function (element) { if (!element) { return undefined; } return element.dataset.id ? parseInt(element.dataset.id, 10) : undefined; }; var selectableChoiceIdentifier = '[data-choice-selectable]'; /** * Choices * @author Josh Johnson */ var Choices = /** @class */ (function () { function Choices(element, userConfig) { if (element === void 0) { element = '[data-choice]'; } if (userConfig === void 0) { userConfig = {}; } var _this = this; this.initialisedOK = undefined; this._hasNonChoicePlaceholder = false; this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; var defaults = Choices.defaults; this.config = __assign(__assign(__assign({}, defaults.allOptions), defaults.options), userConfig); ObjectsInConfig.forEach(function (key) { _this.config[key] = __assign(__assign(__assign({}, defaults.allOptions[key]), defaults.options[key]), userConfig[key]); }); var config = this.config; if (!config.silent) { this._validateConfig(); } var docRoot = config.shadowRoot || document.documentElement; this._docRoot = docRoot; var passedElement = typeof element === 'string' ? docRoot.querySelector(element) : element; if (!passedElement || typeof passedElement !== 'object' || !(isHtmlInputElement(passedElement) || isHtmlSelectElement(passedElement))) { if (!passedElement && typeof element === 'string') { throw TypeError("Selector ".concat(element, " failed to find an element")); } throw TypeError("Expected one of the following types text|select-one|select-multiple"); } var elementType = passedElement.type; var isText = elementType === PassedElementTypes.Text; if (isText || config.maxItemCount !== 1) { config.singleModeForMultiSelect = false; } if (config.singleModeForMultiSelect) { elementType = PassedElementTypes.SelectMultiple; } var isSelectOne = elementType === PassedElementTypes.SelectOne; var isSelectMultiple = elementType === PassedElementTypes.SelectMultiple; var isSelect = isSelectOne || isSelectMultiple; this._elementType = elementType; this._isTextElement = isText; this._isSelectOneElement = isSelectOne; this._isSelectMultipleElement = isSelectMultiple; this._isSelectElement = isSelectOne || isSelectMultiple; this._canAddUserChoices = (isText && config.addItems) || (isSelect && config.addChoices); if (typeof config.renderSelectedChoices !== 'boolean') { config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne; } if (config.closeDropdownOnSelect === 'auto') { config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect; } else { config.closeDropdownOnSelect = coerceBool(config.closeDropdownOnSelect); } if (config.placeholder) { if (config.placeholderValue) { this._hasNonChoicePlaceholder = true; } else if (passedElement.dataset.placeholder) { this._hasNonChoicePlaceholder = true; config.placeholderValue = passedElement.dataset.placeholder; } } if (userConfig.addItemFilter && typeof userConfig.addItemFilter !== 'function') { var re = userConfig.addItemFilter instanceof RegExp ? userConfig.addItemFilter : new RegExp(userConfig.addItemFilter); config.addItemFilter = re.test.bind(re); } if (this._isTextElement) { this.passedElement = new WrappedInput({ element: passedElement, classNames: config.classNames, }); } else { var selectEl = passedElement; this.passedElement = new WrappedSelect({ element: selectEl, classNames: config.classNames, template: function (data) { return _this._templates.option(data); }, extractPlaceholder: config.placeholder && !this._hasNonChoicePlaceholder, }); } this.initialised = false; this._store = new Store(config); this._currentValue = ''; config.searchEnabled = !isText && config.searchEnabled; this._canSearch = config.searchEnabled; this._isScrollingOnIe = false; this._highlightPosition = 0; this._wasTap = true; this._placeholderValue = this._generatePlaceholderValue(); this._baseId = generateId(passedElement, 'choices-'); /** * setting direction in cases where it's explicitly set on passedElement * or when calculated direction is different from the document */ this._direction = passedElement.dir; if (!this._direction) { var elementDirection = window.getComputedStyle(passedElement).direction; var documentDirection = window.getComputedStyle(document.documentElement).direction; if (elementDirection !== documentDirection) { this._direction = elementDirection; } } this._idNames = { itemChoice: 'item-choice', }; this._templates = defaults.templates; this._render = this._render.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onKeyUp = this._onKeyUp.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onInput = this._onInput.bind(this); this._onClick = this._onClick.bind(this); this._onTouchMove = this._onTouchMove.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseOver = this._onMouseOver.bind(this); this._onFormReset = this._onFormReset.bind(this); this._onSelectKey = this._onSelectKey.bind(this); this._onEnterKey = this._onEnterKey.bind(this); this._onEscapeKey = this._onEscapeKey.bind(this); this._onDirectionKey = this._onDirectionKey.bind(this); this._onDeleteKey = this._onDeleteKey.bind(this); this._onChange = this._onChange.bind(this); this._onInvalid = this._onInvalid.bind(this); // If element has already been initialised with Choices, fail silently if (this.passedElement.isActive) { if (!config.silent) { console.warn('Trying to initialise Choices on element already initialised', { element: element }); } this.initialised = true; this.initialisedOK = false; return; } // Let's go this.init(); // preserve the selected item list after setup for form reset this._initialItems = this._store.items.map(function (choice) { return choice.value; }); } Object.defineProperty(Choices, "defaults", { get: function () { return Object.preventExtensions({ get options() { return USER_DEFAULTS; }, get allOptions() { return DEFAULT_CONFIG; }, get templates() { return templates; }, }); }, enumerable: false, configurable: true }); Choices.prototype.init = function () { if (this.initialised || this.initialisedOK !== undefined) { return; } this._searcher = getSearcher(this.config); this._loadChoices(); this._createTemplates(); this._createElements(); this._createStructure(); if ((this._isTextElement && !this.config.addItems) || this.passedElement.element.hasAttribute('disabled') || !!this.passedElement.element.closest('fieldset:disabled')) { this.disable(); } else { this.enable(); this._addEventListeners(); } // should be triggered **after** disabled state to avoid additional re-draws this._initStore(); this.initialised = true; this.initialisedOK = true; var callbackOnInit = this.config.callbackOnInit; // Run callback if it is a function if (typeof callbackOnInit === 'function') { callbackOnInit.call(this); } }; Choices.prototype.destroy = function () { if (!this.initialised) { return; } this._removeEventListeners(); this.passedElement.reveal(); this.containerOuter.unwrap(this.passedElement.element); this._store._listeners = []; // prevents select/input value being wiped this.clearStore(false); this._stopSearch(); this._templates = Choices.defaults.templates; this.initialised = false; this.initialisedOK = undefined; }; Choices.prototype.enable = function () { if (this.passedElement.isDisabled) { this.passedElement.enable(); } if (this.containerOuter.isDisabled) { this._addEventListeners(); this.input.enable(); this.containerOuter.enable(); } return this; }; Choices.prototype.disable = function () { if (!this.passedElement.isDisabled) { this.passedElement.disable(); } if (!this.containerOuter.isDisabled) { this._removeEventListeners(); this.input.disable(); this.containerOuter.disable(); } return this; }; Choices.prototype.highlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, true)); if (runEvent) { this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.unhighlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || !choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, false)); if (runEvent) { this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.highlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (!item.highlighted) { _this._store.dispatch(highlightItem(item, true)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.unhighlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (item.highlighted) { _this._store.dispatch(highlightItem(item, false)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.removeActiveItemsByValue = function (value) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (item) { return item.value === value; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeActiveItems = function (excludedId) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (_a) { var id = _a.id; return id !== excludedId; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeHighlightedItems = function (runEvent) { var _this = this; if (runEvent === void 0) { runEvent = false; } this._store.withTxn(function () { _this._store.highlightedActiveItems.forEach(function (item) { _this._removeItem(item); // If this action was performed by the user // trigger the event if (runEvent) { _this._triggerChange(item.value); } }); }); return this; }; Choices.prototype.showDropdown = function (preventInputFocus) { var _this = this; if (this.dropdown.isActive) { return this; } if (preventInputFocus === undefined) { // eslint-disable-next-line no-param-reassign preventInputFocus = !this._canSearch; } requestAnimationFrame(function () { _this.dropdown.show(); var rect = _this.dropdown.element.getBoundingClientRect(); _this.containerOuter.open(rect.bottom, rect.height); if (!preventInputFocus) { _this.input.focus(); } _this.passedElement.triggerEvent(EventType.showDropdown); var activeElement = _this.choiceList.element.querySelector(getClassNamesSelector(_this.config.classNames.selectedState)); if (activeElement !== null && !isScrolledIntoView(activeElement, _this.choiceList.element)) { // We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll. activeElement.scrollIntoView(); } }); return this; }; Choices.prototype.hideDropdown = function (preventInputBlur) { var _this = this; if (!this.dropdown.isActive) { return this; } this._removeHighlightedChoices(); requestAnimationFrame(function () { _this.dropdown.hide(); _this.containerOuter.close(); if (!preventInputBlur && _this._canSearch) { _this.input.removeActiveDescendant(); _this.input.blur(); } _this.passedElement.triggerEvent(EventType.hideDropdown); }); return this; }; Choices.prototype.getValue = function (valueOnly) { var values = this._store.items.map(function (item) { return (valueOnly ? item.value : getChoiceForOutput(item)); }); return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values; }; Choices.prototype.setValue = function (items) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setValue'); return this; } this._store.withTxn(function () { items.forEach(function (value) { if (value) { _this._addChoice(mapInputToChoice(value, false)); } }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.setChoiceByValue = function (value) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoiceByValue'); return this; } if (this._isTextElement) { return this; } this._store.withTxn(function () { // If only one value has been passed, convert to array var choiceValue = Array.isArray(value) ? value : [value]; // Loop through each value and choiceValue.forEach(function (val) { return _this._findAndSelectChoiceByValue(val); }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ Choices.prototype.setChoices = function (choicesArrayOrFetcher, value, label, replaceChoices, clearSearchFlag, replaceItems) { var _this = this; if (choicesArrayOrFetcher === void 0) { choicesArrayOrFetcher = []; } if (value === void 0) { value = 'value'; } if (label === void 0) { label = 'label'; } if (replaceChoices === void 0) { replaceChoices = false; } if (clearSearchFlag === void 0) { clearSearchFlag = true; } if (replaceItems === void 0) { replaceItems = false; } if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoices'); return this; } if (!this._isSelectElement) { throw new TypeError("setChoices can't be used with INPUT based Choices"); } if (typeof value !== 'string' || !value) { throw new TypeError("value parameter must be a name of 'value' field in passed objects"); } if (typeof choicesArrayOrFetcher === 'function') { // it's a choices fetcher function var fetcher_1 = choicesArrayOrFetcher(this); if (typeof Promise === 'function' && fetcher_1 instanceof Promise) { // that's a promise // eslint-disable-next-line no-promise-executor-return return new Promise(function (resolve) { return requestAnimationFrame(resolve); }) .then(function () { return _this._handleLoadingState(true); }) .then(function () { return fetcher_1; }) .then(function (data) { return _this.setChoices(data, value, label, replaceChoices, clearSearchFlag, replaceItems); }) .catch(function (err) { if (!_this.config.silent) { console.error(err); } }) .then(function () { return _this._handleLoadingState(false); }) .then(function () { return _this; }); } // function returned something else than promise, let's check if it's an array of choices if (!Array.isArray(fetcher_1)) { throw new TypeError(".setChoices first argument function must return either array of choices or Promise, got: ".concat(typeof fetcher_1)); } // recursion with results, it's sync and choices were cleared already return this.setChoices(fetcher_1, value, label, false); } if (!Array.isArray(choicesArrayOrFetcher)) { throw new TypeError(".setChoices must be called either with array of choices with a function resulting into Promise of array of choices"); } this.containerOuter.removeLoadingState(); this._store.withTxn(function () { if (clearSearchFlag) { _this._isSearching = false; } // Clear choices if needed if (replaceChoices) { _this.clearChoices(true, replaceItems); } var isDefaultValue = value === 'value'; var isDefaultLabel = label === 'label'; choicesArrayOrFetcher.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { var group = groupOrChoice; if (!isDefaultLabel) { group = __assign(__assign({}, group), { label: group[label] }); } _this._addGroup(mapInputToChoice(group, true)); } else { var choice = groupOrChoice; if (!isDefaultLabel || !isDefaultValue) { choice = __assign(__assign({}, choice), { value: choice[value], label: choice[label] }); } var choiceFull = mapInputToChoice(choice, false); _this._addChoice(choiceFull); if (choiceFull.placeholder && !_this._hasNonChoicePlaceholder) { _this._placeholderValue = unwrapStringForEscaped(choiceFull.label); } } }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.refresh = function (withEvents, selectFirstOption, deselectAll) { var _this = this; if (withEvents === void 0) { withEvents = false; } if (selectFirstOption === void 0) { selectFirstOption = false; } if (deselectAll === void 0) { deselectAll = false; } if (!this._isSelectElement) { if (!this.config.silent) { console.warn('refresh method can only be used on choices backed by a element'); } return this; } this._store.withTxn(function () { var choicesFromOptions = _this.passedElement.optionsAsChoices(); // Build the list of items which require preserving var existingItems = {}; if (!deselectAll) { _this._store.items.forEach(function (choice) { if (choice.id && choice.active && choice.selected) { existingItems[choice.value] = true; } }); } _this.clearStore(false); var updateChoice = function (choice) { if (deselectAll) { _this._store.dispatch(removeItem$1(choice)); } else if (existingItems[choice.value]) { choice.selected = true; } }; choicesFromOptions.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { groupOrChoice.choices.forEach(updateChoice); return; } updateChoice(groupOrChoice); }); /* @todo only generate add events for the added options instead of all if (withEvents) { items.forEach((choice) => { if (existingItems[choice.value]) { this.passedElement.triggerEvent( EventType.removeItem, this._getChoiceForEvent(choice), ); } }); } */ // load new choices & items _this._addPredefinedChoices(choicesFromOptions, selectFirstOption, withEvents); // re-do search if required if (_this._isSearching) { _this._searchChoices(_this.input.value); } }); return this; }; Choices.prototype.removeChoice = function (value) { var choice = this._store.choices.find(function (c) { return c.value === value; }); if (!choice) { return this; } this._clearNotice(); this._store.dispatch(removeChoice(choice)); // @todo integrate with Store this._searcher.reset(); if (choice.selected) { this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.clearChoices = function (clearOptions, clearItems) { var _this = this; if (clearOptions === void 0) { clearOptions = true; } if (clearItems === void 0) { clearItems = false; } if (clearOptions) { if (clearItems) { this.passedElement.element.replaceChildren(''); } else { this.passedElement.element.querySelectorAll(':not([selected])').forEach(function (el) { el.remove(); }); } } this.itemList.element.replaceChildren(''); this.choiceList.element.replaceChildren(''); this._clearNotice(); this._store.withTxn(function () { var items = clearItems ? [] : _this._store.items; _this._store.reset(); items.forEach(function (item) { _this._store.dispatch(addChoice(item)); _this._store.dispatch(addItem(item)); }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.clearStore = function (clearOptions) { if (clearOptions === void 0) { clearOptions = true; } this.clearChoices(clearOptions, true); this._stopSearch(); this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; return this; }; Choices.prototype.clearInput = function () { var shouldSetInputWidth = !this._isSelectOneElement; this.input.clear(shouldSetInputWidth); this._stopSearch(); return this; }; Choices.prototype._validateConfig = function () { var config = this.config; var invalidConfigOptions = diff(config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); } if (config.allowHTML && config.allowHtmlUserInput) { if (config.addItems) { console.warn('Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks'); } if (config.addChoices) { console.warn('Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks'); } } }; Choices.prototype._render = function (changes) { if (changes === void 0) { changes = { choices: true, groups: true, items: true }; } if (this._store.inTxn()) { return; } if (this._isSelectElement) { if (changes.choices || changes.groups) { this._renderChoices(); } } if (changes.items) { this._renderItems(); } }; Choices.prototype._renderChoices = function () { var _this = this; if (!this._canAddItems()) { return; // block rendering choices if the input limit is reached. } var _a = this, config = _a.config, isSearching = _a._isSearching; var _b = this._store, activeGroups = _b.activeGroups, activeChoices = _b.activeChoices; var renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit; if (this._isSelectElement) { var backingOptions = activeChoices.filter(function (choice) { return !choice.element; }); if (backingOptions.length) { this.passedElement.addOptions(backingOptions); } } var fragment = document.createDocumentFragment(); var renderableChoices = function (choices) { return choices.filter(function (choice) { return !choice.placeholder && (isSearching ? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank : config.renderSelectedChoices || !choice.selected); }); }; var showLabel = config.appendGroupInSearch && isSearching; var selectableChoices = false; var highlightedEl = null; var renderChoices = function (choices, withinGroup) { if (isSearching) { // sortByRank is used to ensure stable sorting, as scores are non-unique // this additionally ensures fuseOptions.sortFn is not ignored choices.sort(sortByRank); } else if (config.shouldSort) { choices.sort(config.sorter); } var choiceLimit = choices.length; choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit; choiceLimit--; choices.every(function (choice, index) { // choiceEl being empty signals the contents has probably significantly changed var dropdownItem = choice.choiceEl || _this._templates.choice(config, choice, config.itemSelectText, showLabel && choice.group ? choice.group.label : undefined); choice.choiceEl = dropdownItem; fragment.appendChild(dropdownItem); if (isSearching || !choice.selected) { selectableChoices = true; } else if (!highlightedEl) { highlightedEl = dropdownItem; } return index < choiceLimit; }); }; if (activeChoices.length) { if (config.resetScrollPosition) { requestAnimationFrame(function () { return _this.choiceList.scrollToTop(); }); } if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) { // If we have a placeholder choice along with groups renderChoices(activeChoices.filter(function (choice) { return choice.placeholder && !choice.group; }), false); } // If we have grouped options if (activeGroups.length && !isSearching) { if (config.shouldSort) { activeGroups.sort(config.sorter); } // render Choices without group first, regardless of sort, otherwise they won't be distinguishable // from the last group renderChoices(activeChoices.filter(function (choice) { return !choice.placeholder && !choice.group; }), false); activeGroups.forEach(function (group) { var groupChoices = renderableChoices(group.choices); if (groupChoices.length) { if (group.label) { var dropdownGroup = group.groupEl || _this._templates.choiceGroup(_this.config, group); group.groupEl = dropdownGroup; dropdownGroup.remove(); fragment.appendChild(dropdownGroup); } renderChoices(groupChoices, true); } }); } else { renderChoices(renderableChoices(activeChoices), false); } } if (!selectableChoices && (isSearching || !fragment.children.length || !config.renderSelectedChoices)) { if (!this._notice) { this._notice = { text: resolveStringFunction(isSearching ? config.noResultsText : config.noChoicesText), type: isSearching ? NoticeTypes.noResults : NoticeTypes.noChoices, }; } fragment.replaceChildren(''); } this._renderNotice(fragment); this.choiceList.element.replaceChildren(fragment); this._highlightChoice(highlightedEl); }; Choices.prototype._renderItems = function () { var _this = this; var items = this._store.items || []; var itemList = this.itemList.element; var config = this.config; var fragment = document.createDocumentFragment(); var itemFromList = function (item) { return itemList.querySelector("[data-item][data-id=\"".concat(item.id, "\"]")); }; var addItemToFragment = function (item) { var el = item.itemEl; if (el && el.parentElement) { return; } el = itemFromList(item) || _this._templates.item(config, item, config.removeItemButton); item.itemEl = el; fragment.appendChild(el); }; // new items items.forEach(addItemToFragment); var addedItems = !!fragment.childNodes.length; if (this._isSelectOneElement) { var existingItems = itemList.children.length; if (addedItems || existingItems > 1) { var placeholder = itemList.querySelector(getClassNamesSelector(config.classNames.placeholder)); if (placeholder) { placeholder.remove(); } } else if (!addedItems && !existingItems && this._placeholderValue) { addedItems = true; addItemToFragment(mapInputToChoice({ selected: true, value: '', label: this._placeholderValue, placeholder: true, }, false)); } } if (addedItems) { itemList.append(fragment); if (config.shouldSortItems && !this._isSelectOneElement) { items.sort(config.sorter); // push sorting into the DOM items.forEach(function (item) { var el = itemFromList(item); if (el) { el.remove(); fragment.append(el); } }); itemList.append(fragment); } } if (this._isTextElement) { // Update the value of the hidden input this.passedElement.value = items.map(function (_a) { var value = _a.value; return value; }).join(config.delimiter); } }; Choices.prototype._displayNotice = function (text, type, openDropdown) { if (openDropdown === void 0) { openDropdown = true; } var oldNotice = this._notice; if (oldNotice && ((oldNotice.type === type && oldNotice.text === text) || (oldNotice.type === NoticeTypes.addChoice && (type === NoticeTypes.noResults || type === NoticeTypes.noChoices)))) { if (openDropdown) { this.showDropdown(true); } return; } this._clearNotice(); this._notice = text ? { text: text, type: type, } : undefined; this._renderNotice(); if (openDropdown && text) { this.showDropdown(true); } }; Choices.prototype._clearNotice = function () { if (!this._notice) { return; } var noticeElement = this.choiceList.element.querySelector(getClassNamesSelector(this.config.classNames.notice)); if (noticeElement) { noticeElement.remove(); } this._notice = undefined; }; Choices.prototype._renderNotice = function (fragment) { var noticeConf = this._notice; if (noticeConf) { var notice = this._templates.notice(this.config, noticeConf.text, noticeConf.type); if (fragment) { fragment.append(notice); } else { this.choiceList.prepend(notice); } } }; /** * @deprecated Use utils.getChoiceForOutput */ // eslint-disable-next-line class-methods-use-this Choices.prototype._getChoiceForOutput = function (choice, keyCode) { return getChoiceForOutput(choice, keyCode); }; Choices.prototype._triggerChange = function (value) { if (value === undefined || value === null) { return; } this.passedElement.triggerEvent(EventType.change, { value: value, }); }; Choices.prototype._handleButtonAction = function (element) { var _this = this; var items = this._store.items; if (!items.length || !this.config.removeItems || !this.config.removeItemButton) { return; } var id = element && parseDataSetId(element.closest('[data-id]')); var itemToRemove = id && items.find(function (item) { return item.id === id; }); if (!itemToRemove) { return; } this._store.withTxn(function () { // Remove item associated with button _this._removeItem(itemToRemove); _this._triggerChange(itemToRemove.value); if (_this._isSelectOneElement && !_this._hasNonChoicePlaceholder) { var placeholderChoice = (_this.config.shouldSort ? _this._store.choices.reverse() : _this._store.choices).find(function (choice) { return choice.placeholder; }); if (placeholderChoice) { _this._addItem(placeholderChoice); _this.unhighlightAll(); if (placeholderChoice.value) { _this._triggerChange(placeholderChoice.value); } } } }); }; Choices.prototype._handleItemAction = function (element, hasShiftKey) { var _this = this; if (hasShiftKey === void 0) { hasShiftKey = false; } var items = this._store.items; if (!items.length || !this.config.removeItems || this._isSelectOneElement) { return; } var id = parseDataSetId(element); if (!id) { return; } // We only want to select one item with a click // so we deselect any items that aren't the target // unless shift is being pressed items.forEach(function (item) { if (item.id === id && !item.highlighted) { _this.highlightItem(item); } else if (!hasShiftKey && item.highlighted) { _this.unhighlightItem(item); } }); // Focus input as without focus, a user cannot do anything with a // highlighted item this.input.focus(); }; Choices.prototype._handleChoiceAction = function (element) { var _this = this; // If we are clicking on an option var id = parseDataSetId(element); var choice = id && this._store.getChoiceById(id); if (!choice || choice.disabled) { return false; } var hasActiveDropdown = this.dropdown.isActive; if (!choice.selected) { if (!this._canAddItems()) { return true; // causes _onEnterKey to early out } this._store.withTxn(function () { _this._addItem(choice, true, true); _this.clearInput(); _this.unhighlightAll(); }); this._triggerChange(choice.value); } // We want to close the dropdown if we are dealing with a single select box if (hasActiveDropdown && this.config.closeDropdownOnSelect) { this.hideDropdown(true); this.containerOuter.element.focus(); } return true; }; Choices.prototype._handleBackspace = function (items) { var config = this.config; if (!config.removeItems || !items.length) { return; } var lastItem = items[items.length - 1]; var hasHighlightedItems = items.some(function (item) { return item.highlighted; }); // If editing the last item is allowed and there are not other selected items, // we can edit the item value. Otherwise if we can remove items, remove all selected items if (config.editItems && !hasHighlightedItems && lastItem) { this.input.value = lastItem.value; this.input.setWidth(); this._removeItem(lastItem); this._triggerChange(lastItem.value); } else { if (!hasHighlightedItems) { // Highlight last item if none already highlighted this.highlightItem(lastItem, false); } this.removeHighlightedItems(true); } }; Choices.prototype._loadChoices = function () { var _a; var _this = this; var config = this.config; if (this._isTextElement) { // Assign preset items from passed object first this._presetChoices = config.items.map(function (e) { return mapInputToChoice(e, false); }); // Add any values passed from attribute if (this.passedElement.value) { var elementItems = this.passedElement.value .split(config.delimiter) .map(function (e) { return mapInputToChoice(e, false, _this.config.allowHtmlUserInput); }); this._presetChoices = this._presetChoices.concat(elementItems); } this._presetChoices.forEach(function (choice) { choice.selected = true; }); } else if (this._isSelectElement) { // Assign preset choices from passed object this._presetChoices = config.choices.map(function (e) { return mapInputToChoice(e, true); }); // Create array of choices from option elements var choicesFromOptions = this.passedElement.optionsAsChoices(); if (choicesFromOptions) { (_a = this._presetChoices).push.apply(_a, choicesFromOptions); } } }; Choices.prototype._handleLoadingState = function (setLoading) { if (setLoading === void 0) { setLoading = true; } var el = this.itemList.element; if (setLoading) { this.disable(); this.containerOuter.addLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(this._templates.placeholder(this.config, this.config.loadingText)); } else { this.input.placeholder = this.config.loadingText; } } else { this.enable(); this.containerOuter.removeLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(''); this._render(); } else { this.input.placeholder = this._placeholderValue || ''; } } }; Choices.prototype._handleSearch = function (value) { if (!this.input.isFocussed) { return; } // Check that we have a value to search and the input was an alphanumeric character if (value !== null && typeof value !== 'undefined' && value.length >= this.config.searchFloor) { var resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; if (resultCount !== null) { // Trigger search event this.passedElement.triggerEvent(EventType.search, { value: value, resultCount: resultCount, }); } } else if (this._store.choices.some(function (option) { return !option.active; })) { this._stopSearch(); } }; Choices.prototype._canAddItems = function () { var config = this.config; var maxItemCount = config.maxItemCount, maxItemText = config.maxItemText; if (!config.singleModeForMultiSelect && maxItemCount > 0 && maxItemCount <= this._store.items.length) { this.choiceList.element.replaceChildren(''); this._notice = undefined; this._displayNotice(typeof maxItemText === 'function' ? maxItemText(maxItemCount) : maxItemText, NoticeTypes.addChoice); return false; } if (this._notice && this._notice.type === NoticeTypes.addChoice) { this._clearNotice(); } return true; }; Choices.prototype._canCreateItem = function (value) { var config = this.config; var canAddItem = true; var notice = ''; if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) { canAddItem = false; notice = resolveNoticeFunction(config.customAddItemText, value, undefined); } if (canAddItem) { var foundChoice = this._store.choices.find(function (choice) { return config.valueComparer(choice.value, value); }); if (foundChoice) { if (this._isSelectElement) { // for exact matches, do not prompt to add it as a custom choice this._displayNotice('', NoticeTypes.addChoice); return false; } if (!config.duplicateItemsAllowed) { canAddItem = false; notice = resolveNoticeFunction(config.uniqueItemText, value, undefined); } } } if (canAddItem) { notice = resolveNoticeFunction(config.addItemText, value, undefined); } if (notice) { this._displayNotice(notice, NoticeTypes.addChoice); } return canAddItem; }; Choices.prototype._searchChoices = function (value) { var newValue = value.trim().replace(/\s{2,}/, ' '); // signal input didn't change search if (!newValue.length || newValue === this._currentValue) { return null; } var searcher = this._searcher; if (searcher.isEmptyIndex()) { searcher.index(this._store.searchableChoices); } // If new value matches the desired length and is not the same as the current value with a space var results = searcher.search(newValue); this._currentValue = newValue; this._highlightPosition = 0; this._isSearching = true; var notice = this._notice; var noticeType = notice && notice.type; if (noticeType !== NoticeTypes.addChoice) { if (!results.length) { this._displayNotice(resolveStringFunction(this.config.noResultsText), NoticeTypes.noResults); } else { this._clearNotice(); } } this._store.dispatch(filterChoices(results)); return results.length; }; Choices.prototype._stopSearch = function () { if (this._isSearching) { this._currentValue = ''; this._isSearching = false; this._clearNotice(); this._store.dispatch(activateChoices(true)); this.passedElement.triggerEvent(EventType.search, { value: '', resultCount: 0, }); } }; Choices.prototype._addEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; // capture events - can cancel event processing or propagation documentElement.addEventListener('touchend', this._onTouchEnd, true); outerElement.addEventListener('keydown', this._onKeyDown, true); outerElement.addEventListener('mousedown', this._onMouseDown, true); // passive events - doesn't call `preventDefault` or `stopPropagation` documentElement.addEventListener('click', this._onClick, { passive: true }); documentElement.addEventListener('touchmove', this._onTouchMove, { passive: true, }); this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { passive: true, }); if (this._isSelectOneElement) { outerElement.addEventListener('focus', this._onFocus, { passive: true, }); outerElement.addEventListener('blur', this._onBlur, { passive: true, }); } inputElement.addEventListener('keyup', this._onKeyUp, { passive: true, }); inputElement.addEventListener('input', this._onInput, { passive: true, }); inputElement.addEventListener('focus', this._onFocus, { passive: true, }); inputElement.addEventListener('blur', this._onBlur, { passive: true, }); if (inputElement.form) { inputElement.form.addEventListener('reset', this._onFormReset, { passive: true, }); } if (passedElement.hasAttribute('required')) { passedElement.addEventListener('change', this._onChange, { passive: true, }); passedElement.addEventListener('invalid', this._onInvalid, { passive: true, }); } this.input.addEventListeners(); }; Choices.prototype._removeEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; documentElement.removeEventListener('touchend', this._onTouchEnd, true); outerElement.removeEventListener('keydown', this._onKeyDown, true); outerElement.removeEventListener('mousedown', this._onMouseDown, true); documentElement.removeEventListener('click', this._onClick); documentElement.removeEventListener('touchmove', this._onTouchMove); this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); if (this._isSelectOneElement) { outerElement.removeEventListener('focus', this._onFocus); outerElement.removeEventListener('blur', this._onBlur); } inputElement.removeEventListener('keyup', this._onKeyUp); inputElement.removeEventListener('input', this._onInput); inputElement.removeEventListener('focus', this._onFocus); inputElement.removeEventListener('blur', this._onBlur); if (inputElement.form) { inputElement.form.removeEventListener('reset', this._onFormReset); } if (passedElement.hasAttribute('required')) { passedElement.removeEventListener('change', this._onChange); passedElement.removeEventListener('invalid', this._onInvalid); } this.input.removeEventListeners(); }; Choices.prototype._onKeyDown = function (event) { var keyCode = event.keyCode; var hasActiveDropdown = this.dropdown.isActive; /* See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs https://stackoverflow.com/a/70866532 - "Unidentified" for mobile http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7) Logic: when a key event is sent, `event.key` represents its printable value _or_ one of a large list of special values indicating meta keys/functionality. In addition, key events for compose functionality contain a value of `Dead` when mid-composition. I can't quite verify it, but non-English IMEs may also be able to generate key codes for code points in the surrogate-pair range, which could potentially be seen as having key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that alone. Here, key.length === 1 means we know for sure the input was printable and not a special `key` value. When the length is greater than 1, it could be either a printable surrogate pair or a special `key` value. We can tell the difference by checking if the _character code_ value (not code point!) is in the "surrogate pair" range or not. We don't use .codePointAt because an invalid code point would return 65535, which wouldn't pass the >= 0x10000 check we would otherwise use. > ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points > of each plane are noncharacters: U+FFFE and U+FFFF on the BMP... */ var wasPrintableChar = event.key.length === 1 || (event.key.length === 2 && event.key.charCodeAt(0) >= 0xd800) || event.key === 'Unidentified'; /* We do not show the dropdown if focusing out with esc or navigating through input fields. An activated search can still be opened with any other key. */ if (!this._isTextElement && !hasActiveDropdown && keyCode !== KeyCodeMap.ESC_KEY && keyCode !== KeyCodeMap.TAB_KEY && keyCode !== KeyCodeMap.SHIFT_KEY) { this.showDropdown(); if (!this.input.isFocussed && wasPrintableChar) { /* We update the input value with the pressed key as the input was not focussed at the time of key press therefore does not have the value of the key. */ this.input.value += event.key; // browsers interpret a space as pagedown if (event.key === ' ') { event.preventDefault(); } } } switch (keyCode) { case KeyCodeMap.A_KEY: return this._onSelectKey(event, this.itemList.element.hasChildNodes()); case KeyCodeMap.ENTER_KEY: return this._onEnterKey(event, hasActiveDropdown); case KeyCodeMap.ESC_KEY: return this._onEscapeKey(event, hasActiveDropdown); case KeyCodeMap.UP_KEY: case KeyCodeMap.PAGE_UP_KEY: case KeyCodeMap.DOWN_KEY: case KeyCodeMap.PAGE_DOWN_KEY: return this._onDirectionKey(event, hasActiveDropdown); case KeyCodeMap.DELETE_KEY: case KeyCodeMap.BACK_KEY: return this._onDeleteKey(event, this._store.items, this.input.isFocussed); } }; Choices.prototype._onKeyUp = function ( /* event: KeyboardEvent */) { this._canSearch = this.config.searchEnabled; }; Choices.prototype._onInput = function ( /* event: InputEvent */) { var value = this.input.value; if (!value) { if (this._isTextElement) { this.hideDropdown(true); } else { this._stopSearch(); } return; } if (!this._canAddItems()) { return; } if (this._canSearch) { // do the search even if the entered text can not be added this._handleSearch(value); } if (!this._canAddUserChoices) { return; } // determine if a notice needs to be displayed for why a search result can't be added this._canCreateItem(value); if (this._isSelectElement) { this._highlightPosition = 0; // reset to select the notice and/or exact match this._highlightChoice(); } }; Choices.prototype._onSelectKey = function (event, hasItems) { // If CTRL + A or CMD + A have been pressed and there are items to select if ((event.ctrlKey || event.metaKey) && hasItems) { this._canSearch = false; var shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; if (shouldHightlightAll) { this.highlightAll(); } } }; Choices.prototype._onEnterKey = function (event, hasActiveDropdown) { var _this = this; var value = this.input.value; var target = event.target; event.preventDefault(); if (target && target.hasAttribute('data-button')) { this._handleButtonAction(target); return; } if (!hasActiveDropdown) { if (this._isSelectElement || this._notice) { this.showDropdown(); } return; } var highlightedChoice = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (highlightedChoice && this._handleChoiceAction(highlightedChoice)) { return; } if (!target || !value) { this.hideDropdown(true); return; } if (!this._canAddItems()) { return; } var addedItem = false; this._store.withTxn(function () { addedItem = _this._findAndSelectChoiceByValue(value, true); if (!addedItem) { if (!_this._canAddUserChoices) { return; } if (!_this._canCreateItem(value)) { return; } _this._addChoice(mapInputToChoice(value, false, _this.config.allowHtmlUserInput), true, true); addedItem = true; } _this.clearInput(); _this.unhighlightAll(); }); if (!addedItem) { return; } this._triggerChange(value); if (this.config.closeDropdownOnSelect) { this.hideDropdown(true); } }; Choices.prototype._onEscapeKey = function (event, hasActiveDropdown) { if (hasActiveDropdown) { event.stopPropagation(); this.hideDropdown(true); this._stopSearch(); this.containerOuter.element.focus(); } }; Choices.prototype._onDirectionKey = function (event, hasActiveDropdown) { var keyCode = event.keyCode; // If up or down key is pressed, traverse through options if (hasActiveDropdown || this._isSelectOneElement) { this.showDropdown(); this._canSearch = false; var directionInt = keyCode === KeyCodeMap.DOWN_KEY || keyCode === KeyCodeMap.PAGE_DOWN_KEY ? 1 : -1; var skipKey = event.metaKey || keyCode === KeyCodeMap.PAGE_DOWN_KEY || keyCode === KeyCodeMap.PAGE_UP_KEY; var nextEl = void 0; if (skipKey) { if (directionInt > 0) { nextEl = this.dropdown.element.querySelector("".concat(selectableChoiceIdentifier, ":last-of-type")); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } else { var currentEl = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (currentEl) { nextEl = getAdjacentEl(currentEl, selectableChoiceIdentifier, directionInt); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } if (nextEl) { // We prevent default to stop the cursor moving // when pressing the arrow if (!isScrolledIntoView(nextEl, this.choiceList.element, directionInt)) { this.choiceList.scrollToChildElement(nextEl, directionInt); } this._highlightChoice(nextEl); } // Prevent default to maintain cursor position whilst // traversing dropdown options event.preventDefault(); } }; Choices.prototype._onDeleteKey = function (event, items, hasFocusedInput) { // If backspace or delete key is pressed and the input has no value if (!this._isSelectOneElement && !event.target.value && hasFocusedInput) { this._handleBackspace(items); event.preventDefault(); } }; Choices.prototype._onTouchMove = function () { if (this._wasTap) { this._wasTap = false; } }; Choices.prototype._onTouchEnd = function (event) { var target = (event || event.touches[0]).target; var touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target); if (touchWasWithinContainer) { var containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; if (containerWasExactTarget) { if (this._isTextElement) { this.input.focus(); } else if (this._isSelectMultipleElement) { this.showDropdown(); } } // Prevents focus event firing event.stopPropagation(); } this._wasTap = true; }; /** * Handles mousedown event in capture mode for containetOuter.element */ Choices.prototype._onMouseDown = function (event) { var target = event.target; if (!(target instanceof Element)) { return; } // If we have our mouse down on the scrollbar and are on IE11... if (IS_IE11 && this.choiceList.element.contains(target)) { // check if click was on a scrollbar area var firstChoice = this.choiceList.element.firstElementChild; this._isScrollingOnIe = this._direction === 'ltr' ? event.offsetX >= firstChoice.offsetWidth : event.offsetX < firstChoice.offsetLeft; } if (target === this.input.element) { return; } var item = target.closest('[data-button],[data-item],[data-choice]'); if (item instanceof HTMLElement) { if ('button' in item.dataset) { this._handleButtonAction(item); } else if ('item' in item.dataset) { this._handleItemAction(item, event.shiftKey); } else if ('choice' in item.dataset) { this._handleChoiceAction(item); } } event.preventDefault(); }; /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ Choices.prototype._onMouseOver = function (_a) { var target = _a.target; if (target instanceof HTMLElement && 'choice' in target.dataset) { this._highlightChoice(target); } }; Choices.prototype._onClick = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var clickWasWithinContainer = containerOuter.element.contains(target); if (clickWasWithinContainer) { if (!this.dropdown.isActive && !containerOuter.isDisabled) { if (this._isTextElement) { if (document.activeElement !== this.input.element) { this.input.focus(); } } else { this.showDropdown(); containerOuter.element.focus(); } } else if (this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target)) { this.hideDropdown(); } } else { containerOuter.removeFocusState(); this.hideDropdown(true); this.unhighlightAll(); } }; Choices.prototype._onFocus = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var focusWasWithinContainer = target && containerOuter.element.contains(target); if (!focusWasWithinContainer) { return; } var targetIsInput = target === this.input.element; if (this._isTextElement) { if (targetIsInput) { containerOuter.addFocusState(); } } else if (this._isSelectMultipleElement) { if (targetIsInput) { this.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown containerOuter.addFocusState(); } } else { containerOuter.addFocusState(); if (targetIsInput) { this.showDropdown(true); } } }; Choices.prototype._onBlur = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var blurWasWithinContainer = target && containerOuter.element.contains(target); if (blurWasWithinContainer && !this._isScrollingOnIe) { if (target === this.input.element) { containerOuter.removeFocusState(); this.hideDropdown(true); if (this._isTextElement || this._isSelectMultipleElement) { this.unhighlightAll(); } } else if (target === this.containerOuter.element) { // Remove the focus state when the past outerContainer was the target containerOuter.removeFocusState(); // Also close the dropdown if search is disabled if (!this.config.searchEnabled) { this.hideDropdown(true); } } } else { // On IE11, clicking the scollbar blurs our input and thus // closes the dropdown. To stop this, we refocus our input // if we know we are on IE *and* are scrolling. this._isScrollingOnIe = false; this.input.element.focus(); } }; Choices.prototype._onFormReset = function () { var _this = this; this._store.withTxn(function () { _this.clearInput(); _this.hideDropdown(); _this.refresh(false, false, true); if (_this._initialItems.length) { _this.setChoiceByValue(_this._initialItems); } }); }; Choices.prototype._onChange = function (event) { if (!event.target.checkValidity()) { return; } this.containerOuter.removeInvalidState(); }; Choices.prototype._onInvalid = function () { this.containerOuter.addInvalidState(); }; /** * Removes any highlighted choice options */ Choices.prototype._removeHighlightedChoices = function () { var highlightedState = this.config.classNames.highlightedState; var highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(getClassNamesSelector(highlightedState))); // Remove any highlighted choices highlightedChoices.forEach(function (choice) { removeClassesFromElement(choice, highlightedState); choice.setAttribute('aria-selected', 'false'); }); }; Choices.prototype._highlightChoice = function (el) { if (el === void 0) { el = null; } var choices = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)); if (!choices.length) { return; } var passedEl = el; var highlightedState = this.config.classNames.highlightedState; this._removeHighlightedChoices(); if (passedEl) { this._highlightPosition = choices.indexOf(passedEl); } else { // Highlight choice based on last known highlight location if (choices.length > this._highlightPosition) { // If we have an option to highlight passedEl = choices[this._highlightPosition]; } else { // Otherwise highlight the option before passedEl = choices[choices.length - 1]; } if (!passedEl) { passedEl = choices[0]; } } addClassesToElement(passedEl, highlightedState); passedEl.setAttribute('aria-selected', 'true'); this.passedElement.triggerEvent(EventType.highlightChoice, { el: passedEl, }); if (this.dropdown.isActive) { // IE11 ignores aria-label and blocks virtual keyboard // if aria-activedescendant is set without a dropdown this.input.setActiveDescendant(passedEl.id); this.containerOuter.setActiveDescendant(passedEl.id); } }; Choices.prototype._addItem = function (item, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (!item.id) { throw new TypeError('item.id must be set before _addItem is called for a choice/item'); } if (this.config.singleModeForMultiSelect || this._isSelectOneElement) { this.removeActiveItems(item.id); } this._store.dispatch(addItem(item)); if (withEvents) { var eventChoice = getChoiceForOutput(item); this.passedElement.triggerEvent(EventType.addItem, eventChoice); if (userTriggered) { this.passedElement.triggerEvent(EventType.choice, eventChoice); } } }; Choices.prototype._removeItem = function (item) { if (!item.id) { return; } this._store.dispatch(removeItem$1(item)); var notice = this._notice; if (notice && notice.type === NoticeTypes.noChoices) { this._clearNotice(); } this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item)); }; Choices.prototype._addChoice = function (choice, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (choice.id) { throw new TypeError('Can not re-add a choice which has already been added'); } var config = this.config; if (!config.duplicateItemsAllowed && this._store.choices.find(function (c) { return config.valueComparer(c.value, choice.value); })) { return; } // Generate unique id, in-place update is required so chaining _addItem works as expected this._lastAddedChoiceId++; choice.id = this._lastAddedChoiceId; choice.elementId = "".concat(this._baseId, "-").concat(this._idNames.itemChoice, "-").concat(choice.id); var prependValue = config.prependValue, appendValue = config.appendValue; if (prependValue) { choice.value = prependValue + choice.value; } if (appendValue) { choice.value += appendValue.toString(); } if ((prependValue || appendValue) && choice.element) { choice.element.value = choice.value; } this._clearNotice(); this._store.dispatch(addChoice(choice)); if (choice.selected) { this._addItem(choice, withEvents, userTriggered); } }; Choices.prototype._addGroup = function (group, withEvents) { var _this = this; if (withEvents === void 0) { withEvents = true; } if (group.id) { throw new TypeError('Can not re-add a group which has already been added'); } this._store.dispatch(addGroup(group)); if (!group.choices) { return; } // add unique id for the group(s), and do not store the full list of choices in this group this._lastAddedGroupId++; group.id = this._lastAddedGroupId; group.choices.forEach(function (item) { item.group = group; if (group.disabled) { item.disabled = true; } _this._addChoice(item, withEvents); }); }; Choices.prototype._createTemplates = function () { var _this = this; var callbackOnCreateTemplates = this.config.callbackOnCreateTemplates; var userTemplates = {}; if (typeof callbackOnCreateTemplates === 'function') { userTemplates = callbackOnCreateTemplates.call(this, strToEl, escapeForTemplate, getClassNames); } var templating = {}; Object.keys(this._templates).forEach(function (name) { if (name in userTemplates) { templating[name] = userTemplates[name].bind(_this); } else { templating[name] = _this._templates[name].bind(_this); } }); this._templates = templating; }; Choices.prototype._createElements = function () { var templating = this._templates; var _a = this, config = _a.config, isSelectOneElement = _a._isSelectOneElement; var position = config.position, classNames = config.classNames; var elementType = this._elementType; this.containerOuter = new Container({ element: templating.containerOuter(config, this._direction, this._isSelectElement, isSelectOneElement, config.searchEnabled, elementType, config.labelId), classNames: classNames, type: elementType, position: position, }); this.containerInner = new Container({ element: templating.containerInner(config), classNames: classNames, type: elementType, position: position, }); this.input = new Input({ element: templating.input(config, this._placeholderValue), classNames: classNames, type: elementType, preventPaste: !config.paste, }); this.choiceList = new List({ element: templating.choiceList(config, isSelectOneElement), }); this.itemList = new List({ element: templating.itemList(config, isSelectOneElement), }); this.dropdown = new Dropdown({ element: templating.dropdown(config), classNames: classNames, type: elementType, }); }; Choices.prototype._createStructure = function () { var _a = this, containerInner = _a.containerInner, containerOuter = _a.containerOuter, passedElement = _a.passedElement; var dropdownElement = this.dropdown.element; // Hide original element passedElement.conceal(); // Wrap input in container preserving DOM ordering containerInner.wrap(passedElement.element); // Wrapper inner container with outer container containerOuter.wrap(containerInner.element); containerOuter.element.appendChild(containerInner.element); containerOuter.element.appendChild(dropdownElement); containerInner.element.appendChild(this.itemList.element); dropdownElement.appendChild(this.choiceList.element); if (this._isSelectOneElement) { this.input.placeholder = this.config.searchPlaceholderValue || ''; if (this.config.searchEnabled) { dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild); } } else { if (!this._isSelectMultipleElement || this.config.searchEnabled) { containerInner.element.appendChild(this.input.element); } if (this._placeholderValue) { this.input.placeholder = this._placeholderValue; } this.input.setWidth(); } this._highlightPosition = 0; this._isSearching = false; }; Choices.prototype._initStore = function () { var _this = this; this._store.subscribe(this._render).withTxn(function () { _this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false); }); if (!this._store.choices.length || (this._isSelectOneElement && this._hasNonChoicePlaceholder)) { this._render(); } }; Choices.prototype._addPredefinedChoices = function (choices, selectFirstOption, withEvents) { var _this = this; if (selectFirstOption === void 0) { selectFirstOption = false; } if (withEvents === void 0) { withEvents = true; } if (selectFirstOption) { /** * If there is a selected choice already or the choice is not the first in * the array, add each choice normally. * * Otherwise we pre-select the first enabled choice in the array ("select-one" only) */ var noSelectedChoices = choices.findIndex(function (choice) { return choice.selected; }) === -1; if (noSelectedChoices) { choices.some(function (choice) { if (choice.disabled || 'choices' in choice) { return false; } choice.selected = true; return true; }); } } choices.forEach(function (item) { if ('choices' in item) { if (_this._isSelectElement) { _this._addGroup(item, withEvents); } } else { _this._addChoice(item, withEvents); } }); }; Choices.prototype._findAndSelectChoiceByValue = function (value, userTriggered) { var _this = this; if (userTriggered === void 0) { userTriggered = false; } // Check 'value' property exists and the choice isn't already selected var foundChoice = this._store.choices.find(function (choice) { return _this.config.valueComparer(choice.value, value); }); if (foundChoice && !foundChoice.disabled && !foundChoice.selected) { this._addItem(foundChoice, true, userTriggered); return true; } return false; }; Choices.prototype._generatePlaceholderValue = function () { var config = this.config; if (!config.placeholder) { return null; } if (this._hasNonChoicePlaceholder) { return config.placeholderValue; } if (this._isSelectElement) { var placeholderOption = this.passedElement.placeholderOption; return placeholderOption ? placeholderOption.text : null; } return null; }; Choices.prototype._warnChoicesInitFailed = function (caller) { if (this.config.silent) { return; } if (!this.initialised) { throw new TypeError("".concat(caller, " called on a non-initialised instance of Choices")); } else if (!this.initialisedOK) { throw new TypeError("".concat(caller, " called for an element which has multiple instances of Choices initialised on it")); } }; Choices.version = '11.2.1'; return Choices; }()); return Choices; })); ================================================ FILE: public/assets/scripts/choices.mjs ================================================ /*! choices.js v11.2.1 | © 2026 Josh Johnson | https://github.com/Choices-js/Choices#readme */ /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var ActionType = { ADD_CHOICE: 'ADD_CHOICE', REMOVE_CHOICE: 'REMOVE_CHOICE', FILTER_CHOICES: 'FILTER_CHOICES', ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', CLEAR_CHOICES: 'CLEAR_CHOICES', ADD_GROUP: 'ADD_GROUP', ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', }; var EventType = { showDropdown: 'showDropdown', hideDropdown: 'hideDropdown', change: 'change', choice: 'choice', search: 'search', addItem: 'addItem', removeItem: 'removeItem', highlightItem: 'highlightItem', highlightChoice: 'highlightChoice', unhighlightItem: 'unhighlightItem', }; var KeyCodeMap = { TAB_KEY: 9, SHIFT_KEY: 16, BACK_KEY: 46, DELETE_KEY: 8, ENTER_KEY: 13, A_KEY: 65, ESC_KEY: 27, UP_KEY: 38, DOWN_KEY: 40, PAGE_UP_KEY: 33, PAGE_DOWN_KEY: 34, }; var ObjectsInConfig = ['fuseOptions', 'classNames']; var PassedElementTypes = { Text: 'text', SelectOne: 'select-one', SelectMultiple: 'select-multiple', }; var addChoice = function (choice) { return ({ type: ActionType.ADD_CHOICE, choice: choice, }); }; var removeChoice = function (choice) { return ({ type: ActionType.REMOVE_CHOICE, choice: choice, }); }; var filterChoices = function (results) { return ({ type: ActionType.FILTER_CHOICES, results: results, }); }; var activateChoices = function (active) { return ({ type: ActionType.ACTIVATE_CHOICES, active: active, }); }; var addGroup = function (group) { return ({ type: ActionType.ADD_GROUP, group: group, }); }; var addItem = function (item) { return ({ type: ActionType.ADD_ITEM, item: item, }); }; var removeItem$1 = function (item) { return ({ type: ActionType.REMOVE_ITEM, item: item, }); }; var highlightItem = function (item, highlighted) { return ({ type: ActionType.HIGHLIGHT_ITEM, item: item, highlighted: highlighted, }); }; var getRandomNumber = function (min, max) { return Math.floor(Math.random() * (max - min) + min); }; var generateChars = function (length) { return Array.from({ length: length }, function () { return getRandomNumber(0, 36).toString(36); }).join(''); }; var generateId = function (element, prefix) { var id = element.id || (element.name && "".concat(element.name, "-").concat(generateChars(2))) || generateChars(4); id = id.replace(/(:|\.|\[|\]|,)/g, ''); id = "".concat(prefix, "-").concat(id); return id; }; var getAdjacentEl = function (startEl, selector, direction) { if (direction === void 0) { direction = 1; } var prop = "".concat(direction > 0 ? 'next' : 'previous', "ElementSibling"); var sibling = startEl[prop]; while (sibling) { if (sibling.matches(selector)) { return sibling; } sibling = sibling[prop]; } return null; }; var isScrolledIntoView = function (element, parent, direction) { if (direction === void 0) { direction = 1; } var isVisible; if (direction > 0) { // In view from bottom isVisible = parent.scrollTop + parent.offsetHeight >= element.offsetTop + element.offsetHeight; } else { // In view from top isVisible = element.offsetTop >= parent.scrollTop; } return isVisible; }; var sanitise = function (value) { if (typeof value !== 'string') { if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { if ('raw' in value) { return sanitise(value.raw); } if ('trusted' in value) { return value.trusted; } } return value; } return value .replace(/&/g, '&') .replace(/>/g, '>') .replace(/= 0 && !window.matchMedia("(min-height: ".concat(dropdownPos + 1, "px)")).matches; } else if (this.position === 'top') { shouldFlip = true; } return shouldFlip; }; Container.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Container.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Container.prototype.open = function (dropdownPos, dropdownHeight) { addClassesToElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; if (this.shouldFlip(dropdownPos, dropdownHeight)) { addClassesToElement(this.element, this.classNames.flippedState); this.isFlipped = true; } }; Container.prototype.close = function () { removeClassesFromElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'false'); this.removeActiveDescendant(); this.isOpen = false; // A dropdown flips if it does not have space within the page if (this.isFlipped) { removeClassesFromElement(this.element, this.classNames.flippedState); this.isFlipped = false; } }; Container.prototype.addFocusState = function () { addClassesToElement(this.element, this.classNames.focusState); }; Container.prototype.removeFocusState = function () { removeClassesFromElement(this.element, this.classNames.focusState); }; Container.prototype.addInvalidState = function () { addClassesToElement(this.element, this.classNames.invalidState); }; Container.prototype.removeInvalidState = function () { removeClassesFromElement(this.element, this.classNames.invalidState); }; Container.prototype.enable = function () { removeClassesFromElement(this.element, this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; }; Container.prototype.disable = function () { addClassesToElement(this.element, this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; }; Container.prototype.wrap = function (element) { var el = this.element; var parentNode = element.parentNode; if (parentNode) { if (element.nextSibling) { parentNode.insertBefore(el, element.nextSibling); } else { parentNode.appendChild(el); } } el.appendChild(element); }; Container.prototype.unwrap = function (element) { var el = this.element; var parentNode = el.parentNode; if (parentNode) { // Move passed element outside this element parentNode.insertBefore(element, el); // Remove this element parentNode.removeChild(el); } }; Container.prototype.addLoadingState = function () { addClassesToElement(this.element, this.classNames.loadingState); this.element.setAttribute('aria-busy', 'true'); this.isLoading = true; }; Container.prototype.removeLoadingState = function () { removeClassesFromElement(this.element, this.classNames.loadingState); this.element.removeAttribute('aria-busy'); this.isLoading = false; }; return Container; }()); var Input = /** @class */ (function () { function Input(_a) { var element = _a.element, type = _a.type, classNames = _a.classNames, preventPaste = _a.preventPaste; this.element = element; this.type = type; this.classNames = classNames; this.preventPaste = preventPaste; this.isFocussed = this.element.isEqualNode(document.activeElement); this.isDisabled = element.disabled; this._onPaste = this._onPaste.bind(this); this._onInput = this._onInput.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } Object.defineProperty(Input.prototype, "placeholder", { set: function (placeholder) { this.element.placeholder = placeholder; }, enumerable: false, configurable: true }); Object.defineProperty(Input.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.value = value; }, enumerable: false, configurable: true }); Input.prototype.addEventListeners = function () { var el = this.element; el.addEventListener('paste', this._onPaste); el.addEventListener('input', this._onInput, { passive: true, }); el.addEventListener('focus', this._onFocus, { passive: true, }); el.addEventListener('blur', this._onBlur, { passive: true, }); }; Input.prototype.removeEventListeners = function () { var el = this.element; el.removeEventListener('input', this._onInput); el.removeEventListener('paste', this._onPaste); el.removeEventListener('focus', this._onFocus); el.removeEventListener('blur', this._onBlur); }; Input.prototype.enable = function () { var el = this.element; el.removeAttribute('disabled'); this.isDisabled = false; }; Input.prototype.disable = function () { var el = this.element; el.setAttribute('disabled', ''); this.isDisabled = true; }; Input.prototype.focus = function () { if (!this.isFocussed) { this.element.focus(); } }; Input.prototype.blur = function () { if (this.isFocussed) { this.element.blur(); } }; Input.prototype.clear = function (setWidth) { if (setWidth === void 0) { setWidth = true; } this.element.value = ''; if (setWidth) { this.setWidth(); } return this; }; /** * Set the correct input width based on placeholder * value or input value */ Input.prototype.setWidth = function () { // Resize input to contents or placeholder var element = this.element; element.style.minWidth = "".concat(element.placeholder.length + 1, "ch"); element.style.width = "".concat(element.value.length + 1, "ch"); }; Input.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Input.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Input.prototype._onInput = function () { if (this.type !== PassedElementTypes.SelectOne) { this.setWidth(); } }; Input.prototype._onPaste = function (event) { if (this.preventPaste) { event.preventDefault(); } }; Input.prototype._onFocus = function () { this.isFocussed = true; }; Input.prototype._onBlur = function () { this.isFocussed = false; }; return Input; }()); var SCROLLING_SPEED = 4; var List = /** @class */ (function () { function List(_a) { var element = _a.element; this.element = element; this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; } List.prototype.prepend = function (node) { var child = this.element.firstElementChild; if (child) { this.element.insertBefore(node, child); } else { this.element.append(node); } }; List.prototype.scrollToTop = function () { this.element.scrollTop = 0; }; List.prototype.scrollToChildElement = function (element, direction) { var _this = this; if (!element) { return; } var listHeight = this.element.offsetHeight; // Scroll position of dropdown var listScrollPosition = this.element.scrollTop + listHeight; var elementHeight = element.offsetHeight; // Distance from bottom of element to top of parent var elementPos = element.offsetTop + elementHeight; // Difference between the element and scroll position var destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop; requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); }; List.prototype._scrollDown = function (scrollPos, strength, destination) { var easing = (destination - scrollPos) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos + distance; }; List.prototype._scrollUp = function (scrollPos, strength, destination) { var easing = (scrollPos - destination) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos - distance; }; List.prototype._animateScroll = function (destination, direction) { var _this = this; var strength = SCROLLING_SPEED; var choiceListScrollTop = this.element.scrollTop; var continueAnimation = false; if (direction > 0) { this._scrollDown(choiceListScrollTop, strength, destination); if (choiceListScrollTop < destination) { continueAnimation = true; } } else { this._scrollUp(choiceListScrollTop, strength, destination); if (choiceListScrollTop > destination) { continueAnimation = true; } } if (continueAnimation) { requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); } }; return List; }()); var WrappedElement = /** @class */ (function () { function WrappedElement(_a) { var element = _a.element, classNames = _a.classNames; this.element = element; this.classNames = classNames; this.isDisabled = false; } Object.defineProperty(WrappedElement.prototype, "isActive", { get: function () { return this.element.dataset.choice === 'active'; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "dir", { get: function () { return this.element.dir; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.setAttribute('value', value); this.element.value = value; }, enumerable: false, configurable: true }); WrappedElement.prototype.conceal = function () { var el = this.element; // Hide passed input addClassesToElement(el, this.classNames.input); el.hidden = true; // Remove element from tab index el.tabIndex = -1; // Backup original styles if any var origStyle = el.getAttribute('style'); if (origStyle) { el.setAttribute('data-choice-orig-style', origStyle); } el.setAttribute('data-choice', 'active'); }; WrappedElement.prototype.reveal = function () { var el = this.element; // Reinstate passed element removeClassesFromElement(el, this.classNames.input); el.hidden = false; el.removeAttribute('tabindex'); // Recover original styles if any var origStyle = el.getAttribute('data-choice-orig-style'); if (origStyle) { el.removeAttribute('data-choice-orig-style'); el.setAttribute('style', origStyle); } else { el.removeAttribute('style'); } el.removeAttribute('data-choice'); }; WrappedElement.prototype.enable = function () { this.element.removeAttribute('disabled'); this.element.disabled = false; this.isDisabled = false; }; WrappedElement.prototype.disable = function () { this.element.setAttribute('disabled', ''); this.element.disabled = true; this.isDisabled = true; }; WrappedElement.prototype.triggerEvent = function (eventType, data) { dispatchEvent(this.element, eventType, data || {}); }; return WrappedElement; }()); var WrappedInput = /** @class */ (function (_super) { __extends(WrappedInput, _super); function WrappedInput() { return _super !== null && _super.apply(this, arguments) || this; } return WrappedInput; }(WrappedElement)); var coerceBool = function (arg, defaultValue) { if (defaultValue === void 0) { defaultValue = true; } return typeof arg === 'undefined' ? defaultValue : !!arg; }; var stringToHtmlClass = function (input) { if (typeof input === 'string') { // eslint-disable-next-line no-param-reassign input = input.split(' ').filter(function (s) { return s.length; }); } if (Array.isArray(input) && input.length) { return input; } return undefined; }; var mapInputToChoice = function (value, allowGroup, allowRawString) { if (allowRawString === void 0) { allowRawString = true; } if (typeof value === 'string') { var sanitisedValue = sanitise(value); var userValue = allowRawString || sanitisedValue === value ? value : { escaped: sanitisedValue, raw: value }; var result_1 = mapInputToChoice({ value: value, label: userValue, selected: true, }, false); return result_1; } var groupOrChoice = value; if ('choices' in groupOrChoice) { if (!allowGroup) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup throw new TypeError("optGroup is not allowed"); } var group = groupOrChoice; var choices = group.choices.map(function (e) { return mapInputToChoice(e, false); }); var result_2 = { id: 0, // actual ID will be assigned during _addGroup label: unwrapStringForRaw(group.label) || group.value, active: !!choices.length, disabled: !!group.disabled, choices: choices, }; return result_2; } var choice = groupOrChoice; var result = { id: 0, // actual ID will be assigned during _addChoice group: null, // actual group will be assigned during _addGroup but before _addChoice score: 0, // used in search rank: 0, // used in search, stable sort order value: choice.value, label: choice.label || choice.value, active: coerceBool(choice.active), selected: coerceBool(choice.selected, false), disabled: coerceBool(choice.disabled, false), placeholder: coerceBool(choice.placeholder, false), highlighted: false, labelClass: stringToHtmlClass(choice.labelClass), labelDescription: choice.labelDescription, customProperties: choice.customProperties, }; return result; }; var isHtmlInputElement = function (e) { return e.tagName === 'INPUT'; }; var isHtmlSelectElement = function (e) { return e.tagName === 'SELECT'; }; var isHtmlOption = function (e) { return e.tagName === 'OPTION'; }; var isHtmlOptgroup = function (e) { return e.tagName === 'OPTGROUP'; }; var WrappedSelect = /** @class */ (function (_super) { __extends(WrappedSelect, _super); function WrappedSelect(_a) { var element = _a.element, classNames = _a.classNames, template = _a.template, extractPlaceholder = _a.extractPlaceholder; var _this = _super.call(this, { element: element, classNames: classNames }) || this; _this.template = template; _this.extractPlaceholder = extractPlaceholder; return _this; } Object.defineProperty(WrappedSelect.prototype, "placeholderOption", { get: function () { return (this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]')); }, enumerable: false, configurable: true }); WrappedSelect.prototype.addOptions = function (choices) { var _this = this; var fragment = document.createDocumentFragment(); choices.forEach(function (obj) { var choice = obj; if (choice.element) { return; } var option = _this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); }; WrappedSelect.prototype.optionsAsChoices = function () { var _this = this; var choices = []; this.element.querySelectorAll(':scope > option, :scope > optgroup').forEach(function (e) { if (isHtmlOption(e)) { choices.push(_this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(_this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; }; // eslint-disable-next-line class-methods-use-this WrappedSelect.prototype._optionToChoice = function (option) { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? { trusted: option.dataset.labelDescription } : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; }; WrappedSelect.prototype._optgroupToChoice = function (optgroup) { var _this = this; var options = optgroup.querySelectorAll('option'); var choices = Array.from(options).map(function (option) { return _this._optionToChoice(option); }); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices: choices, }; }; return WrappedSelect; }(WrappedElement)); var DEFAULT_CLASSNAMES = { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], }; var DEFAULT_CONFIG = { items: [], choices: [], silent: false, renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: function (value) { return !!value && value !== ''; }, removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: function (value) { return "Press Enter to add \"".concat(value, "\""); }, removeItemIconText: function () { return "Remove item"; }, removeItemLabelText: function (value, _valueRaw, i) { return "Remove item: ".concat(i ? sanitise(i.label) : value); }, maxItemText: function (maxItemCount) { return "Only ".concat(maxItemCount, " values can be added"); }, valueComparer: function (value1, value2) { return value1 === value2; }, fuseOptions: { includeScore: true, }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, appendGroupInSearch: false, }; var removeItem = function (item) { var itemEl = item.itemEl; if (itemEl) { itemEl.remove(); item.itemEl = undefined; } }; function items(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_ITEM: { action.item.selected = true; var el = action.item.element; if (el) { el.selected = true; el.setAttribute('selected', ''); } state.push(action.item); break; } case ActionType.REMOVE_ITEM: { action.item.selected = false; var el = action.item.element; if (el) { el.selected = false; el.removeAttribute('selected'); // For a select-one, if all options are deselected, the first item is selected. To set a black value, select.value needs to be set var select = el.parentElement; if (select && isHtmlSelectElement(select) && select.type === PassedElementTypes.SelectOne) { select.value = ''; } } // this is mixing concerns, but this is *so much faster* removeItem(action.item); state = state.filter(function (choice) { return choice.id !== action.item.id; }); break; } case ActionType.REMOVE_CHOICE: { removeItem(action.choice); state = state.filter(function (item) { return item.id !== action.choice.id; }); break; } case ActionType.HIGHLIGHT_ITEM: { var highlighted = action.highlighted; var item = state.find(function (obj) { return obj.id === action.item.id; }); if (item && item.highlighted !== highlighted) { item.highlighted = highlighted; if (context) { updateClassList(item, highlighted ? context.classNames.highlightedState : context.classNames.selectedState, highlighted ? context.classNames.selectedState : context.classNames.highlightedState); } } break; } default: { update = false; break; } } return { state: state, update: update }; } function groups(s, action) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_GROUP: { state.push(action.group); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } /* eslint-disable */ function choices(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_CHOICE: { state.push(action.choice); break; } case ActionType.REMOVE_CHOICE: { action.choice.choiceEl = undefined; if (action.choice.group) { action.choice.group.choices = action.choice.group.choices.filter(function (obj) { return obj.id !== action.choice.id; }); } state = state.filter(function (obj) { return obj.id !== action.choice.id; }); break; } case ActionType.ADD_ITEM: case ActionType.REMOVE_ITEM: { action.item.choiceEl = undefined; break; } case ActionType.FILTER_CHOICES: { // avoid O(n^2) algorithm complexity when searching/filtering choices var scoreLookup_1 = []; action.results.forEach(function (result) { scoreLookup_1[result.item.id] = result; }); state.forEach(function (choice) { var result = scoreLookup_1[choice.id]; if (result !== undefined) { choice.score = result.score; choice.rank = result.rank; choice.active = true; } else { choice.score = 0; choice.rank = 0; choice.active = false; } if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.ACTIVATE_CHOICES: { state.forEach(function (choice) { choice.active = action.active; if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } var reducers = { groups: groups, items: items, choices: choices, }; var Store = /** @class */ (function () { function Store(context) { this._state = this.defaultState; this._listeners = []; this._txn = 0; this._context = context; } Object.defineProperty(Store.prototype, "defaultState", { // eslint-disable-next-line class-methods-use-this get: function () { return { groups: [], items: [], choices: [], }; }, enumerable: false, configurable: true }); // eslint-disable-next-line class-methods-use-this Store.prototype.changeSet = function (init) { return { groups: init, items: init, choices: init, }; }; Store.prototype.reset = function () { this._state = this.defaultState; var changes = this.changeSet(true); if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } }; Store.prototype.subscribe = function (onChange) { this._listeners.push(onChange); return this; }; Store.prototype.dispatch = function (action) { var _this = this; var state = this._state; var hasChanges = false; var changes = this._changeSet || this.changeSet(false); Object.keys(reducers).forEach(function (key) { var stateUpdate = reducers[key](state[key], action, _this._context); if (stateUpdate.update) { hasChanges = true; changes[key] = true; state[key] = stateUpdate.state; } }); if (hasChanges) { if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } } }; Store.prototype.withTxn = function (func) { this._txn++; try { func(); } finally { this._txn = Math.max(0, this._txn - 1); if (!this._txn) { var changeSet_1 = this._changeSet; if (changeSet_1) { this._changeSet = undefined; this._listeners.forEach(function (l) { return l(changeSet_1); }); } } } }; Object.defineProperty(Store.prototype, "state", { /** * Get store object */ get: function () { return this._state; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "items", { /** * Get items from store */ get: function () { return this.state.items; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "highlightedActiveItems", { /** * Get highlighted items from store */ get: function () { return this.items.filter(function (item) { return item.active && item.highlighted; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "choices", { /** * Get choices from store */ get: function () { return this.state.choices; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeChoices", { /** * Get active choices from store */ get: function () { return this.choices.filter(function (choice) { return choice.active; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "searchableChoices", { /** * Get choices that can be searched (excluding placeholders or disabled choices) */ get: function () { var context = this._context; return this.choices.filter(function (choice) { return !choice.placeholder && (context.searchDisabledChoices || !choice.disabled); }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "groups", { /** * Get groups from store */ get: function () { return this.state.groups; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeGroups", { /** * Get active groups from store */ get: function () { var _this = this; return this.state.groups.filter(function (group) { var isActive = group.active && !group.disabled; var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; }); return isActive && hasActiveOptions; }, []); }, enumerable: false, configurable: true }); Store.prototype.inTxn = function () { return this._txn > 0; }; /** * Get single choice by it's ID */ Store.prototype.getChoiceById = function (id) { return this.activeChoices.find(function (choice) { return choice.id === id; }); }; /** * Get group by group id */ Store.prototype.getGroupById = function (id) { return this.groups.find(function (group) { return group.id === id; }); }; return Store; }()); var NoticeTypes = { noChoices: 'no-choices', noResults: 'no-results', addChoice: 'add-choice', generic: '', }; function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } /** * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io) * * Copyright (c) 2023 Kiro Risk (http://kiro.me) * All Rights Reserved. Apache Software License 2.0 * * http://www.apache.org/licenses/LICENSE-2.0 */ function isArray(value) { return !Array.isArray ? getTag(value) === '[object Array]' : Array.isArray(value); } function baseToString(value) { // Exit early for strings to avoid a performance hit in some environments. if (typeof value == 'string') { return value; } let result = value + ''; return result == '0' && 1 / value == -Infinity ? '-0' : result; } function toString(value) { return value == null ? '' : baseToString(value); } function isString(value) { return typeof value === 'string'; } function isNumber(value) { return typeof value === 'number'; } // Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js function isBoolean(value) { return value === true || value === false || isObjectLike(value) && getTag(value) == '[object Boolean]'; } function isObject(value) { return typeof value === 'object'; } // Checks if `value` is object-like. function isObjectLike(value) { return isObject(value) && value !== null; } function isDefined(value) { return value !== undefined && value !== null; } function isBlank(value) { return !value.trim().length; } // Gets the `toStringTag` of `value`. // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js function getTag(value) { return value == null ? value === undefined ? '[object Undefined]' : '[object Null]' : Object.prototype.toString.call(value); } const INCORRECT_INDEX_TYPE = "Incorrect 'index' type"; const LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = key => `Invalid value for key ${key}`; const PATTERN_LENGTH_TOO_LARGE = max => `Pattern length exceeds max of ${max}.`; const MISSING_KEY_PROPERTY = name => `Missing ${name} property in key`; const INVALID_KEY_WEIGHT_VALUE = key => `Property 'weight' in key '${key}' must be a positive integer`; const hasOwn = Object.prototype.hasOwnProperty; class KeyStore { constructor(keys) { this._keys = []; this._keyMap = {}; let totalWeight = 0; keys.forEach(key => { let obj = createKey(key); this._keys.push(obj); this._keyMap[obj.id] = obj; totalWeight += obj.weight; }); // Normalize weights so that their sum is equal to 1 this._keys.forEach(key => { key.weight /= totalWeight; }); } get(keyId) { return this._keyMap[keyId]; } keys() { return this._keys; } toJSON() { return JSON.stringify(this._keys); } } function createKey(key) { let path = null; let id = null; let src = null; let weight = 1; let getFn = null; if (isString(key) || isArray(key)) { src = key; path = createKeyPath(key); id = createKeyId(key); } else { if (!hasOwn.call(key, 'name')) { throw new Error(MISSING_KEY_PROPERTY('name')); } const name = key.name; src = name; if (hasOwn.call(key, 'weight')) { weight = key.weight; if (weight <= 0) { throw new Error(INVALID_KEY_WEIGHT_VALUE(name)); } } path = createKeyPath(name); id = createKeyId(name); getFn = key.getFn; } return { path, id, weight, src, getFn }; } function createKeyPath(key) { return isArray(key) ? key : key.split('.'); } function createKeyId(key) { return isArray(key) ? key.join('.') : key; } function get(obj, path) { let list = []; let arr = false; const deepGet = (obj, path, index) => { if (!isDefined(obj)) { return; } if (!path[index]) { // If there's no path left, we've arrived at the object we care about. list.push(obj); } else { let key = path[index]; const value = obj[key]; if (!isDefined(value)) { return; } // If we're at the last value in the path, and if it's a string/number/bool, // add it to the list if (index === path.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) { list.push(toString(value)); } else if (isArray(value)) { arr = true; // Search each item in the array. for (let i = 0, len = value.length; i < len; i += 1) { deepGet(value[i], path, index + 1); } } else if (path.length) { // An object. Recurse further. deepGet(value, path, index + 1); } } }; // Backwards compatibility (since path used to be a string) deepGet(obj, isString(path) ? path.split('.') : path, 0); return arr ? list : list[0]; } const MatchOptions = { // Whether the matches should be included in the result set. When `true`, each record in the result // set will include the indices of the matched characters. // These can consequently be used for highlighting purposes. includeMatches: false, // When `true`, the matching function will continue to the end of a search pattern even if // a perfect match has already been located in the string. findAllMatches: false, // Minimum number of characters that must be matched before a result is considered a match minMatchCharLength: 1 }; const BasicOptions = { // When `true`, the algorithm continues searching to the end of the input even if a perfect // match is found before the end of the same input. isCaseSensitive: false, // When true, the matching function will continue to the end of a search pattern even if includeScore: false, // List of properties that will be searched. This also supports nested properties. keys: [], // Whether to sort the result list, by score shouldSort: true, // Default sort function: sort by ascending score, ascending index sortFn: (a, b) => a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1 }; const FuzzyOptions = { // Approximately where in the text is the pattern expected to be found? location: 0, // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match // (of both letters and location), a threshold of '1.0' would match anything. threshold: 0.6, // Determines how close the match must be to the fuzzy location (specified above). // An exact letter match which is 'distance' characters away from the fuzzy location // would score as a complete mismatch. A distance of '0' requires the match be at // the exact location specified, a threshold of '1000' would require a perfect match // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. distance: 100 }; const AdvancedOptions = { // When `true`, it enables the use of unix-like search commands useExtendedSearch: false, // The get function to use when fetching an object's properties. // The default will search nested paths *ie foo.bar.baz* getFn: get, // When `true`, search will ignore `location` and `distance`, so it won't matter // where in the string the pattern appears. // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score ignoreLocation: false, // When `true`, the calculation for the relevance score (used for sorting) will // ignore the field-length norm. // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm ignoreFieldNorm: false, // The weight to determine how much field length norm effects scoring. fieldNormWeight: 1 }; var Config = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, BasicOptions), MatchOptions), FuzzyOptions), AdvancedOptions); const SPACE = /[^ ]+/g; // Field-length norm: the shorter the field, the higher the weight. // Set to 3 decimals to reduce index size. function norm(weight = 1, mantissa = 3) { const cache = new Map(); const m = Math.pow(10, mantissa); return { get(value) { const numTokens = value.match(SPACE).length; if (cache.has(numTokens)) { return cache.get(numTokens); } // Default function is 1/sqrt(x), weight makes that variable const norm = 1 / Math.pow(numTokens, 0.5 * weight); // In place of `toFixed(mantissa)`, for faster computation const n = parseFloat(Math.round(norm * m) / m); cache.set(numTokens, n); return n; }, clear() { cache.clear(); } }; } class FuseIndex { constructor({ getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { this.norm = norm(fieldNormWeight, 3); this.getFn = getFn; this.isCreated = false; this.setIndexRecords(); } setSources(docs = []) { this.docs = docs; } setIndexRecords(records = []) { this.records = records; } setKeys(keys = []) { this.keys = keys; this._keysMap = {}; keys.forEach((key, idx) => { this._keysMap[key.id] = idx; }); } create() { if (this.isCreated || !this.docs.length) { return; } this.isCreated = true; // List is Array if (isString(this.docs[0])) { this.docs.forEach((doc, docIndex) => { this._addString(doc, docIndex); }); } else { // List is Array this.docs.forEach((doc, docIndex) => { this._addObject(doc, docIndex); }); } this.norm.clear(); } // Adds a doc to the end of the index add(doc) { const idx = this.size(); if (isString(doc)) { this._addString(doc, idx); } else { this._addObject(doc, idx); } } // Removes the doc at the specified index of the index removeAt(idx) { this.records.splice(idx, 1); // Change ref index of every subsquent doc for (let i = idx, len = this.size(); i < len; i += 1) { this.records[i].i -= 1; } } getValueForItemAtKeyId(item, keyId) { return item[this._keysMap[keyId]]; } size() { return this.records.length; } _addString(doc, docIndex) { if (!isDefined(doc) || isBlank(doc)) { return; } let record = { v: doc, i: docIndex, n: this.norm.get(doc) }; this.records.push(record); } _addObject(doc, docIndex) { let record = { i: docIndex, $: {} }; // Iterate over every key (i.e, path), and fetch the value at that key this.keys.forEach((key, keyIndex) => { let value = key.getFn ? key.getFn(doc) : this.getFn(doc, key.path); if (!isDefined(value)) { return; } if (isArray(value)) { let subRecords = []; const stack = [{ nestedArrIndex: -1, value }]; while (stack.length) { const { nestedArrIndex, value } = stack.pop(); if (!isDefined(value)) { continue; } if (isString(value) && !isBlank(value)) { let subRecord = { v: value, i: nestedArrIndex, n: this.norm.get(value) }; subRecords.push(subRecord); } else if (isArray(value)) { value.forEach((item, k) => { stack.push({ nestedArrIndex: k, value: item }); }); } else ; } record.$[keyIndex] = subRecords; } else if (isString(value) && !isBlank(value)) { let subRecord = { v: value, n: this.norm.get(value) }; record.$[keyIndex] = subRecord; } }); this.records.push(record); } toJSON() { return { keys: this.keys, records: this.records }; } } function createIndex(keys, docs, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { const myIndex = new FuseIndex({ getFn, fieldNormWeight }); myIndex.setKeys(keys.map(createKey)); myIndex.setSources(docs); myIndex.create(); return myIndex; } function parseIndex(data, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { const { keys, records } = data; const myIndex = new FuseIndex({ getFn, fieldNormWeight }); myIndex.setKeys(keys); myIndex.setIndexRecords(records); return myIndex; } function computeScore$1(pattern, { errors = 0, currentLocation = 0, expectedLocation = 0, distance = Config.distance, ignoreLocation = Config.ignoreLocation } = {}) { const accuracy = errors / pattern.length; if (ignoreLocation) { return accuracy; } const proximity = Math.abs(expectedLocation - currentLocation); if (!distance) { // Dodge divide by zero error. return proximity ? 1.0 : accuracy; } return accuracy + proximity / distance; } function convertMaskToIndices(matchmask = [], minMatchCharLength = Config.minMatchCharLength) { let indices = []; let start = -1; let end = -1; let i = 0; for (let len = matchmask.length; i < len; i += 1) { let match = matchmask[i]; if (match && start === -1) { start = i; } else if (!match && start !== -1) { end = i - 1; if (end - start + 1 >= minMatchCharLength) { indices.push([start, end]); } start = -1; } } // (i-1 - start) + 1 => i - start if (matchmask[i - 1] && i - start >= minMatchCharLength) { indices.push([start, i - 1]); } return indices; } // Machine word size const MAX_BITS = 32; function search(text, pattern, patternAlphabet, { location = Config.location, distance = Config.distance, threshold = Config.threshold, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, includeMatches = Config.includeMatches, ignoreLocation = Config.ignoreLocation } = {}) { if (pattern.length > MAX_BITS) { throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS)); } const patternLen = pattern.length; // Set starting location at beginning text and initialize the alphabet. const textLen = text.length; // Handle the case when location > text.length const expectedLocation = Math.max(0, Math.min(location, textLen)); // Highest score beyond which we give up. let currentThreshold = threshold; // Is there a nearby exact match? (speedup) let bestLocation = expectedLocation; // Performance: only computer matches when the minMatchCharLength > 1 // OR if `includeMatches` is true. const computeMatches = minMatchCharLength > 1 || includeMatches; // A mask of the matches, used for building the indices const matchMask = computeMatches ? Array(textLen) : []; let index; // Get all exact matches, here for speed up while ((index = text.indexOf(pattern, bestLocation)) > -1) { let score = computeScore$1(pattern, { currentLocation: index, expectedLocation, distance, ignoreLocation }); currentThreshold = Math.min(score, currentThreshold); bestLocation = index + patternLen; if (computeMatches) { let i = 0; while (i < patternLen) { matchMask[index + i] = 1; i += 1; } } } // Reset the best location bestLocation = -1; let lastBitArr = []; let finalScore = 1; let binMax = patternLen + textLen; const mask = 1 << patternLen - 1; for (let i = 0; i < patternLen; i += 1) { // Scan for the best match; each iteration allows for one more error. // Run a binary search to determine how far from the match location we can stray // at this error level. let binMin = 0; let binMid = binMax; while (binMin < binMid) { const score = computeScore$1(pattern, { errors: i, currentLocation: expectedLocation + binMid, expectedLocation, distance, ignoreLocation }); if (score <= currentThreshold) { binMin = binMid; } else { binMax = binMid; } binMid = Math.floor((binMax - binMin) / 2 + binMin); } // Use the result from this iteration as the maximum for the next. binMax = binMid; let start = Math.max(1, expectedLocation - binMid + 1); let finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen; // Initialize the bit array let bitArr = Array(finish + 2); bitArr[finish + 1] = (1 << i) - 1; for (let j = finish; j >= start; j -= 1) { let currentLocation = j - 1; let charMatch = patternAlphabet[text.charAt(currentLocation)]; if (computeMatches) { // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`) matchMask[currentLocation] = +!!charMatch; } // First pass: exact match bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch; // Subsequent passes: fuzzy match if (i) { bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1]; } if (bitArr[j] & mask) { finalScore = computeScore$1(pattern, { errors: i, currentLocation, expectedLocation, distance, ignoreLocation }); // This match will almost certainly be better than any existing match. // But check anyway. if (finalScore <= currentThreshold) { // Indeed it is currentThreshold = finalScore; bestLocation = currentLocation; // Already passed `loc`, downhill from here on in. if (bestLocation <= expectedLocation) { break; } // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`. start = Math.max(1, 2 * expectedLocation - bestLocation); } } } // No hope for a (better) match at greater error levels. const score = computeScore$1(pattern, { errors: i + 1, currentLocation: expectedLocation, expectedLocation, distance, ignoreLocation }); if (score > currentThreshold) { break; } lastBitArr = bitArr; } const result = { isMatch: bestLocation >= 0, // Count exact matches (those with a score of 0) to be "almost" exact score: Math.max(0.001, finalScore) }; if (computeMatches) { const indices = convertMaskToIndices(matchMask, minMatchCharLength); if (!indices.length) { result.isMatch = false; } else if (includeMatches) { result.indices = indices; } } return result; } function createPatternAlphabet(pattern) { let mask = {}; for (let i = 0, len = pattern.length; i < len; i += 1) { const char = pattern.charAt(i); mask[char] = (mask[char] || 0) | 1 << len - i - 1; } return mask; } class BitapSearch { constructor(pattern, { location = Config.location, threshold = Config.threshold, distance = Config.distance, includeMatches = Config.includeMatches, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, isCaseSensitive = Config.isCaseSensitive, ignoreLocation = Config.ignoreLocation } = {}) { this.options = { location, threshold, distance, includeMatches, findAllMatches, minMatchCharLength, isCaseSensitive, ignoreLocation }; this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); this.chunks = []; if (!this.pattern.length) { return; } const addChunk = (pattern, startIndex) => { this.chunks.push({ pattern, alphabet: createPatternAlphabet(pattern), startIndex }); }; const len = this.pattern.length; if (len > MAX_BITS) { let i = 0; const remainder = len % MAX_BITS; const end = len - remainder; while (i < end) { addChunk(this.pattern.substr(i, MAX_BITS), i); i += MAX_BITS; } if (remainder) { const startIndex = len - MAX_BITS; addChunk(this.pattern.substr(startIndex), startIndex); } } else { addChunk(this.pattern, 0); } } searchIn(text) { const { isCaseSensitive, includeMatches } = this.options; if (!isCaseSensitive) { text = text.toLowerCase(); } // Exact match if (this.pattern === text) { let result = { isMatch: true, score: 0 }; if (includeMatches) { result.indices = [[0, text.length - 1]]; } return result; } // Otherwise, use Bitap algorithm const { location, distance, threshold, findAllMatches, minMatchCharLength, ignoreLocation } = this.options; let allIndices = []; let totalScore = 0; let hasMatches = false; this.chunks.forEach(({ pattern, alphabet, startIndex }) => { const { isMatch, score, indices } = search(text, pattern, alphabet, { location: location + startIndex, distance, threshold, findAllMatches, minMatchCharLength, includeMatches, ignoreLocation }); if (isMatch) { hasMatches = true; } totalScore += score; if (isMatch && indices) { allIndices = [...allIndices, ...indices]; } }); let result = { isMatch: hasMatches, score: hasMatches ? totalScore / this.chunks.length : 1 }; if (hasMatches && includeMatches) { result.indices = allIndices; } return result; } } class BaseMatch { constructor(pattern) { this.pattern = pattern; } static isMultiMatch(pattern) { return getMatch(pattern, this.multiRegex); } static isSingleMatch(pattern) { return getMatch(pattern, this.singleRegex); } search( /*text*/) {} } function getMatch(pattern, exp) { const matches = pattern.match(exp); return matches ? matches[1] : null; } // Token: 'file class ExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'exact'; } static get multiRegex() { return /^="(.*)"$/; } static get singleRegex() { return /^=(.*)$/; } search(text) { const isMatch = text === this.pattern; return { isMatch, score: isMatch ? 0 : 1, indices: [0, this.pattern.length - 1] }; } } // Token: !fire class InverseExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'inverse-exact'; } static get multiRegex() { return /^!"(.*)"$/; } static get singleRegex() { return /^!(.*)$/; } search(text) { const index = text.indexOf(this.pattern); const isMatch = index === -1; return { isMatch, score: isMatch ? 0 : 1, indices: [0, text.length - 1] }; } } // Token: ^file class PrefixExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'prefix-exact'; } static get multiRegex() { return /^\^"(.*)"$/; } static get singleRegex() { return /^\^(.*)$/; } search(text) { const isMatch = text.startsWith(this.pattern); return { isMatch, score: isMatch ? 0 : 1, indices: [0, this.pattern.length - 1] }; } } // Token: !^fire class InversePrefixExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'inverse-prefix-exact'; } static get multiRegex() { return /^!\^"(.*)"$/; } static get singleRegex() { return /^!\^(.*)$/; } search(text) { const isMatch = !text.startsWith(this.pattern); return { isMatch, score: isMatch ? 0 : 1, indices: [0, text.length - 1] }; } } // Token: .file$ class SuffixExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'suffix-exact'; } static get multiRegex() { return /^"(.*)"\$$/; } static get singleRegex() { return /^(.*)\$$/; } search(text) { const isMatch = text.endsWith(this.pattern); return { isMatch, score: isMatch ? 0 : 1, indices: [text.length - this.pattern.length, text.length - 1] }; } } // Token: !.file$ class InverseSuffixExactMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'inverse-suffix-exact'; } static get multiRegex() { return /^!"(.*)"\$$/; } static get singleRegex() { return /^!(.*)\$$/; } search(text) { const isMatch = !text.endsWith(this.pattern); return { isMatch, score: isMatch ? 0 : 1, indices: [0, text.length - 1] }; } } class FuzzyMatch extends BaseMatch { constructor(pattern, { location = Config.location, threshold = Config.threshold, distance = Config.distance, includeMatches = Config.includeMatches, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, isCaseSensitive = Config.isCaseSensitive, ignoreLocation = Config.ignoreLocation } = {}) { super(pattern); this._bitapSearch = new BitapSearch(pattern, { location, threshold, distance, includeMatches, findAllMatches, minMatchCharLength, isCaseSensitive, ignoreLocation }); } static get type() { return 'fuzzy'; } static get multiRegex() { return /^"(.*)"$/; } static get singleRegex() { return /^(.*)$/; } search(text) { return this._bitapSearch.searchIn(text); } } // Token: 'file class IncludeMatch extends BaseMatch { constructor(pattern) { super(pattern); } static get type() { return 'include'; } static get multiRegex() { return /^'"(.*)"$/; } static get singleRegex() { return /^'(.*)$/; } search(text) { let location = 0; let index; const indices = []; const patternLen = this.pattern.length; // Get all exact matches while ((index = text.indexOf(this.pattern, location)) > -1) { location = index + patternLen; indices.push([index, location - 1]); } const isMatch = !!indices.length; return { isMatch, score: isMatch ? 0 : 1, indices }; } } // ❗Order is important. DO NOT CHANGE. const searchers = [ExactMatch, IncludeMatch, PrefixExactMatch, InversePrefixExactMatch, InverseSuffixExactMatch, SuffixExactMatch, InverseExactMatch, FuzzyMatch]; const searchersLen = searchers.length; // Regex to split by spaces, but keep anything in quotes together const SPACE_RE = / +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/; const OR_TOKEN = '|'; // Return a 2D array representation of the query, for simpler parsing. // Example: // "^core go$ | rb$ | py$ xy$" => [["^core", "go$"], ["rb$"], ["py$", "xy$"]] function parseQuery(pattern, options = {}) { return pattern.split(OR_TOKEN).map(item => { let query = item.trim().split(SPACE_RE).filter(item => item && !!item.trim()); let results = []; for (let i = 0, len = query.length; i < len; i += 1) { const queryItem = query[i]; // 1. Handle multiple query match (i.e, once that are quoted, like `"hello world"`) let found = false; let idx = -1; while (!found && ++idx < searchersLen) { const searcher = searchers[idx]; let token = searcher.isMultiMatch(queryItem); if (token) { results.push(new searcher(token, options)); found = true; } } if (found) { continue; } // 2. Handle single query matches (i.e, once that are *not* quoted) idx = -1; while (++idx < searchersLen) { const searcher = searchers[idx]; let token = searcher.isSingleMatch(queryItem); if (token) { results.push(new searcher(token, options)); break; } } } return results; }); } // These extended matchers can return an array of matches, as opposed // to a singl match const MultiMatchSet = new Set([FuzzyMatch.type, IncludeMatch.type]); /** * Command-like searching * ====================== * * Given multiple search terms delimited by spaces.e.g. `^jscript .python$ ruby !java`, * search in a given text. * * Search syntax: * * | Token | Match type | Description | * | ----------- | -------------------------- | -------------------------------------- | * | `jscript` | fuzzy-match | Items that fuzzy match `jscript` | * | `=scheme` | exact-match | Items that are `scheme` | * | `'python` | include-match | Items that include `python` | * | `!ruby` | inverse-exact-match | Items that do not include `ruby` | * | `^java` | prefix-exact-match | Items that start with `java` | * | `!^earlang` | inverse-prefix-exact-match | Items that do not start with `earlang` | * | `.js$` | suffix-exact-match | Items that end with `.js` | * | `!.go$` | inverse-suffix-exact-match | Items that do not end with `.go` | * * A single pipe character acts as an OR operator. For example, the following * query matches entries that start with `core` and end with either`go`, `rb`, * or`py`. * * ``` * ^core go$ | rb$ | py$ * ``` */ class ExtendedSearch { constructor(pattern, { isCaseSensitive = Config.isCaseSensitive, includeMatches = Config.includeMatches, minMatchCharLength = Config.minMatchCharLength, ignoreLocation = Config.ignoreLocation, findAllMatches = Config.findAllMatches, location = Config.location, threshold = Config.threshold, distance = Config.distance } = {}) { this.query = null; this.options = { isCaseSensitive, includeMatches, minMatchCharLength, findAllMatches, ignoreLocation, location, threshold, distance }; this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); this.query = parseQuery(this.pattern, this.options); } static condition(_, options) { return options.useExtendedSearch; } searchIn(text) { const query = this.query; if (!query) { return { isMatch: false, score: 1 }; } const { includeMatches, isCaseSensitive } = this.options; text = isCaseSensitive ? text : text.toLowerCase(); let numMatches = 0; let allIndices = []; let totalScore = 0; // ORs for (let i = 0, qLen = query.length; i < qLen; i += 1) { const searchers = query[i]; // Reset indices allIndices.length = 0; numMatches = 0; // ANDs for (let j = 0, pLen = searchers.length; j < pLen; j += 1) { const searcher = searchers[j]; const { isMatch, indices, score } = searcher.search(text); if (isMatch) { numMatches += 1; totalScore += score; if (includeMatches) { const type = searcher.constructor.type; if (MultiMatchSet.has(type)) { allIndices = [...allIndices, ...indices]; } else { allIndices.push(indices); } } } else { totalScore = 0; numMatches = 0; allIndices.length = 0; break; } } // OR condition, so if TRUE, return if (numMatches) { let result = { isMatch: true, score: totalScore / numMatches }; if (includeMatches) { result.indices = allIndices; } return result; } } // Nothing was matched return { isMatch: false, score: 1 }; } } const registeredSearchers = []; function register(...args) { registeredSearchers.push(...args); } function createSearcher(pattern, options) { for (let i = 0, len = registeredSearchers.length; i < len; i += 1) { let searcherClass = registeredSearchers[i]; if (searcherClass.condition(pattern, options)) { return new searcherClass(pattern, options); } } return new BitapSearch(pattern, options); } const LogicalOperator = { AND: '$and', OR: '$or' }; const KeyType = { PATH: '$path', PATTERN: '$val' }; const isExpression = query => !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]); const isPath = query => !!query[KeyType.PATH]; const isLeaf = query => !isArray(query) && isObject(query) && !isExpression(query); const convertToExplicit = query => ({ [LogicalOperator.AND]: Object.keys(query).map(key => ({ [key]: query[key] })) }); // When `auto` is `true`, the parse function will infer and initialize and add // the appropriate `Searcher` instance function parse(query, options, { auto = true } = {}) { const next = query => { let keys = Object.keys(query); const isQueryPath = isPath(query); if (!isQueryPath && keys.length > 1 && !isExpression(query)) { return next(convertToExplicit(query)); } if (isLeaf(query)) { const key = isQueryPath ? query[KeyType.PATH] : keys[0]; const pattern = isQueryPath ? query[KeyType.PATTERN] : query[key]; if (!isString(pattern)) { throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key)); } const obj = { keyId: createKeyId(key), pattern }; if (auto) { obj.searcher = createSearcher(pattern, options); } return obj; } let node = { children: [], operator: keys[0] }; keys.forEach(key => { const value = query[key]; if (isArray(value)) { value.forEach(item => { node.children.push(next(item)); }); } }); return node; }; if (!isExpression(query)) { query = convertToExplicit(query); } return next(query); } // Practical scoring function function computeScore(results, { ignoreFieldNorm = Config.ignoreFieldNorm }) { results.forEach(result => { let totalScore = 1; result.matches.forEach(({ key, norm, score }) => { const weight = key ? key.weight : null; totalScore *= Math.pow(score === 0 && weight ? Number.EPSILON : score, (weight || 1) * (ignoreFieldNorm ? 1 : norm)); }); result.score = totalScore; }); } function transformMatches(result, data) { const matches = result.matches; data.matches = []; if (!isDefined(matches)) { return; } matches.forEach(match => { if (!isDefined(match.indices) || !match.indices.length) { return; } const { indices, value } = match; let obj = { indices, value }; if (match.key) { obj.key = match.key.src; } if (match.idx > -1) { obj.refIndex = match.idx; } data.matches.push(obj); }); } function transformScore(result, data) { data.score = result.score; } function format(results, docs, { includeMatches = Config.includeMatches, includeScore = Config.includeScore } = {}) { const transformers = []; if (includeMatches) transformers.push(transformMatches); if (includeScore) transformers.push(transformScore); return results.map(result => { const { idx } = result; const data = { item: docs[idx], refIndex: idx }; if (transformers.length) { transformers.forEach(transformer => { transformer(result, data); }); } return data; }); } class Fuse { constructor(docs, options = {}, index) { this.options = _objectSpread2(_objectSpread2({}, Config), options); if (this.options.useExtendedSearch && false) ; this._keyStore = new KeyStore(this.options.keys); this.setCollection(docs, index); } setCollection(docs, index) { this._docs = docs; if (index && !(index instanceof FuseIndex)) { throw new Error(INCORRECT_INDEX_TYPE); } this._myIndex = index || createIndex(this.options.keys, this._docs, { getFn: this.options.getFn, fieldNormWeight: this.options.fieldNormWeight }); } add(doc) { if (!isDefined(doc)) { return; } this._docs.push(doc); this._myIndex.add(doc); } remove(predicate = ( /* doc, idx */) => false) { const results = []; for (let i = 0, len = this._docs.length; i < len; i += 1) { const doc = this._docs[i]; if (predicate(doc, i)) { this.removeAt(i); i -= 1; len -= 1; results.push(doc); } } return results; } removeAt(idx) { this._docs.splice(idx, 1); this._myIndex.removeAt(idx); } getIndex() { return this._myIndex; } search(query, { limit = -1 } = {}) { const { includeMatches, includeScore, shouldSort, sortFn, ignoreFieldNorm } = this.options; let results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query); computeScore(results, { ignoreFieldNorm }); if (shouldSort) { results.sort(sortFn); } if (isNumber(limit) && limit > -1) { results = results.slice(0, limit); } return format(results, this._docs, { includeMatches, includeScore }); } _searchStringList(query) { const searcher = createSearcher(query, this.options); const { records } = this._myIndex; const results = []; // Iterate over every string in the index records.forEach(({ v: text, i: idx, n: norm }) => { if (!isDefined(text)) { return; } const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { results.push({ item: text, idx, matches: [{ score, value: text, norm, indices }] }); } }); return results; } _searchLogical(query) { const expression = parse(query, this.options); const evaluate = (node, item, idx) => { if (!node.children) { const { keyId, searcher } = node; const matches = this._findMatches({ key: this._keyStore.get(keyId), value: this._myIndex.getValueForItemAtKeyId(item, keyId), searcher }); if (matches && matches.length) { return [{ idx, item, matches }]; } return []; } const res = []; for (let i = 0, len = node.children.length; i < len; i += 1) { const child = node.children[i]; const result = evaluate(child, item, idx); if (result.length) { res.push(...result); } else if (node.operator === LogicalOperator.AND) { return []; } } return res; }; const records = this._myIndex.records; const resultMap = {}; const results = []; records.forEach(({ $: item, i: idx }) => { if (isDefined(item)) { let expResults = evaluate(expression, item, idx); if (expResults.length) { // Dedupe when adding if (!resultMap[idx]) { resultMap[idx] = { idx, item, matches: [] }; results.push(resultMap[idx]); } expResults.forEach(({ matches }) => { resultMap[idx].matches.push(...matches); }); } } }); return results; } _searchObjectList(query) { const searcher = createSearcher(query, this.options); const { keys, records } = this._myIndex; const results = []; // List is Array records.forEach(({ $: item, i: idx }) => { if (!isDefined(item)) { return; } let matches = []; // Iterate over every key (i.e, path), and fetch the value at that key keys.forEach((key, keyIndex) => { matches.push(...this._findMatches({ key, value: item[keyIndex], searcher })); }); if (matches.length) { results.push({ idx, item, matches }); } }); return results; } _findMatches({ key, value, searcher }) { if (!isDefined(value)) { return []; } let matches = []; if (isArray(value)) { value.forEach(({ v: text, i: idx, n: norm }) => { if (!isDefined(text)) { return; } const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { matches.push({ score, key, value: text, idx, norm, indices }); } }); } else { const { v: text, n: norm } = value; const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { matches.push({ score, key, value: text, norm, indices }); } } return matches; } } Fuse.version = '7.0.0'; Fuse.createIndex = createIndex; Fuse.parseIndex = parseIndex; Fuse.config = Config; { Fuse.parseQuery = parse; } { register(ExtendedSearch); } var SearchByFuse = /** @class */ (function () { function SearchByFuse(config) { this._haystack = []; this._fuseOptions = __assign(__assign({}, config.fuseOptions), { keys: __spreadArray([], config.searchFields, true), includeMatches: true }); } SearchByFuse.prototype.index = function (data) { this._haystack = data; if (this._fuse) { this._fuse.setCollection(data); } }; SearchByFuse.prototype.reset = function () { this._haystack = []; this._fuse = undefined; }; SearchByFuse.prototype.isEmptyIndex = function () { return !this._haystack.length; }; SearchByFuse.prototype.search = function (needle) { if (!this._fuse) { { this._fuse = new Fuse(this._haystack, this._fuseOptions); } } var results = this._fuse.search(needle); return results.map(function (value, i) { return { item: value.item, score: value.score || 0, rank: i + 1, // If value.score is used for sorting, this can create non-stable sorts! }; }); }; return SearchByFuse; }()); function getSearcher(config) { { return new SearchByFuse(config); } } /** * Helpers to create HTML elements used by Choices * Can be overridden by providing `callbackOnCreateTemplates` option. * `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates` */ var isEmptyObject = function (obj) { // eslint-disable-next-line no-restricted-syntax for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { return false; } } return true; }; var assignCustomProperties = function (el, choice, withCustomProperties) { var dataset = el.dataset; var customProperties = choice.customProperties, labelClass = choice.labelClass, labelDescription = choice.labelDescription; if (labelClass) { dataset.labelClass = getClassNames(labelClass).join(' '); } if (labelDescription) { dataset.labelDescription = unwrapStringForRaw(labelDescription); } if (withCustomProperties && customProperties) { if (typeof customProperties === 'string') { dataset.customProperties = customProperties; } else if (typeof customProperties === 'object' && !isEmptyObject(customProperties)) { dataset.customProperties = JSON.stringify(customProperties); } } }; var addAriaLabel = function (docRoot, id, element) { var label = id && docRoot.querySelector("label[for='".concat(id, "']")); var text = label && label.innerText; if (text) { element.setAttribute('aria-label', text); } }; var templates = { containerOuter: function (_a, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType, labelId) { var containerOuter = _a.classNames.containerOuter; var div = document.createElement('div'); addClassesToElement(div, containerOuter); div.dataset.type = passedElementType; if (dir) { div.dir = dir; } if (isSelectOneElement) { div.tabIndex = 0; } if (isSelectElement) { div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox'); if (searchEnabled) { div.setAttribute('aria-autocomplete', 'list'); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, div); } div.setAttribute('aria-haspopup', 'true'); div.setAttribute('aria-expanded', 'false'); } if (labelId) { div.setAttribute('aria-labelledby', labelId); } return div; }, containerInner: function (_a) { var containerInner = _a.classNames.containerInner; var div = document.createElement('div'); addClassesToElement(div, containerInner); return div; }, itemList: function (_a, isSelectOneElement) { var searchEnabled = _a.searchEnabled, _b = _a.classNames, list = _b.list, listSingle = _b.listSingle, listItems = _b.listItems; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, isSelectOneElement ? listSingle : listItems); if (this._isSelectElement && searchEnabled) { div.setAttribute('role', 'listbox'); } return div; }, placeholder: function (_a, value) { var allowHTML = _a.allowHTML, placeholder = _a.classNames.placeholder; var div = document.createElement('div'); addClassesToElement(div, placeholder); setElementHtml(div, allowHTML, value); return div; }, item: function (_a, choice, removeItemButton) { var allowHTML = _a.allowHTML, removeItemButtonAlignLeft = _a.removeItemButtonAlignLeft, removeItemIconText = _a.removeItemIconText, removeItemLabelText = _a.removeItemLabelText, _b = _a.classNames, item = _b.item, button = _b.button, highlightedState = _b.highlightedState, itemSelectable = _b.itemSelectable, placeholder = _b.placeholder; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); addClassesToElement(div, item); if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, choice.label); addClassesToElement(spanLabel, choice.labelClass); div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, choice.label); } div.dataset.item = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; assignCustomProperties(div, choice, true); if (choice.disabled || this.containerOuter.isDisabled) { div.setAttribute('aria-disabled', 'true'); } if (this._isSelectElement) { div.setAttribute('aria-selected', 'true'); div.setAttribute('role', 'option'); } if (choice.placeholder) { addClassesToElement(div, placeholder); div.dataset.placeholder = ''; } addClassesToElement(div, choice.highlighted ? highlightedState : itemSelectable); if (removeItemButton) { if (choice.disabled) { removeClassesFromElement(div, itemSelectable); } div.dataset.deletable = ''; var removeButton = document.createElement('button'); removeButton.type = 'button'; addClassesToElement(removeButton, button); var eventChoice = getChoiceForOutput(choice); setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value, eventChoice)); var REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value, eventChoice); if (REMOVE_ITEM_LABEL) { removeButton.setAttribute('aria-label', REMOVE_ITEM_LABEL); } removeButton.dataset.button = ''; if (removeItemButtonAlignLeft) { div.insertAdjacentElement('afterbegin', removeButton); } else { div.appendChild(removeButton); } } return div; }, choiceList: function (_a, isSelectOneElement) { var list = _a.classNames.list; var div = document.createElement('div'); addClassesToElement(div, list); if (!isSelectOneElement) { div.setAttribute('aria-multiselectable', 'true'); } div.setAttribute('role', 'listbox'); return div; }, choiceGroup: function (_a, _b) { var allowHTML = _a.allowHTML, _c = _a.classNames, group = _c.group, groupHeading = _c.groupHeading, itemDisabled = _c.itemDisabled; var id = _b.id, label = _b.label, disabled = _b.disabled; var rawLabel = unwrapStringForRaw(label); var div = document.createElement('div'); addClassesToElement(div, group); if (disabled) { addClassesToElement(div, itemDisabled); } div.setAttribute('role', 'group'); div.dataset.group = ''; div.dataset.id = id; div.dataset.value = rawLabel; if (disabled) { div.setAttribute('aria-disabled', 'true'); } var heading = document.createElement('div'); addClassesToElement(heading, groupHeading); setElementHtml(heading, allowHTML, label || ''); div.appendChild(heading); return div; }, choice: function (_a, choice, selectText, groupName) { var allowHTML = _a.allowHTML, _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, itemSelectable = _b.itemSelectable, selectedState = _b.selectedState, itemDisabled = _b.itemDisabled, description = _b.description, placeholder = _b.placeholder; // eslint-disable-next-line prefer-destructuring var label = choice.label; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); div.id = choice.elementId; addClassesToElement(div, item); addClassesToElement(div, itemChoice); if (groupName && typeof label === 'string') { label = escapeForTemplate(allowHTML, label); label += " (".concat(groupName, ")"); label = { trusted: label }; } var describedBy = div; if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, label); addClassesToElement(spanLabel, choice.labelClass); describedBy = spanLabel; div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, label); } if (choice.labelDescription) { var descId = "".concat(choice.elementId, "-description"); describedBy.setAttribute('aria-describedby', descId); var spanDesc = document.createElement('span'); setElementHtml(spanDesc, allowHTML, choice.labelDescription); spanDesc.id = descId; addClassesToElement(spanDesc, description); div.appendChild(spanDesc); } if (choice.selected) { addClassesToElement(div, selectedState); } if (choice.placeholder) { addClassesToElement(div, placeholder); } div.setAttribute('role', choice.group ? 'treeitem' : 'option'); div.dataset.choice = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; if (selectText) { div.dataset.selectText = selectText; } if (choice.group) { div.dataset.groupId = "".concat(choice.group.id); } assignCustomProperties(div, choice, false); if (choice.disabled) { addClassesToElement(div, itemDisabled); div.dataset.choiceDisabled = ''; div.setAttribute('aria-disabled', 'true'); } else { addClassesToElement(div, itemSelectable); div.dataset.choiceSelectable = ''; div.setAttribute('aria-selected', choice.selected ? 'true' : 'false'); } return div; }, input: function (_a, placeholderValue) { var _b = _a.classNames, input = _b.input, inputCloned = _b.inputCloned, labelId = _a.labelId; var inp = document.createElement('input'); inp.type = 'search'; addClassesToElement(inp, input); addClassesToElement(inp, inputCloned); inp.autocomplete = 'off'; inp.autocapitalize = 'off'; inp.spellcheck = false; inp.setAttribute('aria-autocomplete', 'list'); if (placeholderValue) { inp.setAttribute('aria-label', placeholderValue); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, inp); } return inp; }, dropdown: function (_a) { var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, listDropdown); div.setAttribute('aria-expanded', 'false'); return div; }, notice: function (_a, innerHTML, type) { var _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, addChoice = _b.addChoice, noResults = _b.noResults, noChoices = _b.noChoices, noticeItem = _b.notice; if (type === void 0) { type = NoticeTypes.generic; } var notice = document.createElement('div'); setElementHtml(notice, true, innerHTML); addClassesToElement(notice, item); addClassesToElement(notice, itemChoice); addClassesToElement(notice, noticeItem); // eslint-disable-next-line default-case switch (type) { case NoticeTypes.addChoice: addClassesToElement(notice, addChoice); break; case NoticeTypes.noResults: addClassesToElement(notice, noResults); break; case NoticeTypes.noChoices: addClassesToElement(notice, noChoices); break; } if (type === NoticeTypes.addChoice) { notice.dataset.choiceSelectable = ''; notice.dataset.choice = ''; } return notice; }, option: function (choice) { // HtmlOptionElement's label value does not support HTML, so the avoid double escaping unwrap the untrusted string. var labelValue = unwrapStringForRaw(choice.label); var opt = new Option(labelValue, choice.value, false, choice.selected); assignCustomProperties(opt, choice, true); opt.disabled = choice.disabled; if (choice.selected) { opt.setAttribute('selected', ''); } return opt; }, }; /** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ var IS_IE11 = '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style; var USER_DEFAULTS = {}; var parseDataSetId = function (element) { if (!element) { return undefined; } return element.dataset.id ? parseInt(element.dataset.id, 10) : undefined; }; var selectableChoiceIdentifier = '[data-choice-selectable]'; /** * Choices * @author Josh Johnson */ var Choices = /** @class */ (function () { function Choices(element, userConfig) { if (element === void 0) { element = '[data-choice]'; } if (userConfig === void 0) { userConfig = {}; } var _this = this; this.initialisedOK = undefined; this._hasNonChoicePlaceholder = false; this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; var defaults = Choices.defaults; this.config = __assign(__assign(__assign({}, defaults.allOptions), defaults.options), userConfig); ObjectsInConfig.forEach(function (key) { _this.config[key] = __assign(__assign(__assign({}, defaults.allOptions[key]), defaults.options[key]), userConfig[key]); }); var config = this.config; if (!config.silent) { this._validateConfig(); } var docRoot = config.shadowRoot || document.documentElement; this._docRoot = docRoot; var passedElement = typeof element === 'string' ? docRoot.querySelector(element) : element; if (!passedElement || typeof passedElement !== 'object' || !(isHtmlInputElement(passedElement) || isHtmlSelectElement(passedElement))) { if (!passedElement && typeof element === 'string') { throw TypeError("Selector ".concat(element, " failed to find an element")); } throw TypeError("Expected one of the following types text|select-one|select-multiple"); } var elementType = passedElement.type; var isText = elementType === PassedElementTypes.Text; if (isText || config.maxItemCount !== 1) { config.singleModeForMultiSelect = false; } if (config.singleModeForMultiSelect) { elementType = PassedElementTypes.SelectMultiple; } var isSelectOne = elementType === PassedElementTypes.SelectOne; var isSelectMultiple = elementType === PassedElementTypes.SelectMultiple; var isSelect = isSelectOne || isSelectMultiple; this._elementType = elementType; this._isTextElement = isText; this._isSelectOneElement = isSelectOne; this._isSelectMultipleElement = isSelectMultiple; this._isSelectElement = isSelectOne || isSelectMultiple; this._canAddUserChoices = (isText && config.addItems) || (isSelect && config.addChoices); if (typeof config.renderSelectedChoices !== 'boolean') { config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne; } if (config.closeDropdownOnSelect === 'auto') { config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect; } else { config.closeDropdownOnSelect = coerceBool(config.closeDropdownOnSelect); } if (config.placeholder) { if (config.placeholderValue) { this._hasNonChoicePlaceholder = true; } else if (passedElement.dataset.placeholder) { this._hasNonChoicePlaceholder = true; config.placeholderValue = passedElement.dataset.placeholder; } } if (userConfig.addItemFilter && typeof userConfig.addItemFilter !== 'function') { var re = userConfig.addItemFilter instanceof RegExp ? userConfig.addItemFilter : new RegExp(userConfig.addItemFilter); config.addItemFilter = re.test.bind(re); } if (this._isTextElement) { this.passedElement = new WrappedInput({ element: passedElement, classNames: config.classNames, }); } else { var selectEl = passedElement; this.passedElement = new WrappedSelect({ element: selectEl, classNames: config.classNames, template: function (data) { return _this._templates.option(data); }, extractPlaceholder: config.placeholder && !this._hasNonChoicePlaceholder, }); } this.initialised = false; this._store = new Store(config); this._currentValue = ''; config.searchEnabled = !isText && config.searchEnabled; this._canSearch = config.searchEnabled; this._isScrollingOnIe = false; this._highlightPosition = 0; this._wasTap = true; this._placeholderValue = this._generatePlaceholderValue(); this._baseId = generateId(passedElement, 'choices-'); /** * setting direction in cases where it's explicitly set on passedElement * or when calculated direction is different from the document */ this._direction = passedElement.dir; if (!this._direction) { var elementDirection = window.getComputedStyle(passedElement).direction; var documentDirection = window.getComputedStyle(document.documentElement).direction; if (elementDirection !== documentDirection) { this._direction = elementDirection; } } this._idNames = { itemChoice: 'item-choice', }; this._templates = defaults.templates; this._render = this._render.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onKeyUp = this._onKeyUp.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onInput = this._onInput.bind(this); this._onClick = this._onClick.bind(this); this._onTouchMove = this._onTouchMove.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseOver = this._onMouseOver.bind(this); this._onFormReset = this._onFormReset.bind(this); this._onSelectKey = this._onSelectKey.bind(this); this._onEnterKey = this._onEnterKey.bind(this); this._onEscapeKey = this._onEscapeKey.bind(this); this._onDirectionKey = this._onDirectionKey.bind(this); this._onDeleteKey = this._onDeleteKey.bind(this); this._onChange = this._onChange.bind(this); this._onInvalid = this._onInvalid.bind(this); // If element has already been initialised with Choices, fail silently if (this.passedElement.isActive) { if (!config.silent) { console.warn('Trying to initialise Choices on element already initialised', { element: element }); } this.initialised = true; this.initialisedOK = false; return; } // Let's go this.init(); // preserve the selected item list after setup for form reset this._initialItems = this._store.items.map(function (choice) { return choice.value; }); } Object.defineProperty(Choices, "defaults", { get: function () { return Object.preventExtensions({ get options() { return USER_DEFAULTS; }, get allOptions() { return DEFAULT_CONFIG; }, get templates() { return templates; }, }); }, enumerable: false, configurable: true }); Choices.prototype.init = function () { if (this.initialised || this.initialisedOK !== undefined) { return; } this._searcher = getSearcher(this.config); this._loadChoices(); this._createTemplates(); this._createElements(); this._createStructure(); if ((this._isTextElement && !this.config.addItems) || this.passedElement.element.hasAttribute('disabled') || !!this.passedElement.element.closest('fieldset:disabled')) { this.disable(); } else { this.enable(); this._addEventListeners(); } // should be triggered **after** disabled state to avoid additional re-draws this._initStore(); this.initialised = true; this.initialisedOK = true; var callbackOnInit = this.config.callbackOnInit; // Run callback if it is a function if (typeof callbackOnInit === 'function') { callbackOnInit.call(this); } }; Choices.prototype.destroy = function () { if (!this.initialised) { return; } this._removeEventListeners(); this.passedElement.reveal(); this.containerOuter.unwrap(this.passedElement.element); this._store._listeners = []; // prevents select/input value being wiped this.clearStore(false); this._stopSearch(); this._templates = Choices.defaults.templates; this.initialised = false; this.initialisedOK = undefined; }; Choices.prototype.enable = function () { if (this.passedElement.isDisabled) { this.passedElement.enable(); } if (this.containerOuter.isDisabled) { this._addEventListeners(); this.input.enable(); this.containerOuter.enable(); } return this; }; Choices.prototype.disable = function () { if (!this.passedElement.isDisabled) { this.passedElement.disable(); } if (!this.containerOuter.isDisabled) { this._removeEventListeners(); this.input.disable(); this.containerOuter.disable(); } return this; }; Choices.prototype.highlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, true)); if (runEvent) { this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.unhighlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || !choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, false)); if (runEvent) { this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.highlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (!item.highlighted) { _this._store.dispatch(highlightItem(item, true)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.unhighlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (item.highlighted) { _this._store.dispatch(highlightItem(item, false)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.removeActiveItemsByValue = function (value) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (item) { return item.value === value; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeActiveItems = function (excludedId) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (_a) { var id = _a.id; return id !== excludedId; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeHighlightedItems = function (runEvent) { var _this = this; if (runEvent === void 0) { runEvent = false; } this._store.withTxn(function () { _this._store.highlightedActiveItems.forEach(function (item) { _this._removeItem(item); // If this action was performed by the user // trigger the event if (runEvent) { _this._triggerChange(item.value); } }); }); return this; }; Choices.prototype.showDropdown = function (preventInputFocus) { var _this = this; if (this.dropdown.isActive) { return this; } if (preventInputFocus === undefined) { // eslint-disable-next-line no-param-reassign preventInputFocus = !this._canSearch; } requestAnimationFrame(function () { _this.dropdown.show(); var rect = _this.dropdown.element.getBoundingClientRect(); _this.containerOuter.open(rect.bottom, rect.height); if (!preventInputFocus) { _this.input.focus(); } _this.passedElement.triggerEvent(EventType.showDropdown); var activeElement = _this.choiceList.element.querySelector(getClassNamesSelector(_this.config.classNames.selectedState)); if (activeElement !== null && !isScrolledIntoView(activeElement, _this.choiceList.element)) { // We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll. activeElement.scrollIntoView(); } }); return this; }; Choices.prototype.hideDropdown = function (preventInputBlur) { var _this = this; if (!this.dropdown.isActive) { return this; } this._removeHighlightedChoices(); requestAnimationFrame(function () { _this.dropdown.hide(); _this.containerOuter.close(); if (!preventInputBlur && _this._canSearch) { _this.input.removeActiveDescendant(); _this.input.blur(); } _this.passedElement.triggerEvent(EventType.hideDropdown); }); return this; }; Choices.prototype.getValue = function (valueOnly) { var values = this._store.items.map(function (item) { return (valueOnly ? item.value : getChoiceForOutput(item)); }); return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values; }; Choices.prototype.setValue = function (items) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setValue'); return this; } this._store.withTxn(function () { items.forEach(function (value) { if (value) { _this._addChoice(mapInputToChoice(value, false)); } }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.setChoiceByValue = function (value) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoiceByValue'); return this; } if (this._isTextElement) { return this; } this._store.withTxn(function () { // If only one value has been passed, convert to array var choiceValue = Array.isArray(value) ? value : [value]; // Loop through each value and choiceValue.forEach(function (val) { return _this._findAndSelectChoiceByValue(val); }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ Choices.prototype.setChoices = function (choicesArrayOrFetcher, value, label, replaceChoices, clearSearchFlag, replaceItems) { var _this = this; if (choicesArrayOrFetcher === void 0) { choicesArrayOrFetcher = []; } if (value === void 0) { value = 'value'; } if (label === void 0) { label = 'label'; } if (replaceChoices === void 0) { replaceChoices = false; } if (clearSearchFlag === void 0) { clearSearchFlag = true; } if (replaceItems === void 0) { replaceItems = false; } if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoices'); return this; } if (!this._isSelectElement) { throw new TypeError("setChoices can't be used with INPUT based Choices"); } if (typeof value !== 'string' || !value) { throw new TypeError("value parameter must be a name of 'value' field in passed objects"); } if (typeof choicesArrayOrFetcher === 'function') { // it's a choices fetcher function var fetcher_1 = choicesArrayOrFetcher(this); if (typeof Promise === 'function' && fetcher_1 instanceof Promise) { // that's a promise // eslint-disable-next-line no-promise-executor-return return new Promise(function (resolve) { return requestAnimationFrame(resolve); }) .then(function () { return _this._handleLoadingState(true); }) .then(function () { return fetcher_1; }) .then(function (data) { return _this.setChoices(data, value, label, replaceChoices, clearSearchFlag, replaceItems); }) .catch(function (err) { if (!_this.config.silent) { console.error(err); } }) .then(function () { return _this._handleLoadingState(false); }) .then(function () { return _this; }); } // function returned something else than promise, let's check if it's an array of choices if (!Array.isArray(fetcher_1)) { throw new TypeError(".setChoices first argument function must return either array of choices or Promise, got: ".concat(typeof fetcher_1)); } // recursion with results, it's sync and choices were cleared already return this.setChoices(fetcher_1, value, label, false); } if (!Array.isArray(choicesArrayOrFetcher)) { throw new TypeError(".setChoices must be called either with array of choices with a function resulting into Promise of array of choices"); } this.containerOuter.removeLoadingState(); this._store.withTxn(function () { if (clearSearchFlag) { _this._isSearching = false; } // Clear choices if needed if (replaceChoices) { _this.clearChoices(true, replaceItems); } var isDefaultValue = value === 'value'; var isDefaultLabel = label === 'label'; choicesArrayOrFetcher.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { var group = groupOrChoice; if (!isDefaultLabel) { group = __assign(__assign({}, group), { label: group[label] }); } _this._addGroup(mapInputToChoice(group, true)); } else { var choice = groupOrChoice; if (!isDefaultLabel || !isDefaultValue) { choice = __assign(__assign({}, choice), { value: choice[value], label: choice[label] }); } var choiceFull = mapInputToChoice(choice, false); _this._addChoice(choiceFull); if (choiceFull.placeholder && !_this._hasNonChoicePlaceholder) { _this._placeholderValue = unwrapStringForEscaped(choiceFull.label); } } }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.refresh = function (withEvents, selectFirstOption, deselectAll) { var _this = this; if (withEvents === void 0) { withEvents = false; } if (selectFirstOption === void 0) { selectFirstOption = false; } if (deselectAll === void 0) { deselectAll = false; } if (!this._isSelectElement) { if (!this.config.silent) { console.warn('refresh method can only be used on choices backed by a element'); } return this; } this._store.withTxn(function () { var choicesFromOptions = _this.passedElement.optionsAsChoices(); // Build the list of items which require preserving var existingItems = {}; if (!deselectAll) { _this._store.items.forEach(function (choice) { if (choice.id && choice.active && choice.selected) { existingItems[choice.value] = true; } }); } _this.clearStore(false); var updateChoice = function (choice) { if (deselectAll) { _this._store.dispatch(removeItem$1(choice)); } else if (existingItems[choice.value]) { choice.selected = true; } }; choicesFromOptions.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { groupOrChoice.choices.forEach(updateChoice); return; } updateChoice(groupOrChoice); }); /* @todo only generate add events for the added options instead of all if (withEvents) { items.forEach((choice) => { if (existingItems[choice.value]) { this.passedElement.triggerEvent( EventType.removeItem, this._getChoiceForEvent(choice), ); } }); } */ // load new choices & items _this._addPredefinedChoices(choicesFromOptions, selectFirstOption, withEvents); // re-do search if required if (_this._isSearching) { _this._searchChoices(_this.input.value); } }); return this; }; Choices.prototype.removeChoice = function (value) { var choice = this._store.choices.find(function (c) { return c.value === value; }); if (!choice) { return this; } this._clearNotice(); this._store.dispatch(removeChoice(choice)); // @todo integrate with Store this._searcher.reset(); if (choice.selected) { this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.clearChoices = function (clearOptions, clearItems) { var _this = this; if (clearOptions === void 0) { clearOptions = true; } if (clearItems === void 0) { clearItems = false; } if (clearOptions) { if (clearItems) { this.passedElement.element.replaceChildren(''); } else { this.passedElement.element.querySelectorAll(':not([selected])').forEach(function (el) { el.remove(); }); } } this.itemList.element.replaceChildren(''); this.choiceList.element.replaceChildren(''); this._clearNotice(); this._store.withTxn(function () { var items = clearItems ? [] : _this._store.items; _this._store.reset(); items.forEach(function (item) { _this._store.dispatch(addChoice(item)); _this._store.dispatch(addItem(item)); }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.clearStore = function (clearOptions) { if (clearOptions === void 0) { clearOptions = true; } this.clearChoices(clearOptions, true); this._stopSearch(); this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; return this; }; Choices.prototype.clearInput = function () { var shouldSetInputWidth = !this._isSelectOneElement; this.input.clear(shouldSetInputWidth); this._stopSearch(); return this; }; Choices.prototype._validateConfig = function () { var config = this.config; var invalidConfigOptions = diff(config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); } if (config.allowHTML && config.allowHtmlUserInput) { if (config.addItems) { console.warn('Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks'); } if (config.addChoices) { console.warn('Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks'); } } }; Choices.prototype._render = function (changes) { if (changes === void 0) { changes = { choices: true, groups: true, items: true }; } if (this._store.inTxn()) { return; } if (this._isSelectElement) { if (changes.choices || changes.groups) { this._renderChoices(); } } if (changes.items) { this._renderItems(); } }; Choices.prototype._renderChoices = function () { var _this = this; if (!this._canAddItems()) { return; // block rendering choices if the input limit is reached. } var _a = this, config = _a.config, isSearching = _a._isSearching; var _b = this._store, activeGroups = _b.activeGroups, activeChoices = _b.activeChoices; var renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit; if (this._isSelectElement) { var backingOptions = activeChoices.filter(function (choice) { return !choice.element; }); if (backingOptions.length) { this.passedElement.addOptions(backingOptions); } } var fragment = document.createDocumentFragment(); var renderableChoices = function (choices) { return choices.filter(function (choice) { return !choice.placeholder && (isSearching ? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank : config.renderSelectedChoices || !choice.selected); }); }; var showLabel = config.appendGroupInSearch && isSearching; var selectableChoices = false; var highlightedEl = null; var renderChoices = function (choices, withinGroup) { if (isSearching) { // sortByRank is used to ensure stable sorting, as scores are non-unique // this additionally ensures fuseOptions.sortFn is not ignored choices.sort(sortByRank); } else if (config.shouldSort) { choices.sort(config.sorter); } var choiceLimit = choices.length; choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit; choiceLimit--; choices.every(function (choice, index) { // choiceEl being empty signals the contents has probably significantly changed var dropdownItem = choice.choiceEl || _this._templates.choice(config, choice, config.itemSelectText, showLabel && choice.group ? choice.group.label : undefined); choice.choiceEl = dropdownItem; fragment.appendChild(dropdownItem); if (isSearching || !choice.selected) { selectableChoices = true; } else if (!highlightedEl) { highlightedEl = dropdownItem; } return index < choiceLimit; }); }; if (activeChoices.length) { if (config.resetScrollPosition) { requestAnimationFrame(function () { return _this.choiceList.scrollToTop(); }); } if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) { // If we have a placeholder choice along with groups renderChoices(activeChoices.filter(function (choice) { return choice.placeholder && !choice.group; }), false); } // If we have grouped options if (activeGroups.length && !isSearching) { if (config.shouldSort) { activeGroups.sort(config.sorter); } // render Choices without group first, regardless of sort, otherwise they won't be distinguishable // from the last group renderChoices(activeChoices.filter(function (choice) { return !choice.placeholder && !choice.group; }), false); activeGroups.forEach(function (group) { var groupChoices = renderableChoices(group.choices); if (groupChoices.length) { if (group.label) { var dropdownGroup = group.groupEl || _this._templates.choiceGroup(_this.config, group); group.groupEl = dropdownGroup; dropdownGroup.remove(); fragment.appendChild(dropdownGroup); } renderChoices(groupChoices, true); } }); } else { renderChoices(renderableChoices(activeChoices), false); } } if (!selectableChoices && (isSearching || !fragment.children.length || !config.renderSelectedChoices)) { if (!this._notice) { this._notice = { text: resolveStringFunction(isSearching ? config.noResultsText : config.noChoicesText), type: isSearching ? NoticeTypes.noResults : NoticeTypes.noChoices, }; } fragment.replaceChildren(''); } this._renderNotice(fragment); this.choiceList.element.replaceChildren(fragment); this._highlightChoice(highlightedEl); }; Choices.prototype._renderItems = function () { var _this = this; var items = this._store.items || []; var itemList = this.itemList.element; var config = this.config; var fragment = document.createDocumentFragment(); var itemFromList = function (item) { return itemList.querySelector("[data-item][data-id=\"".concat(item.id, "\"]")); }; var addItemToFragment = function (item) { var el = item.itemEl; if (el && el.parentElement) { return; } el = itemFromList(item) || _this._templates.item(config, item, config.removeItemButton); item.itemEl = el; fragment.appendChild(el); }; // new items items.forEach(addItemToFragment); var addedItems = !!fragment.childNodes.length; if (this._isSelectOneElement) { var existingItems = itemList.children.length; if (addedItems || existingItems > 1) { var placeholder = itemList.querySelector(getClassNamesSelector(config.classNames.placeholder)); if (placeholder) { placeholder.remove(); } } else if (!addedItems && !existingItems && this._placeholderValue) { addedItems = true; addItemToFragment(mapInputToChoice({ selected: true, value: '', label: this._placeholderValue, placeholder: true, }, false)); } } if (addedItems) { itemList.append(fragment); if (config.shouldSortItems && !this._isSelectOneElement) { items.sort(config.sorter); // push sorting into the DOM items.forEach(function (item) { var el = itemFromList(item); if (el) { el.remove(); fragment.append(el); } }); itemList.append(fragment); } } if (this._isTextElement) { // Update the value of the hidden input this.passedElement.value = items.map(function (_a) { var value = _a.value; return value; }).join(config.delimiter); } }; Choices.prototype._displayNotice = function (text, type, openDropdown) { if (openDropdown === void 0) { openDropdown = true; } var oldNotice = this._notice; if (oldNotice && ((oldNotice.type === type && oldNotice.text === text) || (oldNotice.type === NoticeTypes.addChoice && (type === NoticeTypes.noResults || type === NoticeTypes.noChoices)))) { if (openDropdown) { this.showDropdown(true); } return; } this._clearNotice(); this._notice = text ? { text: text, type: type, } : undefined; this._renderNotice(); if (openDropdown && text) { this.showDropdown(true); } }; Choices.prototype._clearNotice = function () { if (!this._notice) { return; } var noticeElement = this.choiceList.element.querySelector(getClassNamesSelector(this.config.classNames.notice)); if (noticeElement) { noticeElement.remove(); } this._notice = undefined; }; Choices.prototype._renderNotice = function (fragment) { var noticeConf = this._notice; if (noticeConf) { var notice = this._templates.notice(this.config, noticeConf.text, noticeConf.type); if (fragment) { fragment.append(notice); } else { this.choiceList.prepend(notice); } } }; /** * @deprecated Use utils.getChoiceForOutput */ // eslint-disable-next-line class-methods-use-this Choices.prototype._getChoiceForOutput = function (choice, keyCode) { return getChoiceForOutput(choice, keyCode); }; Choices.prototype._triggerChange = function (value) { if (value === undefined || value === null) { return; } this.passedElement.triggerEvent(EventType.change, { value: value, }); }; Choices.prototype._handleButtonAction = function (element) { var _this = this; var items = this._store.items; if (!items.length || !this.config.removeItems || !this.config.removeItemButton) { return; } var id = element && parseDataSetId(element.closest('[data-id]')); var itemToRemove = id && items.find(function (item) { return item.id === id; }); if (!itemToRemove) { return; } this._store.withTxn(function () { // Remove item associated with button _this._removeItem(itemToRemove); _this._triggerChange(itemToRemove.value); if (_this._isSelectOneElement && !_this._hasNonChoicePlaceholder) { var placeholderChoice = (_this.config.shouldSort ? _this._store.choices.reverse() : _this._store.choices).find(function (choice) { return choice.placeholder; }); if (placeholderChoice) { _this._addItem(placeholderChoice); _this.unhighlightAll(); if (placeholderChoice.value) { _this._triggerChange(placeholderChoice.value); } } } }); }; Choices.prototype._handleItemAction = function (element, hasShiftKey) { var _this = this; if (hasShiftKey === void 0) { hasShiftKey = false; } var items = this._store.items; if (!items.length || !this.config.removeItems || this._isSelectOneElement) { return; } var id = parseDataSetId(element); if (!id) { return; } // We only want to select one item with a click // so we deselect any items that aren't the target // unless shift is being pressed items.forEach(function (item) { if (item.id === id && !item.highlighted) { _this.highlightItem(item); } else if (!hasShiftKey && item.highlighted) { _this.unhighlightItem(item); } }); // Focus input as without focus, a user cannot do anything with a // highlighted item this.input.focus(); }; Choices.prototype._handleChoiceAction = function (element) { var _this = this; // If we are clicking on an option var id = parseDataSetId(element); var choice = id && this._store.getChoiceById(id); if (!choice || choice.disabled) { return false; } var hasActiveDropdown = this.dropdown.isActive; if (!choice.selected) { if (!this._canAddItems()) { return true; // causes _onEnterKey to early out } this._store.withTxn(function () { _this._addItem(choice, true, true); _this.clearInput(); _this.unhighlightAll(); }); this._triggerChange(choice.value); } // We want to close the dropdown if we are dealing with a single select box if (hasActiveDropdown && this.config.closeDropdownOnSelect) { this.hideDropdown(true); this.containerOuter.element.focus(); } return true; }; Choices.prototype._handleBackspace = function (items) { var config = this.config; if (!config.removeItems || !items.length) { return; } var lastItem = items[items.length - 1]; var hasHighlightedItems = items.some(function (item) { return item.highlighted; }); // If editing the last item is allowed and there are not other selected items, // we can edit the item value. Otherwise if we can remove items, remove all selected items if (config.editItems && !hasHighlightedItems && lastItem) { this.input.value = lastItem.value; this.input.setWidth(); this._removeItem(lastItem); this._triggerChange(lastItem.value); } else { if (!hasHighlightedItems) { // Highlight last item if none already highlighted this.highlightItem(lastItem, false); } this.removeHighlightedItems(true); } }; Choices.prototype._loadChoices = function () { var _a; var _this = this; var config = this.config; if (this._isTextElement) { // Assign preset items from passed object first this._presetChoices = config.items.map(function (e) { return mapInputToChoice(e, false); }); // Add any values passed from attribute if (this.passedElement.value) { var elementItems = this.passedElement.value .split(config.delimiter) .map(function (e) { return mapInputToChoice(e, false, _this.config.allowHtmlUserInput); }); this._presetChoices = this._presetChoices.concat(elementItems); } this._presetChoices.forEach(function (choice) { choice.selected = true; }); } else if (this._isSelectElement) { // Assign preset choices from passed object this._presetChoices = config.choices.map(function (e) { return mapInputToChoice(e, true); }); // Create array of choices from option elements var choicesFromOptions = this.passedElement.optionsAsChoices(); if (choicesFromOptions) { (_a = this._presetChoices).push.apply(_a, choicesFromOptions); } } }; Choices.prototype._handleLoadingState = function (setLoading) { if (setLoading === void 0) { setLoading = true; } var el = this.itemList.element; if (setLoading) { this.disable(); this.containerOuter.addLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(this._templates.placeholder(this.config, this.config.loadingText)); } else { this.input.placeholder = this.config.loadingText; } } else { this.enable(); this.containerOuter.removeLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(''); this._render(); } else { this.input.placeholder = this._placeholderValue || ''; } } }; Choices.prototype._handleSearch = function (value) { if (!this.input.isFocussed) { return; } // Check that we have a value to search and the input was an alphanumeric character if (value !== null && typeof value !== 'undefined' && value.length >= this.config.searchFloor) { var resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; if (resultCount !== null) { // Trigger search event this.passedElement.triggerEvent(EventType.search, { value: value, resultCount: resultCount, }); } } else if (this._store.choices.some(function (option) { return !option.active; })) { this._stopSearch(); } }; Choices.prototype._canAddItems = function () { var config = this.config; var maxItemCount = config.maxItemCount, maxItemText = config.maxItemText; if (!config.singleModeForMultiSelect && maxItemCount > 0 && maxItemCount <= this._store.items.length) { this.choiceList.element.replaceChildren(''); this._notice = undefined; this._displayNotice(typeof maxItemText === 'function' ? maxItemText(maxItemCount) : maxItemText, NoticeTypes.addChoice); return false; } if (this._notice && this._notice.type === NoticeTypes.addChoice) { this._clearNotice(); } return true; }; Choices.prototype._canCreateItem = function (value) { var config = this.config; var canAddItem = true; var notice = ''; if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) { canAddItem = false; notice = resolveNoticeFunction(config.customAddItemText, value, undefined); } if (canAddItem) { var foundChoice = this._store.choices.find(function (choice) { return config.valueComparer(choice.value, value); }); if (foundChoice) { if (this._isSelectElement) { // for exact matches, do not prompt to add it as a custom choice this._displayNotice('', NoticeTypes.addChoice); return false; } if (!config.duplicateItemsAllowed) { canAddItem = false; notice = resolveNoticeFunction(config.uniqueItemText, value, undefined); } } } if (canAddItem) { notice = resolveNoticeFunction(config.addItemText, value, undefined); } if (notice) { this._displayNotice(notice, NoticeTypes.addChoice); } return canAddItem; }; Choices.prototype._searchChoices = function (value) { var newValue = value.trim().replace(/\s{2,}/, ' '); // signal input didn't change search if (!newValue.length || newValue === this._currentValue) { return null; } var searcher = this._searcher; if (searcher.isEmptyIndex()) { searcher.index(this._store.searchableChoices); } // If new value matches the desired length and is not the same as the current value with a space var results = searcher.search(newValue); this._currentValue = newValue; this._highlightPosition = 0; this._isSearching = true; var notice = this._notice; var noticeType = notice && notice.type; if (noticeType !== NoticeTypes.addChoice) { if (!results.length) { this._displayNotice(resolveStringFunction(this.config.noResultsText), NoticeTypes.noResults); } else { this._clearNotice(); } } this._store.dispatch(filterChoices(results)); return results.length; }; Choices.prototype._stopSearch = function () { if (this._isSearching) { this._currentValue = ''; this._isSearching = false; this._clearNotice(); this._store.dispatch(activateChoices(true)); this.passedElement.triggerEvent(EventType.search, { value: '', resultCount: 0, }); } }; Choices.prototype._addEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; // capture events - can cancel event processing or propagation documentElement.addEventListener('touchend', this._onTouchEnd, true); outerElement.addEventListener('keydown', this._onKeyDown, true); outerElement.addEventListener('mousedown', this._onMouseDown, true); // passive events - doesn't call `preventDefault` or `stopPropagation` documentElement.addEventListener('click', this._onClick, { passive: true }); documentElement.addEventListener('touchmove', this._onTouchMove, { passive: true, }); this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { passive: true, }); if (this._isSelectOneElement) { outerElement.addEventListener('focus', this._onFocus, { passive: true, }); outerElement.addEventListener('blur', this._onBlur, { passive: true, }); } inputElement.addEventListener('keyup', this._onKeyUp, { passive: true, }); inputElement.addEventListener('input', this._onInput, { passive: true, }); inputElement.addEventListener('focus', this._onFocus, { passive: true, }); inputElement.addEventListener('blur', this._onBlur, { passive: true, }); if (inputElement.form) { inputElement.form.addEventListener('reset', this._onFormReset, { passive: true, }); } if (passedElement.hasAttribute('required')) { passedElement.addEventListener('change', this._onChange, { passive: true, }); passedElement.addEventListener('invalid', this._onInvalid, { passive: true, }); } this.input.addEventListeners(); }; Choices.prototype._removeEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; documentElement.removeEventListener('touchend', this._onTouchEnd, true); outerElement.removeEventListener('keydown', this._onKeyDown, true); outerElement.removeEventListener('mousedown', this._onMouseDown, true); documentElement.removeEventListener('click', this._onClick); documentElement.removeEventListener('touchmove', this._onTouchMove); this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); if (this._isSelectOneElement) { outerElement.removeEventListener('focus', this._onFocus); outerElement.removeEventListener('blur', this._onBlur); } inputElement.removeEventListener('keyup', this._onKeyUp); inputElement.removeEventListener('input', this._onInput); inputElement.removeEventListener('focus', this._onFocus); inputElement.removeEventListener('blur', this._onBlur); if (inputElement.form) { inputElement.form.removeEventListener('reset', this._onFormReset); } if (passedElement.hasAttribute('required')) { passedElement.removeEventListener('change', this._onChange); passedElement.removeEventListener('invalid', this._onInvalid); } this.input.removeEventListeners(); }; Choices.prototype._onKeyDown = function (event) { var keyCode = event.keyCode; var hasActiveDropdown = this.dropdown.isActive; /* See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs https://stackoverflow.com/a/70866532 - "Unidentified" for mobile http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7) Logic: when a key event is sent, `event.key` represents its printable value _or_ one of a large list of special values indicating meta keys/functionality. In addition, key events for compose functionality contain a value of `Dead` when mid-composition. I can't quite verify it, but non-English IMEs may also be able to generate key codes for code points in the surrogate-pair range, which could potentially be seen as having key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that alone. Here, key.length === 1 means we know for sure the input was printable and not a special `key` value. When the length is greater than 1, it could be either a printable surrogate pair or a special `key` value. We can tell the difference by checking if the _character code_ value (not code point!) is in the "surrogate pair" range or not. We don't use .codePointAt because an invalid code point would return 65535, which wouldn't pass the >= 0x10000 check we would otherwise use. > ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points > of each plane are noncharacters: U+FFFE and U+FFFF on the BMP... */ var wasPrintableChar = event.key.length === 1 || (event.key.length === 2 && event.key.charCodeAt(0) >= 0xd800) || event.key === 'Unidentified'; /* We do not show the dropdown if focusing out with esc or navigating through input fields. An activated search can still be opened with any other key. */ if (!this._isTextElement && !hasActiveDropdown && keyCode !== KeyCodeMap.ESC_KEY && keyCode !== KeyCodeMap.TAB_KEY && keyCode !== KeyCodeMap.SHIFT_KEY) { this.showDropdown(); if (!this.input.isFocussed && wasPrintableChar) { /* We update the input value with the pressed key as the input was not focussed at the time of key press therefore does not have the value of the key. */ this.input.value += event.key; // browsers interpret a space as pagedown if (event.key === ' ') { event.preventDefault(); } } } switch (keyCode) { case KeyCodeMap.A_KEY: return this._onSelectKey(event, this.itemList.element.hasChildNodes()); case KeyCodeMap.ENTER_KEY: return this._onEnterKey(event, hasActiveDropdown); case KeyCodeMap.ESC_KEY: return this._onEscapeKey(event, hasActiveDropdown); case KeyCodeMap.UP_KEY: case KeyCodeMap.PAGE_UP_KEY: case KeyCodeMap.DOWN_KEY: case KeyCodeMap.PAGE_DOWN_KEY: return this._onDirectionKey(event, hasActiveDropdown); case KeyCodeMap.DELETE_KEY: case KeyCodeMap.BACK_KEY: return this._onDeleteKey(event, this._store.items, this.input.isFocussed); } }; Choices.prototype._onKeyUp = function ( /* event: KeyboardEvent */) { this._canSearch = this.config.searchEnabled; }; Choices.prototype._onInput = function ( /* event: InputEvent */) { var value = this.input.value; if (!value) { if (this._isTextElement) { this.hideDropdown(true); } else { this._stopSearch(); } return; } if (!this._canAddItems()) { return; } if (this._canSearch) { // do the search even if the entered text can not be added this._handleSearch(value); } if (!this._canAddUserChoices) { return; } // determine if a notice needs to be displayed for why a search result can't be added this._canCreateItem(value); if (this._isSelectElement) { this._highlightPosition = 0; // reset to select the notice and/or exact match this._highlightChoice(); } }; Choices.prototype._onSelectKey = function (event, hasItems) { // If CTRL + A or CMD + A have been pressed and there are items to select if ((event.ctrlKey || event.metaKey) && hasItems) { this._canSearch = false; var shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; if (shouldHightlightAll) { this.highlightAll(); } } }; Choices.prototype._onEnterKey = function (event, hasActiveDropdown) { var _this = this; var value = this.input.value; var target = event.target; event.preventDefault(); if (target && target.hasAttribute('data-button')) { this._handleButtonAction(target); return; } if (!hasActiveDropdown) { if (this._isSelectElement || this._notice) { this.showDropdown(); } return; } var highlightedChoice = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (highlightedChoice && this._handleChoiceAction(highlightedChoice)) { return; } if (!target || !value) { this.hideDropdown(true); return; } if (!this._canAddItems()) { return; } var addedItem = false; this._store.withTxn(function () { addedItem = _this._findAndSelectChoiceByValue(value, true); if (!addedItem) { if (!_this._canAddUserChoices) { return; } if (!_this._canCreateItem(value)) { return; } _this._addChoice(mapInputToChoice(value, false, _this.config.allowHtmlUserInput), true, true); addedItem = true; } _this.clearInput(); _this.unhighlightAll(); }); if (!addedItem) { return; } this._triggerChange(value); if (this.config.closeDropdownOnSelect) { this.hideDropdown(true); } }; Choices.prototype._onEscapeKey = function (event, hasActiveDropdown) { if (hasActiveDropdown) { event.stopPropagation(); this.hideDropdown(true); this._stopSearch(); this.containerOuter.element.focus(); } }; Choices.prototype._onDirectionKey = function (event, hasActiveDropdown) { var keyCode = event.keyCode; // If up or down key is pressed, traverse through options if (hasActiveDropdown || this._isSelectOneElement) { this.showDropdown(); this._canSearch = false; var directionInt = keyCode === KeyCodeMap.DOWN_KEY || keyCode === KeyCodeMap.PAGE_DOWN_KEY ? 1 : -1; var skipKey = event.metaKey || keyCode === KeyCodeMap.PAGE_DOWN_KEY || keyCode === KeyCodeMap.PAGE_UP_KEY; var nextEl = void 0; if (skipKey) { if (directionInt > 0) { nextEl = this.dropdown.element.querySelector("".concat(selectableChoiceIdentifier, ":last-of-type")); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } else { var currentEl = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (currentEl) { nextEl = getAdjacentEl(currentEl, selectableChoiceIdentifier, directionInt); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } if (nextEl) { // We prevent default to stop the cursor moving // when pressing the arrow if (!isScrolledIntoView(nextEl, this.choiceList.element, directionInt)) { this.choiceList.scrollToChildElement(nextEl, directionInt); } this._highlightChoice(nextEl); } // Prevent default to maintain cursor position whilst // traversing dropdown options event.preventDefault(); } }; Choices.prototype._onDeleteKey = function (event, items, hasFocusedInput) { // If backspace or delete key is pressed and the input has no value if (!this._isSelectOneElement && !event.target.value && hasFocusedInput) { this._handleBackspace(items); event.preventDefault(); } }; Choices.prototype._onTouchMove = function () { if (this._wasTap) { this._wasTap = false; } }; Choices.prototype._onTouchEnd = function (event) { var target = (event || event.touches[0]).target; var touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target); if (touchWasWithinContainer) { var containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; if (containerWasExactTarget) { if (this._isTextElement) { this.input.focus(); } else if (this._isSelectMultipleElement) { this.showDropdown(); } } // Prevents focus event firing event.stopPropagation(); } this._wasTap = true; }; /** * Handles mousedown event in capture mode for containetOuter.element */ Choices.prototype._onMouseDown = function (event) { var target = event.target; if (!(target instanceof Element)) { return; } // If we have our mouse down on the scrollbar and are on IE11... if (IS_IE11 && this.choiceList.element.contains(target)) { // check if click was on a scrollbar area var firstChoice = this.choiceList.element.firstElementChild; this._isScrollingOnIe = this._direction === 'ltr' ? event.offsetX >= firstChoice.offsetWidth : event.offsetX < firstChoice.offsetLeft; } if (target === this.input.element) { return; } var item = target.closest('[data-button],[data-item],[data-choice]'); if (item instanceof HTMLElement) { if ('button' in item.dataset) { this._handleButtonAction(item); } else if ('item' in item.dataset) { this._handleItemAction(item, event.shiftKey); } else if ('choice' in item.dataset) { this._handleChoiceAction(item); } } event.preventDefault(); }; /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ Choices.prototype._onMouseOver = function (_a) { var target = _a.target; if (target instanceof HTMLElement && 'choice' in target.dataset) { this._highlightChoice(target); } }; Choices.prototype._onClick = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var clickWasWithinContainer = containerOuter.element.contains(target); if (clickWasWithinContainer) { if (!this.dropdown.isActive && !containerOuter.isDisabled) { if (this._isTextElement) { if (document.activeElement !== this.input.element) { this.input.focus(); } } else { this.showDropdown(); containerOuter.element.focus(); } } else if (this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target)) { this.hideDropdown(); } } else { containerOuter.removeFocusState(); this.hideDropdown(true); this.unhighlightAll(); } }; Choices.prototype._onFocus = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var focusWasWithinContainer = target && containerOuter.element.contains(target); if (!focusWasWithinContainer) { return; } var targetIsInput = target === this.input.element; if (this._isTextElement) { if (targetIsInput) { containerOuter.addFocusState(); } } else if (this._isSelectMultipleElement) { if (targetIsInput) { this.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown containerOuter.addFocusState(); } } else { containerOuter.addFocusState(); if (targetIsInput) { this.showDropdown(true); } } }; Choices.prototype._onBlur = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var blurWasWithinContainer = target && containerOuter.element.contains(target); if (blurWasWithinContainer && !this._isScrollingOnIe) { if (target === this.input.element) { containerOuter.removeFocusState(); this.hideDropdown(true); if (this._isTextElement || this._isSelectMultipleElement) { this.unhighlightAll(); } } else if (target === this.containerOuter.element) { // Remove the focus state when the past outerContainer was the target containerOuter.removeFocusState(); // Also close the dropdown if search is disabled if (!this.config.searchEnabled) { this.hideDropdown(true); } } } else { // On IE11, clicking the scollbar blurs our input and thus // closes the dropdown. To stop this, we refocus our input // if we know we are on IE *and* are scrolling. this._isScrollingOnIe = false; this.input.element.focus(); } }; Choices.prototype._onFormReset = function () { var _this = this; this._store.withTxn(function () { _this.clearInput(); _this.hideDropdown(); _this.refresh(false, false, true); if (_this._initialItems.length) { _this.setChoiceByValue(_this._initialItems); } }); }; Choices.prototype._onChange = function (event) { if (!event.target.checkValidity()) { return; } this.containerOuter.removeInvalidState(); }; Choices.prototype._onInvalid = function () { this.containerOuter.addInvalidState(); }; /** * Removes any highlighted choice options */ Choices.prototype._removeHighlightedChoices = function () { var highlightedState = this.config.classNames.highlightedState; var highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(getClassNamesSelector(highlightedState))); // Remove any highlighted choices highlightedChoices.forEach(function (choice) { removeClassesFromElement(choice, highlightedState); choice.setAttribute('aria-selected', 'false'); }); }; Choices.prototype._highlightChoice = function (el) { if (el === void 0) { el = null; } var choices = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)); if (!choices.length) { return; } var passedEl = el; var highlightedState = this.config.classNames.highlightedState; this._removeHighlightedChoices(); if (passedEl) { this._highlightPosition = choices.indexOf(passedEl); } else { // Highlight choice based on last known highlight location if (choices.length > this._highlightPosition) { // If we have an option to highlight passedEl = choices[this._highlightPosition]; } else { // Otherwise highlight the option before passedEl = choices[choices.length - 1]; } if (!passedEl) { passedEl = choices[0]; } } addClassesToElement(passedEl, highlightedState); passedEl.setAttribute('aria-selected', 'true'); this.passedElement.triggerEvent(EventType.highlightChoice, { el: passedEl, }); if (this.dropdown.isActive) { // IE11 ignores aria-label and blocks virtual keyboard // if aria-activedescendant is set without a dropdown this.input.setActiveDescendant(passedEl.id); this.containerOuter.setActiveDescendant(passedEl.id); } }; Choices.prototype._addItem = function (item, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (!item.id) { throw new TypeError('item.id must be set before _addItem is called for a choice/item'); } if (this.config.singleModeForMultiSelect || this._isSelectOneElement) { this.removeActiveItems(item.id); } this._store.dispatch(addItem(item)); if (withEvents) { var eventChoice = getChoiceForOutput(item); this.passedElement.triggerEvent(EventType.addItem, eventChoice); if (userTriggered) { this.passedElement.triggerEvent(EventType.choice, eventChoice); } } }; Choices.prototype._removeItem = function (item) { if (!item.id) { return; } this._store.dispatch(removeItem$1(item)); var notice = this._notice; if (notice && notice.type === NoticeTypes.noChoices) { this._clearNotice(); } this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item)); }; Choices.prototype._addChoice = function (choice, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (choice.id) { throw new TypeError('Can not re-add a choice which has already been added'); } var config = this.config; if (!config.duplicateItemsAllowed && this._store.choices.find(function (c) { return config.valueComparer(c.value, choice.value); })) { return; } // Generate unique id, in-place update is required so chaining _addItem works as expected this._lastAddedChoiceId++; choice.id = this._lastAddedChoiceId; choice.elementId = "".concat(this._baseId, "-").concat(this._idNames.itemChoice, "-").concat(choice.id); var prependValue = config.prependValue, appendValue = config.appendValue; if (prependValue) { choice.value = prependValue + choice.value; } if (appendValue) { choice.value += appendValue.toString(); } if ((prependValue || appendValue) && choice.element) { choice.element.value = choice.value; } this._clearNotice(); this._store.dispatch(addChoice(choice)); if (choice.selected) { this._addItem(choice, withEvents, userTriggered); } }; Choices.prototype._addGroup = function (group, withEvents) { var _this = this; if (withEvents === void 0) { withEvents = true; } if (group.id) { throw new TypeError('Can not re-add a group which has already been added'); } this._store.dispatch(addGroup(group)); if (!group.choices) { return; } // add unique id for the group(s), and do not store the full list of choices in this group this._lastAddedGroupId++; group.id = this._lastAddedGroupId; group.choices.forEach(function (item) { item.group = group; if (group.disabled) { item.disabled = true; } _this._addChoice(item, withEvents); }); }; Choices.prototype._createTemplates = function () { var _this = this; var callbackOnCreateTemplates = this.config.callbackOnCreateTemplates; var userTemplates = {}; if (typeof callbackOnCreateTemplates === 'function') { userTemplates = callbackOnCreateTemplates.call(this, strToEl, escapeForTemplate, getClassNames); } var templating = {}; Object.keys(this._templates).forEach(function (name) { if (name in userTemplates) { templating[name] = userTemplates[name].bind(_this); } else { templating[name] = _this._templates[name].bind(_this); } }); this._templates = templating; }; Choices.prototype._createElements = function () { var templating = this._templates; var _a = this, config = _a.config, isSelectOneElement = _a._isSelectOneElement; var position = config.position, classNames = config.classNames; var elementType = this._elementType; this.containerOuter = new Container({ element: templating.containerOuter(config, this._direction, this._isSelectElement, isSelectOneElement, config.searchEnabled, elementType, config.labelId), classNames: classNames, type: elementType, position: position, }); this.containerInner = new Container({ element: templating.containerInner(config), classNames: classNames, type: elementType, position: position, }); this.input = new Input({ element: templating.input(config, this._placeholderValue), classNames: classNames, type: elementType, preventPaste: !config.paste, }); this.choiceList = new List({ element: templating.choiceList(config, isSelectOneElement), }); this.itemList = new List({ element: templating.itemList(config, isSelectOneElement), }); this.dropdown = new Dropdown({ element: templating.dropdown(config), classNames: classNames, type: elementType, }); }; Choices.prototype._createStructure = function () { var _a = this, containerInner = _a.containerInner, containerOuter = _a.containerOuter, passedElement = _a.passedElement; var dropdownElement = this.dropdown.element; // Hide original element passedElement.conceal(); // Wrap input in container preserving DOM ordering containerInner.wrap(passedElement.element); // Wrapper inner container with outer container containerOuter.wrap(containerInner.element); containerOuter.element.appendChild(containerInner.element); containerOuter.element.appendChild(dropdownElement); containerInner.element.appendChild(this.itemList.element); dropdownElement.appendChild(this.choiceList.element); if (this._isSelectOneElement) { this.input.placeholder = this.config.searchPlaceholderValue || ''; if (this.config.searchEnabled) { dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild); } } else { if (!this._isSelectMultipleElement || this.config.searchEnabled) { containerInner.element.appendChild(this.input.element); } if (this._placeholderValue) { this.input.placeholder = this._placeholderValue; } this.input.setWidth(); } this._highlightPosition = 0; this._isSearching = false; }; Choices.prototype._initStore = function () { var _this = this; this._store.subscribe(this._render).withTxn(function () { _this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false); }); if (!this._store.choices.length || (this._isSelectOneElement && this._hasNonChoicePlaceholder)) { this._render(); } }; Choices.prototype._addPredefinedChoices = function (choices, selectFirstOption, withEvents) { var _this = this; if (selectFirstOption === void 0) { selectFirstOption = false; } if (withEvents === void 0) { withEvents = true; } if (selectFirstOption) { /** * If there is a selected choice already or the choice is not the first in * the array, add each choice normally. * * Otherwise we pre-select the first enabled choice in the array ("select-one" only) */ var noSelectedChoices = choices.findIndex(function (choice) { return choice.selected; }) === -1; if (noSelectedChoices) { choices.some(function (choice) { if (choice.disabled || 'choices' in choice) { return false; } choice.selected = true; return true; }); } } choices.forEach(function (item) { if ('choices' in item) { if (_this._isSelectElement) { _this._addGroup(item, withEvents); } } else { _this._addChoice(item, withEvents); } }); }; Choices.prototype._findAndSelectChoiceByValue = function (value, userTriggered) { var _this = this; if (userTriggered === void 0) { userTriggered = false; } // Check 'value' property exists and the choice isn't already selected var foundChoice = this._store.choices.find(function (choice) { return _this.config.valueComparer(choice.value, value); }); if (foundChoice && !foundChoice.disabled && !foundChoice.selected) { this._addItem(foundChoice, true, userTriggered); return true; } return false; }; Choices.prototype._generatePlaceholderValue = function () { var config = this.config; if (!config.placeholder) { return null; } if (this._hasNonChoicePlaceholder) { return config.placeholderValue; } if (this._isSelectElement) { var placeholderOption = this.passedElement.placeholderOption; return placeholderOption ? placeholderOption.text : null; } return null; }; Choices.prototype._warnChoicesInitFailed = function (caller) { if (this.config.silent) { return; } if (!this.initialised) { throw new TypeError("".concat(caller, " called on a non-initialised instance of Choices")); } else if (!this.initialisedOK) { throw new TypeError("".concat(caller, " called for an element which has multiple instances of Choices initialised on it")); } }; Choices.version = '11.2.1'; return Choices; }()); export { Choices as default }; ================================================ FILE: public/assets/scripts/choices.search-basic.js ================================================ /*! choices.js v11.2.1 | © 2026 Josh Johnson | https://github.com/Choices-js/Choices#readme */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Choices = factory()); })(this, (function () { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var ActionType = { ADD_CHOICE: 'ADD_CHOICE', REMOVE_CHOICE: 'REMOVE_CHOICE', FILTER_CHOICES: 'FILTER_CHOICES', ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', CLEAR_CHOICES: 'CLEAR_CHOICES', ADD_GROUP: 'ADD_GROUP', ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', }; var EventType = { showDropdown: 'showDropdown', hideDropdown: 'hideDropdown', change: 'change', choice: 'choice', search: 'search', addItem: 'addItem', removeItem: 'removeItem', highlightItem: 'highlightItem', highlightChoice: 'highlightChoice', unhighlightItem: 'unhighlightItem', }; var KeyCodeMap = { TAB_KEY: 9, SHIFT_KEY: 16, BACK_KEY: 46, DELETE_KEY: 8, ENTER_KEY: 13, A_KEY: 65, ESC_KEY: 27, UP_KEY: 38, DOWN_KEY: 40, PAGE_UP_KEY: 33, PAGE_DOWN_KEY: 34, }; var ObjectsInConfig = ['fuseOptions', 'classNames']; var PassedElementTypes = { Text: 'text', SelectOne: 'select-one', SelectMultiple: 'select-multiple', }; var addChoice = function (choice) { return ({ type: ActionType.ADD_CHOICE, choice: choice, }); }; var removeChoice = function (choice) { return ({ type: ActionType.REMOVE_CHOICE, choice: choice, }); }; var filterChoices = function (results) { return ({ type: ActionType.FILTER_CHOICES, results: results, }); }; var activateChoices = function (active) { return ({ type: ActionType.ACTIVATE_CHOICES, active: active, }); }; var addGroup = function (group) { return ({ type: ActionType.ADD_GROUP, group: group, }); }; var addItem = function (item) { return ({ type: ActionType.ADD_ITEM, item: item, }); }; var removeItem$1 = function (item) { return ({ type: ActionType.REMOVE_ITEM, item: item, }); }; var highlightItem = function (item, highlighted) { return ({ type: ActionType.HIGHLIGHT_ITEM, item: item, highlighted: highlighted, }); }; var getRandomNumber = function (min, max) { return Math.floor(Math.random() * (max - min) + min); }; var generateChars = function (length) { return Array.from({ length: length }, function () { return getRandomNumber(0, 36).toString(36); }).join(''); }; var generateId = function (element, prefix) { var id = element.id || (element.name && "".concat(element.name, "-").concat(generateChars(2))) || generateChars(4); id = id.replace(/(:|\.|\[|\]|,)/g, ''); id = "".concat(prefix, "-").concat(id); return id; }; var getAdjacentEl = function (startEl, selector, direction) { if (direction === void 0) { direction = 1; } var prop = "".concat(direction > 0 ? 'next' : 'previous', "ElementSibling"); var sibling = startEl[prop]; while (sibling) { if (sibling.matches(selector)) { return sibling; } sibling = sibling[prop]; } return null; }; var isScrolledIntoView = function (element, parent, direction) { if (direction === void 0) { direction = 1; } var isVisible; if (direction > 0) { // In view from bottom isVisible = parent.scrollTop + parent.offsetHeight >= element.offsetTop + element.offsetHeight; } else { // In view from top isVisible = element.offsetTop >= parent.scrollTop; } return isVisible; }; var sanitise = function (value) { if (typeof value !== 'string') { if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { if ('raw' in value) { return sanitise(value.raw); } if ('trusted' in value) { return value.trusted; } } return value; } return value .replace(/&/g, '&') .replace(/>/g, '>') .replace(/= 0 && !window.matchMedia("(min-height: ".concat(dropdownPos + 1, "px)")).matches; } else if (this.position === 'top') { shouldFlip = true; } return shouldFlip; }; Container.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Container.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Container.prototype.open = function (dropdownPos, dropdownHeight) { addClassesToElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; if (this.shouldFlip(dropdownPos, dropdownHeight)) { addClassesToElement(this.element, this.classNames.flippedState); this.isFlipped = true; } }; Container.prototype.close = function () { removeClassesFromElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'false'); this.removeActiveDescendant(); this.isOpen = false; // A dropdown flips if it does not have space within the page if (this.isFlipped) { removeClassesFromElement(this.element, this.classNames.flippedState); this.isFlipped = false; } }; Container.prototype.addFocusState = function () { addClassesToElement(this.element, this.classNames.focusState); }; Container.prototype.removeFocusState = function () { removeClassesFromElement(this.element, this.classNames.focusState); }; Container.prototype.addInvalidState = function () { addClassesToElement(this.element, this.classNames.invalidState); }; Container.prototype.removeInvalidState = function () { removeClassesFromElement(this.element, this.classNames.invalidState); }; Container.prototype.enable = function () { removeClassesFromElement(this.element, this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; }; Container.prototype.disable = function () { addClassesToElement(this.element, this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; }; Container.prototype.wrap = function (element) { var el = this.element; var parentNode = element.parentNode; if (parentNode) { if (element.nextSibling) { parentNode.insertBefore(el, element.nextSibling); } else { parentNode.appendChild(el); } } el.appendChild(element); }; Container.prototype.unwrap = function (element) { var el = this.element; var parentNode = el.parentNode; if (parentNode) { // Move passed element outside this element parentNode.insertBefore(element, el); // Remove this element parentNode.removeChild(el); } }; Container.prototype.addLoadingState = function () { addClassesToElement(this.element, this.classNames.loadingState); this.element.setAttribute('aria-busy', 'true'); this.isLoading = true; }; Container.prototype.removeLoadingState = function () { removeClassesFromElement(this.element, this.classNames.loadingState); this.element.removeAttribute('aria-busy'); this.isLoading = false; }; return Container; }()); var Input = /** @class */ (function () { function Input(_a) { var element = _a.element, type = _a.type, classNames = _a.classNames, preventPaste = _a.preventPaste; this.element = element; this.type = type; this.classNames = classNames; this.preventPaste = preventPaste; this.isFocussed = this.element.isEqualNode(document.activeElement); this.isDisabled = element.disabled; this._onPaste = this._onPaste.bind(this); this._onInput = this._onInput.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } Object.defineProperty(Input.prototype, "placeholder", { set: function (placeholder) { this.element.placeholder = placeholder; }, enumerable: false, configurable: true }); Object.defineProperty(Input.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.value = value; }, enumerable: false, configurable: true }); Input.prototype.addEventListeners = function () { var el = this.element; el.addEventListener('paste', this._onPaste); el.addEventListener('input', this._onInput, { passive: true, }); el.addEventListener('focus', this._onFocus, { passive: true, }); el.addEventListener('blur', this._onBlur, { passive: true, }); }; Input.prototype.removeEventListeners = function () { var el = this.element; el.removeEventListener('input', this._onInput); el.removeEventListener('paste', this._onPaste); el.removeEventListener('focus', this._onFocus); el.removeEventListener('blur', this._onBlur); }; Input.prototype.enable = function () { var el = this.element; el.removeAttribute('disabled'); this.isDisabled = false; }; Input.prototype.disable = function () { var el = this.element; el.setAttribute('disabled', ''); this.isDisabled = true; }; Input.prototype.focus = function () { if (!this.isFocussed) { this.element.focus(); } }; Input.prototype.blur = function () { if (this.isFocussed) { this.element.blur(); } }; Input.prototype.clear = function (setWidth) { if (setWidth === void 0) { setWidth = true; } this.element.value = ''; if (setWidth) { this.setWidth(); } return this; }; /** * Set the correct input width based on placeholder * value or input value */ Input.prototype.setWidth = function () { // Resize input to contents or placeholder var element = this.element; element.style.minWidth = "".concat(element.placeholder.length + 1, "ch"); element.style.width = "".concat(element.value.length + 1, "ch"); }; Input.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Input.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Input.prototype._onInput = function () { if (this.type !== PassedElementTypes.SelectOne) { this.setWidth(); } }; Input.prototype._onPaste = function (event) { if (this.preventPaste) { event.preventDefault(); } }; Input.prototype._onFocus = function () { this.isFocussed = true; }; Input.prototype._onBlur = function () { this.isFocussed = false; }; return Input; }()); var SCROLLING_SPEED = 4; var List = /** @class */ (function () { function List(_a) { var element = _a.element; this.element = element; this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; } List.prototype.prepend = function (node) { var child = this.element.firstElementChild; if (child) { this.element.insertBefore(node, child); } else { this.element.append(node); } }; List.prototype.scrollToTop = function () { this.element.scrollTop = 0; }; List.prototype.scrollToChildElement = function (element, direction) { var _this = this; if (!element) { return; } var listHeight = this.element.offsetHeight; // Scroll position of dropdown var listScrollPosition = this.element.scrollTop + listHeight; var elementHeight = element.offsetHeight; // Distance from bottom of element to top of parent var elementPos = element.offsetTop + elementHeight; // Difference between the element and scroll position var destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop; requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); }; List.prototype._scrollDown = function (scrollPos, strength, destination) { var easing = (destination - scrollPos) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos + distance; }; List.prototype._scrollUp = function (scrollPos, strength, destination) { var easing = (scrollPos - destination) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos - distance; }; List.prototype._animateScroll = function (destination, direction) { var _this = this; var strength = SCROLLING_SPEED; var choiceListScrollTop = this.element.scrollTop; var continueAnimation = false; if (direction > 0) { this._scrollDown(choiceListScrollTop, strength, destination); if (choiceListScrollTop < destination) { continueAnimation = true; } } else { this._scrollUp(choiceListScrollTop, strength, destination); if (choiceListScrollTop > destination) { continueAnimation = true; } } if (continueAnimation) { requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); } }; return List; }()); var WrappedElement = /** @class */ (function () { function WrappedElement(_a) { var element = _a.element, classNames = _a.classNames; this.element = element; this.classNames = classNames; this.isDisabled = false; } Object.defineProperty(WrappedElement.prototype, "isActive", { get: function () { return this.element.dataset.choice === 'active'; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "dir", { get: function () { return this.element.dir; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.setAttribute('value', value); this.element.value = value; }, enumerable: false, configurable: true }); WrappedElement.prototype.conceal = function () { var el = this.element; // Hide passed input addClassesToElement(el, this.classNames.input); el.hidden = true; // Remove element from tab index el.tabIndex = -1; // Backup original styles if any var origStyle = el.getAttribute('style'); if (origStyle) { el.setAttribute('data-choice-orig-style', origStyle); } el.setAttribute('data-choice', 'active'); }; WrappedElement.prototype.reveal = function () { var el = this.element; // Reinstate passed element removeClassesFromElement(el, this.classNames.input); el.hidden = false; el.removeAttribute('tabindex'); // Recover original styles if any var origStyle = el.getAttribute('data-choice-orig-style'); if (origStyle) { el.removeAttribute('data-choice-orig-style'); el.setAttribute('style', origStyle); } else { el.removeAttribute('style'); } el.removeAttribute('data-choice'); }; WrappedElement.prototype.enable = function () { this.element.removeAttribute('disabled'); this.element.disabled = false; this.isDisabled = false; }; WrappedElement.prototype.disable = function () { this.element.setAttribute('disabled', ''); this.element.disabled = true; this.isDisabled = true; }; WrappedElement.prototype.triggerEvent = function (eventType, data) { dispatchEvent(this.element, eventType, data || {}); }; return WrappedElement; }()); var WrappedInput = /** @class */ (function (_super) { __extends(WrappedInput, _super); function WrappedInput() { return _super !== null && _super.apply(this, arguments) || this; } return WrappedInput; }(WrappedElement)); var coerceBool = function (arg, defaultValue) { if (defaultValue === void 0) { defaultValue = true; } return typeof arg === 'undefined' ? defaultValue : !!arg; }; var stringToHtmlClass = function (input) { if (typeof input === 'string') { // eslint-disable-next-line no-param-reassign input = input.split(' ').filter(function (s) { return s.length; }); } if (Array.isArray(input) && input.length) { return input; } return undefined; }; var mapInputToChoice = function (value, allowGroup, allowRawString) { if (allowRawString === void 0) { allowRawString = true; } if (typeof value === 'string') { var sanitisedValue = sanitise(value); var userValue = allowRawString || sanitisedValue === value ? value : { escaped: sanitisedValue, raw: value }; var result_1 = mapInputToChoice({ value: value, label: userValue, selected: true, }, false); return result_1; } var groupOrChoice = value; if ('choices' in groupOrChoice) { if (!allowGroup) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup throw new TypeError("optGroup is not allowed"); } var group = groupOrChoice; var choices = group.choices.map(function (e) { return mapInputToChoice(e, false); }); var result_2 = { id: 0, // actual ID will be assigned during _addGroup label: unwrapStringForRaw(group.label) || group.value, active: !!choices.length, disabled: !!group.disabled, choices: choices, }; return result_2; } var choice = groupOrChoice; var result = { id: 0, // actual ID will be assigned during _addChoice group: null, // actual group will be assigned during _addGroup but before _addChoice score: 0, // used in search rank: 0, // used in search, stable sort order value: choice.value, label: choice.label || choice.value, active: coerceBool(choice.active), selected: coerceBool(choice.selected, false), disabled: coerceBool(choice.disabled, false), placeholder: coerceBool(choice.placeholder, false), highlighted: false, labelClass: stringToHtmlClass(choice.labelClass), labelDescription: choice.labelDescription, customProperties: choice.customProperties, }; return result; }; var isHtmlInputElement = function (e) { return e.tagName === 'INPUT'; }; var isHtmlSelectElement = function (e) { return e.tagName === 'SELECT'; }; var isHtmlOption = function (e) { return e.tagName === 'OPTION'; }; var isHtmlOptgroup = function (e) { return e.tagName === 'OPTGROUP'; }; var WrappedSelect = /** @class */ (function (_super) { __extends(WrappedSelect, _super); function WrappedSelect(_a) { var element = _a.element, classNames = _a.classNames, template = _a.template, extractPlaceholder = _a.extractPlaceholder; var _this = _super.call(this, { element: element, classNames: classNames }) || this; _this.template = template; _this.extractPlaceholder = extractPlaceholder; return _this; } Object.defineProperty(WrappedSelect.prototype, "placeholderOption", { get: function () { return (this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]')); }, enumerable: false, configurable: true }); WrappedSelect.prototype.addOptions = function (choices) { var _this = this; var fragment = document.createDocumentFragment(); choices.forEach(function (obj) { var choice = obj; if (choice.element) { return; } var option = _this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); }; WrappedSelect.prototype.optionsAsChoices = function () { var _this = this; var choices = []; this.element.querySelectorAll(':scope > option, :scope > optgroup').forEach(function (e) { if (isHtmlOption(e)) { choices.push(_this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(_this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; }; // eslint-disable-next-line class-methods-use-this WrappedSelect.prototype._optionToChoice = function (option) { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? { trusted: option.dataset.labelDescription } : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; }; WrappedSelect.prototype._optgroupToChoice = function (optgroup) { var _this = this; var options = optgroup.querySelectorAll('option'); var choices = Array.from(options).map(function (option) { return _this._optionToChoice(option); }); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices: choices, }; }; return WrappedSelect; }(WrappedElement)); var DEFAULT_CLASSNAMES = { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], }; var DEFAULT_CONFIG = { items: [], choices: [], silent: false, renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: function (value) { return !!value && value !== ''; }, removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: function (value) { return "Press Enter to add \"".concat(value, "\""); }, removeItemIconText: function () { return "Remove item"; }, removeItemLabelText: function (value, _valueRaw, i) { return "Remove item: ".concat(i ? sanitise(i.label) : value); }, maxItemText: function (maxItemCount) { return "Only ".concat(maxItemCount, " values can be added"); }, valueComparer: function (value1, value2) { return value1 === value2; }, fuseOptions: { includeScore: true, }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, appendGroupInSearch: false, }; var removeItem = function (item) { var itemEl = item.itemEl; if (itemEl) { itemEl.remove(); item.itemEl = undefined; } }; function items(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_ITEM: { action.item.selected = true; var el = action.item.element; if (el) { el.selected = true; el.setAttribute('selected', ''); } state.push(action.item); break; } case ActionType.REMOVE_ITEM: { action.item.selected = false; var el = action.item.element; if (el) { el.selected = false; el.removeAttribute('selected'); // For a select-one, if all options are deselected, the first item is selected. To set a black value, select.value needs to be set var select = el.parentElement; if (select && isHtmlSelectElement(select) && select.type === PassedElementTypes.SelectOne) { select.value = ''; } } // this is mixing concerns, but this is *so much faster* removeItem(action.item); state = state.filter(function (choice) { return choice.id !== action.item.id; }); break; } case ActionType.REMOVE_CHOICE: { removeItem(action.choice); state = state.filter(function (item) { return item.id !== action.choice.id; }); break; } case ActionType.HIGHLIGHT_ITEM: { var highlighted = action.highlighted; var item = state.find(function (obj) { return obj.id === action.item.id; }); if (item && item.highlighted !== highlighted) { item.highlighted = highlighted; if (context) { updateClassList(item, highlighted ? context.classNames.highlightedState : context.classNames.selectedState, highlighted ? context.classNames.selectedState : context.classNames.highlightedState); } } break; } default: { update = false; break; } } return { state: state, update: update }; } function groups(s, action) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_GROUP: { state.push(action.group); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } /* eslint-disable */ function choices(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_CHOICE: { state.push(action.choice); break; } case ActionType.REMOVE_CHOICE: { action.choice.choiceEl = undefined; if (action.choice.group) { action.choice.group.choices = action.choice.group.choices.filter(function (obj) { return obj.id !== action.choice.id; }); } state = state.filter(function (obj) { return obj.id !== action.choice.id; }); break; } case ActionType.ADD_ITEM: case ActionType.REMOVE_ITEM: { action.item.choiceEl = undefined; break; } case ActionType.FILTER_CHOICES: { // avoid O(n^2) algorithm complexity when searching/filtering choices var scoreLookup_1 = []; action.results.forEach(function (result) { scoreLookup_1[result.item.id] = result; }); state.forEach(function (choice) { var result = scoreLookup_1[choice.id]; if (result !== undefined) { choice.score = result.score; choice.rank = result.rank; choice.active = true; } else { choice.score = 0; choice.rank = 0; choice.active = false; } if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.ACTIVATE_CHOICES: { state.forEach(function (choice) { choice.active = action.active; if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } var reducers = { groups: groups, items: items, choices: choices, }; var Store = /** @class */ (function () { function Store(context) { this._state = this.defaultState; this._listeners = []; this._txn = 0; this._context = context; } Object.defineProperty(Store.prototype, "defaultState", { // eslint-disable-next-line class-methods-use-this get: function () { return { groups: [], items: [], choices: [], }; }, enumerable: false, configurable: true }); // eslint-disable-next-line class-methods-use-this Store.prototype.changeSet = function (init) { return { groups: init, items: init, choices: init, }; }; Store.prototype.reset = function () { this._state = this.defaultState; var changes = this.changeSet(true); if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } }; Store.prototype.subscribe = function (onChange) { this._listeners.push(onChange); return this; }; Store.prototype.dispatch = function (action) { var _this = this; var state = this._state; var hasChanges = false; var changes = this._changeSet || this.changeSet(false); Object.keys(reducers).forEach(function (key) { var stateUpdate = reducers[key](state[key], action, _this._context); if (stateUpdate.update) { hasChanges = true; changes[key] = true; state[key] = stateUpdate.state; } }); if (hasChanges) { if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } } }; Store.prototype.withTxn = function (func) { this._txn++; try { func(); } finally { this._txn = Math.max(0, this._txn - 1); if (!this._txn) { var changeSet_1 = this._changeSet; if (changeSet_1) { this._changeSet = undefined; this._listeners.forEach(function (l) { return l(changeSet_1); }); } } } }; Object.defineProperty(Store.prototype, "state", { /** * Get store object */ get: function () { return this._state; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "items", { /** * Get items from store */ get: function () { return this.state.items; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "highlightedActiveItems", { /** * Get highlighted items from store */ get: function () { return this.items.filter(function (item) { return item.active && item.highlighted; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "choices", { /** * Get choices from store */ get: function () { return this.state.choices; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeChoices", { /** * Get active choices from store */ get: function () { return this.choices.filter(function (choice) { return choice.active; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "searchableChoices", { /** * Get choices that can be searched (excluding placeholders or disabled choices) */ get: function () { var context = this._context; return this.choices.filter(function (choice) { return !choice.placeholder && (context.searchDisabledChoices || !choice.disabled); }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "groups", { /** * Get groups from store */ get: function () { return this.state.groups; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeGroups", { /** * Get active groups from store */ get: function () { var _this = this; return this.state.groups.filter(function (group) { var isActive = group.active && !group.disabled; var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; }); return isActive && hasActiveOptions; }, []); }, enumerable: false, configurable: true }); Store.prototype.inTxn = function () { return this._txn > 0; }; /** * Get single choice by it's ID */ Store.prototype.getChoiceById = function (id) { return this.activeChoices.find(function (choice) { return choice.id === id; }); }; /** * Get group by group id */ Store.prototype.getGroupById = function (id) { return this.groups.find(function (group) { return group.id === id; }); }; return Store; }()); var NoticeTypes = { noChoices: 'no-choices', noResults: 'no-results', addChoice: 'add-choice', generic: '', }; function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } /** * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io) * * Copyright (c) 2023 Kiro Risk (http://kiro.me) * All Rights Reserved. Apache Software License 2.0 * * http://www.apache.org/licenses/LICENSE-2.0 */ function isArray(value) { return !Array.isArray ? getTag(value) === '[object Array]' : Array.isArray(value); } function baseToString(value) { // Exit early for strings to avoid a performance hit in some environments. if (typeof value == 'string') { return value; } let result = value + ''; return result == '0' && 1 / value == -Infinity ? '-0' : result; } function toString(value) { return value == null ? '' : baseToString(value); } function isString(value) { return typeof value === 'string'; } function isNumber(value) { return typeof value === 'number'; } // Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js function isBoolean(value) { return value === true || value === false || isObjectLike(value) && getTag(value) == '[object Boolean]'; } function isObject(value) { return typeof value === 'object'; } // Checks if `value` is object-like. function isObjectLike(value) { return isObject(value) && value !== null; } function isDefined(value) { return value !== undefined && value !== null; } function isBlank(value) { return !value.trim().length; } // Gets the `toStringTag` of `value`. // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js function getTag(value) { return value == null ? value === undefined ? '[object Undefined]' : '[object Null]' : Object.prototype.toString.call(value); } const EXTENDED_SEARCH_UNAVAILABLE = 'Extended search is not available'; const LOGICAL_SEARCH_UNAVAILABLE = 'Logical search is not available'; const INCORRECT_INDEX_TYPE = "Incorrect 'index' type"; const LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = key => `Invalid value for key ${key}`; const PATTERN_LENGTH_TOO_LARGE = max => `Pattern length exceeds max of ${max}.`; const MISSING_KEY_PROPERTY = name => `Missing ${name} property in key`; const INVALID_KEY_WEIGHT_VALUE = key => `Property 'weight' in key '${key}' must be a positive integer`; const hasOwn = Object.prototype.hasOwnProperty; class KeyStore { constructor(keys) { this._keys = []; this._keyMap = {}; let totalWeight = 0; keys.forEach(key => { let obj = createKey(key); this._keys.push(obj); this._keyMap[obj.id] = obj; totalWeight += obj.weight; }); // Normalize weights so that their sum is equal to 1 this._keys.forEach(key => { key.weight /= totalWeight; }); } get(keyId) { return this._keyMap[keyId]; } keys() { return this._keys; } toJSON() { return JSON.stringify(this._keys); } } function createKey(key) { let path = null; let id = null; let src = null; let weight = 1; let getFn = null; if (isString(key) || isArray(key)) { src = key; path = createKeyPath(key); id = createKeyId(key); } else { if (!hasOwn.call(key, 'name')) { throw new Error(MISSING_KEY_PROPERTY('name')); } const name = key.name; src = name; if (hasOwn.call(key, 'weight')) { weight = key.weight; if (weight <= 0) { throw new Error(INVALID_KEY_WEIGHT_VALUE(name)); } } path = createKeyPath(name); id = createKeyId(name); getFn = key.getFn; } return { path, id, weight, src, getFn }; } function createKeyPath(key) { return isArray(key) ? key : key.split('.'); } function createKeyId(key) { return isArray(key) ? key.join('.') : key; } function get(obj, path) { let list = []; let arr = false; const deepGet = (obj, path, index) => { if (!isDefined(obj)) { return; } if (!path[index]) { // If there's no path left, we've arrived at the object we care about. list.push(obj); } else { let key = path[index]; const value = obj[key]; if (!isDefined(value)) { return; } // If we're at the last value in the path, and if it's a string/number/bool, // add it to the list if (index === path.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) { list.push(toString(value)); } else if (isArray(value)) { arr = true; // Search each item in the array. for (let i = 0, len = value.length; i < len; i += 1) { deepGet(value[i], path, index + 1); } } else if (path.length) { // An object. Recurse further. deepGet(value, path, index + 1); } } }; // Backwards compatibility (since path used to be a string) deepGet(obj, isString(path) ? path.split('.') : path, 0); return arr ? list : list[0]; } const MatchOptions = { // Whether the matches should be included in the result set. When `true`, each record in the result // set will include the indices of the matched characters. // These can consequently be used for highlighting purposes. includeMatches: false, // When `true`, the matching function will continue to the end of a search pattern even if // a perfect match has already been located in the string. findAllMatches: false, // Minimum number of characters that must be matched before a result is considered a match minMatchCharLength: 1 }; const BasicOptions = { // When `true`, the algorithm continues searching to the end of the input even if a perfect // match is found before the end of the same input. isCaseSensitive: false, // When true, the matching function will continue to the end of a search pattern even if includeScore: false, // List of properties that will be searched. This also supports nested properties. keys: [], // Whether to sort the result list, by score shouldSort: true, // Default sort function: sort by ascending score, ascending index sortFn: (a, b) => a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1 }; const FuzzyOptions = { // Approximately where in the text is the pattern expected to be found? location: 0, // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match // (of both letters and location), a threshold of '1.0' would match anything. threshold: 0.6, // Determines how close the match must be to the fuzzy location (specified above). // An exact letter match which is 'distance' characters away from the fuzzy location // would score as a complete mismatch. A distance of '0' requires the match be at // the exact location specified, a threshold of '1000' would require a perfect match // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. distance: 100 }; const AdvancedOptions = { // When `true`, it enables the use of unix-like search commands useExtendedSearch: false, // The get function to use when fetching an object's properties. // The default will search nested paths *ie foo.bar.baz* getFn: get, // When `true`, search will ignore `location` and `distance`, so it won't matter // where in the string the pattern appears. // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score ignoreLocation: false, // When `true`, the calculation for the relevance score (used for sorting) will // ignore the field-length norm. // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm ignoreFieldNorm: false, // The weight to determine how much field length norm effects scoring. fieldNormWeight: 1 }; var Config = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, BasicOptions), MatchOptions), FuzzyOptions), AdvancedOptions); const SPACE = /[^ ]+/g; // Field-length norm: the shorter the field, the higher the weight. // Set to 3 decimals to reduce index size. function norm(weight = 1, mantissa = 3) { const cache = new Map(); const m = Math.pow(10, mantissa); return { get(value) { const numTokens = value.match(SPACE).length; if (cache.has(numTokens)) { return cache.get(numTokens); } // Default function is 1/sqrt(x), weight makes that variable const norm = 1 / Math.pow(numTokens, 0.5 * weight); // In place of `toFixed(mantissa)`, for faster computation const n = parseFloat(Math.round(norm * m) / m); cache.set(numTokens, n); return n; }, clear() { cache.clear(); } }; } class FuseIndex { constructor({ getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { this.norm = norm(fieldNormWeight, 3); this.getFn = getFn; this.isCreated = false; this.setIndexRecords(); } setSources(docs = []) { this.docs = docs; } setIndexRecords(records = []) { this.records = records; } setKeys(keys = []) { this.keys = keys; this._keysMap = {}; keys.forEach((key, idx) => { this._keysMap[key.id] = idx; }); } create() { if (this.isCreated || !this.docs.length) { return; } this.isCreated = true; // List is Array if (isString(this.docs[0])) { this.docs.forEach((doc, docIndex) => { this._addString(doc, docIndex); }); } else { // List is Array this.docs.forEach((doc, docIndex) => { this._addObject(doc, docIndex); }); } this.norm.clear(); } // Adds a doc to the end of the index add(doc) { const idx = this.size(); if (isString(doc)) { this._addString(doc, idx); } else { this._addObject(doc, idx); } } // Removes the doc at the specified index of the index removeAt(idx) { this.records.splice(idx, 1); // Change ref index of every subsquent doc for (let i = idx, len = this.size(); i < len; i += 1) { this.records[i].i -= 1; } } getValueForItemAtKeyId(item, keyId) { return item[this._keysMap[keyId]]; } size() { return this.records.length; } _addString(doc, docIndex) { if (!isDefined(doc) || isBlank(doc)) { return; } let record = { v: doc, i: docIndex, n: this.norm.get(doc) }; this.records.push(record); } _addObject(doc, docIndex) { let record = { i: docIndex, $: {} }; // Iterate over every key (i.e, path), and fetch the value at that key this.keys.forEach((key, keyIndex) => { let value = key.getFn ? key.getFn(doc) : this.getFn(doc, key.path); if (!isDefined(value)) { return; } if (isArray(value)) { let subRecords = []; const stack = [{ nestedArrIndex: -1, value }]; while (stack.length) { const { nestedArrIndex, value } = stack.pop(); if (!isDefined(value)) { continue; } if (isString(value) && !isBlank(value)) { let subRecord = { v: value, i: nestedArrIndex, n: this.norm.get(value) }; subRecords.push(subRecord); } else if (isArray(value)) { value.forEach((item, k) => { stack.push({ nestedArrIndex: k, value: item }); }); } else ; } record.$[keyIndex] = subRecords; } else if (isString(value) && !isBlank(value)) { let subRecord = { v: value, n: this.norm.get(value) }; record.$[keyIndex] = subRecord; } }); this.records.push(record); } toJSON() { return { keys: this.keys, records: this.records }; } } function createIndex(keys, docs, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { const myIndex = new FuseIndex({ getFn, fieldNormWeight }); myIndex.setKeys(keys.map(createKey)); myIndex.setSources(docs); myIndex.create(); return myIndex; } function parseIndex(data, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { const { keys, records } = data; const myIndex = new FuseIndex({ getFn, fieldNormWeight }); myIndex.setKeys(keys); myIndex.setIndexRecords(records); return myIndex; } function computeScore$1(pattern, { errors = 0, currentLocation = 0, expectedLocation = 0, distance = Config.distance, ignoreLocation = Config.ignoreLocation } = {}) { const accuracy = errors / pattern.length; if (ignoreLocation) { return accuracy; } const proximity = Math.abs(expectedLocation - currentLocation); if (!distance) { // Dodge divide by zero error. return proximity ? 1.0 : accuracy; } return accuracy + proximity / distance; } function convertMaskToIndices(matchmask = [], minMatchCharLength = Config.minMatchCharLength) { let indices = []; let start = -1; let end = -1; let i = 0; for (let len = matchmask.length; i < len; i += 1) { let match = matchmask[i]; if (match && start === -1) { start = i; } else if (!match && start !== -1) { end = i - 1; if (end - start + 1 >= minMatchCharLength) { indices.push([start, end]); } start = -1; } } // (i-1 - start) + 1 => i - start if (matchmask[i - 1] && i - start >= minMatchCharLength) { indices.push([start, i - 1]); } return indices; } // Machine word size const MAX_BITS = 32; function search(text, pattern, patternAlphabet, { location = Config.location, distance = Config.distance, threshold = Config.threshold, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, includeMatches = Config.includeMatches, ignoreLocation = Config.ignoreLocation } = {}) { if (pattern.length > MAX_BITS) { throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS)); } const patternLen = pattern.length; // Set starting location at beginning text and initialize the alphabet. const textLen = text.length; // Handle the case when location > text.length const expectedLocation = Math.max(0, Math.min(location, textLen)); // Highest score beyond which we give up. let currentThreshold = threshold; // Is there a nearby exact match? (speedup) let bestLocation = expectedLocation; // Performance: only computer matches when the minMatchCharLength > 1 // OR if `includeMatches` is true. const computeMatches = minMatchCharLength > 1 || includeMatches; // A mask of the matches, used for building the indices const matchMask = computeMatches ? Array(textLen) : []; let index; // Get all exact matches, here for speed up while ((index = text.indexOf(pattern, bestLocation)) > -1) { let score = computeScore$1(pattern, { currentLocation: index, expectedLocation, distance, ignoreLocation }); currentThreshold = Math.min(score, currentThreshold); bestLocation = index + patternLen; if (computeMatches) { let i = 0; while (i < patternLen) { matchMask[index + i] = 1; i += 1; } } } // Reset the best location bestLocation = -1; let lastBitArr = []; let finalScore = 1; let binMax = patternLen + textLen; const mask = 1 << patternLen - 1; for (let i = 0; i < patternLen; i += 1) { // Scan for the best match; each iteration allows for one more error. // Run a binary search to determine how far from the match location we can stray // at this error level. let binMin = 0; let binMid = binMax; while (binMin < binMid) { const score = computeScore$1(pattern, { errors: i, currentLocation: expectedLocation + binMid, expectedLocation, distance, ignoreLocation }); if (score <= currentThreshold) { binMin = binMid; } else { binMax = binMid; } binMid = Math.floor((binMax - binMin) / 2 + binMin); } // Use the result from this iteration as the maximum for the next. binMax = binMid; let start = Math.max(1, expectedLocation - binMid + 1); let finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen; // Initialize the bit array let bitArr = Array(finish + 2); bitArr[finish + 1] = (1 << i) - 1; for (let j = finish; j >= start; j -= 1) { let currentLocation = j - 1; let charMatch = patternAlphabet[text.charAt(currentLocation)]; if (computeMatches) { // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`) matchMask[currentLocation] = +!!charMatch; } // First pass: exact match bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch; // Subsequent passes: fuzzy match if (i) { bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1]; } if (bitArr[j] & mask) { finalScore = computeScore$1(pattern, { errors: i, currentLocation, expectedLocation, distance, ignoreLocation }); // This match will almost certainly be better than any existing match. // But check anyway. if (finalScore <= currentThreshold) { // Indeed it is currentThreshold = finalScore; bestLocation = currentLocation; // Already passed `loc`, downhill from here on in. if (bestLocation <= expectedLocation) { break; } // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`. start = Math.max(1, 2 * expectedLocation - bestLocation); } } } // No hope for a (better) match at greater error levels. const score = computeScore$1(pattern, { errors: i + 1, currentLocation: expectedLocation, expectedLocation, distance, ignoreLocation }); if (score > currentThreshold) { break; } lastBitArr = bitArr; } const result = { isMatch: bestLocation >= 0, // Count exact matches (those with a score of 0) to be "almost" exact score: Math.max(0.001, finalScore) }; if (computeMatches) { const indices = convertMaskToIndices(matchMask, minMatchCharLength); if (!indices.length) { result.isMatch = false; } else if (includeMatches) { result.indices = indices; } } return result; } function createPatternAlphabet(pattern) { let mask = {}; for (let i = 0, len = pattern.length; i < len; i += 1) { const char = pattern.charAt(i); mask[char] = (mask[char] || 0) | 1 << len - i - 1; } return mask; } class BitapSearch { constructor(pattern, { location = Config.location, threshold = Config.threshold, distance = Config.distance, includeMatches = Config.includeMatches, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, isCaseSensitive = Config.isCaseSensitive, ignoreLocation = Config.ignoreLocation } = {}) { this.options = { location, threshold, distance, includeMatches, findAllMatches, minMatchCharLength, isCaseSensitive, ignoreLocation }; this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); this.chunks = []; if (!this.pattern.length) { return; } const addChunk = (pattern, startIndex) => { this.chunks.push({ pattern, alphabet: createPatternAlphabet(pattern), startIndex }); }; const len = this.pattern.length; if (len > MAX_BITS) { let i = 0; const remainder = len % MAX_BITS; const end = len - remainder; while (i < end) { addChunk(this.pattern.substr(i, MAX_BITS), i); i += MAX_BITS; } if (remainder) { const startIndex = len - MAX_BITS; addChunk(this.pattern.substr(startIndex), startIndex); } } else { addChunk(this.pattern, 0); } } searchIn(text) { const { isCaseSensitive, includeMatches } = this.options; if (!isCaseSensitive) { text = text.toLowerCase(); } // Exact match if (this.pattern === text) { let result = { isMatch: true, score: 0 }; if (includeMatches) { result.indices = [[0, text.length - 1]]; } return result; } // Otherwise, use Bitap algorithm const { location, distance, threshold, findAllMatches, minMatchCharLength, ignoreLocation } = this.options; let allIndices = []; let totalScore = 0; let hasMatches = false; this.chunks.forEach(({ pattern, alphabet, startIndex }) => { const { isMatch, score, indices } = search(text, pattern, alphabet, { location: location + startIndex, distance, threshold, findAllMatches, minMatchCharLength, includeMatches, ignoreLocation }); if (isMatch) { hasMatches = true; } totalScore += score; if (isMatch && indices) { allIndices = [...allIndices, ...indices]; } }); let result = { isMatch: hasMatches, score: hasMatches ? totalScore / this.chunks.length : 1 }; if (hasMatches && includeMatches) { result.indices = allIndices; } return result; } } const registeredSearchers = []; function createSearcher(pattern, options) { for (let i = 0, len = registeredSearchers.length; i < len; i += 1) { let searcherClass = registeredSearchers[i]; if (searcherClass.condition(pattern, options)) { return new searcherClass(pattern, options); } } return new BitapSearch(pattern, options); } const LogicalOperator = { AND: '$and', OR: '$or' }; const KeyType = { PATH: '$path', PATTERN: '$val' }; const isExpression = query => !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]); const isPath = query => !!query[KeyType.PATH]; const isLeaf = query => !isArray(query) && isObject(query) && !isExpression(query); const convertToExplicit = query => ({ [LogicalOperator.AND]: Object.keys(query).map(key => ({ [key]: query[key] })) }); // When `auto` is `true`, the parse function will infer and initialize and add // the appropriate `Searcher` instance function parse(query, options, { auto = true } = {}) { const next = query => { let keys = Object.keys(query); const isQueryPath = isPath(query); if (!isQueryPath && keys.length > 1 && !isExpression(query)) { return next(convertToExplicit(query)); } if (isLeaf(query)) { const key = isQueryPath ? query[KeyType.PATH] : keys[0]; const pattern = isQueryPath ? query[KeyType.PATTERN] : query[key]; if (!isString(pattern)) { throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key)); } const obj = { keyId: createKeyId(key), pattern }; if (auto) { obj.searcher = createSearcher(pattern, options); } return obj; } let node = { children: [], operator: keys[0] }; keys.forEach(key => { const value = query[key]; if (isArray(value)) { value.forEach(item => { node.children.push(next(item)); }); } }); return node; }; if (!isExpression(query)) { query = convertToExplicit(query); } return next(query); } // Practical scoring function function computeScore(results, { ignoreFieldNorm = Config.ignoreFieldNorm }) { results.forEach(result => { let totalScore = 1; result.matches.forEach(({ key, norm, score }) => { const weight = key ? key.weight : null; totalScore *= Math.pow(score === 0 && weight ? Number.EPSILON : score, (weight || 1) * (ignoreFieldNorm ? 1 : norm)); }); result.score = totalScore; }); } function transformMatches(result, data) { const matches = result.matches; data.matches = []; if (!isDefined(matches)) { return; } matches.forEach(match => { if (!isDefined(match.indices) || !match.indices.length) { return; } const { indices, value } = match; let obj = { indices, value }; if (match.key) { obj.key = match.key.src; } if (match.idx > -1) { obj.refIndex = match.idx; } data.matches.push(obj); }); } function transformScore(result, data) { data.score = result.score; } function format(results, docs, { includeMatches = Config.includeMatches, includeScore = Config.includeScore } = {}) { const transformers = []; if (includeMatches) transformers.push(transformMatches); if (includeScore) transformers.push(transformScore); return results.map(result => { const { idx } = result; const data = { item: docs[idx], refIndex: idx }; if (transformers.length) { transformers.forEach(transformer => { transformer(result, data); }); } return data; }); } class Fuse { constructor(docs, options = {}, index) { this.options = _objectSpread2(_objectSpread2({}, Config), options); if (this.options.useExtendedSearch && true) { throw new Error(EXTENDED_SEARCH_UNAVAILABLE); } this._keyStore = new KeyStore(this.options.keys); this.setCollection(docs, index); } setCollection(docs, index) { this._docs = docs; if (index && !(index instanceof FuseIndex)) { throw new Error(INCORRECT_INDEX_TYPE); } this._myIndex = index || createIndex(this.options.keys, this._docs, { getFn: this.options.getFn, fieldNormWeight: this.options.fieldNormWeight }); } add(doc) { if (!isDefined(doc)) { return; } this._docs.push(doc); this._myIndex.add(doc); } remove(predicate = ( /* doc, idx */) => false) { const results = []; for (let i = 0, len = this._docs.length; i < len; i += 1) { const doc = this._docs[i]; if (predicate(doc, i)) { this.removeAt(i); i -= 1; len -= 1; results.push(doc); } } return results; } removeAt(idx) { this._docs.splice(idx, 1); this._myIndex.removeAt(idx); } getIndex() { return this._myIndex; } search(query, { limit = -1 } = {}) { const { includeMatches, includeScore, shouldSort, sortFn, ignoreFieldNorm } = this.options; let results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query); computeScore(results, { ignoreFieldNorm }); if (shouldSort) { results.sort(sortFn); } if (isNumber(limit) && limit > -1) { results = results.slice(0, limit); } return format(results, this._docs, { includeMatches, includeScore }); } _searchStringList(query) { const searcher = createSearcher(query, this.options); const { records } = this._myIndex; const results = []; // Iterate over every string in the index records.forEach(({ v: text, i: idx, n: norm }) => { if (!isDefined(text)) { return; } const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { results.push({ item: text, idx, matches: [{ score, value: text, norm, indices }] }); } }); return results; } _searchLogical(query) { { throw new Error(LOGICAL_SEARCH_UNAVAILABLE); } } _searchObjectList(query) { const searcher = createSearcher(query, this.options); const { keys, records } = this._myIndex; const results = []; // List is Array records.forEach(({ $: item, i: idx }) => { if (!isDefined(item)) { return; } let matches = []; // Iterate over every key (i.e, path), and fetch the value at that key keys.forEach((key, keyIndex) => { matches.push(...this._findMatches({ key, value: item[keyIndex], searcher })); }); if (matches.length) { results.push({ idx, item, matches }); } }); return results; } _findMatches({ key, value, searcher }) { if (!isDefined(value)) { return []; } let matches = []; if (isArray(value)) { value.forEach(({ v: text, i: idx, n: norm }) => { if (!isDefined(text)) { return; } const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { matches.push({ score, key, value: text, idx, norm, indices }); } }); } else { const { v: text, n: norm } = value; const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { matches.push({ score, key, value: text, norm, indices }); } } return matches; } } Fuse.version = '7.0.0'; Fuse.createIndex = createIndex; Fuse.parseIndex = parseIndex; Fuse.config = Config; { Fuse.parseQuery = parse; } var SearchByFuse = /** @class */ (function () { function SearchByFuse(config) { this._haystack = []; this._fuseOptions = __assign(__assign({}, config.fuseOptions), { keys: __spreadArray([], config.searchFields, true), includeMatches: true }); } SearchByFuse.prototype.index = function (data) { this._haystack = data; if (this._fuse) { this._fuse.setCollection(data); } }; SearchByFuse.prototype.reset = function () { this._haystack = []; this._fuse = undefined; }; SearchByFuse.prototype.isEmptyIndex = function () { return !this._haystack.length; }; SearchByFuse.prototype.search = function (needle) { if (!this._fuse) { { this._fuse = new Fuse(this._haystack, this._fuseOptions); } } var results = this._fuse.search(needle); return results.map(function (value, i) { return { item: value.item, score: value.score || 0, rank: i + 1, // If value.score is used for sorting, this can create non-stable sorts! }; }); }; return SearchByFuse; }()); function getSearcher(config) { { return new SearchByFuse(config); } } /** * Helpers to create HTML elements used by Choices * Can be overridden by providing `callbackOnCreateTemplates` option. * `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates` */ var isEmptyObject = function (obj) { // eslint-disable-next-line no-restricted-syntax for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { return false; } } return true; }; var assignCustomProperties = function (el, choice, withCustomProperties) { var dataset = el.dataset; var customProperties = choice.customProperties, labelClass = choice.labelClass, labelDescription = choice.labelDescription; if (labelClass) { dataset.labelClass = getClassNames(labelClass).join(' '); } if (labelDescription) { dataset.labelDescription = unwrapStringForRaw(labelDescription); } if (withCustomProperties && customProperties) { if (typeof customProperties === 'string') { dataset.customProperties = customProperties; } else if (typeof customProperties === 'object' && !isEmptyObject(customProperties)) { dataset.customProperties = JSON.stringify(customProperties); } } }; var addAriaLabel = function (docRoot, id, element) { var label = id && docRoot.querySelector("label[for='".concat(id, "']")); var text = label && label.innerText; if (text) { element.setAttribute('aria-label', text); } }; var templates = { containerOuter: function (_a, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType, labelId) { var containerOuter = _a.classNames.containerOuter; var div = document.createElement('div'); addClassesToElement(div, containerOuter); div.dataset.type = passedElementType; if (dir) { div.dir = dir; } if (isSelectOneElement) { div.tabIndex = 0; } if (isSelectElement) { div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox'); if (searchEnabled) { div.setAttribute('aria-autocomplete', 'list'); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, div); } div.setAttribute('aria-haspopup', 'true'); div.setAttribute('aria-expanded', 'false'); } if (labelId) { div.setAttribute('aria-labelledby', labelId); } return div; }, containerInner: function (_a) { var containerInner = _a.classNames.containerInner; var div = document.createElement('div'); addClassesToElement(div, containerInner); return div; }, itemList: function (_a, isSelectOneElement) { var searchEnabled = _a.searchEnabled, _b = _a.classNames, list = _b.list, listSingle = _b.listSingle, listItems = _b.listItems; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, isSelectOneElement ? listSingle : listItems); if (this._isSelectElement && searchEnabled) { div.setAttribute('role', 'listbox'); } return div; }, placeholder: function (_a, value) { var allowHTML = _a.allowHTML, placeholder = _a.classNames.placeholder; var div = document.createElement('div'); addClassesToElement(div, placeholder); setElementHtml(div, allowHTML, value); return div; }, item: function (_a, choice, removeItemButton) { var allowHTML = _a.allowHTML, removeItemButtonAlignLeft = _a.removeItemButtonAlignLeft, removeItemIconText = _a.removeItemIconText, removeItemLabelText = _a.removeItemLabelText, _b = _a.classNames, item = _b.item, button = _b.button, highlightedState = _b.highlightedState, itemSelectable = _b.itemSelectable, placeholder = _b.placeholder; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); addClassesToElement(div, item); if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, choice.label); addClassesToElement(spanLabel, choice.labelClass); div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, choice.label); } div.dataset.item = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; assignCustomProperties(div, choice, true); if (choice.disabled || this.containerOuter.isDisabled) { div.setAttribute('aria-disabled', 'true'); } if (this._isSelectElement) { div.setAttribute('aria-selected', 'true'); div.setAttribute('role', 'option'); } if (choice.placeholder) { addClassesToElement(div, placeholder); div.dataset.placeholder = ''; } addClassesToElement(div, choice.highlighted ? highlightedState : itemSelectable); if (removeItemButton) { if (choice.disabled) { removeClassesFromElement(div, itemSelectable); } div.dataset.deletable = ''; var removeButton = document.createElement('button'); removeButton.type = 'button'; addClassesToElement(removeButton, button); var eventChoice = getChoiceForOutput(choice); setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value, eventChoice)); var REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value, eventChoice); if (REMOVE_ITEM_LABEL) { removeButton.setAttribute('aria-label', REMOVE_ITEM_LABEL); } removeButton.dataset.button = ''; if (removeItemButtonAlignLeft) { div.insertAdjacentElement('afterbegin', removeButton); } else { div.appendChild(removeButton); } } return div; }, choiceList: function (_a, isSelectOneElement) { var list = _a.classNames.list; var div = document.createElement('div'); addClassesToElement(div, list); if (!isSelectOneElement) { div.setAttribute('aria-multiselectable', 'true'); } div.setAttribute('role', 'listbox'); return div; }, choiceGroup: function (_a, _b) { var allowHTML = _a.allowHTML, _c = _a.classNames, group = _c.group, groupHeading = _c.groupHeading, itemDisabled = _c.itemDisabled; var id = _b.id, label = _b.label, disabled = _b.disabled; var rawLabel = unwrapStringForRaw(label); var div = document.createElement('div'); addClassesToElement(div, group); if (disabled) { addClassesToElement(div, itemDisabled); } div.setAttribute('role', 'group'); div.dataset.group = ''; div.dataset.id = id; div.dataset.value = rawLabel; if (disabled) { div.setAttribute('aria-disabled', 'true'); } var heading = document.createElement('div'); addClassesToElement(heading, groupHeading); setElementHtml(heading, allowHTML, label || ''); div.appendChild(heading); return div; }, choice: function (_a, choice, selectText, groupName) { var allowHTML = _a.allowHTML, _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, itemSelectable = _b.itemSelectable, selectedState = _b.selectedState, itemDisabled = _b.itemDisabled, description = _b.description, placeholder = _b.placeholder; // eslint-disable-next-line prefer-destructuring var label = choice.label; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); div.id = choice.elementId; addClassesToElement(div, item); addClassesToElement(div, itemChoice); if (groupName && typeof label === 'string') { label = escapeForTemplate(allowHTML, label); label += " (".concat(groupName, ")"); label = { trusted: label }; } var describedBy = div; if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, label); addClassesToElement(spanLabel, choice.labelClass); describedBy = spanLabel; div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, label); } if (choice.labelDescription) { var descId = "".concat(choice.elementId, "-description"); describedBy.setAttribute('aria-describedby', descId); var spanDesc = document.createElement('span'); setElementHtml(spanDesc, allowHTML, choice.labelDescription); spanDesc.id = descId; addClassesToElement(spanDesc, description); div.appendChild(spanDesc); } if (choice.selected) { addClassesToElement(div, selectedState); } if (choice.placeholder) { addClassesToElement(div, placeholder); } div.setAttribute('role', choice.group ? 'treeitem' : 'option'); div.dataset.choice = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; if (selectText) { div.dataset.selectText = selectText; } if (choice.group) { div.dataset.groupId = "".concat(choice.group.id); } assignCustomProperties(div, choice, false); if (choice.disabled) { addClassesToElement(div, itemDisabled); div.dataset.choiceDisabled = ''; div.setAttribute('aria-disabled', 'true'); } else { addClassesToElement(div, itemSelectable); div.dataset.choiceSelectable = ''; div.setAttribute('aria-selected', choice.selected ? 'true' : 'false'); } return div; }, input: function (_a, placeholderValue) { var _b = _a.classNames, input = _b.input, inputCloned = _b.inputCloned, labelId = _a.labelId; var inp = document.createElement('input'); inp.type = 'search'; addClassesToElement(inp, input); addClassesToElement(inp, inputCloned); inp.autocomplete = 'off'; inp.autocapitalize = 'off'; inp.spellcheck = false; inp.setAttribute('aria-autocomplete', 'list'); if (placeholderValue) { inp.setAttribute('aria-label', placeholderValue); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, inp); } return inp; }, dropdown: function (_a) { var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, listDropdown); div.setAttribute('aria-expanded', 'false'); return div; }, notice: function (_a, innerHTML, type) { var _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, addChoice = _b.addChoice, noResults = _b.noResults, noChoices = _b.noChoices, noticeItem = _b.notice; if (type === void 0) { type = NoticeTypes.generic; } var notice = document.createElement('div'); setElementHtml(notice, true, innerHTML); addClassesToElement(notice, item); addClassesToElement(notice, itemChoice); addClassesToElement(notice, noticeItem); // eslint-disable-next-line default-case switch (type) { case NoticeTypes.addChoice: addClassesToElement(notice, addChoice); break; case NoticeTypes.noResults: addClassesToElement(notice, noResults); break; case NoticeTypes.noChoices: addClassesToElement(notice, noChoices); break; } if (type === NoticeTypes.addChoice) { notice.dataset.choiceSelectable = ''; notice.dataset.choice = ''; } return notice; }, option: function (choice) { // HtmlOptionElement's label value does not support HTML, so the avoid double escaping unwrap the untrusted string. var labelValue = unwrapStringForRaw(choice.label); var opt = new Option(labelValue, choice.value, false, choice.selected); assignCustomProperties(opt, choice, true); opt.disabled = choice.disabled; if (choice.selected) { opt.setAttribute('selected', ''); } return opt; }, }; /** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ var IS_IE11 = '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style; var USER_DEFAULTS = {}; var parseDataSetId = function (element) { if (!element) { return undefined; } return element.dataset.id ? parseInt(element.dataset.id, 10) : undefined; }; var selectableChoiceIdentifier = '[data-choice-selectable]'; /** * Choices * @author Josh Johnson */ var Choices = /** @class */ (function () { function Choices(element, userConfig) { if (element === void 0) { element = '[data-choice]'; } if (userConfig === void 0) { userConfig = {}; } var _this = this; this.initialisedOK = undefined; this._hasNonChoicePlaceholder = false; this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; var defaults = Choices.defaults; this.config = __assign(__assign(__assign({}, defaults.allOptions), defaults.options), userConfig); ObjectsInConfig.forEach(function (key) { _this.config[key] = __assign(__assign(__assign({}, defaults.allOptions[key]), defaults.options[key]), userConfig[key]); }); var config = this.config; if (!config.silent) { this._validateConfig(); } var docRoot = config.shadowRoot || document.documentElement; this._docRoot = docRoot; var passedElement = typeof element === 'string' ? docRoot.querySelector(element) : element; if (!passedElement || typeof passedElement !== 'object' || !(isHtmlInputElement(passedElement) || isHtmlSelectElement(passedElement))) { if (!passedElement && typeof element === 'string') { throw TypeError("Selector ".concat(element, " failed to find an element")); } throw TypeError("Expected one of the following types text|select-one|select-multiple"); } var elementType = passedElement.type; var isText = elementType === PassedElementTypes.Text; if (isText || config.maxItemCount !== 1) { config.singleModeForMultiSelect = false; } if (config.singleModeForMultiSelect) { elementType = PassedElementTypes.SelectMultiple; } var isSelectOne = elementType === PassedElementTypes.SelectOne; var isSelectMultiple = elementType === PassedElementTypes.SelectMultiple; var isSelect = isSelectOne || isSelectMultiple; this._elementType = elementType; this._isTextElement = isText; this._isSelectOneElement = isSelectOne; this._isSelectMultipleElement = isSelectMultiple; this._isSelectElement = isSelectOne || isSelectMultiple; this._canAddUserChoices = (isText && config.addItems) || (isSelect && config.addChoices); if (typeof config.renderSelectedChoices !== 'boolean') { config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne; } if (config.closeDropdownOnSelect === 'auto') { config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect; } else { config.closeDropdownOnSelect = coerceBool(config.closeDropdownOnSelect); } if (config.placeholder) { if (config.placeholderValue) { this._hasNonChoicePlaceholder = true; } else if (passedElement.dataset.placeholder) { this._hasNonChoicePlaceholder = true; config.placeholderValue = passedElement.dataset.placeholder; } } if (userConfig.addItemFilter && typeof userConfig.addItemFilter !== 'function') { var re = userConfig.addItemFilter instanceof RegExp ? userConfig.addItemFilter : new RegExp(userConfig.addItemFilter); config.addItemFilter = re.test.bind(re); } if (this._isTextElement) { this.passedElement = new WrappedInput({ element: passedElement, classNames: config.classNames, }); } else { var selectEl = passedElement; this.passedElement = new WrappedSelect({ element: selectEl, classNames: config.classNames, template: function (data) { return _this._templates.option(data); }, extractPlaceholder: config.placeholder && !this._hasNonChoicePlaceholder, }); } this.initialised = false; this._store = new Store(config); this._currentValue = ''; config.searchEnabled = !isText && config.searchEnabled; this._canSearch = config.searchEnabled; this._isScrollingOnIe = false; this._highlightPosition = 0; this._wasTap = true; this._placeholderValue = this._generatePlaceholderValue(); this._baseId = generateId(passedElement, 'choices-'); /** * setting direction in cases where it's explicitly set on passedElement * or when calculated direction is different from the document */ this._direction = passedElement.dir; if (!this._direction) { var elementDirection = window.getComputedStyle(passedElement).direction; var documentDirection = window.getComputedStyle(document.documentElement).direction; if (elementDirection !== documentDirection) { this._direction = elementDirection; } } this._idNames = { itemChoice: 'item-choice', }; this._templates = defaults.templates; this._render = this._render.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onKeyUp = this._onKeyUp.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onInput = this._onInput.bind(this); this._onClick = this._onClick.bind(this); this._onTouchMove = this._onTouchMove.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseOver = this._onMouseOver.bind(this); this._onFormReset = this._onFormReset.bind(this); this._onSelectKey = this._onSelectKey.bind(this); this._onEnterKey = this._onEnterKey.bind(this); this._onEscapeKey = this._onEscapeKey.bind(this); this._onDirectionKey = this._onDirectionKey.bind(this); this._onDeleteKey = this._onDeleteKey.bind(this); this._onChange = this._onChange.bind(this); this._onInvalid = this._onInvalid.bind(this); // If element has already been initialised with Choices, fail silently if (this.passedElement.isActive) { if (!config.silent) { console.warn('Trying to initialise Choices on element already initialised', { element: element }); } this.initialised = true; this.initialisedOK = false; return; } // Let's go this.init(); // preserve the selected item list after setup for form reset this._initialItems = this._store.items.map(function (choice) { return choice.value; }); } Object.defineProperty(Choices, "defaults", { get: function () { return Object.preventExtensions({ get options() { return USER_DEFAULTS; }, get allOptions() { return DEFAULT_CONFIG; }, get templates() { return templates; }, }); }, enumerable: false, configurable: true }); Choices.prototype.init = function () { if (this.initialised || this.initialisedOK !== undefined) { return; } this._searcher = getSearcher(this.config); this._loadChoices(); this._createTemplates(); this._createElements(); this._createStructure(); if ((this._isTextElement && !this.config.addItems) || this.passedElement.element.hasAttribute('disabled') || !!this.passedElement.element.closest('fieldset:disabled')) { this.disable(); } else { this.enable(); this._addEventListeners(); } // should be triggered **after** disabled state to avoid additional re-draws this._initStore(); this.initialised = true; this.initialisedOK = true; var callbackOnInit = this.config.callbackOnInit; // Run callback if it is a function if (typeof callbackOnInit === 'function') { callbackOnInit.call(this); } }; Choices.prototype.destroy = function () { if (!this.initialised) { return; } this._removeEventListeners(); this.passedElement.reveal(); this.containerOuter.unwrap(this.passedElement.element); this._store._listeners = []; // prevents select/input value being wiped this.clearStore(false); this._stopSearch(); this._templates = Choices.defaults.templates; this.initialised = false; this.initialisedOK = undefined; }; Choices.prototype.enable = function () { if (this.passedElement.isDisabled) { this.passedElement.enable(); } if (this.containerOuter.isDisabled) { this._addEventListeners(); this.input.enable(); this.containerOuter.enable(); } return this; }; Choices.prototype.disable = function () { if (!this.passedElement.isDisabled) { this.passedElement.disable(); } if (!this.containerOuter.isDisabled) { this._removeEventListeners(); this.input.disable(); this.containerOuter.disable(); } return this; }; Choices.prototype.highlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, true)); if (runEvent) { this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.unhighlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || !choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, false)); if (runEvent) { this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.highlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (!item.highlighted) { _this._store.dispatch(highlightItem(item, true)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.unhighlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (item.highlighted) { _this._store.dispatch(highlightItem(item, false)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.removeActiveItemsByValue = function (value) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (item) { return item.value === value; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeActiveItems = function (excludedId) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (_a) { var id = _a.id; return id !== excludedId; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeHighlightedItems = function (runEvent) { var _this = this; if (runEvent === void 0) { runEvent = false; } this._store.withTxn(function () { _this._store.highlightedActiveItems.forEach(function (item) { _this._removeItem(item); // If this action was performed by the user // trigger the event if (runEvent) { _this._triggerChange(item.value); } }); }); return this; }; Choices.prototype.showDropdown = function (preventInputFocus) { var _this = this; if (this.dropdown.isActive) { return this; } if (preventInputFocus === undefined) { // eslint-disable-next-line no-param-reassign preventInputFocus = !this._canSearch; } requestAnimationFrame(function () { _this.dropdown.show(); var rect = _this.dropdown.element.getBoundingClientRect(); _this.containerOuter.open(rect.bottom, rect.height); if (!preventInputFocus) { _this.input.focus(); } _this.passedElement.triggerEvent(EventType.showDropdown); var activeElement = _this.choiceList.element.querySelector(getClassNamesSelector(_this.config.classNames.selectedState)); if (activeElement !== null && !isScrolledIntoView(activeElement, _this.choiceList.element)) { // We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll. activeElement.scrollIntoView(); } }); return this; }; Choices.prototype.hideDropdown = function (preventInputBlur) { var _this = this; if (!this.dropdown.isActive) { return this; } this._removeHighlightedChoices(); requestAnimationFrame(function () { _this.dropdown.hide(); _this.containerOuter.close(); if (!preventInputBlur && _this._canSearch) { _this.input.removeActiveDescendant(); _this.input.blur(); } _this.passedElement.triggerEvent(EventType.hideDropdown); }); return this; }; Choices.prototype.getValue = function (valueOnly) { var values = this._store.items.map(function (item) { return (valueOnly ? item.value : getChoiceForOutput(item)); }); return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values; }; Choices.prototype.setValue = function (items) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setValue'); return this; } this._store.withTxn(function () { items.forEach(function (value) { if (value) { _this._addChoice(mapInputToChoice(value, false)); } }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.setChoiceByValue = function (value) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoiceByValue'); return this; } if (this._isTextElement) { return this; } this._store.withTxn(function () { // If only one value has been passed, convert to array var choiceValue = Array.isArray(value) ? value : [value]; // Loop through each value and choiceValue.forEach(function (val) { return _this._findAndSelectChoiceByValue(val); }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ Choices.prototype.setChoices = function (choicesArrayOrFetcher, value, label, replaceChoices, clearSearchFlag, replaceItems) { var _this = this; if (choicesArrayOrFetcher === void 0) { choicesArrayOrFetcher = []; } if (value === void 0) { value = 'value'; } if (label === void 0) { label = 'label'; } if (replaceChoices === void 0) { replaceChoices = false; } if (clearSearchFlag === void 0) { clearSearchFlag = true; } if (replaceItems === void 0) { replaceItems = false; } if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoices'); return this; } if (!this._isSelectElement) { throw new TypeError("setChoices can't be used with INPUT based Choices"); } if (typeof value !== 'string' || !value) { throw new TypeError("value parameter must be a name of 'value' field in passed objects"); } if (typeof choicesArrayOrFetcher === 'function') { // it's a choices fetcher function var fetcher_1 = choicesArrayOrFetcher(this); if (typeof Promise === 'function' && fetcher_1 instanceof Promise) { // that's a promise // eslint-disable-next-line no-promise-executor-return return new Promise(function (resolve) { return requestAnimationFrame(resolve); }) .then(function () { return _this._handleLoadingState(true); }) .then(function () { return fetcher_1; }) .then(function (data) { return _this.setChoices(data, value, label, replaceChoices, clearSearchFlag, replaceItems); }) .catch(function (err) { if (!_this.config.silent) { console.error(err); } }) .then(function () { return _this._handleLoadingState(false); }) .then(function () { return _this; }); } // function returned something else than promise, let's check if it's an array of choices if (!Array.isArray(fetcher_1)) { throw new TypeError(".setChoices first argument function must return either array of choices or Promise, got: ".concat(typeof fetcher_1)); } // recursion with results, it's sync and choices were cleared already return this.setChoices(fetcher_1, value, label, false); } if (!Array.isArray(choicesArrayOrFetcher)) { throw new TypeError(".setChoices must be called either with array of choices with a function resulting into Promise of array of choices"); } this.containerOuter.removeLoadingState(); this._store.withTxn(function () { if (clearSearchFlag) { _this._isSearching = false; } // Clear choices if needed if (replaceChoices) { _this.clearChoices(true, replaceItems); } var isDefaultValue = value === 'value'; var isDefaultLabel = label === 'label'; choicesArrayOrFetcher.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { var group = groupOrChoice; if (!isDefaultLabel) { group = __assign(__assign({}, group), { label: group[label] }); } _this._addGroup(mapInputToChoice(group, true)); } else { var choice = groupOrChoice; if (!isDefaultLabel || !isDefaultValue) { choice = __assign(__assign({}, choice), { value: choice[value], label: choice[label] }); } var choiceFull = mapInputToChoice(choice, false); _this._addChoice(choiceFull); if (choiceFull.placeholder && !_this._hasNonChoicePlaceholder) { _this._placeholderValue = unwrapStringForEscaped(choiceFull.label); } } }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.refresh = function (withEvents, selectFirstOption, deselectAll) { var _this = this; if (withEvents === void 0) { withEvents = false; } if (selectFirstOption === void 0) { selectFirstOption = false; } if (deselectAll === void 0) { deselectAll = false; } if (!this._isSelectElement) { if (!this.config.silent) { console.warn('refresh method can only be used on choices backed by a element'); } return this; } this._store.withTxn(function () { var choicesFromOptions = _this.passedElement.optionsAsChoices(); // Build the list of items which require preserving var existingItems = {}; if (!deselectAll) { _this._store.items.forEach(function (choice) { if (choice.id && choice.active && choice.selected) { existingItems[choice.value] = true; } }); } _this.clearStore(false); var updateChoice = function (choice) { if (deselectAll) { _this._store.dispatch(removeItem$1(choice)); } else if (existingItems[choice.value]) { choice.selected = true; } }; choicesFromOptions.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { groupOrChoice.choices.forEach(updateChoice); return; } updateChoice(groupOrChoice); }); /* @todo only generate add events for the added options instead of all if (withEvents) { items.forEach((choice) => { if (existingItems[choice.value]) { this.passedElement.triggerEvent( EventType.removeItem, this._getChoiceForEvent(choice), ); } }); } */ // load new choices & items _this._addPredefinedChoices(choicesFromOptions, selectFirstOption, withEvents); // re-do search if required if (_this._isSearching) { _this._searchChoices(_this.input.value); } }); return this; }; Choices.prototype.removeChoice = function (value) { var choice = this._store.choices.find(function (c) { return c.value === value; }); if (!choice) { return this; } this._clearNotice(); this._store.dispatch(removeChoice(choice)); // @todo integrate with Store this._searcher.reset(); if (choice.selected) { this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.clearChoices = function (clearOptions, clearItems) { var _this = this; if (clearOptions === void 0) { clearOptions = true; } if (clearItems === void 0) { clearItems = false; } if (clearOptions) { if (clearItems) { this.passedElement.element.replaceChildren(''); } else { this.passedElement.element.querySelectorAll(':not([selected])').forEach(function (el) { el.remove(); }); } } this.itemList.element.replaceChildren(''); this.choiceList.element.replaceChildren(''); this._clearNotice(); this._store.withTxn(function () { var items = clearItems ? [] : _this._store.items; _this._store.reset(); items.forEach(function (item) { _this._store.dispatch(addChoice(item)); _this._store.dispatch(addItem(item)); }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.clearStore = function (clearOptions) { if (clearOptions === void 0) { clearOptions = true; } this.clearChoices(clearOptions, true); this._stopSearch(); this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; return this; }; Choices.prototype.clearInput = function () { var shouldSetInputWidth = !this._isSelectOneElement; this.input.clear(shouldSetInputWidth); this._stopSearch(); return this; }; Choices.prototype._validateConfig = function () { var config = this.config; var invalidConfigOptions = diff(config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); } if (config.allowHTML && config.allowHtmlUserInput) { if (config.addItems) { console.warn('Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks'); } if (config.addChoices) { console.warn('Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks'); } } }; Choices.prototype._render = function (changes) { if (changes === void 0) { changes = { choices: true, groups: true, items: true }; } if (this._store.inTxn()) { return; } if (this._isSelectElement) { if (changes.choices || changes.groups) { this._renderChoices(); } } if (changes.items) { this._renderItems(); } }; Choices.prototype._renderChoices = function () { var _this = this; if (!this._canAddItems()) { return; // block rendering choices if the input limit is reached. } var _a = this, config = _a.config, isSearching = _a._isSearching; var _b = this._store, activeGroups = _b.activeGroups, activeChoices = _b.activeChoices; var renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit; if (this._isSelectElement) { var backingOptions = activeChoices.filter(function (choice) { return !choice.element; }); if (backingOptions.length) { this.passedElement.addOptions(backingOptions); } } var fragment = document.createDocumentFragment(); var renderableChoices = function (choices) { return choices.filter(function (choice) { return !choice.placeholder && (isSearching ? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank : config.renderSelectedChoices || !choice.selected); }); }; var showLabel = config.appendGroupInSearch && isSearching; var selectableChoices = false; var highlightedEl = null; var renderChoices = function (choices, withinGroup) { if (isSearching) { // sortByRank is used to ensure stable sorting, as scores are non-unique // this additionally ensures fuseOptions.sortFn is not ignored choices.sort(sortByRank); } else if (config.shouldSort) { choices.sort(config.sorter); } var choiceLimit = choices.length; choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit; choiceLimit--; choices.every(function (choice, index) { // choiceEl being empty signals the contents has probably significantly changed var dropdownItem = choice.choiceEl || _this._templates.choice(config, choice, config.itemSelectText, showLabel && choice.group ? choice.group.label : undefined); choice.choiceEl = dropdownItem; fragment.appendChild(dropdownItem); if (isSearching || !choice.selected) { selectableChoices = true; } else if (!highlightedEl) { highlightedEl = dropdownItem; } return index < choiceLimit; }); }; if (activeChoices.length) { if (config.resetScrollPosition) { requestAnimationFrame(function () { return _this.choiceList.scrollToTop(); }); } if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) { // If we have a placeholder choice along with groups renderChoices(activeChoices.filter(function (choice) { return choice.placeholder && !choice.group; }), false); } // If we have grouped options if (activeGroups.length && !isSearching) { if (config.shouldSort) { activeGroups.sort(config.sorter); } // render Choices without group first, regardless of sort, otherwise they won't be distinguishable // from the last group renderChoices(activeChoices.filter(function (choice) { return !choice.placeholder && !choice.group; }), false); activeGroups.forEach(function (group) { var groupChoices = renderableChoices(group.choices); if (groupChoices.length) { if (group.label) { var dropdownGroup = group.groupEl || _this._templates.choiceGroup(_this.config, group); group.groupEl = dropdownGroup; dropdownGroup.remove(); fragment.appendChild(dropdownGroup); } renderChoices(groupChoices, true); } }); } else { renderChoices(renderableChoices(activeChoices), false); } } if (!selectableChoices && (isSearching || !fragment.children.length || !config.renderSelectedChoices)) { if (!this._notice) { this._notice = { text: resolveStringFunction(isSearching ? config.noResultsText : config.noChoicesText), type: isSearching ? NoticeTypes.noResults : NoticeTypes.noChoices, }; } fragment.replaceChildren(''); } this._renderNotice(fragment); this.choiceList.element.replaceChildren(fragment); this._highlightChoice(highlightedEl); }; Choices.prototype._renderItems = function () { var _this = this; var items = this._store.items || []; var itemList = this.itemList.element; var config = this.config; var fragment = document.createDocumentFragment(); var itemFromList = function (item) { return itemList.querySelector("[data-item][data-id=\"".concat(item.id, "\"]")); }; var addItemToFragment = function (item) { var el = item.itemEl; if (el && el.parentElement) { return; } el = itemFromList(item) || _this._templates.item(config, item, config.removeItemButton); item.itemEl = el; fragment.appendChild(el); }; // new items items.forEach(addItemToFragment); var addedItems = !!fragment.childNodes.length; if (this._isSelectOneElement) { var existingItems = itemList.children.length; if (addedItems || existingItems > 1) { var placeholder = itemList.querySelector(getClassNamesSelector(config.classNames.placeholder)); if (placeholder) { placeholder.remove(); } } else if (!addedItems && !existingItems && this._placeholderValue) { addedItems = true; addItemToFragment(mapInputToChoice({ selected: true, value: '', label: this._placeholderValue, placeholder: true, }, false)); } } if (addedItems) { itemList.append(fragment); if (config.shouldSortItems && !this._isSelectOneElement) { items.sort(config.sorter); // push sorting into the DOM items.forEach(function (item) { var el = itemFromList(item); if (el) { el.remove(); fragment.append(el); } }); itemList.append(fragment); } } if (this._isTextElement) { // Update the value of the hidden input this.passedElement.value = items.map(function (_a) { var value = _a.value; return value; }).join(config.delimiter); } }; Choices.prototype._displayNotice = function (text, type, openDropdown) { if (openDropdown === void 0) { openDropdown = true; } var oldNotice = this._notice; if (oldNotice && ((oldNotice.type === type && oldNotice.text === text) || (oldNotice.type === NoticeTypes.addChoice && (type === NoticeTypes.noResults || type === NoticeTypes.noChoices)))) { if (openDropdown) { this.showDropdown(true); } return; } this._clearNotice(); this._notice = text ? { text: text, type: type, } : undefined; this._renderNotice(); if (openDropdown && text) { this.showDropdown(true); } }; Choices.prototype._clearNotice = function () { if (!this._notice) { return; } var noticeElement = this.choiceList.element.querySelector(getClassNamesSelector(this.config.classNames.notice)); if (noticeElement) { noticeElement.remove(); } this._notice = undefined; }; Choices.prototype._renderNotice = function (fragment) { var noticeConf = this._notice; if (noticeConf) { var notice = this._templates.notice(this.config, noticeConf.text, noticeConf.type); if (fragment) { fragment.append(notice); } else { this.choiceList.prepend(notice); } } }; /** * @deprecated Use utils.getChoiceForOutput */ // eslint-disable-next-line class-methods-use-this Choices.prototype._getChoiceForOutput = function (choice, keyCode) { return getChoiceForOutput(choice, keyCode); }; Choices.prototype._triggerChange = function (value) { if (value === undefined || value === null) { return; } this.passedElement.triggerEvent(EventType.change, { value: value, }); }; Choices.prototype._handleButtonAction = function (element) { var _this = this; var items = this._store.items; if (!items.length || !this.config.removeItems || !this.config.removeItemButton) { return; } var id = element && parseDataSetId(element.closest('[data-id]')); var itemToRemove = id && items.find(function (item) { return item.id === id; }); if (!itemToRemove) { return; } this._store.withTxn(function () { // Remove item associated with button _this._removeItem(itemToRemove); _this._triggerChange(itemToRemove.value); if (_this._isSelectOneElement && !_this._hasNonChoicePlaceholder) { var placeholderChoice = (_this.config.shouldSort ? _this._store.choices.reverse() : _this._store.choices).find(function (choice) { return choice.placeholder; }); if (placeholderChoice) { _this._addItem(placeholderChoice); _this.unhighlightAll(); if (placeholderChoice.value) { _this._triggerChange(placeholderChoice.value); } } } }); }; Choices.prototype._handleItemAction = function (element, hasShiftKey) { var _this = this; if (hasShiftKey === void 0) { hasShiftKey = false; } var items = this._store.items; if (!items.length || !this.config.removeItems || this._isSelectOneElement) { return; } var id = parseDataSetId(element); if (!id) { return; } // We only want to select one item with a click // so we deselect any items that aren't the target // unless shift is being pressed items.forEach(function (item) { if (item.id === id && !item.highlighted) { _this.highlightItem(item); } else if (!hasShiftKey && item.highlighted) { _this.unhighlightItem(item); } }); // Focus input as without focus, a user cannot do anything with a // highlighted item this.input.focus(); }; Choices.prototype._handleChoiceAction = function (element) { var _this = this; // If we are clicking on an option var id = parseDataSetId(element); var choice = id && this._store.getChoiceById(id); if (!choice || choice.disabled) { return false; } var hasActiveDropdown = this.dropdown.isActive; if (!choice.selected) { if (!this._canAddItems()) { return true; // causes _onEnterKey to early out } this._store.withTxn(function () { _this._addItem(choice, true, true); _this.clearInput(); _this.unhighlightAll(); }); this._triggerChange(choice.value); } // We want to close the dropdown if we are dealing with a single select box if (hasActiveDropdown && this.config.closeDropdownOnSelect) { this.hideDropdown(true); this.containerOuter.element.focus(); } return true; }; Choices.prototype._handleBackspace = function (items) { var config = this.config; if (!config.removeItems || !items.length) { return; } var lastItem = items[items.length - 1]; var hasHighlightedItems = items.some(function (item) { return item.highlighted; }); // If editing the last item is allowed and there are not other selected items, // we can edit the item value. Otherwise if we can remove items, remove all selected items if (config.editItems && !hasHighlightedItems && lastItem) { this.input.value = lastItem.value; this.input.setWidth(); this._removeItem(lastItem); this._triggerChange(lastItem.value); } else { if (!hasHighlightedItems) { // Highlight last item if none already highlighted this.highlightItem(lastItem, false); } this.removeHighlightedItems(true); } }; Choices.prototype._loadChoices = function () { var _a; var _this = this; var config = this.config; if (this._isTextElement) { // Assign preset items from passed object first this._presetChoices = config.items.map(function (e) { return mapInputToChoice(e, false); }); // Add any values passed from attribute if (this.passedElement.value) { var elementItems = this.passedElement.value .split(config.delimiter) .map(function (e) { return mapInputToChoice(e, false, _this.config.allowHtmlUserInput); }); this._presetChoices = this._presetChoices.concat(elementItems); } this._presetChoices.forEach(function (choice) { choice.selected = true; }); } else if (this._isSelectElement) { // Assign preset choices from passed object this._presetChoices = config.choices.map(function (e) { return mapInputToChoice(e, true); }); // Create array of choices from option elements var choicesFromOptions = this.passedElement.optionsAsChoices(); if (choicesFromOptions) { (_a = this._presetChoices).push.apply(_a, choicesFromOptions); } } }; Choices.prototype._handleLoadingState = function (setLoading) { if (setLoading === void 0) { setLoading = true; } var el = this.itemList.element; if (setLoading) { this.disable(); this.containerOuter.addLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(this._templates.placeholder(this.config, this.config.loadingText)); } else { this.input.placeholder = this.config.loadingText; } } else { this.enable(); this.containerOuter.removeLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(''); this._render(); } else { this.input.placeholder = this._placeholderValue || ''; } } }; Choices.prototype._handleSearch = function (value) { if (!this.input.isFocussed) { return; } // Check that we have a value to search and the input was an alphanumeric character if (value !== null && typeof value !== 'undefined' && value.length >= this.config.searchFloor) { var resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; if (resultCount !== null) { // Trigger search event this.passedElement.triggerEvent(EventType.search, { value: value, resultCount: resultCount, }); } } else if (this._store.choices.some(function (option) { return !option.active; })) { this._stopSearch(); } }; Choices.prototype._canAddItems = function () { var config = this.config; var maxItemCount = config.maxItemCount, maxItemText = config.maxItemText; if (!config.singleModeForMultiSelect && maxItemCount > 0 && maxItemCount <= this._store.items.length) { this.choiceList.element.replaceChildren(''); this._notice = undefined; this._displayNotice(typeof maxItemText === 'function' ? maxItemText(maxItemCount) : maxItemText, NoticeTypes.addChoice); return false; } if (this._notice && this._notice.type === NoticeTypes.addChoice) { this._clearNotice(); } return true; }; Choices.prototype._canCreateItem = function (value) { var config = this.config; var canAddItem = true; var notice = ''; if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) { canAddItem = false; notice = resolveNoticeFunction(config.customAddItemText, value, undefined); } if (canAddItem) { var foundChoice = this._store.choices.find(function (choice) { return config.valueComparer(choice.value, value); }); if (foundChoice) { if (this._isSelectElement) { // for exact matches, do not prompt to add it as a custom choice this._displayNotice('', NoticeTypes.addChoice); return false; } if (!config.duplicateItemsAllowed) { canAddItem = false; notice = resolveNoticeFunction(config.uniqueItemText, value, undefined); } } } if (canAddItem) { notice = resolveNoticeFunction(config.addItemText, value, undefined); } if (notice) { this._displayNotice(notice, NoticeTypes.addChoice); } return canAddItem; }; Choices.prototype._searchChoices = function (value) { var newValue = value.trim().replace(/\s{2,}/, ' '); // signal input didn't change search if (!newValue.length || newValue === this._currentValue) { return null; } var searcher = this._searcher; if (searcher.isEmptyIndex()) { searcher.index(this._store.searchableChoices); } // If new value matches the desired length and is not the same as the current value with a space var results = searcher.search(newValue); this._currentValue = newValue; this._highlightPosition = 0; this._isSearching = true; var notice = this._notice; var noticeType = notice && notice.type; if (noticeType !== NoticeTypes.addChoice) { if (!results.length) { this._displayNotice(resolveStringFunction(this.config.noResultsText), NoticeTypes.noResults); } else { this._clearNotice(); } } this._store.dispatch(filterChoices(results)); return results.length; }; Choices.prototype._stopSearch = function () { if (this._isSearching) { this._currentValue = ''; this._isSearching = false; this._clearNotice(); this._store.dispatch(activateChoices(true)); this.passedElement.triggerEvent(EventType.search, { value: '', resultCount: 0, }); } }; Choices.prototype._addEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; // capture events - can cancel event processing or propagation documentElement.addEventListener('touchend', this._onTouchEnd, true); outerElement.addEventListener('keydown', this._onKeyDown, true); outerElement.addEventListener('mousedown', this._onMouseDown, true); // passive events - doesn't call `preventDefault` or `stopPropagation` documentElement.addEventListener('click', this._onClick, { passive: true }); documentElement.addEventListener('touchmove', this._onTouchMove, { passive: true, }); this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { passive: true, }); if (this._isSelectOneElement) { outerElement.addEventListener('focus', this._onFocus, { passive: true, }); outerElement.addEventListener('blur', this._onBlur, { passive: true, }); } inputElement.addEventListener('keyup', this._onKeyUp, { passive: true, }); inputElement.addEventListener('input', this._onInput, { passive: true, }); inputElement.addEventListener('focus', this._onFocus, { passive: true, }); inputElement.addEventListener('blur', this._onBlur, { passive: true, }); if (inputElement.form) { inputElement.form.addEventListener('reset', this._onFormReset, { passive: true, }); } if (passedElement.hasAttribute('required')) { passedElement.addEventListener('change', this._onChange, { passive: true, }); passedElement.addEventListener('invalid', this._onInvalid, { passive: true, }); } this.input.addEventListeners(); }; Choices.prototype._removeEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; documentElement.removeEventListener('touchend', this._onTouchEnd, true); outerElement.removeEventListener('keydown', this._onKeyDown, true); outerElement.removeEventListener('mousedown', this._onMouseDown, true); documentElement.removeEventListener('click', this._onClick); documentElement.removeEventListener('touchmove', this._onTouchMove); this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); if (this._isSelectOneElement) { outerElement.removeEventListener('focus', this._onFocus); outerElement.removeEventListener('blur', this._onBlur); } inputElement.removeEventListener('keyup', this._onKeyUp); inputElement.removeEventListener('input', this._onInput); inputElement.removeEventListener('focus', this._onFocus); inputElement.removeEventListener('blur', this._onBlur); if (inputElement.form) { inputElement.form.removeEventListener('reset', this._onFormReset); } if (passedElement.hasAttribute('required')) { passedElement.removeEventListener('change', this._onChange); passedElement.removeEventListener('invalid', this._onInvalid); } this.input.removeEventListeners(); }; Choices.prototype._onKeyDown = function (event) { var keyCode = event.keyCode; var hasActiveDropdown = this.dropdown.isActive; /* See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs https://stackoverflow.com/a/70866532 - "Unidentified" for mobile http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7) Logic: when a key event is sent, `event.key` represents its printable value _or_ one of a large list of special values indicating meta keys/functionality. In addition, key events for compose functionality contain a value of `Dead` when mid-composition. I can't quite verify it, but non-English IMEs may also be able to generate key codes for code points in the surrogate-pair range, which could potentially be seen as having key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that alone. Here, key.length === 1 means we know for sure the input was printable and not a special `key` value. When the length is greater than 1, it could be either a printable surrogate pair or a special `key` value. We can tell the difference by checking if the _character code_ value (not code point!) is in the "surrogate pair" range or not. We don't use .codePointAt because an invalid code point would return 65535, which wouldn't pass the >= 0x10000 check we would otherwise use. > ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points > of each plane are noncharacters: U+FFFE and U+FFFF on the BMP... */ var wasPrintableChar = event.key.length === 1 || (event.key.length === 2 && event.key.charCodeAt(0) >= 0xd800) || event.key === 'Unidentified'; /* We do not show the dropdown if focusing out with esc or navigating through input fields. An activated search can still be opened with any other key. */ if (!this._isTextElement && !hasActiveDropdown && keyCode !== KeyCodeMap.ESC_KEY && keyCode !== KeyCodeMap.TAB_KEY && keyCode !== KeyCodeMap.SHIFT_KEY) { this.showDropdown(); if (!this.input.isFocussed && wasPrintableChar) { /* We update the input value with the pressed key as the input was not focussed at the time of key press therefore does not have the value of the key. */ this.input.value += event.key; // browsers interpret a space as pagedown if (event.key === ' ') { event.preventDefault(); } } } switch (keyCode) { case KeyCodeMap.A_KEY: return this._onSelectKey(event, this.itemList.element.hasChildNodes()); case KeyCodeMap.ENTER_KEY: return this._onEnterKey(event, hasActiveDropdown); case KeyCodeMap.ESC_KEY: return this._onEscapeKey(event, hasActiveDropdown); case KeyCodeMap.UP_KEY: case KeyCodeMap.PAGE_UP_KEY: case KeyCodeMap.DOWN_KEY: case KeyCodeMap.PAGE_DOWN_KEY: return this._onDirectionKey(event, hasActiveDropdown); case KeyCodeMap.DELETE_KEY: case KeyCodeMap.BACK_KEY: return this._onDeleteKey(event, this._store.items, this.input.isFocussed); } }; Choices.prototype._onKeyUp = function ( /* event: KeyboardEvent */) { this._canSearch = this.config.searchEnabled; }; Choices.prototype._onInput = function ( /* event: InputEvent */) { var value = this.input.value; if (!value) { if (this._isTextElement) { this.hideDropdown(true); } else { this._stopSearch(); } return; } if (!this._canAddItems()) { return; } if (this._canSearch) { // do the search even if the entered text can not be added this._handleSearch(value); } if (!this._canAddUserChoices) { return; } // determine if a notice needs to be displayed for why a search result can't be added this._canCreateItem(value); if (this._isSelectElement) { this._highlightPosition = 0; // reset to select the notice and/or exact match this._highlightChoice(); } }; Choices.prototype._onSelectKey = function (event, hasItems) { // If CTRL + A or CMD + A have been pressed and there are items to select if ((event.ctrlKey || event.metaKey) && hasItems) { this._canSearch = false; var shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; if (shouldHightlightAll) { this.highlightAll(); } } }; Choices.prototype._onEnterKey = function (event, hasActiveDropdown) { var _this = this; var value = this.input.value; var target = event.target; event.preventDefault(); if (target && target.hasAttribute('data-button')) { this._handleButtonAction(target); return; } if (!hasActiveDropdown) { if (this._isSelectElement || this._notice) { this.showDropdown(); } return; } var highlightedChoice = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (highlightedChoice && this._handleChoiceAction(highlightedChoice)) { return; } if (!target || !value) { this.hideDropdown(true); return; } if (!this._canAddItems()) { return; } var addedItem = false; this._store.withTxn(function () { addedItem = _this._findAndSelectChoiceByValue(value, true); if (!addedItem) { if (!_this._canAddUserChoices) { return; } if (!_this._canCreateItem(value)) { return; } _this._addChoice(mapInputToChoice(value, false, _this.config.allowHtmlUserInput), true, true); addedItem = true; } _this.clearInput(); _this.unhighlightAll(); }); if (!addedItem) { return; } this._triggerChange(value); if (this.config.closeDropdownOnSelect) { this.hideDropdown(true); } }; Choices.prototype._onEscapeKey = function (event, hasActiveDropdown) { if (hasActiveDropdown) { event.stopPropagation(); this.hideDropdown(true); this._stopSearch(); this.containerOuter.element.focus(); } }; Choices.prototype._onDirectionKey = function (event, hasActiveDropdown) { var keyCode = event.keyCode; // If up or down key is pressed, traverse through options if (hasActiveDropdown || this._isSelectOneElement) { this.showDropdown(); this._canSearch = false; var directionInt = keyCode === KeyCodeMap.DOWN_KEY || keyCode === KeyCodeMap.PAGE_DOWN_KEY ? 1 : -1; var skipKey = event.metaKey || keyCode === KeyCodeMap.PAGE_DOWN_KEY || keyCode === KeyCodeMap.PAGE_UP_KEY; var nextEl = void 0; if (skipKey) { if (directionInt > 0) { nextEl = this.dropdown.element.querySelector("".concat(selectableChoiceIdentifier, ":last-of-type")); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } else { var currentEl = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (currentEl) { nextEl = getAdjacentEl(currentEl, selectableChoiceIdentifier, directionInt); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } if (nextEl) { // We prevent default to stop the cursor moving // when pressing the arrow if (!isScrolledIntoView(nextEl, this.choiceList.element, directionInt)) { this.choiceList.scrollToChildElement(nextEl, directionInt); } this._highlightChoice(nextEl); } // Prevent default to maintain cursor position whilst // traversing dropdown options event.preventDefault(); } }; Choices.prototype._onDeleteKey = function (event, items, hasFocusedInput) { // If backspace or delete key is pressed and the input has no value if (!this._isSelectOneElement && !event.target.value && hasFocusedInput) { this._handleBackspace(items); event.preventDefault(); } }; Choices.prototype._onTouchMove = function () { if (this._wasTap) { this._wasTap = false; } }; Choices.prototype._onTouchEnd = function (event) { var target = (event || event.touches[0]).target; var touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target); if (touchWasWithinContainer) { var containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; if (containerWasExactTarget) { if (this._isTextElement) { this.input.focus(); } else if (this._isSelectMultipleElement) { this.showDropdown(); } } // Prevents focus event firing event.stopPropagation(); } this._wasTap = true; }; /** * Handles mousedown event in capture mode for containetOuter.element */ Choices.prototype._onMouseDown = function (event) { var target = event.target; if (!(target instanceof Element)) { return; } // If we have our mouse down on the scrollbar and are on IE11... if (IS_IE11 && this.choiceList.element.contains(target)) { // check if click was on a scrollbar area var firstChoice = this.choiceList.element.firstElementChild; this._isScrollingOnIe = this._direction === 'ltr' ? event.offsetX >= firstChoice.offsetWidth : event.offsetX < firstChoice.offsetLeft; } if (target === this.input.element) { return; } var item = target.closest('[data-button],[data-item],[data-choice]'); if (item instanceof HTMLElement) { if ('button' in item.dataset) { this._handleButtonAction(item); } else if ('item' in item.dataset) { this._handleItemAction(item, event.shiftKey); } else if ('choice' in item.dataset) { this._handleChoiceAction(item); } } event.preventDefault(); }; /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ Choices.prototype._onMouseOver = function (_a) { var target = _a.target; if (target instanceof HTMLElement && 'choice' in target.dataset) { this._highlightChoice(target); } }; Choices.prototype._onClick = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var clickWasWithinContainer = containerOuter.element.contains(target); if (clickWasWithinContainer) { if (!this.dropdown.isActive && !containerOuter.isDisabled) { if (this._isTextElement) { if (document.activeElement !== this.input.element) { this.input.focus(); } } else { this.showDropdown(); containerOuter.element.focus(); } } else if (this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target)) { this.hideDropdown(); } } else { containerOuter.removeFocusState(); this.hideDropdown(true); this.unhighlightAll(); } }; Choices.prototype._onFocus = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var focusWasWithinContainer = target && containerOuter.element.contains(target); if (!focusWasWithinContainer) { return; } var targetIsInput = target === this.input.element; if (this._isTextElement) { if (targetIsInput) { containerOuter.addFocusState(); } } else if (this._isSelectMultipleElement) { if (targetIsInput) { this.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown containerOuter.addFocusState(); } } else { containerOuter.addFocusState(); if (targetIsInput) { this.showDropdown(true); } } }; Choices.prototype._onBlur = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var blurWasWithinContainer = target && containerOuter.element.contains(target); if (blurWasWithinContainer && !this._isScrollingOnIe) { if (target === this.input.element) { containerOuter.removeFocusState(); this.hideDropdown(true); if (this._isTextElement || this._isSelectMultipleElement) { this.unhighlightAll(); } } else if (target === this.containerOuter.element) { // Remove the focus state when the past outerContainer was the target containerOuter.removeFocusState(); // Also close the dropdown if search is disabled if (!this.config.searchEnabled) { this.hideDropdown(true); } } } else { // On IE11, clicking the scollbar blurs our input and thus // closes the dropdown. To stop this, we refocus our input // if we know we are on IE *and* are scrolling. this._isScrollingOnIe = false; this.input.element.focus(); } }; Choices.prototype._onFormReset = function () { var _this = this; this._store.withTxn(function () { _this.clearInput(); _this.hideDropdown(); _this.refresh(false, false, true); if (_this._initialItems.length) { _this.setChoiceByValue(_this._initialItems); } }); }; Choices.prototype._onChange = function (event) { if (!event.target.checkValidity()) { return; } this.containerOuter.removeInvalidState(); }; Choices.prototype._onInvalid = function () { this.containerOuter.addInvalidState(); }; /** * Removes any highlighted choice options */ Choices.prototype._removeHighlightedChoices = function () { var highlightedState = this.config.classNames.highlightedState; var highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(getClassNamesSelector(highlightedState))); // Remove any highlighted choices highlightedChoices.forEach(function (choice) { removeClassesFromElement(choice, highlightedState); choice.setAttribute('aria-selected', 'false'); }); }; Choices.prototype._highlightChoice = function (el) { if (el === void 0) { el = null; } var choices = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)); if (!choices.length) { return; } var passedEl = el; var highlightedState = this.config.classNames.highlightedState; this._removeHighlightedChoices(); if (passedEl) { this._highlightPosition = choices.indexOf(passedEl); } else { // Highlight choice based on last known highlight location if (choices.length > this._highlightPosition) { // If we have an option to highlight passedEl = choices[this._highlightPosition]; } else { // Otherwise highlight the option before passedEl = choices[choices.length - 1]; } if (!passedEl) { passedEl = choices[0]; } } addClassesToElement(passedEl, highlightedState); passedEl.setAttribute('aria-selected', 'true'); this.passedElement.triggerEvent(EventType.highlightChoice, { el: passedEl, }); if (this.dropdown.isActive) { // IE11 ignores aria-label and blocks virtual keyboard // if aria-activedescendant is set without a dropdown this.input.setActiveDescendant(passedEl.id); this.containerOuter.setActiveDescendant(passedEl.id); } }; Choices.prototype._addItem = function (item, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (!item.id) { throw new TypeError('item.id must be set before _addItem is called for a choice/item'); } if (this.config.singleModeForMultiSelect || this._isSelectOneElement) { this.removeActiveItems(item.id); } this._store.dispatch(addItem(item)); if (withEvents) { var eventChoice = getChoiceForOutput(item); this.passedElement.triggerEvent(EventType.addItem, eventChoice); if (userTriggered) { this.passedElement.triggerEvent(EventType.choice, eventChoice); } } }; Choices.prototype._removeItem = function (item) { if (!item.id) { return; } this._store.dispatch(removeItem$1(item)); var notice = this._notice; if (notice && notice.type === NoticeTypes.noChoices) { this._clearNotice(); } this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item)); }; Choices.prototype._addChoice = function (choice, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (choice.id) { throw new TypeError('Can not re-add a choice which has already been added'); } var config = this.config; if (!config.duplicateItemsAllowed && this._store.choices.find(function (c) { return config.valueComparer(c.value, choice.value); })) { return; } // Generate unique id, in-place update is required so chaining _addItem works as expected this._lastAddedChoiceId++; choice.id = this._lastAddedChoiceId; choice.elementId = "".concat(this._baseId, "-").concat(this._idNames.itemChoice, "-").concat(choice.id); var prependValue = config.prependValue, appendValue = config.appendValue; if (prependValue) { choice.value = prependValue + choice.value; } if (appendValue) { choice.value += appendValue.toString(); } if ((prependValue || appendValue) && choice.element) { choice.element.value = choice.value; } this._clearNotice(); this._store.dispatch(addChoice(choice)); if (choice.selected) { this._addItem(choice, withEvents, userTriggered); } }; Choices.prototype._addGroup = function (group, withEvents) { var _this = this; if (withEvents === void 0) { withEvents = true; } if (group.id) { throw new TypeError('Can not re-add a group which has already been added'); } this._store.dispatch(addGroup(group)); if (!group.choices) { return; } // add unique id for the group(s), and do not store the full list of choices in this group this._lastAddedGroupId++; group.id = this._lastAddedGroupId; group.choices.forEach(function (item) { item.group = group; if (group.disabled) { item.disabled = true; } _this._addChoice(item, withEvents); }); }; Choices.prototype._createTemplates = function () { var _this = this; var callbackOnCreateTemplates = this.config.callbackOnCreateTemplates; var userTemplates = {}; if (typeof callbackOnCreateTemplates === 'function') { userTemplates = callbackOnCreateTemplates.call(this, strToEl, escapeForTemplate, getClassNames); } var templating = {}; Object.keys(this._templates).forEach(function (name) { if (name in userTemplates) { templating[name] = userTemplates[name].bind(_this); } else { templating[name] = _this._templates[name].bind(_this); } }); this._templates = templating; }; Choices.prototype._createElements = function () { var templating = this._templates; var _a = this, config = _a.config, isSelectOneElement = _a._isSelectOneElement; var position = config.position, classNames = config.classNames; var elementType = this._elementType; this.containerOuter = new Container({ element: templating.containerOuter(config, this._direction, this._isSelectElement, isSelectOneElement, config.searchEnabled, elementType, config.labelId), classNames: classNames, type: elementType, position: position, }); this.containerInner = new Container({ element: templating.containerInner(config), classNames: classNames, type: elementType, position: position, }); this.input = new Input({ element: templating.input(config, this._placeholderValue), classNames: classNames, type: elementType, preventPaste: !config.paste, }); this.choiceList = new List({ element: templating.choiceList(config, isSelectOneElement), }); this.itemList = new List({ element: templating.itemList(config, isSelectOneElement), }); this.dropdown = new Dropdown({ element: templating.dropdown(config), classNames: classNames, type: elementType, }); }; Choices.prototype._createStructure = function () { var _a = this, containerInner = _a.containerInner, containerOuter = _a.containerOuter, passedElement = _a.passedElement; var dropdownElement = this.dropdown.element; // Hide original element passedElement.conceal(); // Wrap input in container preserving DOM ordering containerInner.wrap(passedElement.element); // Wrapper inner container with outer container containerOuter.wrap(containerInner.element); containerOuter.element.appendChild(containerInner.element); containerOuter.element.appendChild(dropdownElement); containerInner.element.appendChild(this.itemList.element); dropdownElement.appendChild(this.choiceList.element); if (this._isSelectOneElement) { this.input.placeholder = this.config.searchPlaceholderValue || ''; if (this.config.searchEnabled) { dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild); } } else { if (!this._isSelectMultipleElement || this.config.searchEnabled) { containerInner.element.appendChild(this.input.element); } if (this._placeholderValue) { this.input.placeholder = this._placeholderValue; } this.input.setWidth(); } this._highlightPosition = 0; this._isSearching = false; }; Choices.prototype._initStore = function () { var _this = this; this._store.subscribe(this._render).withTxn(function () { _this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false); }); if (!this._store.choices.length || (this._isSelectOneElement && this._hasNonChoicePlaceholder)) { this._render(); } }; Choices.prototype._addPredefinedChoices = function (choices, selectFirstOption, withEvents) { var _this = this; if (selectFirstOption === void 0) { selectFirstOption = false; } if (withEvents === void 0) { withEvents = true; } if (selectFirstOption) { /** * If there is a selected choice already or the choice is not the first in * the array, add each choice normally. * * Otherwise we pre-select the first enabled choice in the array ("select-one" only) */ var noSelectedChoices = choices.findIndex(function (choice) { return choice.selected; }) === -1; if (noSelectedChoices) { choices.some(function (choice) { if (choice.disabled || 'choices' in choice) { return false; } choice.selected = true; return true; }); } } choices.forEach(function (item) { if ('choices' in item) { if (_this._isSelectElement) { _this._addGroup(item, withEvents); } } else { _this._addChoice(item, withEvents); } }); }; Choices.prototype._findAndSelectChoiceByValue = function (value, userTriggered) { var _this = this; if (userTriggered === void 0) { userTriggered = false; } // Check 'value' property exists and the choice isn't already selected var foundChoice = this._store.choices.find(function (choice) { return _this.config.valueComparer(choice.value, value); }); if (foundChoice && !foundChoice.disabled && !foundChoice.selected) { this._addItem(foundChoice, true, userTriggered); return true; } return false; }; Choices.prototype._generatePlaceholderValue = function () { var config = this.config; if (!config.placeholder) { return null; } if (this._hasNonChoicePlaceholder) { return config.placeholderValue; } if (this._isSelectElement) { var placeholderOption = this.passedElement.placeholderOption; return placeholderOption ? placeholderOption.text : null; } return null; }; Choices.prototype._warnChoicesInitFailed = function (caller) { if (this.config.silent) { return; } if (!this.initialised) { throw new TypeError("".concat(caller, " called on a non-initialised instance of Choices")); } else if (!this.initialisedOK) { throw new TypeError("".concat(caller, " called for an element which has multiple instances of Choices initialised on it")); } }; Choices.version = '11.2.1'; return Choices; }()); return Choices; })); ================================================ FILE: public/assets/scripts/choices.search-basic.mjs ================================================ /*! choices.js v11.2.1 | © 2026 Josh Johnson | https://github.com/Choices-js/Choices#readme */ /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var ActionType = { ADD_CHOICE: 'ADD_CHOICE', REMOVE_CHOICE: 'REMOVE_CHOICE', FILTER_CHOICES: 'FILTER_CHOICES', ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', CLEAR_CHOICES: 'CLEAR_CHOICES', ADD_GROUP: 'ADD_GROUP', ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', }; var EventType = { showDropdown: 'showDropdown', hideDropdown: 'hideDropdown', change: 'change', choice: 'choice', search: 'search', addItem: 'addItem', removeItem: 'removeItem', highlightItem: 'highlightItem', highlightChoice: 'highlightChoice', unhighlightItem: 'unhighlightItem', }; var KeyCodeMap = { TAB_KEY: 9, SHIFT_KEY: 16, BACK_KEY: 46, DELETE_KEY: 8, ENTER_KEY: 13, A_KEY: 65, ESC_KEY: 27, UP_KEY: 38, DOWN_KEY: 40, PAGE_UP_KEY: 33, PAGE_DOWN_KEY: 34, }; var ObjectsInConfig = ['fuseOptions', 'classNames']; var PassedElementTypes = { Text: 'text', SelectOne: 'select-one', SelectMultiple: 'select-multiple', }; var addChoice = function (choice) { return ({ type: ActionType.ADD_CHOICE, choice: choice, }); }; var removeChoice = function (choice) { return ({ type: ActionType.REMOVE_CHOICE, choice: choice, }); }; var filterChoices = function (results) { return ({ type: ActionType.FILTER_CHOICES, results: results, }); }; var activateChoices = function (active) { return ({ type: ActionType.ACTIVATE_CHOICES, active: active, }); }; var addGroup = function (group) { return ({ type: ActionType.ADD_GROUP, group: group, }); }; var addItem = function (item) { return ({ type: ActionType.ADD_ITEM, item: item, }); }; var removeItem$1 = function (item) { return ({ type: ActionType.REMOVE_ITEM, item: item, }); }; var highlightItem = function (item, highlighted) { return ({ type: ActionType.HIGHLIGHT_ITEM, item: item, highlighted: highlighted, }); }; var getRandomNumber = function (min, max) { return Math.floor(Math.random() * (max - min) + min); }; var generateChars = function (length) { return Array.from({ length: length }, function () { return getRandomNumber(0, 36).toString(36); }).join(''); }; var generateId = function (element, prefix) { var id = element.id || (element.name && "".concat(element.name, "-").concat(generateChars(2))) || generateChars(4); id = id.replace(/(:|\.|\[|\]|,)/g, ''); id = "".concat(prefix, "-").concat(id); return id; }; var getAdjacentEl = function (startEl, selector, direction) { if (direction === void 0) { direction = 1; } var prop = "".concat(direction > 0 ? 'next' : 'previous', "ElementSibling"); var sibling = startEl[prop]; while (sibling) { if (sibling.matches(selector)) { return sibling; } sibling = sibling[prop]; } return null; }; var isScrolledIntoView = function (element, parent, direction) { if (direction === void 0) { direction = 1; } var isVisible; if (direction > 0) { // In view from bottom isVisible = parent.scrollTop + parent.offsetHeight >= element.offsetTop + element.offsetHeight; } else { // In view from top isVisible = element.offsetTop >= parent.scrollTop; } return isVisible; }; var sanitise = function (value) { if (typeof value !== 'string') { if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { if ('raw' in value) { return sanitise(value.raw); } if ('trusted' in value) { return value.trusted; } } return value; } return value .replace(/&/g, '&') .replace(/>/g, '>') .replace(/= 0 && !window.matchMedia("(min-height: ".concat(dropdownPos + 1, "px)")).matches; } else if (this.position === 'top') { shouldFlip = true; } return shouldFlip; }; Container.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Container.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Container.prototype.open = function (dropdownPos, dropdownHeight) { addClassesToElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; if (this.shouldFlip(dropdownPos, dropdownHeight)) { addClassesToElement(this.element, this.classNames.flippedState); this.isFlipped = true; } }; Container.prototype.close = function () { removeClassesFromElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'false'); this.removeActiveDescendant(); this.isOpen = false; // A dropdown flips if it does not have space within the page if (this.isFlipped) { removeClassesFromElement(this.element, this.classNames.flippedState); this.isFlipped = false; } }; Container.prototype.addFocusState = function () { addClassesToElement(this.element, this.classNames.focusState); }; Container.prototype.removeFocusState = function () { removeClassesFromElement(this.element, this.classNames.focusState); }; Container.prototype.addInvalidState = function () { addClassesToElement(this.element, this.classNames.invalidState); }; Container.prototype.removeInvalidState = function () { removeClassesFromElement(this.element, this.classNames.invalidState); }; Container.prototype.enable = function () { removeClassesFromElement(this.element, this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; }; Container.prototype.disable = function () { addClassesToElement(this.element, this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; }; Container.prototype.wrap = function (element) { var el = this.element; var parentNode = element.parentNode; if (parentNode) { if (element.nextSibling) { parentNode.insertBefore(el, element.nextSibling); } else { parentNode.appendChild(el); } } el.appendChild(element); }; Container.prototype.unwrap = function (element) { var el = this.element; var parentNode = el.parentNode; if (parentNode) { // Move passed element outside this element parentNode.insertBefore(element, el); // Remove this element parentNode.removeChild(el); } }; Container.prototype.addLoadingState = function () { addClassesToElement(this.element, this.classNames.loadingState); this.element.setAttribute('aria-busy', 'true'); this.isLoading = true; }; Container.prototype.removeLoadingState = function () { removeClassesFromElement(this.element, this.classNames.loadingState); this.element.removeAttribute('aria-busy'); this.isLoading = false; }; return Container; }()); var Input = /** @class */ (function () { function Input(_a) { var element = _a.element, type = _a.type, classNames = _a.classNames, preventPaste = _a.preventPaste; this.element = element; this.type = type; this.classNames = classNames; this.preventPaste = preventPaste; this.isFocussed = this.element.isEqualNode(document.activeElement); this.isDisabled = element.disabled; this._onPaste = this._onPaste.bind(this); this._onInput = this._onInput.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } Object.defineProperty(Input.prototype, "placeholder", { set: function (placeholder) { this.element.placeholder = placeholder; }, enumerable: false, configurable: true }); Object.defineProperty(Input.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.value = value; }, enumerable: false, configurable: true }); Input.prototype.addEventListeners = function () { var el = this.element; el.addEventListener('paste', this._onPaste); el.addEventListener('input', this._onInput, { passive: true, }); el.addEventListener('focus', this._onFocus, { passive: true, }); el.addEventListener('blur', this._onBlur, { passive: true, }); }; Input.prototype.removeEventListeners = function () { var el = this.element; el.removeEventListener('input', this._onInput); el.removeEventListener('paste', this._onPaste); el.removeEventListener('focus', this._onFocus); el.removeEventListener('blur', this._onBlur); }; Input.prototype.enable = function () { var el = this.element; el.removeAttribute('disabled'); this.isDisabled = false; }; Input.prototype.disable = function () { var el = this.element; el.setAttribute('disabled', ''); this.isDisabled = true; }; Input.prototype.focus = function () { if (!this.isFocussed) { this.element.focus(); } }; Input.prototype.blur = function () { if (this.isFocussed) { this.element.blur(); } }; Input.prototype.clear = function (setWidth) { if (setWidth === void 0) { setWidth = true; } this.element.value = ''; if (setWidth) { this.setWidth(); } return this; }; /** * Set the correct input width based on placeholder * value or input value */ Input.prototype.setWidth = function () { // Resize input to contents or placeholder var element = this.element; element.style.minWidth = "".concat(element.placeholder.length + 1, "ch"); element.style.width = "".concat(element.value.length + 1, "ch"); }; Input.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Input.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Input.prototype._onInput = function () { if (this.type !== PassedElementTypes.SelectOne) { this.setWidth(); } }; Input.prototype._onPaste = function (event) { if (this.preventPaste) { event.preventDefault(); } }; Input.prototype._onFocus = function () { this.isFocussed = true; }; Input.prototype._onBlur = function () { this.isFocussed = false; }; return Input; }()); var SCROLLING_SPEED = 4; var List = /** @class */ (function () { function List(_a) { var element = _a.element; this.element = element; this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; } List.prototype.prepend = function (node) { var child = this.element.firstElementChild; if (child) { this.element.insertBefore(node, child); } else { this.element.append(node); } }; List.prototype.scrollToTop = function () { this.element.scrollTop = 0; }; List.prototype.scrollToChildElement = function (element, direction) { var _this = this; if (!element) { return; } var listHeight = this.element.offsetHeight; // Scroll position of dropdown var listScrollPosition = this.element.scrollTop + listHeight; var elementHeight = element.offsetHeight; // Distance from bottom of element to top of parent var elementPos = element.offsetTop + elementHeight; // Difference between the element and scroll position var destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop; requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); }; List.prototype._scrollDown = function (scrollPos, strength, destination) { var easing = (destination - scrollPos) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos + distance; }; List.prototype._scrollUp = function (scrollPos, strength, destination) { var easing = (scrollPos - destination) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos - distance; }; List.prototype._animateScroll = function (destination, direction) { var _this = this; var strength = SCROLLING_SPEED; var choiceListScrollTop = this.element.scrollTop; var continueAnimation = false; if (direction > 0) { this._scrollDown(choiceListScrollTop, strength, destination); if (choiceListScrollTop < destination) { continueAnimation = true; } } else { this._scrollUp(choiceListScrollTop, strength, destination); if (choiceListScrollTop > destination) { continueAnimation = true; } } if (continueAnimation) { requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); } }; return List; }()); var WrappedElement = /** @class */ (function () { function WrappedElement(_a) { var element = _a.element, classNames = _a.classNames; this.element = element; this.classNames = classNames; this.isDisabled = false; } Object.defineProperty(WrappedElement.prototype, "isActive", { get: function () { return this.element.dataset.choice === 'active'; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "dir", { get: function () { return this.element.dir; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.setAttribute('value', value); this.element.value = value; }, enumerable: false, configurable: true }); WrappedElement.prototype.conceal = function () { var el = this.element; // Hide passed input addClassesToElement(el, this.classNames.input); el.hidden = true; // Remove element from tab index el.tabIndex = -1; // Backup original styles if any var origStyle = el.getAttribute('style'); if (origStyle) { el.setAttribute('data-choice-orig-style', origStyle); } el.setAttribute('data-choice', 'active'); }; WrappedElement.prototype.reveal = function () { var el = this.element; // Reinstate passed element removeClassesFromElement(el, this.classNames.input); el.hidden = false; el.removeAttribute('tabindex'); // Recover original styles if any var origStyle = el.getAttribute('data-choice-orig-style'); if (origStyle) { el.removeAttribute('data-choice-orig-style'); el.setAttribute('style', origStyle); } else { el.removeAttribute('style'); } el.removeAttribute('data-choice'); }; WrappedElement.prototype.enable = function () { this.element.removeAttribute('disabled'); this.element.disabled = false; this.isDisabled = false; }; WrappedElement.prototype.disable = function () { this.element.setAttribute('disabled', ''); this.element.disabled = true; this.isDisabled = true; }; WrappedElement.prototype.triggerEvent = function (eventType, data) { dispatchEvent(this.element, eventType, data || {}); }; return WrappedElement; }()); var WrappedInput = /** @class */ (function (_super) { __extends(WrappedInput, _super); function WrappedInput() { return _super !== null && _super.apply(this, arguments) || this; } return WrappedInput; }(WrappedElement)); var coerceBool = function (arg, defaultValue) { if (defaultValue === void 0) { defaultValue = true; } return typeof arg === 'undefined' ? defaultValue : !!arg; }; var stringToHtmlClass = function (input) { if (typeof input === 'string') { // eslint-disable-next-line no-param-reassign input = input.split(' ').filter(function (s) { return s.length; }); } if (Array.isArray(input) && input.length) { return input; } return undefined; }; var mapInputToChoice = function (value, allowGroup, allowRawString) { if (allowRawString === void 0) { allowRawString = true; } if (typeof value === 'string') { var sanitisedValue = sanitise(value); var userValue = allowRawString || sanitisedValue === value ? value : { escaped: sanitisedValue, raw: value }; var result_1 = mapInputToChoice({ value: value, label: userValue, selected: true, }, false); return result_1; } var groupOrChoice = value; if ('choices' in groupOrChoice) { if (!allowGroup) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup throw new TypeError("optGroup is not allowed"); } var group = groupOrChoice; var choices = group.choices.map(function (e) { return mapInputToChoice(e, false); }); var result_2 = { id: 0, // actual ID will be assigned during _addGroup label: unwrapStringForRaw(group.label) || group.value, active: !!choices.length, disabled: !!group.disabled, choices: choices, }; return result_2; } var choice = groupOrChoice; var result = { id: 0, // actual ID will be assigned during _addChoice group: null, // actual group will be assigned during _addGroup but before _addChoice score: 0, // used in search rank: 0, // used in search, stable sort order value: choice.value, label: choice.label || choice.value, active: coerceBool(choice.active), selected: coerceBool(choice.selected, false), disabled: coerceBool(choice.disabled, false), placeholder: coerceBool(choice.placeholder, false), highlighted: false, labelClass: stringToHtmlClass(choice.labelClass), labelDescription: choice.labelDescription, customProperties: choice.customProperties, }; return result; }; var isHtmlInputElement = function (e) { return e.tagName === 'INPUT'; }; var isHtmlSelectElement = function (e) { return e.tagName === 'SELECT'; }; var isHtmlOption = function (e) { return e.tagName === 'OPTION'; }; var isHtmlOptgroup = function (e) { return e.tagName === 'OPTGROUP'; }; var WrappedSelect = /** @class */ (function (_super) { __extends(WrappedSelect, _super); function WrappedSelect(_a) { var element = _a.element, classNames = _a.classNames, template = _a.template, extractPlaceholder = _a.extractPlaceholder; var _this = _super.call(this, { element: element, classNames: classNames }) || this; _this.template = template; _this.extractPlaceholder = extractPlaceholder; return _this; } Object.defineProperty(WrappedSelect.prototype, "placeholderOption", { get: function () { return (this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]')); }, enumerable: false, configurable: true }); WrappedSelect.prototype.addOptions = function (choices) { var _this = this; var fragment = document.createDocumentFragment(); choices.forEach(function (obj) { var choice = obj; if (choice.element) { return; } var option = _this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); }; WrappedSelect.prototype.optionsAsChoices = function () { var _this = this; var choices = []; this.element.querySelectorAll(':scope > option, :scope > optgroup').forEach(function (e) { if (isHtmlOption(e)) { choices.push(_this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(_this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; }; // eslint-disable-next-line class-methods-use-this WrappedSelect.prototype._optionToChoice = function (option) { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? { trusted: option.dataset.labelDescription } : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; }; WrappedSelect.prototype._optgroupToChoice = function (optgroup) { var _this = this; var options = optgroup.querySelectorAll('option'); var choices = Array.from(options).map(function (option) { return _this._optionToChoice(option); }); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices: choices, }; }; return WrappedSelect; }(WrappedElement)); var DEFAULT_CLASSNAMES = { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], }; var DEFAULT_CONFIG = { items: [], choices: [], silent: false, renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: function (value) { return !!value && value !== ''; }, removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: function (value) { return "Press Enter to add \"".concat(value, "\""); }, removeItemIconText: function () { return "Remove item"; }, removeItemLabelText: function (value, _valueRaw, i) { return "Remove item: ".concat(i ? sanitise(i.label) : value); }, maxItemText: function (maxItemCount) { return "Only ".concat(maxItemCount, " values can be added"); }, valueComparer: function (value1, value2) { return value1 === value2; }, fuseOptions: { includeScore: true, }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, appendGroupInSearch: false, }; var removeItem = function (item) { var itemEl = item.itemEl; if (itemEl) { itemEl.remove(); item.itemEl = undefined; } }; function items(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_ITEM: { action.item.selected = true; var el = action.item.element; if (el) { el.selected = true; el.setAttribute('selected', ''); } state.push(action.item); break; } case ActionType.REMOVE_ITEM: { action.item.selected = false; var el = action.item.element; if (el) { el.selected = false; el.removeAttribute('selected'); // For a select-one, if all options are deselected, the first item is selected. To set a black value, select.value needs to be set var select = el.parentElement; if (select && isHtmlSelectElement(select) && select.type === PassedElementTypes.SelectOne) { select.value = ''; } } // this is mixing concerns, but this is *so much faster* removeItem(action.item); state = state.filter(function (choice) { return choice.id !== action.item.id; }); break; } case ActionType.REMOVE_CHOICE: { removeItem(action.choice); state = state.filter(function (item) { return item.id !== action.choice.id; }); break; } case ActionType.HIGHLIGHT_ITEM: { var highlighted = action.highlighted; var item = state.find(function (obj) { return obj.id === action.item.id; }); if (item && item.highlighted !== highlighted) { item.highlighted = highlighted; if (context) { updateClassList(item, highlighted ? context.classNames.highlightedState : context.classNames.selectedState, highlighted ? context.classNames.selectedState : context.classNames.highlightedState); } } break; } default: { update = false; break; } } return { state: state, update: update }; } function groups(s, action) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_GROUP: { state.push(action.group); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } /* eslint-disable */ function choices(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_CHOICE: { state.push(action.choice); break; } case ActionType.REMOVE_CHOICE: { action.choice.choiceEl = undefined; if (action.choice.group) { action.choice.group.choices = action.choice.group.choices.filter(function (obj) { return obj.id !== action.choice.id; }); } state = state.filter(function (obj) { return obj.id !== action.choice.id; }); break; } case ActionType.ADD_ITEM: case ActionType.REMOVE_ITEM: { action.item.choiceEl = undefined; break; } case ActionType.FILTER_CHOICES: { // avoid O(n^2) algorithm complexity when searching/filtering choices var scoreLookup_1 = []; action.results.forEach(function (result) { scoreLookup_1[result.item.id] = result; }); state.forEach(function (choice) { var result = scoreLookup_1[choice.id]; if (result !== undefined) { choice.score = result.score; choice.rank = result.rank; choice.active = true; } else { choice.score = 0; choice.rank = 0; choice.active = false; } if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.ACTIVATE_CHOICES: { state.forEach(function (choice) { choice.active = action.active; if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } var reducers = { groups: groups, items: items, choices: choices, }; var Store = /** @class */ (function () { function Store(context) { this._state = this.defaultState; this._listeners = []; this._txn = 0; this._context = context; } Object.defineProperty(Store.prototype, "defaultState", { // eslint-disable-next-line class-methods-use-this get: function () { return { groups: [], items: [], choices: [], }; }, enumerable: false, configurable: true }); // eslint-disable-next-line class-methods-use-this Store.prototype.changeSet = function (init) { return { groups: init, items: init, choices: init, }; }; Store.prototype.reset = function () { this._state = this.defaultState; var changes = this.changeSet(true); if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } }; Store.prototype.subscribe = function (onChange) { this._listeners.push(onChange); return this; }; Store.prototype.dispatch = function (action) { var _this = this; var state = this._state; var hasChanges = false; var changes = this._changeSet || this.changeSet(false); Object.keys(reducers).forEach(function (key) { var stateUpdate = reducers[key](state[key], action, _this._context); if (stateUpdate.update) { hasChanges = true; changes[key] = true; state[key] = stateUpdate.state; } }); if (hasChanges) { if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } } }; Store.prototype.withTxn = function (func) { this._txn++; try { func(); } finally { this._txn = Math.max(0, this._txn - 1); if (!this._txn) { var changeSet_1 = this._changeSet; if (changeSet_1) { this._changeSet = undefined; this._listeners.forEach(function (l) { return l(changeSet_1); }); } } } }; Object.defineProperty(Store.prototype, "state", { /** * Get store object */ get: function () { return this._state; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "items", { /** * Get items from store */ get: function () { return this.state.items; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "highlightedActiveItems", { /** * Get highlighted items from store */ get: function () { return this.items.filter(function (item) { return item.active && item.highlighted; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "choices", { /** * Get choices from store */ get: function () { return this.state.choices; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeChoices", { /** * Get active choices from store */ get: function () { return this.choices.filter(function (choice) { return choice.active; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "searchableChoices", { /** * Get choices that can be searched (excluding placeholders or disabled choices) */ get: function () { var context = this._context; return this.choices.filter(function (choice) { return !choice.placeholder && (context.searchDisabledChoices || !choice.disabled); }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "groups", { /** * Get groups from store */ get: function () { return this.state.groups; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeGroups", { /** * Get active groups from store */ get: function () { var _this = this; return this.state.groups.filter(function (group) { var isActive = group.active && !group.disabled; var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; }); return isActive && hasActiveOptions; }, []); }, enumerable: false, configurable: true }); Store.prototype.inTxn = function () { return this._txn > 0; }; /** * Get single choice by it's ID */ Store.prototype.getChoiceById = function (id) { return this.activeChoices.find(function (choice) { return choice.id === id; }); }; /** * Get group by group id */ Store.prototype.getGroupById = function (id) { return this.groups.find(function (group) { return group.id === id; }); }; return Store; }()); var NoticeTypes = { noChoices: 'no-choices', noResults: 'no-results', addChoice: 'add-choice', generic: '', }; function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } /** * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io) * * Copyright (c) 2023 Kiro Risk (http://kiro.me) * All Rights Reserved. Apache Software License 2.0 * * http://www.apache.org/licenses/LICENSE-2.0 */ function isArray(value) { return !Array.isArray ? getTag(value) === '[object Array]' : Array.isArray(value); } function baseToString(value) { // Exit early for strings to avoid a performance hit in some environments. if (typeof value == 'string') { return value; } let result = value + ''; return result == '0' && 1 / value == -Infinity ? '-0' : result; } function toString(value) { return value == null ? '' : baseToString(value); } function isString(value) { return typeof value === 'string'; } function isNumber(value) { return typeof value === 'number'; } // Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js function isBoolean(value) { return value === true || value === false || isObjectLike(value) && getTag(value) == '[object Boolean]'; } function isObject(value) { return typeof value === 'object'; } // Checks if `value` is object-like. function isObjectLike(value) { return isObject(value) && value !== null; } function isDefined(value) { return value !== undefined && value !== null; } function isBlank(value) { return !value.trim().length; } // Gets the `toStringTag` of `value`. // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js function getTag(value) { return value == null ? value === undefined ? '[object Undefined]' : '[object Null]' : Object.prototype.toString.call(value); } const EXTENDED_SEARCH_UNAVAILABLE = 'Extended search is not available'; const LOGICAL_SEARCH_UNAVAILABLE = 'Logical search is not available'; const INCORRECT_INDEX_TYPE = "Incorrect 'index' type"; const LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = key => `Invalid value for key ${key}`; const PATTERN_LENGTH_TOO_LARGE = max => `Pattern length exceeds max of ${max}.`; const MISSING_KEY_PROPERTY = name => `Missing ${name} property in key`; const INVALID_KEY_WEIGHT_VALUE = key => `Property 'weight' in key '${key}' must be a positive integer`; const hasOwn = Object.prototype.hasOwnProperty; class KeyStore { constructor(keys) { this._keys = []; this._keyMap = {}; let totalWeight = 0; keys.forEach(key => { let obj = createKey(key); this._keys.push(obj); this._keyMap[obj.id] = obj; totalWeight += obj.weight; }); // Normalize weights so that their sum is equal to 1 this._keys.forEach(key => { key.weight /= totalWeight; }); } get(keyId) { return this._keyMap[keyId]; } keys() { return this._keys; } toJSON() { return JSON.stringify(this._keys); } } function createKey(key) { let path = null; let id = null; let src = null; let weight = 1; let getFn = null; if (isString(key) || isArray(key)) { src = key; path = createKeyPath(key); id = createKeyId(key); } else { if (!hasOwn.call(key, 'name')) { throw new Error(MISSING_KEY_PROPERTY('name')); } const name = key.name; src = name; if (hasOwn.call(key, 'weight')) { weight = key.weight; if (weight <= 0) { throw new Error(INVALID_KEY_WEIGHT_VALUE(name)); } } path = createKeyPath(name); id = createKeyId(name); getFn = key.getFn; } return { path, id, weight, src, getFn }; } function createKeyPath(key) { return isArray(key) ? key : key.split('.'); } function createKeyId(key) { return isArray(key) ? key.join('.') : key; } function get(obj, path) { let list = []; let arr = false; const deepGet = (obj, path, index) => { if (!isDefined(obj)) { return; } if (!path[index]) { // If there's no path left, we've arrived at the object we care about. list.push(obj); } else { let key = path[index]; const value = obj[key]; if (!isDefined(value)) { return; } // If we're at the last value in the path, and if it's a string/number/bool, // add it to the list if (index === path.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) { list.push(toString(value)); } else if (isArray(value)) { arr = true; // Search each item in the array. for (let i = 0, len = value.length; i < len; i += 1) { deepGet(value[i], path, index + 1); } } else if (path.length) { // An object. Recurse further. deepGet(value, path, index + 1); } } }; // Backwards compatibility (since path used to be a string) deepGet(obj, isString(path) ? path.split('.') : path, 0); return arr ? list : list[0]; } const MatchOptions = { // Whether the matches should be included in the result set. When `true`, each record in the result // set will include the indices of the matched characters. // These can consequently be used for highlighting purposes. includeMatches: false, // When `true`, the matching function will continue to the end of a search pattern even if // a perfect match has already been located in the string. findAllMatches: false, // Minimum number of characters that must be matched before a result is considered a match minMatchCharLength: 1 }; const BasicOptions = { // When `true`, the algorithm continues searching to the end of the input even if a perfect // match is found before the end of the same input. isCaseSensitive: false, // When true, the matching function will continue to the end of a search pattern even if includeScore: false, // List of properties that will be searched. This also supports nested properties. keys: [], // Whether to sort the result list, by score shouldSort: true, // Default sort function: sort by ascending score, ascending index sortFn: (a, b) => a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1 }; const FuzzyOptions = { // Approximately where in the text is the pattern expected to be found? location: 0, // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match // (of both letters and location), a threshold of '1.0' would match anything. threshold: 0.6, // Determines how close the match must be to the fuzzy location (specified above). // An exact letter match which is 'distance' characters away from the fuzzy location // would score as a complete mismatch. A distance of '0' requires the match be at // the exact location specified, a threshold of '1000' would require a perfect match // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. distance: 100 }; const AdvancedOptions = { // When `true`, it enables the use of unix-like search commands useExtendedSearch: false, // The get function to use when fetching an object's properties. // The default will search nested paths *ie foo.bar.baz* getFn: get, // When `true`, search will ignore `location` and `distance`, so it won't matter // where in the string the pattern appears. // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score ignoreLocation: false, // When `true`, the calculation for the relevance score (used for sorting) will // ignore the field-length norm. // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm ignoreFieldNorm: false, // The weight to determine how much field length norm effects scoring. fieldNormWeight: 1 }; var Config = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, BasicOptions), MatchOptions), FuzzyOptions), AdvancedOptions); const SPACE = /[^ ]+/g; // Field-length norm: the shorter the field, the higher the weight. // Set to 3 decimals to reduce index size. function norm(weight = 1, mantissa = 3) { const cache = new Map(); const m = Math.pow(10, mantissa); return { get(value) { const numTokens = value.match(SPACE).length; if (cache.has(numTokens)) { return cache.get(numTokens); } // Default function is 1/sqrt(x), weight makes that variable const norm = 1 / Math.pow(numTokens, 0.5 * weight); // In place of `toFixed(mantissa)`, for faster computation const n = parseFloat(Math.round(norm * m) / m); cache.set(numTokens, n); return n; }, clear() { cache.clear(); } }; } class FuseIndex { constructor({ getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { this.norm = norm(fieldNormWeight, 3); this.getFn = getFn; this.isCreated = false; this.setIndexRecords(); } setSources(docs = []) { this.docs = docs; } setIndexRecords(records = []) { this.records = records; } setKeys(keys = []) { this.keys = keys; this._keysMap = {}; keys.forEach((key, idx) => { this._keysMap[key.id] = idx; }); } create() { if (this.isCreated || !this.docs.length) { return; } this.isCreated = true; // List is Array if (isString(this.docs[0])) { this.docs.forEach((doc, docIndex) => { this._addString(doc, docIndex); }); } else { // List is Array this.docs.forEach((doc, docIndex) => { this._addObject(doc, docIndex); }); } this.norm.clear(); } // Adds a doc to the end of the index add(doc) { const idx = this.size(); if (isString(doc)) { this._addString(doc, idx); } else { this._addObject(doc, idx); } } // Removes the doc at the specified index of the index removeAt(idx) { this.records.splice(idx, 1); // Change ref index of every subsquent doc for (let i = idx, len = this.size(); i < len; i += 1) { this.records[i].i -= 1; } } getValueForItemAtKeyId(item, keyId) { return item[this._keysMap[keyId]]; } size() { return this.records.length; } _addString(doc, docIndex) { if (!isDefined(doc) || isBlank(doc)) { return; } let record = { v: doc, i: docIndex, n: this.norm.get(doc) }; this.records.push(record); } _addObject(doc, docIndex) { let record = { i: docIndex, $: {} }; // Iterate over every key (i.e, path), and fetch the value at that key this.keys.forEach((key, keyIndex) => { let value = key.getFn ? key.getFn(doc) : this.getFn(doc, key.path); if (!isDefined(value)) { return; } if (isArray(value)) { let subRecords = []; const stack = [{ nestedArrIndex: -1, value }]; while (stack.length) { const { nestedArrIndex, value } = stack.pop(); if (!isDefined(value)) { continue; } if (isString(value) && !isBlank(value)) { let subRecord = { v: value, i: nestedArrIndex, n: this.norm.get(value) }; subRecords.push(subRecord); } else if (isArray(value)) { value.forEach((item, k) => { stack.push({ nestedArrIndex: k, value: item }); }); } else ; } record.$[keyIndex] = subRecords; } else if (isString(value) && !isBlank(value)) { let subRecord = { v: value, n: this.norm.get(value) }; record.$[keyIndex] = subRecord; } }); this.records.push(record); } toJSON() { return { keys: this.keys, records: this.records }; } } function createIndex(keys, docs, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { const myIndex = new FuseIndex({ getFn, fieldNormWeight }); myIndex.setKeys(keys.map(createKey)); myIndex.setSources(docs); myIndex.create(); return myIndex; } function parseIndex(data, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) { const { keys, records } = data; const myIndex = new FuseIndex({ getFn, fieldNormWeight }); myIndex.setKeys(keys); myIndex.setIndexRecords(records); return myIndex; } function computeScore$1(pattern, { errors = 0, currentLocation = 0, expectedLocation = 0, distance = Config.distance, ignoreLocation = Config.ignoreLocation } = {}) { const accuracy = errors / pattern.length; if (ignoreLocation) { return accuracy; } const proximity = Math.abs(expectedLocation - currentLocation); if (!distance) { // Dodge divide by zero error. return proximity ? 1.0 : accuracy; } return accuracy + proximity / distance; } function convertMaskToIndices(matchmask = [], minMatchCharLength = Config.minMatchCharLength) { let indices = []; let start = -1; let end = -1; let i = 0; for (let len = matchmask.length; i < len; i += 1) { let match = matchmask[i]; if (match && start === -1) { start = i; } else if (!match && start !== -1) { end = i - 1; if (end - start + 1 >= minMatchCharLength) { indices.push([start, end]); } start = -1; } } // (i-1 - start) + 1 => i - start if (matchmask[i - 1] && i - start >= minMatchCharLength) { indices.push([start, i - 1]); } return indices; } // Machine word size const MAX_BITS = 32; function search(text, pattern, patternAlphabet, { location = Config.location, distance = Config.distance, threshold = Config.threshold, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, includeMatches = Config.includeMatches, ignoreLocation = Config.ignoreLocation } = {}) { if (pattern.length > MAX_BITS) { throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS)); } const patternLen = pattern.length; // Set starting location at beginning text and initialize the alphabet. const textLen = text.length; // Handle the case when location > text.length const expectedLocation = Math.max(0, Math.min(location, textLen)); // Highest score beyond which we give up. let currentThreshold = threshold; // Is there a nearby exact match? (speedup) let bestLocation = expectedLocation; // Performance: only computer matches when the minMatchCharLength > 1 // OR if `includeMatches` is true. const computeMatches = minMatchCharLength > 1 || includeMatches; // A mask of the matches, used for building the indices const matchMask = computeMatches ? Array(textLen) : []; let index; // Get all exact matches, here for speed up while ((index = text.indexOf(pattern, bestLocation)) > -1) { let score = computeScore$1(pattern, { currentLocation: index, expectedLocation, distance, ignoreLocation }); currentThreshold = Math.min(score, currentThreshold); bestLocation = index + patternLen; if (computeMatches) { let i = 0; while (i < patternLen) { matchMask[index + i] = 1; i += 1; } } } // Reset the best location bestLocation = -1; let lastBitArr = []; let finalScore = 1; let binMax = patternLen + textLen; const mask = 1 << patternLen - 1; for (let i = 0; i < patternLen; i += 1) { // Scan for the best match; each iteration allows for one more error. // Run a binary search to determine how far from the match location we can stray // at this error level. let binMin = 0; let binMid = binMax; while (binMin < binMid) { const score = computeScore$1(pattern, { errors: i, currentLocation: expectedLocation + binMid, expectedLocation, distance, ignoreLocation }); if (score <= currentThreshold) { binMin = binMid; } else { binMax = binMid; } binMid = Math.floor((binMax - binMin) / 2 + binMin); } // Use the result from this iteration as the maximum for the next. binMax = binMid; let start = Math.max(1, expectedLocation - binMid + 1); let finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen; // Initialize the bit array let bitArr = Array(finish + 2); bitArr[finish + 1] = (1 << i) - 1; for (let j = finish; j >= start; j -= 1) { let currentLocation = j - 1; let charMatch = patternAlphabet[text.charAt(currentLocation)]; if (computeMatches) { // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`) matchMask[currentLocation] = +!!charMatch; } // First pass: exact match bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch; // Subsequent passes: fuzzy match if (i) { bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1]; } if (bitArr[j] & mask) { finalScore = computeScore$1(pattern, { errors: i, currentLocation, expectedLocation, distance, ignoreLocation }); // This match will almost certainly be better than any existing match. // But check anyway. if (finalScore <= currentThreshold) { // Indeed it is currentThreshold = finalScore; bestLocation = currentLocation; // Already passed `loc`, downhill from here on in. if (bestLocation <= expectedLocation) { break; } // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`. start = Math.max(1, 2 * expectedLocation - bestLocation); } } } // No hope for a (better) match at greater error levels. const score = computeScore$1(pattern, { errors: i + 1, currentLocation: expectedLocation, expectedLocation, distance, ignoreLocation }); if (score > currentThreshold) { break; } lastBitArr = bitArr; } const result = { isMatch: bestLocation >= 0, // Count exact matches (those with a score of 0) to be "almost" exact score: Math.max(0.001, finalScore) }; if (computeMatches) { const indices = convertMaskToIndices(matchMask, minMatchCharLength); if (!indices.length) { result.isMatch = false; } else if (includeMatches) { result.indices = indices; } } return result; } function createPatternAlphabet(pattern) { let mask = {}; for (let i = 0, len = pattern.length; i < len; i += 1) { const char = pattern.charAt(i); mask[char] = (mask[char] || 0) | 1 << len - i - 1; } return mask; } class BitapSearch { constructor(pattern, { location = Config.location, threshold = Config.threshold, distance = Config.distance, includeMatches = Config.includeMatches, findAllMatches = Config.findAllMatches, minMatchCharLength = Config.minMatchCharLength, isCaseSensitive = Config.isCaseSensitive, ignoreLocation = Config.ignoreLocation } = {}) { this.options = { location, threshold, distance, includeMatches, findAllMatches, minMatchCharLength, isCaseSensitive, ignoreLocation }; this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); this.chunks = []; if (!this.pattern.length) { return; } const addChunk = (pattern, startIndex) => { this.chunks.push({ pattern, alphabet: createPatternAlphabet(pattern), startIndex }); }; const len = this.pattern.length; if (len > MAX_BITS) { let i = 0; const remainder = len % MAX_BITS; const end = len - remainder; while (i < end) { addChunk(this.pattern.substr(i, MAX_BITS), i); i += MAX_BITS; } if (remainder) { const startIndex = len - MAX_BITS; addChunk(this.pattern.substr(startIndex), startIndex); } } else { addChunk(this.pattern, 0); } } searchIn(text) { const { isCaseSensitive, includeMatches } = this.options; if (!isCaseSensitive) { text = text.toLowerCase(); } // Exact match if (this.pattern === text) { let result = { isMatch: true, score: 0 }; if (includeMatches) { result.indices = [[0, text.length - 1]]; } return result; } // Otherwise, use Bitap algorithm const { location, distance, threshold, findAllMatches, minMatchCharLength, ignoreLocation } = this.options; let allIndices = []; let totalScore = 0; let hasMatches = false; this.chunks.forEach(({ pattern, alphabet, startIndex }) => { const { isMatch, score, indices } = search(text, pattern, alphabet, { location: location + startIndex, distance, threshold, findAllMatches, minMatchCharLength, includeMatches, ignoreLocation }); if (isMatch) { hasMatches = true; } totalScore += score; if (isMatch && indices) { allIndices = [...allIndices, ...indices]; } }); let result = { isMatch: hasMatches, score: hasMatches ? totalScore / this.chunks.length : 1 }; if (hasMatches && includeMatches) { result.indices = allIndices; } return result; } } const registeredSearchers = []; function createSearcher(pattern, options) { for (let i = 0, len = registeredSearchers.length; i < len; i += 1) { let searcherClass = registeredSearchers[i]; if (searcherClass.condition(pattern, options)) { return new searcherClass(pattern, options); } } return new BitapSearch(pattern, options); } const LogicalOperator = { AND: '$and', OR: '$or' }; const KeyType = { PATH: '$path', PATTERN: '$val' }; const isExpression = query => !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]); const isPath = query => !!query[KeyType.PATH]; const isLeaf = query => !isArray(query) && isObject(query) && !isExpression(query); const convertToExplicit = query => ({ [LogicalOperator.AND]: Object.keys(query).map(key => ({ [key]: query[key] })) }); // When `auto` is `true`, the parse function will infer and initialize and add // the appropriate `Searcher` instance function parse(query, options, { auto = true } = {}) { const next = query => { let keys = Object.keys(query); const isQueryPath = isPath(query); if (!isQueryPath && keys.length > 1 && !isExpression(query)) { return next(convertToExplicit(query)); } if (isLeaf(query)) { const key = isQueryPath ? query[KeyType.PATH] : keys[0]; const pattern = isQueryPath ? query[KeyType.PATTERN] : query[key]; if (!isString(pattern)) { throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key)); } const obj = { keyId: createKeyId(key), pattern }; if (auto) { obj.searcher = createSearcher(pattern, options); } return obj; } let node = { children: [], operator: keys[0] }; keys.forEach(key => { const value = query[key]; if (isArray(value)) { value.forEach(item => { node.children.push(next(item)); }); } }); return node; }; if (!isExpression(query)) { query = convertToExplicit(query); } return next(query); } // Practical scoring function function computeScore(results, { ignoreFieldNorm = Config.ignoreFieldNorm }) { results.forEach(result => { let totalScore = 1; result.matches.forEach(({ key, norm, score }) => { const weight = key ? key.weight : null; totalScore *= Math.pow(score === 0 && weight ? Number.EPSILON : score, (weight || 1) * (ignoreFieldNorm ? 1 : norm)); }); result.score = totalScore; }); } function transformMatches(result, data) { const matches = result.matches; data.matches = []; if (!isDefined(matches)) { return; } matches.forEach(match => { if (!isDefined(match.indices) || !match.indices.length) { return; } const { indices, value } = match; let obj = { indices, value }; if (match.key) { obj.key = match.key.src; } if (match.idx > -1) { obj.refIndex = match.idx; } data.matches.push(obj); }); } function transformScore(result, data) { data.score = result.score; } function format(results, docs, { includeMatches = Config.includeMatches, includeScore = Config.includeScore } = {}) { const transformers = []; if (includeMatches) transformers.push(transformMatches); if (includeScore) transformers.push(transformScore); return results.map(result => { const { idx } = result; const data = { item: docs[idx], refIndex: idx }; if (transformers.length) { transformers.forEach(transformer => { transformer(result, data); }); } return data; }); } class Fuse { constructor(docs, options = {}, index) { this.options = _objectSpread2(_objectSpread2({}, Config), options); if (this.options.useExtendedSearch && true) { throw new Error(EXTENDED_SEARCH_UNAVAILABLE); } this._keyStore = new KeyStore(this.options.keys); this.setCollection(docs, index); } setCollection(docs, index) { this._docs = docs; if (index && !(index instanceof FuseIndex)) { throw new Error(INCORRECT_INDEX_TYPE); } this._myIndex = index || createIndex(this.options.keys, this._docs, { getFn: this.options.getFn, fieldNormWeight: this.options.fieldNormWeight }); } add(doc) { if (!isDefined(doc)) { return; } this._docs.push(doc); this._myIndex.add(doc); } remove(predicate = ( /* doc, idx */) => false) { const results = []; for (let i = 0, len = this._docs.length; i < len; i += 1) { const doc = this._docs[i]; if (predicate(doc, i)) { this.removeAt(i); i -= 1; len -= 1; results.push(doc); } } return results; } removeAt(idx) { this._docs.splice(idx, 1); this._myIndex.removeAt(idx); } getIndex() { return this._myIndex; } search(query, { limit = -1 } = {}) { const { includeMatches, includeScore, shouldSort, sortFn, ignoreFieldNorm } = this.options; let results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query); computeScore(results, { ignoreFieldNorm }); if (shouldSort) { results.sort(sortFn); } if (isNumber(limit) && limit > -1) { results = results.slice(0, limit); } return format(results, this._docs, { includeMatches, includeScore }); } _searchStringList(query) { const searcher = createSearcher(query, this.options); const { records } = this._myIndex; const results = []; // Iterate over every string in the index records.forEach(({ v: text, i: idx, n: norm }) => { if (!isDefined(text)) { return; } const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { results.push({ item: text, idx, matches: [{ score, value: text, norm, indices }] }); } }); return results; } _searchLogical(query) { { throw new Error(LOGICAL_SEARCH_UNAVAILABLE); } } _searchObjectList(query) { const searcher = createSearcher(query, this.options); const { keys, records } = this._myIndex; const results = []; // List is Array records.forEach(({ $: item, i: idx }) => { if (!isDefined(item)) { return; } let matches = []; // Iterate over every key (i.e, path), and fetch the value at that key keys.forEach((key, keyIndex) => { matches.push(...this._findMatches({ key, value: item[keyIndex], searcher })); }); if (matches.length) { results.push({ idx, item, matches }); } }); return results; } _findMatches({ key, value, searcher }) { if (!isDefined(value)) { return []; } let matches = []; if (isArray(value)) { value.forEach(({ v: text, i: idx, n: norm }) => { if (!isDefined(text)) { return; } const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { matches.push({ score, key, value: text, idx, norm, indices }); } }); } else { const { v: text, n: norm } = value; const { isMatch, score, indices } = searcher.searchIn(text); if (isMatch) { matches.push({ score, key, value: text, norm, indices }); } } return matches; } } Fuse.version = '7.0.0'; Fuse.createIndex = createIndex; Fuse.parseIndex = parseIndex; Fuse.config = Config; { Fuse.parseQuery = parse; } var SearchByFuse = /** @class */ (function () { function SearchByFuse(config) { this._haystack = []; this._fuseOptions = __assign(__assign({}, config.fuseOptions), { keys: __spreadArray([], config.searchFields, true), includeMatches: true }); } SearchByFuse.prototype.index = function (data) { this._haystack = data; if (this._fuse) { this._fuse.setCollection(data); } }; SearchByFuse.prototype.reset = function () { this._haystack = []; this._fuse = undefined; }; SearchByFuse.prototype.isEmptyIndex = function () { return !this._haystack.length; }; SearchByFuse.prototype.search = function (needle) { if (!this._fuse) { { this._fuse = new Fuse(this._haystack, this._fuseOptions); } } var results = this._fuse.search(needle); return results.map(function (value, i) { return { item: value.item, score: value.score || 0, rank: i + 1, // If value.score is used for sorting, this can create non-stable sorts! }; }); }; return SearchByFuse; }()); function getSearcher(config) { { return new SearchByFuse(config); } } /** * Helpers to create HTML elements used by Choices * Can be overridden by providing `callbackOnCreateTemplates` option. * `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates` */ var isEmptyObject = function (obj) { // eslint-disable-next-line no-restricted-syntax for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { return false; } } return true; }; var assignCustomProperties = function (el, choice, withCustomProperties) { var dataset = el.dataset; var customProperties = choice.customProperties, labelClass = choice.labelClass, labelDescription = choice.labelDescription; if (labelClass) { dataset.labelClass = getClassNames(labelClass).join(' '); } if (labelDescription) { dataset.labelDescription = unwrapStringForRaw(labelDescription); } if (withCustomProperties && customProperties) { if (typeof customProperties === 'string') { dataset.customProperties = customProperties; } else if (typeof customProperties === 'object' && !isEmptyObject(customProperties)) { dataset.customProperties = JSON.stringify(customProperties); } } }; var addAriaLabel = function (docRoot, id, element) { var label = id && docRoot.querySelector("label[for='".concat(id, "']")); var text = label && label.innerText; if (text) { element.setAttribute('aria-label', text); } }; var templates = { containerOuter: function (_a, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType, labelId) { var containerOuter = _a.classNames.containerOuter; var div = document.createElement('div'); addClassesToElement(div, containerOuter); div.dataset.type = passedElementType; if (dir) { div.dir = dir; } if (isSelectOneElement) { div.tabIndex = 0; } if (isSelectElement) { div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox'); if (searchEnabled) { div.setAttribute('aria-autocomplete', 'list'); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, div); } div.setAttribute('aria-haspopup', 'true'); div.setAttribute('aria-expanded', 'false'); } if (labelId) { div.setAttribute('aria-labelledby', labelId); } return div; }, containerInner: function (_a) { var containerInner = _a.classNames.containerInner; var div = document.createElement('div'); addClassesToElement(div, containerInner); return div; }, itemList: function (_a, isSelectOneElement) { var searchEnabled = _a.searchEnabled, _b = _a.classNames, list = _b.list, listSingle = _b.listSingle, listItems = _b.listItems; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, isSelectOneElement ? listSingle : listItems); if (this._isSelectElement && searchEnabled) { div.setAttribute('role', 'listbox'); } return div; }, placeholder: function (_a, value) { var allowHTML = _a.allowHTML, placeholder = _a.classNames.placeholder; var div = document.createElement('div'); addClassesToElement(div, placeholder); setElementHtml(div, allowHTML, value); return div; }, item: function (_a, choice, removeItemButton) { var allowHTML = _a.allowHTML, removeItemButtonAlignLeft = _a.removeItemButtonAlignLeft, removeItemIconText = _a.removeItemIconText, removeItemLabelText = _a.removeItemLabelText, _b = _a.classNames, item = _b.item, button = _b.button, highlightedState = _b.highlightedState, itemSelectable = _b.itemSelectable, placeholder = _b.placeholder; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); addClassesToElement(div, item); if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, choice.label); addClassesToElement(spanLabel, choice.labelClass); div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, choice.label); } div.dataset.item = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; assignCustomProperties(div, choice, true); if (choice.disabled || this.containerOuter.isDisabled) { div.setAttribute('aria-disabled', 'true'); } if (this._isSelectElement) { div.setAttribute('aria-selected', 'true'); div.setAttribute('role', 'option'); } if (choice.placeholder) { addClassesToElement(div, placeholder); div.dataset.placeholder = ''; } addClassesToElement(div, choice.highlighted ? highlightedState : itemSelectable); if (removeItemButton) { if (choice.disabled) { removeClassesFromElement(div, itemSelectable); } div.dataset.deletable = ''; var removeButton = document.createElement('button'); removeButton.type = 'button'; addClassesToElement(removeButton, button); var eventChoice = getChoiceForOutput(choice); setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value, eventChoice)); var REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value, eventChoice); if (REMOVE_ITEM_LABEL) { removeButton.setAttribute('aria-label', REMOVE_ITEM_LABEL); } removeButton.dataset.button = ''; if (removeItemButtonAlignLeft) { div.insertAdjacentElement('afterbegin', removeButton); } else { div.appendChild(removeButton); } } return div; }, choiceList: function (_a, isSelectOneElement) { var list = _a.classNames.list; var div = document.createElement('div'); addClassesToElement(div, list); if (!isSelectOneElement) { div.setAttribute('aria-multiselectable', 'true'); } div.setAttribute('role', 'listbox'); return div; }, choiceGroup: function (_a, _b) { var allowHTML = _a.allowHTML, _c = _a.classNames, group = _c.group, groupHeading = _c.groupHeading, itemDisabled = _c.itemDisabled; var id = _b.id, label = _b.label, disabled = _b.disabled; var rawLabel = unwrapStringForRaw(label); var div = document.createElement('div'); addClassesToElement(div, group); if (disabled) { addClassesToElement(div, itemDisabled); } div.setAttribute('role', 'group'); div.dataset.group = ''; div.dataset.id = id; div.dataset.value = rawLabel; if (disabled) { div.setAttribute('aria-disabled', 'true'); } var heading = document.createElement('div'); addClassesToElement(heading, groupHeading); setElementHtml(heading, allowHTML, label || ''); div.appendChild(heading); return div; }, choice: function (_a, choice, selectText, groupName) { var allowHTML = _a.allowHTML, _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, itemSelectable = _b.itemSelectable, selectedState = _b.selectedState, itemDisabled = _b.itemDisabled, description = _b.description, placeholder = _b.placeholder; // eslint-disable-next-line prefer-destructuring var label = choice.label; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); div.id = choice.elementId; addClassesToElement(div, item); addClassesToElement(div, itemChoice); if (groupName && typeof label === 'string') { label = escapeForTemplate(allowHTML, label); label += " (".concat(groupName, ")"); label = { trusted: label }; } var describedBy = div; if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, label); addClassesToElement(spanLabel, choice.labelClass); describedBy = spanLabel; div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, label); } if (choice.labelDescription) { var descId = "".concat(choice.elementId, "-description"); describedBy.setAttribute('aria-describedby', descId); var spanDesc = document.createElement('span'); setElementHtml(spanDesc, allowHTML, choice.labelDescription); spanDesc.id = descId; addClassesToElement(spanDesc, description); div.appendChild(spanDesc); } if (choice.selected) { addClassesToElement(div, selectedState); } if (choice.placeholder) { addClassesToElement(div, placeholder); } div.setAttribute('role', choice.group ? 'treeitem' : 'option'); div.dataset.choice = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; if (selectText) { div.dataset.selectText = selectText; } if (choice.group) { div.dataset.groupId = "".concat(choice.group.id); } assignCustomProperties(div, choice, false); if (choice.disabled) { addClassesToElement(div, itemDisabled); div.dataset.choiceDisabled = ''; div.setAttribute('aria-disabled', 'true'); } else { addClassesToElement(div, itemSelectable); div.dataset.choiceSelectable = ''; div.setAttribute('aria-selected', choice.selected ? 'true' : 'false'); } return div; }, input: function (_a, placeholderValue) { var _b = _a.classNames, input = _b.input, inputCloned = _b.inputCloned, labelId = _a.labelId; var inp = document.createElement('input'); inp.type = 'search'; addClassesToElement(inp, input); addClassesToElement(inp, inputCloned); inp.autocomplete = 'off'; inp.autocapitalize = 'off'; inp.spellcheck = false; inp.setAttribute('aria-autocomplete', 'list'); if (placeholderValue) { inp.setAttribute('aria-label', placeholderValue); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, inp); } return inp; }, dropdown: function (_a) { var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, listDropdown); div.setAttribute('aria-expanded', 'false'); return div; }, notice: function (_a, innerHTML, type) { var _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, addChoice = _b.addChoice, noResults = _b.noResults, noChoices = _b.noChoices, noticeItem = _b.notice; if (type === void 0) { type = NoticeTypes.generic; } var notice = document.createElement('div'); setElementHtml(notice, true, innerHTML); addClassesToElement(notice, item); addClassesToElement(notice, itemChoice); addClassesToElement(notice, noticeItem); // eslint-disable-next-line default-case switch (type) { case NoticeTypes.addChoice: addClassesToElement(notice, addChoice); break; case NoticeTypes.noResults: addClassesToElement(notice, noResults); break; case NoticeTypes.noChoices: addClassesToElement(notice, noChoices); break; } if (type === NoticeTypes.addChoice) { notice.dataset.choiceSelectable = ''; notice.dataset.choice = ''; } return notice; }, option: function (choice) { // HtmlOptionElement's label value does not support HTML, so the avoid double escaping unwrap the untrusted string. var labelValue = unwrapStringForRaw(choice.label); var opt = new Option(labelValue, choice.value, false, choice.selected); assignCustomProperties(opt, choice, true); opt.disabled = choice.disabled; if (choice.selected) { opt.setAttribute('selected', ''); } return opt; }, }; /** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ var IS_IE11 = '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style; var USER_DEFAULTS = {}; var parseDataSetId = function (element) { if (!element) { return undefined; } return element.dataset.id ? parseInt(element.dataset.id, 10) : undefined; }; var selectableChoiceIdentifier = '[data-choice-selectable]'; /** * Choices * @author Josh Johnson */ var Choices = /** @class */ (function () { function Choices(element, userConfig) { if (element === void 0) { element = '[data-choice]'; } if (userConfig === void 0) { userConfig = {}; } var _this = this; this.initialisedOK = undefined; this._hasNonChoicePlaceholder = false; this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; var defaults = Choices.defaults; this.config = __assign(__assign(__assign({}, defaults.allOptions), defaults.options), userConfig); ObjectsInConfig.forEach(function (key) { _this.config[key] = __assign(__assign(__assign({}, defaults.allOptions[key]), defaults.options[key]), userConfig[key]); }); var config = this.config; if (!config.silent) { this._validateConfig(); } var docRoot = config.shadowRoot || document.documentElement; this._docRoot = docRoot; var passedElement = typeof element === 'string' ? docRoot.querySelector(element) : element; if (!passedElement || typeof passedElement !== 'object' || !(isHtmlInputElement(passedElement) || isHtmlSelectElement(passedElement))) { if (!passedElement && typeof element === 'string') { throw TypeError("Selector ".concat(element, " failed to find an element")); } throw TypeError("Expected one of the following types text|select-one|select-multiple"); } var elementType = passedElement.type; var isText = elementType === PassedElementTypes.Text; if (isText || config.maxItemCount !== 1) { config.singleModeForMultiSelect = false; } if (config.singleModeForMultiSelect) { elementType = PassedElementTypes.SelectMultiple; } var isSelectOne = elementType === PassedElementTypes.SelectOne; var isSelectMultiple = elementType === PassedElementTypes.SelectMultiple; var isSelect = isSelectOne || isSelectMultiple; this._elementType = elementType; this._isTextElement = isText; this._isSelectOneElement = isSelectOne; this._isSelectMultipleElement = isSelectMultiple; this._isSelectElement = isSelectOne || isSelectMultiple; this._canAddUserChoices = (isText && config.addItems) || (isSelect && config.addChoices); if (typeof config.renderSelectedChoices !== 'boolean') { config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne; } if (config.closeDropdownOnSelect === 'auto') { config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect; } else { config.closeDropdownOnSelect = coerceBool(config.closeDropdownOnSelect); } if (config.placeholder) { if (config.placeholderValue) { this._hasNonChoicePlaceholder = true; } else if (passedElement.dataset.placeholder) { this._hasNonChoicePlaceholder = true; config.placeholderValue = passedElement.dataset.placeholder; } } if (userConfig.addItemFilter && typeof userConfig.addItemFilter !== 'function') { var re = userConfig.addItemFilter instanceof RegExp ? userConfig.addItemFilter : new RegExp(userConfig.addItemFilter); config.addItemFilter = re.test.bind(re); } if (this._isTextElement) { this.passedElement = new WrappedInput({ element: passedElement, classNames: config.classNames, }); } else { var selectEl = passedElement; this.passedElement = new WrappedSelect({ element: selectEl, classNames: config.classNames, template: function (data) { return _this._templates.option(data); }, extractPlaceholder: config.placeholder && !this._hasNonChoicePlaceholder, }); } this.initialised = false; this._store = new Store(config); this._currentValue = ''; config.searchEnabled = !isText && config.searchEnabled; this._canSearch = config.searchEnabled; this._isScrollingOnIe = false; this._highlightPosition = 0; this._wasTap = true; this._placeholderValue = this._generatePlaceholderValue(); this._baseId = generateId(passedElement, 'choices-'); /** * setting direction in cases where it's explicitly set on passedElement * or when calculated direction is different from the document */ this._direction = passedElement.dir; if (!this._direction) { var elementDirection = window.getComputedStyle(passedElement).direction; var documentDirection = window.getComputedStyle(document.documentElement).direction; if (elementDirection !== documentDirection) { this._direction = elementDirection; } } this._idNames = { itemChoice: 'item-choice', }; this._templates = defaults.templates; this._render = this._render.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onKeyUp = this._onKeyUp.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onInput = this._onInput.bind(this); this._onClick = this._onClick.bind(this); this._onTouchMove = this._onTouchMove.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseOver = this._onMouseOver.bind(this); this._onFormReset = this._onFormReset.bind(this); this._onSelectKey = this._onSelectKey.bind(this); this._onEnterKey = this._onEnterKey.bind(this); this._onEscapeKey = this._onEscapeKey.bind(this); this._onDirectionKey = this._onDirectionKey.bind(this); this._onDeleteKey = this._onDeleteKey.bind(this); this._onChange = this._onChange.bind(this); this._onInvalid = this._onInvalid.bind(this); // If element has already been initialised with Choices, fail silently if (this.passedElement.isActive) { if (!config.silent) { console.warn('Trying to initialise Choices on element already initialised', { element: element }); } this.initialised = true; this.initialisedOK = false; return; } // Let's go this.init(); // preserve the selected item list after setup for form reset this._initialItems = this._store.items.map(function (choice) { return choice.value; }); } Object.defineProperty(Choices, "defaults", { get: function () { return Object.preventExtensions({ get options() { return USER_DEFAULTS; }, get allOptions() { return DEFAULT_CONFIG; }, get templates() { return templates; }, }); }, enumerable: false, configurable: true }); Choices.prototype.init = function () { if (this.initialised || this.initialisedOK !== undefined) { return; } this._searcher = getSearcher(this.config); this._loadChoices(); this._createTemplates(); this._createElements(); this._createStructure(); if ((this._isTextElement && !this.config.addItems) || this.passedElement.element.hasAttribute('disabled') || !!this.passedElement.element.closest('fieldset:disabled')) { this.disable(); } else { this.enable(); this._addEventListeners(); } // should be triggered **after** disabled state to avoid additional re-draws this._initStore(); this.initialised = true; this.initialisedOK = true; var callbackOnInit = this.config.callbackOnInit; // Run callback if it is a function if (typeof callbackOnInit === 'function') { callbackOnInit.call(this); } }; Choices.prototype.destroy = function () { if (!this.initialised) { return; } this._removeEventListeners(); this.passedElement.reveal(); this.containerOuter.unwrap(this.passedElement.element); this._store._listeners = []; // prevents select/input value being wiped this.clearStore(false); this._stopSearch(); this._templates = Choices.defaults.templates; this.initialised = false; this.initialisedOK = undefined; }; Choices.prototype.enable = function () { if (this.passedElement.isDisabled) { this.passedElement.enable(); } if (this.containerOuter.isDisabled) { this._addEventListeners(); this.input.enable(); this.containerOuter.enable(); } return this; }; Choices.prototype.disable = function () { if (!this.passedElement.isDisabled) { this.passedElement.disable(); } if (!this.containerOuter.isDisabled) { this._removeEventListeners(); this.input.disable(); this.containerOuter.disable(); } return this; }; Choices.prototype.highlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, true)); if (runEvent) { this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.unhighlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || !choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, false)); if (runEvent) { this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.highlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (!item.highlighted) { _this._store.dispatch(highlightItem(item, true)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.unhighlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (item.highlighted) { _this._store.dispatch(highlightItem(item, false)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.removeActiveItemsByValue = function (value) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (item) { return item.value === value; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeActiveItems = function (excludedId) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (_a) { var id = _a.id; return id !== excludedId; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeHighlightedItems = function (runEvent) { var _this = this; if (runEvent === void 0) { runEvent = false; } this._store.withTxn(function () { _this._store.highlightedActiveItems.forEach(function (item) { _this._removeItem(item); // If this action was performed by the user // trigger the event if (runEvent) { _this._triggerChange(item.value); } }); }); return this; }; Choices.prototype.showDropdown = function (preventInputFocus) { var _this = this; if (this.dropdown.isActive) { return this; } if (preventInputFocus === undefined) { // eslint-disable-next-line no-param-reassign preventInputFocus = !this._canSearch; } requestAnimationFrame(function () { _this.dropdown.show(); var rect = _this.dropdown.element.getBoundingClientRect(); _this.containerOuter.open(rect.bottom, rect.height); if (!preventInputFocus) { _this.input.focus(); } _this.passedElement.triggerEvent(EventType.showDropdown); var activeElement = _this.choiceList.element.querySelector(getClassNamesSelector(_this.config.classNames.selectedState)); if (activeElement !== null && !isScrolledIntoView(activeElement, _this.choiceList.element)) { // We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll. activeElement.scrollIntoView(); } }); return this; }; Choices.prototype.hideDropdown = function (preventInputBlur) { var _this = this; if (!this.dropdown.isActive) { return this; } this._removeHighlightedChoices(); requestAnimationFrame(function () { _this.dropdown.hide(); _this.containerOuter.close(); if (!preventInputBlur && _this._canSearch) { _this.input.removeActiveDescendant(); _this.input.blur(); } _this.passedElement.triggerEvent(EventType.hideDropdown); }); return this; }; Choices.prototype.getValue = function (valueOnly) { var values = this._store.items.map(function (item) { return (valueOnly ? item.value : getChoiceForOutput(item)); }); return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values; }; Choices.prototype.setValue = function (items) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setValue'); return this; } this._store.withTxn(function () { items.forEach(function (value) { if (value) { _this._addChoice(mapInputToChoice(value, false)); } }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.setChoiceByValue = function (value) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoiceByValue'); return this; } if (this._isTextElement) { return this; } this._store.withTxn(function () { // If only one value has been passed, convert to array var choiceValue = Array.isArray(value) ? value : [value]; // Loop through each value and choiceValue.forEach(function (val) { return _this._findAndSelectChoiceByValue(val); }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ Choices.prototype.setChoices = function (choicesArrayOrFetcher, value, label, replaceChoices, clearSearchFlag, replaceItems) { var _this = this; if (choicesArrayOrFetcher === void 0) { choicesArrayOrFetcher = []; } if (value === void 0) { value = 'value'; } if (label === void 0) { label = 'label'; } if (replaceChoices === void 0) { replaceChoices = false; } if (clearSearchFlag === void 0) { clearSearchFlag = true; } if (replaceItems === void 0) { replaceItems = false; } if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoices'); return this; } if (!this._isSelectElement) { throw new TypeError("setChoices can't be used with INPUT based Choices"); } if (typeof value !== 'string' || !value) { throw new TypeError("value parameter must be a name of 'value' field in passed objects"); } if (typeof choicesArrayOrFetcher === 'function') { // it's a choices fetcher function var fetcher_1 = choicesArrayOrFetcher(this); if (typeof Promise === 'function' && fetcher_1 instanceof Promise) { // that's a promise // eslint-disable-next-line no-promise-executor-return return new Promise(function (resolve) { return requestAnimationFrame(resolve); }) .then(function () { return _this._handleLoadingState(true); }) .then(function () { return fetcher_1; }) .then(function (data) { return _this.setChoices(data, value, label, replaceChoices, clearSearchFlag, replaceItems); }) .catch(function (err) { if (!_this.config.silent) { console.error(err); } }) .then(function () { return _this._handleLoadingState(false); }) .then(function () { return _this; }); } // function returned something else than promise, let's check if it's an array of choices if (!Array.isArray(fetcher_1)) { throw new TypeError(".setChoices first argument function must return either array of choices or Promise, got: ".concat(typeof fetcher_1)); } // recursion with results, it's sync and choices were cleared already return this.setChoices(fetcher_1, value, label, false); } if (!Array.isArray(choicesArrayOrFetcher)) { throw new TypeError(".setChoices must be called either with array of choices with a function resulting into Promise of array of choices"); } this.containerOuter.removeLoadingState(); this._store.withTxn(function () { if (clearSearchFlag) { _this._isSearching = false; } // Clear choices if needed if (replaceChoices) { _this.clearChoices(true, replaceItems); } var isDefaultValue = value === 'value'; var isDefaultLabel = label === 'label'; choicesArrayOrFetcher.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { var group = groupOrChoice; if (!isDefaultLabel) { group = __assign(__assign({}, group), { label: group[label] }); } _this._addGroup(mapInputToChoice(group, true)); } else { var choice = groupOrChoice; if (!isDefaultLabel || !isDefaultValue) { choice = __assign(__assign({}, choice), { value: choice[value], label: choice[label] }); } var choiceFull = mapInputToChoice(choice, false); _this._addChoice(choiceFull); if (choiceFull.placeholder && !_this._hasNonChoicePlaceholder) { _this._placeholderValue = unwrapStringForEscaped(choiceFull.label); } } }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.refresh = function (withEvents, selectFirstOption, deselectAll) { var _this = this; if (withEvents === void 0) { withEvents = false; } if (selectFirstOption === void 0) { selectFirstOption = false; } if (deselectAll === void 0) { deselectAll = false; } if (!this._isSelectElement) { if (!this.config.silent) { console.warn('refresh method can only be used on choices backed by a element'); } return this; } this._store.withTxn(function () { var choicesFromOptions = _this.passedElement.optionsAsChoices(); // Build the list of items which require preserving var existingItems = {}; if (!deselectAll) { _this._store.items.forEach(function (choice) { if (choice.id && choice.active && choice.selected) { existingItems[choice.value] = true; } }); } _this.clearStore(false); var updateChoice = function (choice) { if (deselectAll) { _this._store.dispatch(removeItem$1(choice)); } else if (existingItems[choice.value]) { choice.selected = true; } }; choicesFromOptions.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { groupOrChoice.choices.forEach(updateChoice); return; } updateChoice(groupOrChoice); }); /* @todo only generate add events for the added options instead of all if (withEvents) { items.forEach((choice) => { if (existingItems[choice.value]) { this.passedElement.triggerEvent( EventType.removeItem, this._getChoiceForEvent(choice), ); } }); } */ // load new choices & items _this._addPredefinedChoices(choicesFromOptions, selectFirstOption, withEvents); // re-do search if required if (_this._isSearching) { _this._searchChoices(_this.input.value); } }); return this; }; Choices.prototype.removeChoice = function (value) { var choice = this._store.choices.find(function (c) { return c.value === value; }); if (!choice) { return this; } this._clearNotice(); this._store.dispatch(removeChoice(choice)); // @todo integrate with Store this._searcher.reset(); if (choice.selected) { this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.clearChoices = function (clearOptions, clearItems) { var _this = this; if (clearOptions === void 0) { clearOptions = true; } if (clearItems === void 0) { clearItems = false; } if (clearOptions) { if (clearItems) { this.passedElement.element.replaceChildren(''); } else { this.passedElement.element.querySelectorAll(':not([selected])').forEach(function (el) { el.remove(); }); } } this.itemList.element.replaceChildren(''); this.choiceList.element.replaceChildren(''); this._clearNotice(); this._store.withTxn(function () { var items = clearItems ? [] : _this._store.items; _this._store.reset(); items.forEach(function (item) { _this._store.dispatch(addChoice(item)); _this._store.dispatch(addItem(item)); }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.clearStore = function (clearOptions) { if (clearOptions === void 0) { clearOptions = true; } this.clearChoices(clearOptions, true); this._stopSearch(); this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; return this; }; Choices.prototype.clearInput = function () { var shouldSetInputWidth = !this._isSelectOneElement; this.input.clear(shouldSetInputWidth); this._stopSearch(); return this; }; Choices.prototype._validateConfig = function () { var config = this.config; var invalidConfigOptions = diff(config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); } if (config.allowHTML && config.allowHtmlUserInput) { if (config.addItems) { console.warn('Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks'); } if (config.addChoices) { console.warn('Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks'); } } }; Choices.prototype._render = function (changes) { if (changes === void 0) { changes = { choices: true, groups: true, items: true }; } if (this._store.inTxn()) { return; } if (this._isSelectElement) { if (changes.choices || changes.groups) { this._renderChoices(); } } if (changes.items) { this._renderItems(); } }; Choices.prototype._renderChoices = function () { var _this = this; if (!this._canAddItems()) { return; // block rendering choices if the input limit is reached. } var _a = this, config = _a.config, isSearching = _a._isSearching; var _b = this._store, activeGroups = _b.activeGroups, activeChoices = _b.activeChoices; var renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit; if (this._isSelectElement) { var backingOptions = activeChoices.filter(function (choice) { return !choice.element; }); if (backingOptions.length) { this.passedElement.addOptions(backingOptions); } } var fragment = document.createDocumentFragment(); var renderableChoices = function (choices) { return choices.filter(function (choice) { return !choice.placeholder && (isSearching ? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank : config.renderSelectedChoices || !choice.selected); }); }; var showLabel = config.appendGroupInSearch && isSearching; var selectableChoices = false; var highlightedEl = null; var renderChoices = function (choices, withinGroup) { if (isSearching) { // sortByRank is used to ensure stable sorting, as scores are non-unique // this additionally ensures fuseOptions.sortFn is not ignored choices.sort(sortByRank); } else if (config.shouldSort) { choices.sort(config.sorter); } var choiceLimit = choices.length; choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit; choiceLimit--; choices.every(function (choice, index) { // choiceEl being empty signals the contents has probably significantly changed var dropdownItem = choice.choiceEl || _this._templates.choice(config, choice, config.itemSelectText, showLabel && choice.group ? choice.group.label : undefined); choice.choiceEl = dropdownItem; fragment.appendChild(dropdownItem); if (isSearching || !choice.selected) { selectableChoices = true; } else if (!highlightedEl) { highlightedEl = dropdownItem; } return index < choiceLimit; }); }; if (activeChoices.length) { if (config.resetScrollPosition) { requestAnimationFrame(function () { return _this.choiceList.scrollToTop(); }); } if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) { // If we have a placeholder choice along with groups renderChoices(activeChoices.filter(function (choice) { return choice.placeholder && !choice.group; }), false); } // If we have grouped options if (activeGroups.length && !isSearching) { if (config.shouldSort) { activeGroups.sort(config.sorter); } // render Choices without group first, regardless of sort, otherwise they won't be distinguishable // from the last group renderChoices(activeChoices.filter(function (choice) { return !choice.placeholder && !choice.group; }), false); activeGroups.forEach(function (group) { var groupChoices = renderableChoices(group.choices); if (groupChoices.length) { if (group.label) { var dropdownGroup = group.groupEl || _this._templates.choiceGroup(_this.config, group); group.groupEl = dropdownGroup; dropdownGroup.remove(); fragment.appendChild(dropdownGroup); } renderChoices(groupChoices, true); } }); } else { renderChoices(renderableChoices(activeChoices), false); } } if (!selectableChoices && (isSearching || !fragment.children.length || !config.renderSelectedChoices)) { if (!this._notice) { this._notice = { text: resolveStringFunction(isSearching ? config.noResultsText : config.noChoicesText), type: isSearching ? NoticeTypes.noResults : NoticeTypes.noChoices, }; } fragment.replaceChildren(''); } this._renderNotice(fragment); this.choiceList.element.replaceChildren(fragment); this._highlightChoice(highlightedEl); }; Choices.prototype._renderItems = function () { var _this = this; var items = this._store.items || []; var itemList = this.itemList.element; var config = this.config; var fragment = document.createDocumentFragment(); var itemFromList = function (item) { return itemList.querySelector("[data-item][data-id=\"".concat(item.id, "\"]")); }; var addItemToFragment = function (item) { var el = item.itemEl; if (el && el.parentElement) { return; } el = itemFromList(item) || _this._templates.item(config, item, config.removeItemButton); item.itemEl = el; fragment.appendChild(el); }; // new items items.forEach(addItemToFragment); var addedItems = !!fragment.childNodes.length; if (this._isSelectOneElement) { var existingItems = itemList.children.length; if (addedItems || existingItems > 1) { var placeholder = itemList.querySelector(getClassNamesSelector(config.classNames.placeholder)); if (placeholder) { placeholder.remove(); } } else if (!addedItems && !existingItems && this._placeholderValue) { addedItems = true; addItemToFragment(mapInputToChoice({ selected: true, value: '', label: this._placeholderValue, placeholder: true, }, false)); } } if (addedItems) { itemList.append(fragment); if (config.shouldSortItems && !this._isSelectOneElement) { items.sort(config.sorter); // push sorting into the DOM items.forEach(function (item) { var el = itemFromList(item); if (el) { el.remove(); fragment.append(el); } }); itemList.append(fragment); } } if (this._isTextElement) { // Update the value of the hidden input this.passedElement.value = items.map(function (_a) { var value = _a.value; return value; }).join(config.delimiter); } }; Choices.prototype._displayNotice = function (text, type, openDropdown) { if (openDropdown === void 0) { openDropdown = true; } var oldNotice = this._notice; if (oldNotice && ((oldNotice.type === type && oldNotice.text === text) || (oldNotice.type === NoticeTypes.addChoice && (type === NoticeTypes.noResults || type === NoticeTypes.noChoices)))) { if (openDropdown) { this.showDropdown(true); } return; } this._clearNotice(); this._notice = text ? { text: text, type: type, } : undefined; this._renderNotice(); if (openDropdown && text) { this.showDropdown(true); } }; Choices.prototype._clearNotice = function () { if (!this._notice) { return; } var noticeElement = this.choiceList.element.querySelector(getClassNamesSelector(this.config.classNames.notice)); if (noticeElement) { noticeElement.remove(); } this._notice = undefined; }; Choices.prototype._renderNotice = function (fragment) { var noticeConf = this._notice; if (noticeConf) { var notice = this._templates.notice(this.config, noticeConf.text, noticeConf.type); if (fragment) { fragment.append(notice); } else { this.choiceList.prepend(notice); } } }; /** * @deprecated Use utils.getChoiceForOutput */ // eslint-disable-next-line class-methods-use-this Choices.prototype._getChoiceForOutput = function (choice, keyCode) { return getChoiceForOutput(choice, keyCode); }; Choices.prototype._triggerChange = function (value) { if (value === undefined || value === null) { return; } this.passedElement.triggerEvent(EventType.change, { value: value, }); }; Choices.prototype._handleButtonAction = function (element) { var _this = this; var items = this._store.items; if (!items.length || !this.config.removeItems || !this.config.removeItemButton) { return; } var id = element && parseDataSetId(element.closest('[data-id]')); var itemToRemove = id && items.find(function (item) { return item.id === id; }); if (!itemToRemove) { return; } this._store.withTxn(function () { // Remove item associated with button _this._removeItem(itemToRemove); _this._triggerChange(itemToRemove.value); if (_this._isSelectOneElement && !_this._hasNonChoicePlaceholder) { var placeholderChoice = (_this.config.shouldSort ? _this._store.choices.reverse() : _this._store.choices).find(function (choice) { return choice.placeholder; }); if (placeholderChoice) { _this._addItem(placeholderChoice); _this.unhighlightAll(); if (placeholderChoice.value) { _this._triggerChange(placeholderChoice.value); } } } }); }; Choices.prototype._handleItemAction = function (element, hasShiftKey) { var _this = this; if (hasShiftKey === void 0) { hasShiftKey = false; } var items = this._store.items; if (!items.length || !this.config.removeItems || this._isSelectOneElement) { return; } var id = parseDataSetId(element); if (!id) { return; } // We only want to select one item with a click // so we deselect any items that aren't the target // unless shift is being pressed items.forEach(function (item) { if (item.id === id && !item.highlighted) { _this.highlightItem(item); } else if (!hasShiftKey && item.highlighted) { _this.unhighlightItem(item); } }); // Focus input as without focus, a user cannot do anything with a // highlighted item this.input.focus(); }; Choices.prototype._handleChoiceAction = function (element) { var _this = this; // If we are clicking on an option var id = parseDataSetId(element); var choice = id && this._store.getChoiceById(id); if (!choice || choice.disabled) { return false; } var hasActiveDropdown = this.dropdown.isActive; if (!choice.selected) { if (!this._canAddItems()) { return true; // causes _onEnterKey to early out } this._store.withTxn(function () { _this._addItem(choice, true, true); _this.clearInput(); _this.unhighlightAll(); }); this._triggerChange(choice.value); } // We want to close the dropdown if we are dealing with a single select box if (hasActiveDropdown && this.config.closeDropdownOnSelect) { this.hideDropdown(true); this.containerOuter.element.focus(); } return true; }; Choices.prototype._handleBackspace = function (items) { var config = this.config; if (!config.removeItems || !items.length) { return; } var lastItem = items[items.length - 1]; var hasHighlightedItems = items.some(function (item) { return item.highlighted; }); // If editing the last item is allowed and there are not other selected items, // we can edit the item value. Otherwise if we can remove items, remove all selected items if (config.editItems && !hasHighlightedItems && lastItem) { this.input.value = lastItem.value; this.input.setWidth(); this._removeItem(lastItem); this._triggerChange(lastItem.value); } else { if (!hasHighlightedItems) { // Highlight last item if none already highlighted this.highlightItem(lastItem, false); } this.removeHighlightedItems(true); } }; Choices.prototype._loadChoices = function () { var _a; var _this = this; var config = this.config; if (this._isTextElement) { // Assign preset items from passed object first this._presetChoices = config.items.map(function (e) { return mapInputToChoice(e, false); }); // Add any values passed from attribute if (this.passedElement.value) { var elementItems = this.passedElement.value .split(config.delimiter) .map(function (e) { return mapInputToChoice(e, false, _this.config.allowHtmlUserInput); }); this._presetChoices = this._presetChoices.concat(elementItems); } this._presetChoices.forEach(function (choice) { choice.selected = true; }); } else if (this._isSelectElement) { // Assign preset choices from passed object this._presetChoices = config.choices.map(function (e) { return mapInputToChoice(e, true); }); // Create array of choices from option elements var choicesFromOptions = this.passedElement.optionsAsChoices(); if (choicesFromOptions) { (_a = this._presetChoices).push.apply(_a, choicesFromOptions); } } }; Choices.prototype._handleLoadingState = function (setLoading) { if (setLoading === void 0) { setLoading = true; } var el = this.itemList.element; if (setLoading) { this.disable(); this.containerOuter.addLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(this._templates.placeholder(this.config, this.config.loadingText)); } else { this.input.placeholder = this.config.loadingText; } } else { this.enable(); this.containerOuter.removeLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(''); this._render(); } else { this.input.placeholder = this._placeholderValue || ''; } } }; Choices.prototype._handleSearch = function (value) { if (!this.input.isFocussed) { return; } // Check that we have a value to search and the input was an alphanumeric character if (value !== null && typeof value !== 'undefined' && value.length >= this.config.searchFloor) { var resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; if (resultCount !== null) { // Trigger search event this.passedElement.triggerEvent(EventType.search, { value: value, resultCount: resultCount, }); } } else if (this._store.choices.some(function (option) { return !option.active; })) { this._stopSearch(); } }; Choices.prototype._canAddItems = function () { var config = this.config; var maxItemCount = config.maxItemCount, maxItemText = config.maxItemText; if (!config.singleModeForMultiSelect && maxItemCount > 0 && maxItemCount <= this._store.items.length) { this.choiceList.element.replaceChildren(''); this._notice = undefined; this._displayNotice(typeof maxItemText === 'function' ? maxItemText(maxItemCount) : maxItemText, NoticeTypes.addChoice); return false; } if (this._notice && this._notice.type === NoticeTypes.addChoice) { this._clearNotice(); } return true; }; Choices.prototype._canCreateItem = function (value) { var config = this.config; var canAddItem = true; var notice = ''; if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) { canAddItem = false; notice = resolveNoticeFunction(config.customAddItemText, value, undefined); } if (canAddItem) { var foundChoice = this._store.choices.find(function (choice) { return config.valueComparer(choice.value, value); }); if (foundChoice) { if (this._isSelectElement) { // for exact matches, do not prompt to add it as a custom choice this._displayNotice('', NoticeTypes.addChoice); return false; } if (!config.duplicateItemsAllowed) { canAddItem = false; notice = resolveNoticeFunction(config.uniqueItemText, value, undefined); } } } if (canAddItem) { notice = resolveNoticeFunction(config.addItemText, value, undefined); } if (notice) { this._displayNotice(notice, NoticeTypes.addChoice); } return canAddItem; }; Choices.prototype._searchChoices = function (value) { var newValue = value.trim().replace(/\s{2,}/, ' '); // signal input didn't change search if (!newValue.length || newValue === this._currentValue) { return null; } var searcher = this._searcher; if (searcher.isEmptyIndex()) { searcher.index(this._store.searchableChoices); } // If new value matches the desired length and is not the same as the current value with a space var results = searcher.search(newValue); this._currentValue = newValue; this._highlightPosition = 0; this._isSearching = true; var notice = this._notice; var noticeType = notice && notice.type; if (noticeType !== NoticeTypes.addChoice) { if (!results.length) { this._displayNotice(resolveStringFunction(this.config.noResultsText), NoticeTypes.noResults); } else { this._clearNotice(); } } this._store.dispatch(filterChoices(results)); return results.length; }; Choices.prototype._stopSearch = function () { if (this._isSearching) { this._currentValue = ''; this._isSearching = false; this._clearNotice(); this._store.dispatch(activateChoices(true)); this.passedElement.triggerEvent(EventType.search, { value: '', resultCount: 0, }); } }; Choices.prototype._addEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; // capture events - can cancel event processing or propagation documentElement.addEventListener('touchend', this._onTouchEnd, true); outerElement.addEventListener('keydown', this._onKeyDown, true); outerElement.addEventListener('mousedown', this._onMouseDown, true); // passive events - doesn't call `preventDefault` or `stopPropagation` documentElement.addEventListener('click', this._onClick, { passive: true }); documentElement.addEventListener('touchmove', this._onTouchMove, { passive: true, }); this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { passive: true, }); if (this._isSelectOneElement) { outerElement.addEventListener('focus', this._onFocus, { passive: true, }); outerElement.addEventListener('blur', this._onBlur, { passive: true, }); } inputElement.addEventListener('keyup', this._onKeyUp, { passive: true, }); inputElement.addEventListener('input', this._onInput, { passive: true, }); inputElement.addEventListener('focus', this._onFocus, { passive: true, }); inputElement.addEventListener('blur', this._onBlur, { passive: true, }); if (inputElement.form) { inputElement.form.addEventListener('reset', this._onFormReset, { passive: true, }); } if (passedElement.hasAttribute('required')) { passedElement.addEventListener('change', this._onChange, { passive: true, }); passedElement.addEventListener('invalid', this._onInvalid, { passive: true, }); } this.input.addEventListeners(); }; Choices.prototype._removeEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; documentElement.removeEventListener('touchend', this._onTouchEnd, true); outerElement.removeEventListener('keydown', this._onKeyDown, true); outerElement.removeEventListener('mousedown', this._onMouseDown, true); documentElement.removeEventListener('click', this._onClick); documentElement.removeEventListener('touchmove', this._onTouchMove); this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); if (this._isSelectOneElement) { outerElement.removeEventListener('focus', this._onFocus); outerElement.removeEventListener('blur', this._onBlur); } inputElement.removeEventListener('keyup', this._onKeyUp); inputElement.removeEventListener('input', this._onInput); inputElement.removeEventListener('focus', this._onFocus); inputElement.removeEventListener('blur', this._onBlur); if (inputElement.form) { inputElement.form.removeEventListener('reset', this._onFormReset); } if (passedElement.hasAttribute('required')) { passedElement.removeEventListener('change', this._onChange); passedElement.removeEventListener('invalid', this._onInvalid); } this.input.removeEventListeners(); }; Choices.prototype._onKeyDown = function (event) { var keyCode = event.keyCode; var hasActiveDropdown = this.dropdown.isActive; /* See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs https://stackoverflow.com/a/70866532 - "Unidentified" for mobile http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7) Logic: when a key event is sent, `event.key` represents its printable value _or_ one of a large list of special values indicating meta keys/functionality. In addition, key events for compose functionality contain a value of `Dead` when mid-composition. I can't quite verify it, but non-English IMEs may also be able to generate key codes for code points in the surrogate-pair range, which could potentially be seen as having key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that alone. Here, key.length === 1 means we know for sure the input was printable and not a special `key` value. When the length is greater than 1, it could be either a printable surrogate pair or a special `key` value. We can tell the difference by checking if the _character code_ value (not code point!) is in the "surrogate pair" range or not. We don't use .codePointAt because an invalid code point would return 65535, which wouldn't pass the >= 0x10000 check we would otherwise use. > ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points > of each plane are noncharacters: U+FFFE and U+FFFF on the BMP... */ var wasPrintableChar = event.key.length === 1 || (event.key.length === 2 && event.key.charCodeAt(0) >= 0xd800) || event.key === 'Unidentified'; /* We do not show the dropdown if focusing out with esc or navigating through input fields. An activated search can still be opened with any other key. */ if (!this._isTextElement && !hasActiveDropdown && keyCode !== KeyCodeMap.ESC_KEY && keyCode !== KeyCodeMap.TAB_KEY && keyCode !== KeyCodeMap.SHIFT_KEY) { this.showDropdown(); if (!this.input.isFocussed && wasPrintableChar) { /* We update the input value with the pressed key as the input was not focussed at the time of key press therefore does not have the value of the key. */ this.input.value += event.key; // browsers interpret a space as pagedown if (event.key === ' ') { event.preventDefault(); } } } switch (keyCode) { case KeyCodeMap.A_KEY: return this._onSelectKey(event, this.itemList.element.hasChildNodes()); case KeyCodeMap.ENTER_KEY: return this._onEnterKey(event, hasActiveDropdown); case KeyCodeMap.ESC_KEY: return this._onEscapeKey(event, hasActiveDropdown); case KeyCodeMap.UP_KEY: case KeyCodeMap.PAGE_UP_KEY: case KeyCodeMap.DOWN_KEY: case KeyCodeMap.PAGE_DOWN_KEY: return this._onDirectionKey(event, hasActiveDropdown); case KeyCodeMap.DELETE_KEY: case KeyCodeMap.BACK_KEY: return this._onDeleteKey(event, this._store.items, this.input.isFocussed); } }; Choices.prototype._onKeyUp = function ( /* event: KeyboardEvent */) { this._canSearch = this.config.searchEnabled; }; Choices.prototype._onInput = function ( /* event: InputEvent */) { var value = this.input.value; if (!value) { if (this._isTextElement) { this.hideDropdown(true); } else { this._stopSearch(); } return; } if (!this._canAddItems()) { return; } if (this._canSearch) { // do the search even if the entered text can not be added this._handleSearch(value); } if (!this._canAddUserChoices) { return; } // determine if a notice needs to be displayed for why a search result can't be added this._canCreateItem(value); if (this._isSelectElement) { this._highlightPosition = 0; // reset to select the notice and/or exact match this._highlightChoice(); } }; Choices.prototype._onSelectKey = function (event, hasItems) { // If CTRL + A or CMD + A have been pressed and there are items to select if ((event.ctrlKey || event.metaKey) && hasItems) { this._canSearch = false; var shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; if (shouldHightlightAll) { this.highlightAll(); } } }; Choices.prototype._onEnterKey = function (event, hasActiveDropdown) { var _this = this; var value = this.input.value; var target = event.target; event.preventDefault(); if (target && target.hasAttribute('data-button')) { this._handleButtonAction(target); return; } if (!hasActiveDropdown) { if (this._isSelectElement || this._notice) { this.showDropdown(); } return; } var highlightedChoice = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (highlightedChoice && this._handleChoiceAction(highlightedChoice)) { return; } if (!target || !value) { this.hideDropdown(true); return; } if (!this._canAddItems()) { return; } var addedItem = false; this._store.withTxn(function () { addedItem = _this._findAndSelectChoiceByValue(value, true); if (!addedItem) { if (!_this._canAddUserChoices) { return; } if (!_this._canCreateItem(value)) { return; } _this._addChoice(mapInputToChoice(value, false, _this.config.allowHtmlUserInput), true, true); addedItem = true; } _this.clearInput(); _this.unhighlightAll(); }); if (!addedItem) { return; } this._triggerChange(value); if (this.config.closeDropdownOnSelect) { this.hideDropdown(true); } }; Choices.prototype._onEscapeKey = function (event, hasActiveDropdown) { if (hasActiveDropdown) { event.stopPropagation(); this.hideDropdown(true); this._stopSearch(); this.containerOuter.element.focus(); } }; Choices.prototype._onDirectionKey = function (event, hasActiveDropdown) { var keyCode = event.keyCode; // If up or down key is pressed, traverse through options if (hasActiveDropdown || this._isSelectOneElement) { this.showDropdown(); this._canSearch = false; var directionInt = keyCode === KeyCodeMap.DOWN_KEY || keyCode === KeyCodeMap.PAGE_DOWN_KEY ? 1 : -1; var skipKey = event.metaKey || keyCode === KeyCodeMap.PAGE_DOWN_KEY || keyCode === KeyCodeMap.PAGE_UP_KEY; var nextEl = void 0; if (skipKey) { if (directionInt > 0) { nextEl = this.dropdown.element.querySelector("".concat(selectableChoiceIdentifier, ":last-of-type")); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } else { var currentEl = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (currentEl) { nextEl = getAdjacentEl(currentEl, selectableChoiceIdentifier, directionInt); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } if (nextEl) { // We prevent default to stop the cursor moving // when pressing the arrow if (!isScrolledIntoView(nextEl, this.choiceList.element, directionInt)) { this.choiceList.scrollToChildElement(nextEl, directionInt); } this._highlightChoice(nextEl); } // Prevent default to maintain cursor position whilst // traversing dropdown options event.preventDefault(); } }; Choices.prototype._onDeleteKey = function (event, items, hasFocusedInput) { // If backspace or delete key is pressed and the input has no value if (!this._isSelectOneElement && !event.target.value && hasFocusedInput) { this._handleBackspace(items); event.preventDefault(); } }; Choices.prototype._onTouchMove = function () { if (this._wasTap) { this._wasTap = false; } }; Choices.prototype._onTouchEnd = function (event) { var target = (event || event.touches[0]).target; var touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target); if (touchWasWithinContainer) { var containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; if (containerWasExactTarget) { if (this._isTextElement) { this.input.focus(); } else if (this._isSelectMultipleElement) { this.showDropdown(); } } // Prevents focus event firing event.stopPropagation(); } this._wasTap = true; }; /** * Handles mousedown event in capture mode for containetOuter.element */ Choices.prototype._onMouseDown = function (event) { var target = event.target; if (!(target instanceof Element)) { return; } // If we have our mouse down on the scrollbar and are on IE11... if (IS_IE11 && this.choiceList.element.contains(target)) { // check if click was on a scrollbar area var firstChoice = this.choiceList.element.firstElementChild; this._isScrollingOnIe = this._direction === 'ltr' ? event.offsetX >= firstChoice.offsetWidth : event.offsetX < firstChoice.offsetLeft; } if (target === this.input.element) { return; } var item = target.closest('[data-button],[data-item],[data-choice]'); if (item instanceof HTMLElement) { if ('button' in item.dataset) { this._handleButtonAction(item); } else if ('item' in item.dataset) { this._handleItemAction(item, event.shiftKey); } else if ('choice' in item.dataset) { this._handleChoiceAction(item); } } event.preventDefault(); }; /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ Choices.prototype._onMouseOver = function (_a) { var target = _a.target; if (target instanceof HTMLElement && 'choice' in target.dataset) { this._highlightChoice(target); } }; Choices.prototype._onClick = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var clickWasWithinContainer = containerOuter.element.contains(target); if (clickWasWithinContainer) { if (!this.dropdown.isActive && !containerOuter.isDisabled) { if (this._isTextElement) { if (document.activeElement !== this.input.element) { this.input.focus(); } } else { this.showDropdown(); containerOuter.element.focus(); } } else if (this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target)) { this.hideDropdown(); } } else { containerOuter.removeFocusState(); this.hideDropdown(true); this.unhighlightAll(); } }; Choices.prototype._onFocus = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var focusWasWithinContainer = target && containerOuter.element.contains(target); if (!focusWasWithinContainer) { return; } var targetIsInput = target === this.input.element; if (this._isTextElement) { if (targetIsInput) { containerOuter.addFocusState(); } } else if (this._isSelectMultipleElement) { if (targetIsInput) { this.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown containerOuter.addFocusState(); } } else { containerOuter.addFocusState(); if (targetIsInput) { this.showDropdown(true); } } }; Choices.prototype._onBlur = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var blurWasWithinContainer = target && containerOuter.element.contains(target); if (blurWasWithinContainer && !this._isScrollingOnIe) { if (target === this.input.element) { containerOuter.removeFocusState(); this.hideDropdown(true); if (this._isTextElement || this._isSelectMultipleElement) { this.unhighlightAll(); } } else if (target === this.containerOuter.element) { // Remove the focus state when the past outerContainer was the target containerOuter.removeFocusState(); // Also close the dropdown if search is disabled if (!this.config.searchEnabled) { this.hideDropdown(true); } } } else { // On IE11, clicking the scollbar blurs our input and thus // closes the dropdown. To stop this, we refocus our input // if we know we are on IE *and* are scrolling. this._isScrollingOnIe = false; this.input.element.focus(); } }; Choices.prototype._onFormReset = function () { var _this = this; this._store.withTxn(function () { _this.clearInput(); _this.hideDropdown(); _this.refresh(false, false, true); if (_this._initialItems.length) { _this.setChoiceByValue(_this._initialItems); } }); }; Choices.prototype._onChange = function (event) { if (!event.target.checkValidity()) { return; } this.containerOuter.removeInvalidState(); }; Choices.prototype._onInvalid = function () { this.containerOuter.addInvalidState(); }; /** * Removes any highlighted choice options */ Choices.prototype._removeHighlightedChoices = function () { var highlightedState = this.config.classNames.highlightedState; var highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(getClassNamesSelector(highlightedState))); // Remove any highlighted choices highlightedChoices.forEach(function (choice) { removeClassesFromElement(choice, highlightedState); choice.setAttribute('aria-selected', 'false'); }); }; Choices.prototype._highlightChoice = function (el) { if (el === void 0) { el = null; } var choices = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)); if (!choices.length) { return; } var passedEl = el; var highlightedState = this.config.classNames.highlightedState; this._removeHighlightedChoices(); if (passedEl) { this._highlightPosition = choices.indexOf(passedEl); } else { // Highlight choice based on last known highlight location if (choices.length > this._highlightPosition) { // If we have an option to highlight passedEl = choices[this._highlightPosition]; } else { // Otherwise highlight the option before passedEl = choices[choices.length - 1]; } if (!passedEl) { passedEl = choices[0]; } } addClassesToElement(passedEl, highlightedState); passedEl.setAttribute('aria-selected', 'true'); this.passedElement.triggerEvent(EventType.highlightChoice, { el: passedEl, }); if (this.dropdown.isActive) { // IE11 ignores aria-label and blocks virtual keyboard // if aria-activedescendant is set without a dropdown this.input.setActiveDescendant(passedEl.id); this.containerOuter.setActiveDescendant(passedEl.id); } }; Choices.prototype._addItem = function (item, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (!item.id) { throw new TypeError('item.id must be set before _addItem is called for a choice/item'); } if (this.config.singleModeForMultiSelect || this._isSelectOneElement) { this.removeActiveItems(item.id); } this._store.dispatch(addItem(item)); if (withEvents) { var eventChoice = getChoiceForOutput(item); this.passedElement.triggerEvent(EventType.addItem, eventChoice); if (userTriggered) { this.passedElement.triggerEvent(EventType.choice, eventChoice); } } }; Choices.prototype._removeItem = function (item) { if (!item.id) { return; } this._store.dispatch(removeItem$1(item)); var notice = this._notice; if (notice && notice.type === NoticeTypes.noChoices) { this._clearNotice(); } this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item)); }; Choices.prototype._addChoice = function (choice, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (choice.id) { throw new TypeError('Can not re-add a choice which has already been added'); } var config = this.config; if (!config.duplicateItemsAllowed && this._store.choices.find(function (c) { return config.valueComparer(c.value, choice.value); })) { return; } // Generate unique id, in-place update is required so chaining _addItem works as expected this._lastAddedChoiceId++; choice.id = this._lastAddedChoiceId; choice.elementId = "".concat(this._baseId, "-").concat(this._idNames.itemChoice, "-").concat(choice.id); var prependValue = config.prependValue, appendValue = config.appendValue; if (prependValue) { choice.value = prependValue + choice.value; } if (appendValue) { choice.value += appendValue.toString(); } if ((prependValue || appendValue) && choice.element) { choice.element.value = choice.value; } this._clearNotice(); this._store.dispatch(addChoice(choice)); if (choice.selected) { this._addItem(choice, withEvents, userTriggered); } }; Choices.prototype._addGroup = function (group, withEvents) { var _this = this; if (withEvents === void 0) { withEvents = true; } if (group.id) { throw new TypeError('Can not re-add a group which has already been added'); } this._store.dispatch(addGroup(group)); if (!group.choices) { return; } // add unique id for the group(s), and do not store the full list of choices in this group this._lastAddedGroupId++; group.id = this._lastAddedGroupId; group.choices.forEach(function (item) { item.group = group; if (group.disabled) { item.disabled = true; } _this._addChoice(item, withEvents); }); }; Choices.prototype._createTemplates = function () { var _this = this; var callbackOnCreateTemplates = this.config.callbackOnCreateTemplates; var userTemplates = {}; if (typeof callbackOnCreateTemplates === 'function') { userTemplates = callbackOnCreateTemplates.call(this, strToEl, escapeForTemplate, getClassNames); } var templating = {}; Object.keys(this._templates).forEach(function (name) { if (name in userTemplates) { templating[name] = userTemplates[name].bind(_this); } else { templating[name] = _this._templates[name].bind(_this); } }); this._templates = templating; }; Choices.prototype._createElements = function () { var templating = this._templates; var _a = this, config = _a.config, isSelectOneElement = _a._isSelectOneElement; var position = config.position, classNames = config.classNames; var elementType = this._elementType; this.containerOuter = new Container({ element: templating.containerOuter(config, this._direction, this._isSelectElement, isSelectOneElement, config.searchEnabled, elementType, config.labelId), classNames: classNames, type: elementType, position: position, }); this.containerInner = new Container({ element: templating.containerInner(config), classNames: classNames, type: elementType, position: position, }); this.input = new Input({ element: templating.input(config, this._placeholderValue), classNames: classNames, type: elementType, preventPaste: !config.paste, }); this.choiceList = new List({ element: templating.choiceList(config, isSelectOneElement), }); this.itemList = new List({ element: templating.itemList(config, isSelectOneElement), }); this.dropdown = new Dropdown({ element: templating.dropdown(config), classNames: classNames, type: elementType, }); }; Choices.prototype._createStructure = function () { var _a = this, containerInner = _a.containerInner, containerOuter = _a.containerOuter, passedElement = _a.passedElement; var dropdownElement = this.dropdown.element; // Hide original element passedElement.conceal(); // Wrap input in container preserving DOM ordering containerInner.wrap(passedElement.element); // Wrapper inner container with outer container containerOuter.wrap(containerInner.element); containerOuter.element.appendChild(containerInner.element); containerOuter.element.appendChild(dropdownElement); containerInner.element.appendChild(this.itemList.element); dropdownElement.appendChild(this.choiceList.element); if (this._isSelectOneElement) { this.input.placeholder = this.config.searchPlaceholderValue || ''; if (this.config.searchEnabled) { dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild); } } else { if (!this._isSelectMultipleElement || this.config.searchEnabled) { containerInner.element.appendChild(this.input.element); } if (this._placeholderValue) { this.input.placeholder = this._placeholderValue; } this.input.setWidth(); } this._highlightPosition = 0; this._isSearching = false; }; Choices.prototype._initStore = function () { var _this = this; this._store.subscribe(this._render).withTxn(function () { _this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false); }); if (!this._store.choices.length || (this._isSelectOneElement && this._hasNonChoicePlaceholder)) { this._render(); } }; Choices.prototype._addPredefinedChoices = function (choices, selectFirstOption, withEvents) { var _this = this; if (selectFirstOption === void 0) { selectFirstOption = false; } if (withEvents === void 0) { withEvents = true; } if (selectFirstOption) { /** * If there is a selected choice already or the choice is not the first in * the array, add each choice normally. * * Otherwise we pre-select the first enabled choice in the array ("select-one" only) */ var noSelectedChoices = choices.findIndex(function (choice) { return choice.selected; }) === -1; if (noSelectedChoices) { choices.some(function (choice) { if (choice.disabled || 'choices' in choice) { return false; } choice.selected = true; return true; }); } } choices.forEach(function (item) { if ('choices' in item) { if (_this._isSelectElement) { _this._addGroup(item, withEvents); } } else { _this._addChoice(item, withEvents); } }); }; Choices.prototype._findAndSelectChoiceByValue = function (value, userTriggered) { var _this = this; if (userTriggered === void 0) { userTriggered = false; } // Check 'value' property exists and the choice isn't already selected var foundChoice = this._store.choices.find(function (choice) { return _this.config.valueComparer(choice.value, value); }); if (foundChoice && !foundChoice.disabled && !foundChoice.selected) { this._addItem(foundChoice, true, userTriggered); return true; } return false; }; Choices.prototype._generatePlaceholderValue = function () { var config = this.config; if (!config.placeholder) { return null; } if (this._hasNonChoicePlaceholder) { return config.placeholderValue; } if (this._isSelectElement) { var placeholderOption = this.passedElement.placeholderOption; return placeholderOption ? placeholderOption.text : null; } return null; }; Choices.prototype._warnChoicesInitFailed = function (caller) { if (this.config.silent) { return; } if (!this.initialised) { throw new TypeError("".concat(caller, " called on a non-initialised instance of Choices")); } else if (!this.initialisedOK) { throw new TypeError("".concat(caller, " called for an element which has multiple instances of Choices initialised on it")); } }; Choices.version = '11.2.1'; return Choices; }()); export { Choices as default }; ================================================ FILE: public/assets/scripts/choices.search-kmp.js ================================================ /*! choices.js v11.2.1 | © 2026 Josh Johnson | https://github.com/Choices-js/Choices#readme */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Choices = factory()); })(this, (function () { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var ActionType = { ADD_CHOICE: 'ADD_CHOICE', REMOVE_CHOICE: 'REMOVE_CHOICE', FILTER_CHOICES: 'FILTER_CHOICES', ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', CLEAR_CHOICES: 'CLEAR_CHOICES', ADD_GROUP: 'ADD_GROUP', ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', }; var EventType = { showDropdown: 'showDropdown', hideDropdown: 'hideDropdown', change: 'change', choice: 'choice', search: 'search', addItem: 'addItem', removeItem: 'removeItem', highlightItem: 'highlightItem', highlightChoice: 'highlightChoice', unhighlightItem: 'unhighlightItem', }; var KeyCodeMap = { TAB_KEY: 9, SHIFT_KEY: 16, BACK_KEY: 46, DELETE_KEY: 8, ENTER_KEY: 13, A_KEY: 65, ESC_KEY: 27, UP_KEY: 38, DOWN_KEY: 40, PAGE_UP_KEY: 33, PAGE_DOWN_KEY: 34, }; var ObjectsInConfig = ['fuseOptions', 'classNames']; var PassedElementTypes = { Text: 'text', SelectOne: 'select-one', SelectMultiple: 'select-multiple', }; var addChoice = function (choice) { return ({ type: ActionType.ADD_CHOICE, choice: choice, }); }; var removeChoice = function (choice) { return ({ type: ActionType.REMOVE_CHOICE, choice: choice, }); }; var filterChoices = function (results) { return ({ type: ActionType.FILTER_CHOICES, results: results, }); }; var activateChoices = function (active) { return ({ type: ActionType.ACTIVATE_CHOICES, active: active, }); }; var addGroup = function (group) { return ({ type: ActionType.ADD_GROUP, group: group, }); }; var addItem = function (item) { return ({ type: ActionType.ADD_ITEM, item: item, }); }; var removeItem$1 = function (item) { return ({ type: ActionType.REMOVE_ITEM, item: item, }); }; var highlightItem = function (item, highlighted) { return ({ type: ActionType.HIGHLIGHT_ITEM, item: item, highlighted: highlighted, }); }; var getRandomNumber = function (min, max) { return Math.floor(Math.random() * (max - min) + min); }; var generateChars = function (length) { return Array.from({ length: length }, function () { return getRandomNumber(0, 36).toString(36); }).join(''); }; var generateId = function (element, prefix) { var id = element.id || (element.name && "".concat(element.name, "-").concat(generateChars(2))) || generateChars(4); id = id.replace(/(:|\.|\[|\]|,)/g, ''); id = "".concat(prefix, "-").concat(id); return id; }; var getAdjacentEl = function (startEl, selector, direction) { if (direction === void 0) { direction = 1; } var prop = "".concat(direction > 0 ? 'next' : 'previous', "ElementSibling"); var sibling = startEl[prop]; while (sibling) { if (sibling.matches(selector)) { return sibling; } sibling = sibling[prop]; } return null; }; var isScrolledIntoView = function (element, parent, direction) { if (direction === void 0) { direction = 1; } var isVisible; if (direction > 0) { // In view from bottom isVisible = parent.scrollTop + parent.offsetHeight >= element.offsetTop + element.offsetHeight; } else { // In view from top isVisible = element.offsetTop >= parent.scrollTop; } return isVisible; }; var sanitise = function (value) { if (typeof value !== 'string') { if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { if ('raw' in value) { return sanitise(value.raw); } if ('trusted' in value) { return value.trusted; } } return value; } return value .replace(/&/g, '&') .replace(/>/g, '>') .replace(/= 0 && !window.matchMedia("(min-height: ".concat(dropdownPos + 1, "px)")).matches; } else if (this.position === 'top') { shouldFlip = true; } return shouldFlip; }; Container.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Container.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Container.prototype.open = function (dropdownPos, dropdownHeight) { addClassesToElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; if (this.shouldFlip(dropdownPos, dropdownHeight)) { addClassesToElement(this.element, this.classNames.flippedState); this.isFlipped = true; } }; Container.prototype.close = function () { removeClassesFromElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'false'); this.removeActiveDescendant(); this.isOpen = false; // A dropdown flips if it does not have space within the page if (this.isFlipped) { removeClassesFromElement(this.element, this.classNames.flippedState); this.isFlipped = false; } }; Container.prototype.addFocusState = function () { addClassesToElement(this.element, this.classNames.focusState); }; Container.prototype.removeFocusState = function () { removeClassesFromElement(this.element, this.classNames.focusState); }; Container.prototype.addInvalidState = function () { addClassesToElement(this.element, this.classNames.invalidState); }; Container.prototype.removeInvalidState = function () { removeClassesFromElement(this.element, this.classNames.invalidState); }; Container.prototype.enable = function () { removeClassesFromElement(this.element, this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; }; Container.prototype.disable = function () { addClassesToElement(this.element, this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; }; Container.prototype.wrap = function (element) { var el = this.element; var parentNode = element.parentNode; if (parentNode) { if (element.nextSibling) { parentNode.insertBefore(el, element.nextSibling); } else { parentNode.appendChild(el); } } el.appendChild(element); }; Container.prototype.unwrap = function (element) { var el = this.element; var parentNode = el.parentNode; if (parentNode) { // Move passed element outside this element parentNode.insertBefore(element, el); // Remove this element parentNode.removeChild(el); } }; Container.prototype.addLoadingState = function () { addClassesToElement(this.element, this.classNames.loadingState); this.element.setAttribute('aria-busy', 'true'); this.isLoading = true; }; Container.prototype.removeLoadingState = function () { removeClassesFromElement(this.element, this.classNames.loadingState); this.element.removeAttribute('aria-busy'); this.isLoading = false; }; return Container; }()); var Input = /** @class */ (function () { function Input(_a) { var element = _a.element, type = _a.type, classNames = _a.classNames, preventPaste = _a.preventPaste; this.element = element; this.type = type; this.classNames = classNames; this.preventPaste = preventPaste; this.isFocussed = this.element.isEqualNode(document.activeElement); this.isDisabled = element.disabled; this._onPaste = this._onPaste.bind(this); this._onInput = this._onInput.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } Object.defineProperty(Input.prototype, "placeholder", { set: function (placeholder) { this.element.placeholder = placeholder; }, enumerable: false, configurable: true }); Object.defineProperty(Input.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.value = value; }, enumerable: false, configurable: true }); Input.prototype.addEventListeners = function () { var el = this.element; el.addEventListener('paste', this._onPaste); el.addEventListener('input', this._onInput, { passive: true, }); el.addEventListener('focus', this._onFocus, { passive: true, }); el.addEventListener('blur', this._onBlur, { passive: true, }); }; Input.prototype.removeEventListeners = function () { var el = this.element; el.removeEventListener('input', this._onInput); el.removeEventListener('paste', this._onPaste); el.removeEventListener('focus', this._onFocus); el.removeEventListener('blur', this._onBlur); }; Input.prototype.enable = function () { var el = this.element; el.removeAttribute('disabled'); this.isDisabled = false; }; Input.prototype.disable = function () { var el = this.element; el.setAttribute('disabled', ''); this.isDisabled = true; }; Input.prototype.focus = function () { if (!this.isFocussed) { this.element.focus(); } }; Input.prototype.blur = function () { if (this.isFocussed) { this.element.blur(); } }; Input.prototype.clear = function (setWidth) { if (setWidth === void 0) { setWidth = true; } this.element.value = ''; if (setWidth) { this.setWidth(); } return this; }; /** * Set the correct input width based on placeholder * value or input value */ Input.prototype.setWidth = function () { // Resize input to contents or placeholder var element = this.element; element.style.minWidth = "".concat(element.placeholder.length + 1, "ch"); element.style.width = "".concat(element.value.length + 1, "ch"); }; Input.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Input.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Input.prototype._onInput = function () { if (this.type !== PassedElementTypes.SelectOne) { this.setWidth(); } }; Input.prototype._onPaste = function (event) { if (this.preventPaste) { event.preventDefault(); } }; Input.prototype._onFocus = function () { this.isFocussed = true; }; Input.prototype._onBlur = function () { this.isFocussed = false; }; return Input; }()); var SCROLLING_SPEED = 4; var List = /** @class */ (function () { function List(_a) { var element = _a.element; this.element = element; this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; } List.prototype.prepend = function (node) { var child = this.element.firstElementChild; if (child) { this.element.insertBefore(node, child); } else { this.element.append(node); } }; List.prototype.scrollToTop = function () { this.element.scrollTop = 0; }; List.prototype.scrollToChildElement = function (element, direction) { var _this = this; if (!element) { return; } var listHeight = this.element.offsetHeight; // Scroll position of dropdown var listScrollPosition = this.element.scrollTop + listHeight; var elementHeight = element.offsetHeight; // Distance from bottom of element to top of parent var elementPos = element.offsetTop + elementHeight; // Difference between the element and scroll position var destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop; requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); }; List.prototype._scrollDown = function (scrollPos, strength, destination) { var easing = (destination - scrollPos) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos + distance; }; List.prototype._scrollUp = function (scrollPos, strength, destination) { var easing = (scrollPos - destination) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos - distance; }; List.prototype._animateScroll = function (destination, direction) { var _this = this; var strength = SCROLLING_SPEED; var choiceListScrollTop = this.element.scrollTop; var continueAnimation = false; if (direction > 0) { this._scrollDown(choiceListScrollTop, strength, destination); if (choiceListScrollTop < destination) { continueAnimation = true; } } else { this._scrollUp(choiceListScrollTop, strength, destination); if (choiceListScrollTop > destination) { continueAnimation = true; } } if (continueAnimation) { requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); } }; return List; }()); var WrappedElement = /** @class */ (function () { function WrappedElement(_a) { var element = _a.element, classNames = _a.classNames; this.element = element; this.classNames = classNames; this.isDisabled = false; } Object.defineProperty(WrappedElement.prototype, "isActive", { get: function () { return this.element.dataset.choice === 'active'; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "dir", { get: function () { return this.element.dir; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.setAttribute('value', value); this.element.value = value; }, enumerable: false, configurable: true }); WrappedElement.prototype.conceal = function () { var el = this.element; // Hide passed input addClassesToElement(el, this.classNames.input); el.hidden = true; // Remove element from tab index el.tabIndex = -1; // Backup original styles if any var origStyle = el.getAttribute('style'); if (origStyle) { el.setAttribute('data-choice-orig-style', origStyle); } el.setAttribute('data-choice', 'active'); }; WrappedElement.prototype.reveal = function () { var el = this.element; // Reinstate passed element removeClassesFromElement(el, this.classNames.input); el.hidden = false; el.removeAttribute('tabindex'); // Recover original styles if any var origStyle = el.getAttribute('data-choice-orig-style'); if (origStyle) { el.removeAttribute('data-choice-orig-style'); el.setAttribute('style', origStyle); } else { el.removeAttribute('style'); } el.removeAttribute('data-choice'); }; WrappedElement.prototype.enable = function () { this.element.removeAttribute('disabled'); this.element.disabled = false; this.isDisabled = false; }; WrappedElement.prototype.disable = function () { this.element.setAttribute('disabled', ''); this.element.disabled = true; this.isDisabled = true; }; WrappedElement.prototype.triggerEvent = function (eventType, data) { dispatchEvent(this.element, eventType, data || {}); }; return WrappedElement; }()); var WrappedInput = /** @class */ (function (_super) { __extends(WrappedInput, _super); function WrappedInput() { return _super !== null && _super.apply(this, arguments) || this; } return WrappedInput; }(WrappedElement)); var coerceBool = function (arg, defaultValue) { if (defaultValue === void 0) { defaultValue = true; } return typeof arg === 'undefined' ? defaultValue : !!arg; }; var stringToHtmlClass = function (input) { if (typeof input === 'string') { // eslint-disable-next-line no-param-reassign input = input.split(' ').filter(function (s) { return s.length; }); } if (Array.isArray(input) && input.length) { return input; } return undefined; }; var mapInputToChoice = function (value, allowGroup, allowRawString) { if (allowRawString === void 0) { allowRawString = true; } if (typeof value === 'string') { var sanitisedValue = sanitise(value); var userValue = allowRawString || sanitisedValue === value ? value : { escaped: sanitisedValue, raw: value }; var result_1 = mapInputToChoice({ value: value, label: userValue, selected: true, }, false); return result_1; } var groupOrChoice = value; if ('choices' in groupOrChoice) { if (!allowGroup) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup throw new TypeError("optGroup is not allowed"); } var group = groupOrChoice; var choices = group.choices.map(function (e) { return mapInputToChoice(e, false); }); var result_2 = { id: 0, // actual ID will be assigned during _addGroup label: unwrapStringForRaw(group.label) || group.value, active: !!choices.length, disabled: !!group.disabled, choices: choices, }; return result_2; } var choice = groupOrChoice; var result = { id: 0, // actual ID will be assigned during _addChoice group: null, // actual group will be assigned during _addGroup but before _addChoice score: 0, // used in search rank: 0, // used in search, stable sort order value: choice.value, label: choice.label || choice.value, active: coerceBool(choice.active), selected: coerceBool(choice.selected, false), disabled: coerceBool(choice.disabled, false), placeholder: coerceBool(choice.placeholder, false), highlighted: false, labelClass: stringToHtmlClass(choice.labelClass), labelDescription: choice.labelDescription, customProperties: choice.customProperties, }; return result; }; var isHtmlInputElement = function (e) { return e.tagName === 'INPUT'; }; var isHtmlSelectElement = function (e) { return e.tagName === 'SELECT'; }; var isHtmlOption = function (e) { return e.tagName === 'OPTION'; }; var isHtmlOptgroup = function (e) { return e.tagName === 'OPTGROUP'; }; var WrappedSelect = /** @class */ (function (_super) { __extends(WrappedSelect, _super); function WrappedSelect(_a) { var element = _a.element, classNames = _a.classNames, template = _a.template, extractPlaceholder = _a.extractPlaceholder; var _this = _super.call(this, { element: element, classNames: classNames }) || this; _this.template = template; _this.extractPlaceholder = extractPlaceholder; return _this; } Object.defineProperty(WrappedSelect.prototype, "placeholderOption", { get: function () { return (this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]')); }, enumerable: false, configurable: true }); WrappedSelect.prototype.addOptions = function (choices) { var _this = this; var fragment = document.createDocumentFragment(); choices.forEach(function (obj) { var choice = obj; if (choice.element) { return; } var option = _this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); }; WrappedSelect.prototype.optionsAsChoices = function () { var _this = this; var choices = []; this.element.querySelectorAll(':scope > option, :scope > optgroup').forEach(function (e) { if (isHtmlOption(e)) { choices.push(_this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(_this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; }; // eslint-disable-next-line class-methods-use-this WrappedSelect.prototype._optionToChoice = function (option) { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? { trusted: option.dataset.labelDescription } : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; }; WrappedSelect.prototype._optgroupToChoice = function (optgroup) { var _this = this; var options = optgroup.querySelectorAll('option'); var choices = Array.from(options).map(function (option) { return _this._optionToChoice(option); }); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices: choices, }; }; return WrappedSelect; }(WrappedElement)); var DEFAULT_CLASSNAMES = { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], }; var DEFAULT_CONFIG = { items: [], choices: [], silent: false, renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: function (value) { return !!value && value !== ''; }, removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: function (value) { return "Press Enter to add \"".concat(value, "\""); }, removeItemIconText: function () { return "Remove item"; }, removeItemLabelText: function (value, _valueRaw, i) { return "Remove item: ".concat(i ? sanitise(i.label) : value); }, maxItemText: function (maxItemCount) { return "Only ".concat(maxItemCount, " values can be added"); }, valueComparer: function (value1, value2) { return value1 === value2; }, fuseOptions: { includeScore: true, }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, appendGroupInSearch: false, }; var removeItem = function (item) { var itemEl = item.itemEl; if (itemEl) { itemEl.remove(); item.itemEl = undefined; } }; function items(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_ITEM: { action.item.selected = true; var el = action.item.element; if (el) { el.selected = true; el.setAttribute('selected', ''); } state.push(action.item); break; } case ActionType.REMOVE_ITEM: { action.item.selected = false; var el = action.item.element; if (el) { el.selected = false; el.removeAttribute('selected'); // For a select-one, if all options are deselected, the first item is selected. To set a black value, select.value needs to be set var select = el.parentElement; if (select && isHtmlSelectElement(select) && select.type === PassedElementTypes.SelectOne) { select.value = ''; } } // this is mixing concerns, but this is *so much faster* removeItem(action.item); state = state.filter(function (choice) { return choice.id !== action.item.id; }); break; } case ActionType.REMOVE_CHOICE: { removeItem(action.choice); state = state.filter(function (item) { return item.id !== action.choice.id; }); break; } case ActionType.HIGHLIGHT_ITEM: { var highlighted = action.highlighted; var item = state.find(function (obj) { return obj.id === action.item.id; }); if (item && item.highlighted !== highlighted) { item.highlighted = highlighted; if (context) { updateClassList(item, highlighted ? context.classNames.highlightedState : context.classNames.selectedState, highlighted ? context.classNames.selectedState : context.classNames.highlightedState); } } break; } default: { update = false; break; } } return { state: state, update: update }; } function groups(s, action) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_GROUP: { state.push(action.group); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } /* eslint-disable */ function choices(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_CHOICE: { state.push(action.choice); break; } case ActionType.REMOVE_CHOICE: { action.choice.choiceEl = undefined; if (action.choice.group) { action.choice.group.choices = action.choice.group.choices.filter(function (obj) { return obj.id !== action.choice.id; }); } state = state.filter(function (obj) { return obj.id !== action.choice.id; }); break; } case ActionType.ADD_ITEM: case ActionType.REMOVE_ITEM: { action.item.choiceEl = undefined; break; } case ActionType.FILTER_CHOICES: { // avoid O(n^2) algorithm complexity when searching/filtering choices var scoreLookup_1 = []; action.results.forEach(function (result) { scoreLookup_1[result.item.id] = result; }); state.forEach(function (choice) { var result = scoreLookup_1[choice.id]; if (result !== undefined) { choice.score = result.score; choice.rank = result.rank; choice.active = true; } else { choice.score = 0; choice.rank = 0; choice.active = false; } if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.ACTIVATE_CHOICES: { state.forEach(function (choice) { choice.active = action.active; if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } var reducers = { groups: groups, items: items, choices: choices, }; var Store = /** @class */ (function () { function Store(context) { this._state = this.defaultState; this._listeners = []; this._txn = 0; this._context = context; } Object.defineProperty(Store.prototype, "defaultState", { // eslint-disable-next-line class-methods-use-this get: function () { return { groups: [], items: [], choices: [], }; }, enumerable: false, configurable: true }); // eslint-disable-next-line class-methods-use-this Store.prototype.changeSet = function (init) { return { groups: init, items: init, choices: init, }; }; Store.prototype.reset = function () { this._state = this.defaultState; var changes = this.changeSet(true); if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } }; Store.prototype.subscribe = function (onChange) { this._listeners.push(onChange); return this; }; Store.prototype.dispatch = function (action) { var _this = this; var state = this._state; var hasChanges = false; var changes = this._changeSet || this.changeSet(false); Object.keys(reducers).forEach(function (key) { var stateUpdate = reducers[key](state[key], action, _this._context); if (stateUpdate.update) { hasChanges = true; changes[key] = true; state[key] = stateUpdate.state; } }); if (hasChanges) { if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } } }; Store.prototype.withTxn = function (func) { this._txn++; try { func(); } finally { this._txn = Math.max(0, this._txn - 1); if (!this._txn) { var changeSet_1 = this._changeSet; if (changeSet_1) { this._changeSet = undefined; this._listeners.forEach(function (l) { return l(changeSet_1); }); } } } }; Object.defineProperty(Store.prototype, "state", { /** * Get store object */ get: function () { return this._state; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "items", { /** * Get items from store */ get: function () { return this.state.items; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "highlightedActiveItems", { /** * Get highlighted items from store */ get: function () { return this.items.filter(function (item) { return item.active && item.highlighted; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "choices", { /** * Get choices from store */ get: function () { return this.state.choices; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeChoices", { /** * Get active choices from store */ get: function () { return this.choices.filter(function (choice) { return choice.active; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "searchableChoices", { /** * Get choices that can be searched (excluding placeholders or disabled choices) */ get: function () { var context = this._context; return this.choices.filter(function (choice) { return !choice.placeholder && (context.searchDisabledChoices || !choice.disabled); }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "groups", { /** * Get groups from store */ get: function () { return this.state.groups; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeGroups", { /** * Get active groups from store */ get: function () { var _this = this; return this.state.groups.filter(function (group) { var isActive = group.active && !group.disabled; var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; }); return isActive && hasActiveOptions; }, []); }, enumerable: false, configurable: true }); Store.prototype.inTxn = function () { return this._txn > 0; }; /** * Get single choice by it's ID */ Store.prototype.getChoiceById = function (id) { return this.activeChoices.find(function (choice) { return choice.id === id; }); }; /** * Get group by group id */ Store.prototype.getGroupById = function (id) { return this.groups.find(function (group) { return group.id === id; }); }; return Store; }()); var NoticeTypes = { noChoices: 'no-choices', noResults: 'no-results', addChoice: 'add-choice', generic: '', }; function kmpSearch(pattern, text) { if (pattern.length === 0) { return 0; // Immediate match } // Compute longest suffix-prefix table var lsp = [0]; // Base case for (var i = 1; i < pattern.length; i++) { var j_1 = lsp[i - 1]; // Start by assuming we're extending the previous LSP while (j_1 > 0 && pattern.charAt(i) !== pattern.charAt(j_1)) { j_1 = lsp[j_1 - 1]; } if (pattern.charAt(i) === pattern.charAt(j_1)) { j_1++; } lsp.push(j_1); } // Walk through text string var j = 0; // Number of chars matched in pattern for (var i = 0; i < text.length; i++) { while (j > 0 && text.charAt(i) !== pattern.charAt(j)) { j = lsp[j - 1]; // Fall back in the pattern } if (text.charAt(i) === pattern.charAt(j)) { j++; // Next char matched, increment position if (j === pattern.length) { return i - (j - 1); } } } return -1; // Not found } var SearchByKMP = /** @class */ (function () { function SearchByKMP(config) { this._haystack = []; this._fields = config.searchFields; } SearchByKMP.prototype.index = function (data) { this._haystack = data; }; SearchByKMP.prototype.reset = function () { this._haystack = []; }; SearchByKMP.prototype.isEmptyIndex = function () { return !this._haystack.length; }; SearchByKMP.prototype.search = function (_needle) { var fields = this._fields; if (!fields || !fields.length || !_needle) { return []; } var needle = _needle.toLowerCase(); var results = []; var count = 0; for (var i = 0, j = this._haystack.length; i < j; i++) { var obj = this._haystack[i]; for (var k = 0, l = this._fields.length; k < l; k++) { var field = this._fields[k]; if (field in obj && kmpSearch(needle, obj[field].toLowerCase()) !== -1) { results.push({ item: obj, score: count, rank: count + 1, }); count++; break; } } } return results; }; return SearchByKMP; }()); function getSearcher(config) { { return new SearchByKMP(config); } } /** * Helpers to create HTML elements used by Choices * Can be overridden by providing `callbackOnCreateTemplates` option. * `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates` */ var isEmptyObject = function (obj) { // eslint-disable-next-line no-restricted-syntax for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { return false; } } return true; }; var assignCustomProperties = function (el, choice, withCustomProperties) { var dataset = el.dataset; var customProperties = choice.customProperties, labelClass = choice.labelClass, labelDescription = choice.labelDescription; if (labelClass) { dataset.labelClass = getClassNames(labelClass).join(' '); } if (labelDescription) { dataset.labelDescription = unwrapStringForRaw(labelDescription); } if (withCustomProperties && customProperties) { if (typeof customProperties === 'string') { dataset.customProperties = customProperties; } else if (typeof customProperties === 'object' && !isEmptyObject(customProperties)) { dataset.customProperties = JSON.stringify(customProperties); } } }; var addAriaLabel = function (docRoot, id, element) { var label = id && docRoot.querySelector("label[for='".concat(id, "']")); var text = label && label.innerText; if (text) { element.setAttribute('aria-label', text); } }; var templates = { containerOuter: function (_a, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType, labelId) { var containerOuter = _a.classNames.containerOuter; var div = document.createElement('div'); addClassesToElement(div, containerOuter); div.dataset.type = passedElementType; if (dir) { div.dir = dir; } if (isSelectOneElement) { div.tabIndex = 0; } if (isSelectElement) { div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox'); if (searchEnabled) { div.setAttribute('aria-autocomplete', 'list'); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, div); } div.setAttribute('aria-haspopup', 'true'); div.setAttribute('aria-expanded', 'false'); } if (labelId) { div.setAttribute('aria-labelledby', labelId); } return div; }, containerInner: function (_a) { var containerInner = _a.classNames.containerInner; var div = document.createElement('div'); addClassesToElement(div, containerInner); return div; }, itemList: function (_a, isSelectOneElement) { var searchEnabled = _a.searchEnabled, _b = _a.classNames, list = _b.list, listSingle = _b.listSingle, listItems = _b.listItems; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, isSelectOneElement ? listSingle : listItems); if (this._isSelectElement && searchEnabled) { div.setAttribute('role', 'listbox'); } return div; }, placeholder: function (_a, value) { var allowHTML = _a.allowHTML, placeholder = _a.classNames.placeholder; var div = document.createElement('div'); addClassesToElement(div, placeholder); setElementHtml(div, allowHTML, value); return div; }, item: function (_a, choice, removeItemButton) { var allowHTML = _a.allowHTML, removeItemButtonAlignLeft = _a.removeItemButtonAlignLeft, removeItemIconText = _a.removeItemIconText, removeItemLabelText = _a.removeItemLabelText, _b = _a.classNames, item = _b.item, button = _b.button, highlightedState = _b.highlightedState, itemSelectable = _b.itemSelectable, placeholder = _b.placeholder; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); addClassesToElement(div, item); if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, choice.label); addClassesToElement(spanLabel, choice.labelClass); div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, choice.label); } div.dataset.item = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; assignCustomProperties(div, choice, true); if (choice.disabled || this.containerOuter.isDisabled) { div.setAttribute('aria-disabled', 'true'); } if (this._isSelectElement) { div.setAttribute('aria-selected', 'true'); div.setAttribute('role', 'option'); } if (choice.placeholder) { addClassesToElement(div, placeholder); div.dataset.placeholder = ''; } addClassesToElement(div, choice.highlighted ? highlightedState : itemSelectable); if (removeItemButton) { if (choice.disabled) { removeClassesFromElement(div, itemSelectable); } div.dataset.deletable = ''; var removeButton = document.createElement('button'); removeButton.type = 'button'; addClassesToElement(removeButton, button); var eventChoice = getChoiceForOutput(choice); setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value, eventChoice)); var REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value, eventChoice); if (REMOVE_ITEM_LABEL) { removeButton.setAttribute('aria-label', REMOVE_ITEM_LABEL); } removeButton.dataset.button = ''; if (removeItemButtonAlignLeft) { div.insertAdjacentElement('afterbegin', removeButton); } else { div.appendChild(removeButton); } } return div; }, choiceList: function (_a, isSelectOneElement) { var list = _a.classNames.list; var div = document.createElement('div'); addClassesToElement(div, list); if (!isSelectOneElement) { div.setAttribute('aria-multiselectable', 'true'); } div.setAttribute('role', 'listbox'); return div; }, choiceGroup: function (_a, _b) { var allowHTML = _a.allowHTML, _c = _a.classNames, group = _c.group, groupHeading = _c.groupHeading, itemDisabled = _c.itemDisabled; var id = _b.id, label = _b.label, disabled = _b.disabled; var rawLabel = unwrapStringForRaw(label); var div = document.createElement('div'); addClassesToElement(div, group); if (disabled) { addClassesToElement(div, itemDisabled); } div.setAttribute('role', 'group'); div.dataset.group = ''; div.dataset.id = id; div.dataset.value = rawLabel; if (disabled) { div.setAttribute('aria-disabled', 'true'); } var heading = document.createElement('div'); addClassesToElement(heading, groupHeading); setElementHtml(heading, allowHTML, label || ''); div.appendChild(heading); return div; }, choice: function (_a, choice, selectText, groupName) { var allowHTML = _a.allowHTML, _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, itemSelectable = _b.itemSelectable, selectedState = _b.selectedState, itemDisabled = _b.itemDisabled, description = _b.description, placeholder = _b.placeholder; // eslint-disable-next-line prefer-destructuring var label = choice.label; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); div.id = choice.elementId; addClassesToElement(div, item); addClassesToElement(div, itemChoice); if (groupName && typeof label === 'string') { label = escapeForTemplate(allowHTML, label); label += " (".concat(groupName, ")"); label = { trusted: label }; } var describedBy = div; if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, label); addClassesToElement(spanLabel, choice.labelClass); describedBy = spanLabel; div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, label); } if (choice.labelDescription) { var descId = "".concat(choice.elementId, "-description"); describedBy.setAttribute('aria-describedby', descId); var spanDesc = document.createElement('span'); setElementHtml(spanDesc, allowHTML, choice.labelDescription); spanDesc.id = descId; addClassesToElement(spanDesc, description); div.appendChild(spanDesc); } if (choice.selected) { addClassesToElement(div, selectedState); } if (choice.placeholder) { addClassesToElement(div, placeholder); } div.setAttribute('role', choice.group ? 'treeitem' : 'option'); div.dataset.choice = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; if (selectText) { div.dataset.selectText = selectText; } if (choice.group) { div.dataset.groupId = "".concat(choice.group.id); } assignCustomProperties(div, choice, false); if (choice.disabled) { addClassesToElement(div, itemDisabled); div.dataset.choiceDisabled = ''; div.setAttribute('aria-disabled', 'true'); } else { addClassesToElement(div, itemSelectable); div.dataset.choiceSelectable = ''; div.setAttribute('aria-selected', choice.selected ? 'true' : 'false'); } return div; }, input: function (_a, placeholderValue) { var _b = _a.classNames, input = _b.input, inputCloned = _b.inputCloned, labelId = _a.labelId; var inp = document.createElement('input'); inp.type = 'search'; addClassesToElement(inp, input); addClassesToElement(inp, inputCloned); inp.autocomplete = 'off'; inp.autocapitalize = 'off'; inp.spellcheck = false; inp.setAttribute('aria-autocomplete', 'list'); if (placeholderValue) { inp.setAttribute('aria-label', placeholderValue); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, inp); } return inp; }, dropdown: function (_a) { var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, listDropdown); div.setAttribute('aria-expanded', 'false'); return div; }, notice: function (_a, innerHTML, type) { var _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, addChoice = _b.addChoice, noResults = _b.noResults, noChoices = _b.noChoices, noticeItem = _b.notice; if (type === void 0) { type = NoticeTypes.generic; } var notice = document.createElement('div'); setElementHtml(notice, true, innerHTML); addClassesToElement(notice, item); addClassesToElement(notice, itemChoice); addClassesToElement(notice, noticeItem); // eslint-disable-next-line default-case switch (type) { case NoticeTypes.addChoice: addClassesToElement(notice, addChoice); break; case NoticeTypes.noResults: addClassesToElement(notice, noResults); break; case NoticeTypes.noChoices: addClassesToElement(notice, noChoices); break; } if (type === NoticeTypes.addChoice) { notice.dataset.choiceSelectable = ''; notice.dataset.choice = ''; } return notice; }, option: function (choice) { // HtmlOptionElement's label value does not support HTML, so the avoid double escaping unwrap the untrusted string. var labelValue = unwrapStringForRaw(choice.label); var opt = new Option(labelValue, choice.value, false, choice.selected); assignCustomProperties(opt, choice, true); opt.disabled = choice.disabled; if (choice.selected) { opt.setAttribute('selected', ''); } return opt; }, }; /** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ var IS_IE11 = '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style; var USER_DEFAULTS = {}; var parseDataSetId = function (element) { if (!element) { return undefined; } return element.dataset.id ? parseInt(element.dataset.id, 10) : undefined; }; var selectableChoiceIdentifier = '[data-choice-selectable]'; /** * Choices * @author Josh Johnson */ var Choices = /** @class */ (function () { function Choices(element, userConfig) { if (element === void 0) { element = '[data-choice]'; } if (userConfig === void 0) { userConfig = {}; } var _this = this; this.initialisedOK = undefined; this._hasNonChoicePlaceholder = false; this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; var defaults = Choices.defaults; this.config = __assign(__assign(__assign({}, defaults.allOptions), defaults.options), userConfig); ObjectsInConfig.forEach(function (key) { _this.config[key] = __assign(__assign(__assign({}, defaults.allOptions[key]), defaults.options[key]), userConfig[key]); }); var config = this.config; if (!config.silent) { this._validateConfig(); } var docRoot = config.shadowRoot || document.documentElement; this._docRoot = docRoot; var passedElement = typeof element === 'string' ? docRoot.querySelector(element) : element; if (!passedElement || typeof passedElement !== 'object' || !(isHtmlInputElement(passedElement) || isHtmlSelectElement(passedElement))) { if (!passedElement && typeof element === 'string') { throw TypeError("Selector ".concat(element, " failed to find an element")); } throw TypeError("Expected one of the following types text|select-one|select-multiple"); } var elementType = passedElement.type; var isText = elementType === PassedElementTypes.Text; if (isText || config.maxItemCount !== 1) { config.singleModeForMultiSelect = false; } if (config.singleModeForMultiSelect) { elementType = PassedElementTypes.SelectMultiple; } var isSelectOne = elementType === PassedElementTypes.SelectOne; var isSelectMultiple = elementType === PassedElementTypes.SelectMultiple; var isSelect = isSelectOne || isSelectMultiple; this._elementType = elementType; this._isTextElement = isText; this._isSelectOneElement = isSelectOne; this._isSelectMultipleElement = isSelectMultiple; this._isSelectElement = isSelectOne || isSelectMultiple; this._canAddUserChoices = (isText && config.addItems) || (isSelect && config.addChoices); if (typeof config.renderSelectedChoices !== 'boolean') { config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne; } if (config.closeDropdownOnSelect === 'auto') { config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect; } else { config.closeDropdownOnSelect = coerceBool(config.closeDropdownOnSelect); } if (config.placeholder) { if (config.placeholderValue) { this._hasNonChoicePlaceholder = true; } else if (passedElement.dataset.placeholder) { this._hasNonChoicePlaceholder = true; config.placeholderValue = passedElement.dataset.placeholder; } } if (userConfig.addItemFilter && typeof userConfig.addItemFilter !== 'function') { var re = userConfig.addItemFilter instanceof RegExp ? userConfig.addItemFilter : new RegExp(userConfig.addItemFilter); config.addItemFilter = re.test.bind(re); } if (this._isTextElement) { this.passedElement = new WrappedInput({ element: passedElement, classNames: config.classNames, }); } else { var selectEl = passedElement; this.passedElement = new WrappedSelect({ element: selectEl, classNames: config.classNames, template: function (data) { return _this._templates.option(data); }, extractPlaceholder: config.placeholder && !this._hasNonChoicePlaceholder, }); } this.initialised = false; this._store = new Store(config); this._currentValue = ''; config.searchEnabled = !isText && config.searchEnabled; this._canSearch = config.searchEnabled; this._isScrollingOnIe = false; this._highlightPosition = 0; this._wasTap = true; this._placeholderValue = this._generatePlaceholderValue(); this._baseId = generateId(passedElement, 'choices-'); /** * setting direction in cases where it's explicitly set on passedElement * or when calculated direction is different from the document */ this._direction = passedElement.dir; if (!this._direction) { var elementDirection = window.getComputedStyle(passedElement).direction; var documentDirection = window.getComputedStyle(document.documentElement).direction; if (elementDirection !== documentDirection) { this._direction = elementDirection; } } this._idNames = { itemChoice: 'item-choice', }; this._templates = defaults.templates; this._render = this._render.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onKeyUp = this._onKeyUp.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onInput = this._onInput.bind(this); this._onClick = this._onClick.bind(this); this._onTouchMove = this._onTouchMove.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseOver = this._onMouseOver.bind(this); this._onFormReset = this._onFormReset.bind(this); this._onSelectKey = this._onSelectKey.bind(this); this._onEnterKey = this._onEnterKey.bind(this); this._onEscapeKey = this._onEscapeKey.bind(this); this._onDirectionKey = this._onDirectionKey.bind(this); this._onDeleteKey = this._onDeleteKey.bind(this); this._onChange = this._onChange.bind(this); this._onInvalid = this._onInvalid.bind(this); // If element has already been initialised with Choices, fail silently if (this.passedElement.isActive) { if (!config.silent) { console.warn('Trying to initialise Choices on element already initialised', { element: element }); } this.initialised = true; this.initialisedOK = false; return; } // Let's go this.init(); // preserve the selected item list after setup for form reset this._initialItems = this._store.items.map(function (choice) { return choice.value; }); } Object.defineProperty(Choices, "defaults", { get: function () { return Object.preventExtensions({ get options() { return USER_DEFAULTS; }, get allOptions() { return DEFAULT_CONFIG; }, get templates() { return templates; }, }); }, enumerable: false, configurable: true }); Choices.prototype.init = function () { if (this.initialised || this.initialisedOK !== undefined) { return; } this._searcher = getSearcher(this.config); this._loadChoices(); this._createTemplates(); this._createElements(); this._createStructure(); if ((this._isTextElement && !this.config.addItems) || this.passedElement.element.hasAttribute('disabled') || !!this.passedElement.element.closest('fieldset:disabled')) { this.disable(); } else { this.enable(); this._addEventListeners(); } // should be triggered **after** disabled state to avoid additional re-draws this._initStore(); this.initialised = true; this.initialisedOK = true; var callbackOnInit = this.config.callbackOnInit; // Run callback if it is a function if (typeof callbackOnInit === 'function') { callbackOnInit.call(this); } }; Choices.prototype.destroy = function () { if (!this.initialised) { return; } this._removeEventListeners(); this.passedElement.reveal(); this.containerOuter.unwrap(this.passedElement.element); this._store._listeners = []; // prevents select/input value being wiped this.clearStore(false); this._stopSearch(); this._templates = Choices.defaults.templates; this.initialised = false; this.initialisedOK = undefined; }; Choices.prototype.enable = function () { if (this.passedElement.isDisabled) { this.passedElement.enable(); } if (this.containerOuter.isDisabled) { this._addEventListeners(); this.input.enable(); this.containerOuter.enable(); } return this; }; Choices.prototype.disable = function () { if (!this.passedElement.isDisabled) { this.passedElement.disable(); } if (!this.containerOuter.isDisabled) { this._removeEventListeners(); this.input.disable(); this.containerOuter.disable(); } return this; }; Choices.prototype.highlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, true)); if (runEvent) { this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.unhighlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || !choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, false)); if (runEvent) { this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.highlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (!item.highlighted) { _this._store.dispatch(highlightItem(item, true)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.unhighlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (item.highlighted) { _this._store.dispatch(highlightItem(item, false)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.removeActiveItemsByValue = function (value) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (item) { return item.value === value; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeActiveItems = function (excludedId) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (_a) { var id = _a.id; return id !== excludedId; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeHighlightedItems = function (runEvent) { var _this = this; if (runEvent === void 0) { runEvent = false; } this._store.withTxn(function () { _this._store.highlightedActiveItems.forEach(function (item) { _this._removeItem(item); // If this action was performed by the user // trigger the event if (runEvent) { _this._triggerChange(item.value); } }); }); return this; }; Choices.prototype.showDropdown = function (preventInputFocus) { var _this = this; if (this.dropdown.isActive) { return this; } if (preventInputFocus === undefined) { // eslint-disable-next-line no-param-reassign preventInputFocus = !this._canSearch; } requestAnimationFrame(function () { _this.dropdown.show(); var rect = _this.dropdown.element.getBoundingClientRect(); _this.containerOuter.open(rect.bottom, rect.height); if (!preventInputFocus) { _this.input.focus(); } _this.passedElement.triggerEvent(EventType.showDropdown); var activeElement = _this.choiceList.element.querySelector(getClassNamesSelector(_this.config.classNames.selectedState)); if (activeElement !== null && !isScrolledIntoView(activeElement, _this.choiceList.element)) { // We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll. activeElement.scrollIntoView(); } }); return this; }; Choices.prototype.hideDropdown = function (preventInputBlur) { var _this = this; if (!this.dropdown.isActive) { return this; } this._removeHighlightedChoices(); requestAnimationFrame(function () { _this.dropdown.hide(); _this.containerOuter.close(); if (!preventInputBlur && _this._canSearch) { _this.input.removeActiveDescendant(); _this.input.blur(); } _this.passedElement.triggerEvent(EventType.hideDropdown); }); return this; }; Choices.prototype.getValue = function (valueOnly) { var values = this._store.items.map(function (item) { return (valueOnly ? item.value : getChoiceForOutput(item)); }); return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values; }; Choices.prototype.setValue = function (items) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setValue'); return this; } this._store.withTxn(function () { items.forEach(function (value) { if (value) { _this._addChoice(mapInputToChoice(value, false)); } }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.setChoiceByValue = function (value) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoiceByValue'); return this; } if (this._isTextElement) { return this; } this._store.withTxn(function () { // If only one value has been passed, convert to array var choiceValue = Array.isArray(value) ? value : [value]; // Loop through each value and choiceValue.forEach(function (val) { return _this._findAndSelectChoiceByValue(val); }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ Choices.prototype.setChoices = function (choicesArrayOrFetcher, value, label, replaceChoices, clearSearchFlag, replaceItems) { var _this = this; if (choicesArrayOrFetcher === void 0) { choicesArrayOrFetcher = []; } if (value === void 0) { value = 'value'; } if (label === void 0) { label = 'label'; } if (replaceChoices === void 0) { replaceChoices = false; } if (clearSearchFlag === void 0) { clearSearchFlag = true; } if (replaceItems === void 0) { replaceItems = false; } if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoices'); return this; } if (!this._isSelectElement) { throw new TypeError("setChoices can't be used with INPUT based Choices"); } if (typeof value !== 'string' || !value) { throw new TypeError("value parameter must be a name of 'value' field in passed objects"); } if (typeof choicesArrayOrFetcher === 'function') { // it's a choices fetcher function var fetcher_1 = choicesArrayOrFetcher(this); if (typeof Promise === 'function' && fetcher_1 instanceof Promise) { // that's a promise // eslint-disable-next-line no-promise-executor-return return new Promise(function (resolve) { return requestAnimationFrame(resolve); }) .then(function () { return _this._handleLoadingState(true); }) .then(function () { return fetcher_1; }) .then(function (data) { return _this.setChoices(data, value, label, replaceChoices, clearSearchFlag, replaceItems); }) .catch(function (err) { if (!_this.config.silent) { console.error(err); } }) .then(function () { return _this._handleLoadingState(false); }) .then(function () { return _this; }); } // function returned something else than promise, let's check if it's an array of choices if (!Array.isArray(fetcher_1)) { throw new TypeError(".setChoices first argument function must return either array of choices or Promise, got: ".concat(typeof fetcher_1)); } // recursion with results, it's sync and choices were cleared already return this.setChoices(fetcher_1, value, label, false); } if (!Array.isArray(choicesArrayOrFetcher)) { throw new TypeError(".setChoices must be called either with array of choices with a function resulting into Promise of array of choices"); } this.containerOuter.removeLoadingState(); this._store.withTxn(function () { if (clearSearchFlag) { _this._isSearching = false; } // Clear choices if needed if (replaceChoices) { _this.clearChoices(true, replaceItems); } var isDefaultValue = value === 'value'; var isDefaultLabel = label === 'label'; choicesArrayOrFetcher.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { var group = groupOrChoice; if (!isDefaultLabel) { group = __assign(__assign({}, group), { label: group[label] }); } _this._addGroup(mapInputToChoice(group, true)); } else { var choice = groupOrChoice; if (!isDefaultLabel || !isDefaultValue) { choice = __assign(__assign({}, choice), { value: choice[value], label: choice[label] }); } var choiceFull = mapInputToChoice(choice, false); _this._addChoice(choiceFull); if (choiceFull.placeholder && !_this._hasNonChoicePlaceholder) { _this._placeholderValue = unwrapStringForEscaped(choiceFull.label); } } }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.refresh = function (withEvents, selectFirstOption, deselectAll) { var _this = this; if (withEvents === void 0) { withEvents = false; } if (selectFirstOption === void 0) { selectFirstOption = false; } if (deselectAll === void 0) { deselectAll = false; } if (!this._isSelectElement) { if (!this.config.silent) { console.warn('refresh method can only be used on choices backed by a element'); } return this; } this._store.withTxn(function () { var choicesFromOptions = _this.passedElement.optionsAsChoices(); // Build the list of items which require preserving var existingItems = {}; if (!deselectAll) { _this._store.items.forEach(function (choice) { if (choice.id && choice.active && choice.selected) { existingItems[choice.value] = true; } }); } _this.clearStore(false); var updateChoice = function (choice) { if (deselectAll) { _this._store.dispatch(removeItem$1(choice)); } else if (existingItems[choice.value]) { choice.selected = true; } }; choicesFromOptions.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { groupOrChoice.choices.forEach(updateChoice); return; } updateChoice(groupOrChoice); }); /* @todo only generate add events for the added options instead of all if (withEvents) { items.forEach((choice) => { if (existingItems[choice.value]) { this.passedElement.triggerEvent( EventType.removeItem, this._getChoiceForEvent(choice), ); } }); } */ // load new choices & items _this._addPredefinedChoices(choicesFromOptions, selectFirstOption, withEvents); // re-do search if required if (_this._isSearching) { _this._searchChoices(_this.input.value); } }); return this; }; Choices.prototype.removeChoice = function (value) { var choice = this._store.choices.find(function (c) { return c.value === value; }); if (!choice) { return this; } this._clearNotice(); this._store.dispatch(removeChoice(choice)); // @todo integrate with Store this._searcher.reset(); if (choice.selected) { this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.clearChoices = function (clearOptions, clearItems) { var _this = this; if (clearOptions === void 0) { clearOptions = true; } if (clearItems === void 0) { clearItems = false; } if (clearOptions) { if (clearItems) { this.passedElement.element.replaceChildren(''); } else { this.passedElement.element.querySelectorAll(':not([selected])').forEach(function (el) { el.remove(); }); } } this.itemList.element.replaceChildren(''); this.choiceList.element.replaceChildren(''); this._clearNotice(); this._store.withTxn(function () { var items = clearItems ? [] : _this._store.items; _this._store.reset(); items.forEach(function (item) { _this._store.dispatch(addChoice(item)); _this._store.dispatch(addItem(item)); }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.clearStore = function (clearOptions) { if (clearOptions === void 0) { clearOptions = true; } this.clearChoices(clearOptions, true); this._stopSearch(); this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; return this; }; Choices.prototype.clearInput = function () { var shouldSetInputWidth = !this._isSelectOneElement; this.input.clear(shouldSetInputWidth); this._stopSearch(); return this; }; Choices.prototype._validateConfig = function () { var config = this.config; var invalidConfigOptions = diff(config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); } if (config.allowHTML && config.allowHtmlUserInput) { if (config.addItems) { console.warn('Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks'); } if (config.addChoices) { console.warn('Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks'); } } }; Choices.prototype._render = function (changes) { if (changes === void 0) { changes = { choices: true, groups: true, items: true }; } if (this._store.inTxn()) { return; } if (this._isSelectElement) { if (changes.choices || changes.groups) { this._renderChoices(); } } if (changes.items) { this._renderItems(); } }; Choices.prototype._renderChoices = function () { var _this = this; if (!this._canAddItems()) { return; // block rendering choices if the input limit is reached. } var _a = this, config = _a.config, isSearching = _a._isSearching; var _b = this._store, activeGroups = _b.activeGroups, activeChoices = _b.activeChoices; var renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit; if (this._isSelectElement) { var backingOptions = activeChoices.filter(function (choice) { return !choice.element; }); if (backingOptions.length) { this.passedElement.addOptions(backingOptions); } } var fragment = document.createDocumentFragment(); var renderableChoices = function (choices) { return choices.filter(function (choice) { return !choice.placeholder && (isSearching ? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank : config.renderSelectedChoices || !choice.selected); }); }; var showLabel = config.appendGroupInSearch && isSearching; var selectableChoices = false; var highlightedEl = null; var renderChoices = function (choices, withinGroup) { if (isSearching) { // sortByRank is used to ensure stable sorting, as scores are non-unique // this additionally ensures fuseOptions.sortFn is not ignored choices.sort(sortByRank); } else if (config.shouldSort) { choices.sort(config.sorter); } var choiceLimit = choices.length; choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit; choiceLimit--; choices.every(function (choice, index) { // choiceEl being empty signals the contents has probably significantly changed var dropdownItem = choice.choiceEl || _this._templates.choice(config, choice, config.itemSelectText, showLabel && choice.group ? choice.group.label : undefined); choice.choiceEl = dropdownItem; fragment.appendChild(dropdownItem); if (isSearching || !choice.selected) { selectableChoices = true; } else if (!highlightedEl) { highlightedEl = dropdownItem; } return index < choiceLimit; }); }; if (activeChoices.length) { if (config.resetScrollPosition) { requestAnimationFrame(function () { return _this.choiceList.scrollToTop(); }); } if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) { // If we have a placeholder choice along with groups renderChoices(activeChoices.filter(function (choice) { return choice.placeholder && !choice.group; }), false); } // If we have grouped options if (activeGroups.length && !isSearching) { if (config.shouldSort) { activeGroups.sort(config.sorter); } // render Choices without group first, regardless of sort, otherwise they won't be distinguishable // from the last group renderChoices(activeChoices.filter(function (choice) { return !choice.placeholder && !choice.group; }), false); activeGroups.forEach(function (group) { var groupChoices = renderableChoices(group.choices); if (groupChoices.length) { if (group.label) { var dropdownGroup = group.groupEl || _this._templates.choiceGroup(_this.config, group); group.groupEl = dropdownGroup; dropdownGroup.remove(); fragment.appendChild(dropdownGroup); } renderChoices(groupChoices, true); } }); } else { renderChoices(renderableChoices(activeChoices), false); } } if (!selectableChoices && (isSearching || !fragment.children.length || !config.renderSelectedChoices)) { if (!this._notice) { this._notice = { text: resolveStringFunction(isSearching ? config.noResultsText : config.noChoicesText), type: isSearching ? NoticeTypes.noResults : NoticeTypes.noChoices, }; } fragment.replaceChildren(''); } this._renderNotice(fragment); this.choiceList.element.replaceChildren(fragment); this._highlightChoice(highlightedEl); }; Choices.prototype._renderItems = function () { var _this = this; var items = this._store.items || []; var itemList = this.itemList.element; var config = this.config; var fragment = document.createDocumentFragment(); var itemFromList = function (item) { return itemList.querySelector("[data-item][data-id=\"".concat(item.id, "\"]")); }; var addItemToFragment = function (item) { var el = item.itemEl; if (el && el.parentElement) { return; } el = itemFromList(item) || _this._templates.item(config, item, config.removeItemButton); item.itemEl = el; fragment.appendChild(el); }; // new items items.forEach(addItemToFragment); var addedItems = !!fragment.childNodes.length; if (this._isSelectOneElement) { var existingItems = itemList.children.length; if (addedItems || existingItems > 1) { var placeholder = itemList.querySelector(getClassNamesSelector(config.classNames.placeholder)); if (placeholder) { placeholder.remove(); } } else if (!addedItems && !existingItems && this._placeholderValue) { addedItems = true; addItemToFragment(mapInputToChoice({ selected: true, value: '', label: this._placeholderValue, placeholder: true, }, false)); } } if (addedItems) { itemList.append(fragment); if (config.shouldSortItems && !this._isSelectOneElement) { items.sort(config.sorter); // push sorting into the DOM items.forEach(function (item) { var el = itemFromList(item); if (el) { el.remove(); fragment.append(el); } }); itemList.append(fragment); } } if (this._isTextElement) { // Update the value of the hidden input this.passedElement.value = items.map(function (_a) { var value = _a.value; return value; }).join(config.delimiter); } }; Choices.prototype._displayNotice = function (text, type, openDropdown) { if (openDropdown === void 0) { openDropdown = true; } var oldNotice = this._notice; if (oldNotice && ((oldNotice.type === type && oldNotice.text === text) || (oldNotice.type === NoticeTypes.addChoice && (type === NoticeTypes.noResults || type === NoticeTypes.noChoices)))) { if (openDropdown) { this.showDropdown(true); } return; } this._clearNotice(); this._notice = text ? { text: text, type: type, } : undefined; this._renderNotice(); if (openDropdown && text) { this.showDropdown(true); } }; Choices.prototype._clearNotice = function () { if (!this._notice) { return; } var noticeElement = this.choiceList.element.querySelector(getClassNamesSelector(this.config.classNames.notice)); if (noticeElement) { noticeElement.remove(); } this._notice = undefined; }; Choices.prototype._renderNotice = function (fragment) { var noticeConf = this._notice; if (noticeConf) { var notice = this._templates.notice(this.config, noticeConf.text, noticeConf.type); if (fragment) { fragment.append(notice); } else { this.choiceList.prepend(notice); } } }; /** * @deprecated Use utils.getChoiceForOutput */ // eslint-disable-next-line class-methods-use-this Choices.prototype._getChoiceForOutput = function (choice, keyCode) { return getChoiceForOutput(choice, keyCode); }; Choices.prototype._triggerChange = function (value) { if (value === undefined || value === null) { return; } this.passedElement.triggerEvent(EventType.change, { value: value, }); }; Choices.prototype._handleButtonAction = function (element) { var _this = this; var items = this._store.items; if (!items.length || !this.config.removeItems || !this.config.removeItemButton) { return; } var id = element && parseDataSetId(element.closest('[data-id]')); var itemToRemove = id && items.find(function (item) { return item.id === id; }); if (!itemToRemove) { return; } this._store.withTxn(function () { // Remove item associated with button _this._removeItem(itemToRemove); _this._triggerChange(itemToRemove.value); if (_this._isSelectOneElement && !_this._hasNonChoicePlaceholder) { var placeholderChoice = (_this.config.shouldSort ? _this._store.choices.reverse() : _this._store.choices).find(function (choice) { return choice.placeholder; }); if (placeholderChoice) { _this._addItem(placeholderChoice); _this.unhighlightAll(); if (placeholderChoice.value) { _this._triggerChange(placeholderChoice.value); } } } }); }; Choices.prototype._handleItemAction = function (element, hasShiftKey) { var _this = this; if (hasShiftKey === void 0) { hasShiftKey = false; } var items = this._store.items; if (!items.length || !this.config.removeItems || this._isSelectOneElement) { return; } var id = parseDataSetId(element); if (!id) { return; } // We only want to select one item with a click // so we deselect any items that aren't the target // unless shift is being pressed items.forEach(function (item) { if (item.id === id && !item.highlighted) { _this.highlightItem(item); } else if (!hasShiftKey && item.highlighted) { _this.unhighlightItem(item); } }); // Focus input as without focus, a user cannot do anything with a // highlighted item this.input.focus(); }; Choices.prototype._handleChoiceAction = function (element) { var _this = this; // If we are clicking on an option var id = parseDataSetId(element); var choice = id && this._store.getChoiceById(id); if (!choice || choice.disabled) { return false; } var hasActiveDropdown = this.dropdown.isActive; if (!choice.selected) { if (!this._canAddItems()) { return true; // causes _onEnterKey to early out } this._store.withTxn(function () { _this._addItem(choice, true, true); _this.clearInput(); _this.unhighlightAll(); }); this._triggerChange(choice.value); } // We want to close the dropdown if we are dealing with a single select box if (hasActiveDropdown && this.config.closeDropdownOnSelect) { this.hideDropdown(true); this.containerOuter.element.focus(); } return true; }; Choices.prototype._handleBackspace = function (items) { var config = this.config; if (!config.removeItems || !items.length) { return; } var lastItem = items[items.length - 1]; var hasHighlightedItems = items.some(function (item) { return item.highlighted; }); // If editing the last item is allowed and there are not other selected items, // we can edit the item value. Otherwise if we can remove items, remove all selected items if (config.editItems && !hasHighlightedItems && lastItem) { this.input.value = lastItem.value; this.input.setWidth(); this._removeItem(lastItem); this._triggerChange(lastItem.value); } else { if (!hasHighlightedItems) { // Highlight last item if none already highlighted this.highlightItem(lastItem, false); } this.removeHighlightedItems(true); } }; Choices.prototype._loadChoices = function () { var _a; var _this = this; var config = this.config; if (this._isTextElement) { // Assign preset items from passed object first this._presetChoices = config.items.map(function (e) { return mapInputToChoice(e, false); }); // Add any values passed from attribute if (this.passedElement.value) { var elementItems = this.passedElement.value .split(config.delimiter) .map(function (e) { return mapInputToChoice(e, false, _this.config.allowHtmlUserInput); }); this._presetChoices = this._presetChoices.concat(elementItems); } this._presetChoices.forEach(function (choice) { choice.selected = true; }); } else if (this._isSelectElement) { // Assign preset choices from passed object this._presetChoices = config.choices.map(function (e) { return mapInputToChoice(e, true); }); // Create array of choices from option elements var choicesFromOptions = this.passedElement.optionsAsChoices(); if (choicesFromOptions) { (_a = this._presetChoices).push.apply(_a, choicesFromOptions); } } }; Choices.prototype._handleLoadingState = function (setLoading) { if (setLoading === void 0) { setLoading = true; } var el = this.itemList.element; if (setLoading) { this.disable(); this.containerOuter.addLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(this._templates.placeholder(this.config, this.config.loadingText)); } else { this.input.placeholder = this.config.loadingText; } } else { this.enable(); this.containerOuter.removeLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(''); this._render(); } else { this.input.placeholder = this._placeholderValue || ''; } } }; Choices.prototype._handleSearch = function (value) { if (!this.input.isFocussed) { return; } // Check that we have a value to search and the input was an alphanumeric character if (value !== null && typeof value !== 'undefined' && value.length >= this.config.searchFloor) { var resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; if (resultCount !== null) { // Trigger search event this.passedElement.triggerEvent(EventType.search, { value: value, resultCount: resultCount, }); } } else if (this._store.choices.some(function (option) { return !option.active; })) { this._stopSearch(); } }; Choices.prototype._canAddItems = function () { var config = this.config; var maxItemCount = config.maxItemCount, maxItemText = config.maxItemText; if (!config.singleModeForMultiSelect && maxItemCount > 0 && maxItemCount <= this._store.items.length) { this.choiceList.element.replaceChildren(''); this._notice = undefined; this._displayNotice(typeof maxItemText === 'function' ? maxItemText(maxItemCount) : maxItemText, NoticeTypes.addChoice); return false; } if (this._notice && this._notice.type === NoticeTypes.addChoice) { this._clearNotice(); } return true; }; Choices.prototype._canCreateItem = function (value) { var config = this.config; var canAddItem = true; var notice = ''; if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) { canAddItem = false; notice = resolveNoticeFunction(config.customAddItemText, value, undefined); } if (canAddItem) { var foundChoice = this._store.choices.find(function (choice) { return config.valueComparer(choice.value, value); }); if (foundChoice) { if (this._isSelectElement) { // for exact matches, do not prompt to add it as a custom choice this._displayNotice('', NoticeTypes.addChoice); return false; } if (!config.duplicateItemsAllowed) { canAddItem = false; notice = resolveNoticeFunction(config.uniqueItemText, value, undefined); } } } if (canAddItem) { notice = resolveNoticeFunction(config.addItemText, value, undefined); } if (notice) { this._displayNotice(notice, NoticeTypes.addChoice); } return canAddItem; }; Choices.prototype._searchChoices = function (value) { var newValue = value.trim().replace(/\s{2,}/, ' '); // signal input didn't change search if (!newValue.length || newValue === this._currentValue) { return null; } var searcher = this._searcher; if (searcher.isEmptyIndex()) { searcher.index(this._store.searchableChoices); } // If new value matches the desired length and is not the same as the current value with a space var results = searcher.search(newValue); this._currentValue = newValue; this._highlightPosition = 0; this._isSearching = true; var notice = this._notice; var noticeType = notice && notice.type; if (noticeType !== NoticeTypes.addChoice) { if (!results.length) { this._displayNotice(resolveStringFunction(this.config.noResultsText), NoticeTypes.noResults); } else { this._clearNotice(); } } this._store.dispatch(filterChoices(results)); return results.length; }; Choices.prototype._stopSearch = function () { if (this._isSearching) { this._currentValue = ''; this._isSearching = false; this._clearNotice(); this._store.dispatch(activateChoices(true)); this.passedElement.triggerEvent(EventType.search, { value: '', resultCount: 0, }); } }; Choices.prototype._addEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; // capture events - can cancel event processing or propagation documentElement.addEventListener('touchend', this._onTouchEnd, true); outerElement.addEventListener('keydown', this._onKeyDown, true); outerElement.addEventListener('mousedown', this._onMouseDown, true); // passive events - doesn't call `preventDefault` or `stopPropagation` documentElement.addEventListener('click', this._onClick, { passive: true }); documentElement.addEventListener('touchmove', this._onTouchMove, { passive: true, }); this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { passive: true, }); if (this._isSelectOneElement) { outerElement.addEventListener('focus', this._onFocus, { passive: true, }); outerElement.addEventListener('blur', this._onBlur, { passive: true, }); } inputElement.addEventListener('keyup', this._onKeyUp, { passive: true, }); inputElement.addEventListener('input', this._onInput, { passive: true, }); inputElement.addEventListener('focus', this._onFocus, { passive: true, }); inputElement.addEventListener('blur', this._onBlur, { passive: true, }); if (inputElement.form) { inputElement.form.addEventListener('reset', this._onFormReset, { passive: true, }); } if (passedElement.hasAttribute('required')) { passedElement.addEventListener('change', this._onChange, { passive: true, }); passedElement.addEventListener('invalid', this._onInvalid, { passive: true, }); } this.input.addEventListeners(); }; Choices.prototype._removeEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; documentElement.removeEventListener('touchend', this._onTouchEnd, true); outerElement.removeEventListener('keydown', this._onKeyDown, true); outerElement.removeEventListener('mousedown', this._onMouseDown, true); documentElement.removeEventListener('click', this._onClick); documentElement.removeEventListener('touchmove', this._onTouchMove); this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); if (this._isSelectOneElement) { outerElement.removeEventListener('focus', this._onFocus); outerElement.removeEventListener('blur', this._onBlur); } inputElement.removeEventListener('keyup', this._onKeyUp); inputElement.removeEventListener('input', this._onInput); inputElement.removeEventListener('focus', this._onFocus); inputElement.removeEventListener('blur', this._onBlur); if (inputElement.form) { inputElement.form.removeEventListener('reset', this._onFormReset); } if (passedElement.hasAttribute('required')) { passedElement.removeEventListener('change', this._onChange); passedElement.removeEventListener('invalid', this._onInvalid); } this.input.removeEventListeners(); }; Choices.prototype._onKeyDown = function (event) { var keyCode = event.keyCode; var hasActiveDropdown = this.dropdown.isActive; /* See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs https://stackoverflow.com/a/70866532 - "Unidentified" for mobile http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7) Logic: when a key event is sent, `event.key` represents its printable value _or_ one of a large list of special values indicating meta keys/functionality. In addition, key events for compose functionality contain a value of `Dead` when mid-composition. I can't quite verify it, but non-English IMEs may also be able to generate key codes for code points in the surrogate-pair range, which could potentially be seen as having key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that alone. Here, key.length === 1 means we know for sure the input was printable and not a special `key` value. When the length is greater than 1, it could be either a printable surrogate pair or a special `key` value. We can tell the difference by checking if the _character code_ value (not code point!) is in the "surrogate pair" range or not. We don't use .codePointAt because an invalid code point would return 65535, which wouldn't pass the >= 0x10000 check we would otherwise use. > ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points > of each plane are noncharacters: U+FFFE and U+FFFF on the BMP... */ var wasPrintableChar = event.key.length === 1 || (event.key.length === 2 && event.key.charCodeAt(0) >= 0xd800) || event.key === 'Unidentified'; /* We do not show the dropdown if focusing out with esc or navigating through input fields. An activated search can still be opened with any other key. */ if (!this._isTextElement && !hasActiveDropdown && keyCode !== KeyCodeMap.ESC_KEY && keyCode !== KeyCodeMap.TAB_KEY && keyCode !== KeyCodeMap.SHIFT_KEY) { this.showDropdown(); if (!this.input.isFocussed && wasPrintableChar) { /* We update the input value with the pressed key as the input was not focussed at the time of key press therefore does not have the value of the key. */ this.input.value += event.key; // browsers interpret a space as pagedown if (event.key === ' ') { event.preventDefault(); } } } switch (keyCode) { case KeyCodeMap.A_KEY: return this._onSelectKey(event, this.itemList.element.hasChildNodes()); case KeyCodeMap.ENTER_KEY: return this._onEnterKey(event, hasActiveDropdown); case KeyCodeMap.ESC_KEY: return this._onEscapeKey(event, hasActiveDropdown); case KeyCodeMap.UP_KEY: case KeyCodeMap.PAGE_UP_KEY: case KeyCodeMap.DOWN_KEY: case KeyCodeMap.PAGE_DOWN_KEY: return this._onDirectionKey(event, hasActiveDropdown); case KeyCodeMap.DELETE_KEY: case KeyCodeMap.BACK_KEY: return this._onDeleteKey(event, this._store.items, this.input.isFocussed); } }; Choices.prototype._onKeyUp = function ( /* event: KeyboardEvent */) { this._canSearch = this.config.searchEnabled; }; Choices.prototype._onInput = function ( /* event: InputEvent */) { var value = this.input.value; if (!value) { if (this._isTextElement) { this.hideDropdown(true); } else { this._stopSearch(); } return; } if (!this._canAddItems()) { return; } if (this._canSearch) { // do the search even if the entered text can not be added this._handleSearch(value); } if (!this._canAddUserChoices) { return; } // determine if a notice needs to be displayed for why a search result can't be added this._canCreateItem(value); if (this._isSelectElement) { this._highlightPosition = 0; // reset to select the notice and/or exact match this._highlightChoice(); } }; Choices.prototype._onSelectKey = function (event, hasItems) { // If CTRL + A or CMD + A have been pressed and there are items to select if ((event.ctrlKey || event.metaKey) && hasItems) { this._canSearch = false; var shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; if (shouldHightlightAll) { this.highlightAll(); } } }; Choices.prototype._onEnterKey = function (event, hasActiveDropdown) { var _this = this; var value = this.input.value; var target = event.target; event.preventDefault(); if (target && target.hasAttribute('data-button')) { this._handleButtonAction(target); return; } if (!hasActiveDropdown) { if (this._isSelectElement || this._notice) { this.showDropdown(); } return; } var highlightedChoice = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (highlightedChoice && this._handleChoiceAction(highlightedChoice)) { return; } if (!target || !value) { this.hideDropdown(true); return; } if (!this._canAddItems()) { return; } var addedItem = false; this._store.withTxn(function () { addedItem = _this._findAndSelectChoiceByValue(value, true); if (!addedItem) { if (!_this._canAddUserChoices) { return; } if (!_this._canCreateItem(value)) { return; } _this._addChoice(mapInputToChoice(value, false, _this.config.allowHtmlUserInput), true, true); addedItem = true; } _this.clearInput(); _this.unhighlightAll(); }); if (!addedItem) { return; } this._triggerChange(value); if (this.config.closeDropdownOnSelect) { this.hideDropdown(true); } }; Choices.prototype._onEscapeKey = function (event, hasActiveDropdown) { if (hasActiveDropdown) { event.stopPropagation(); this.hideDropdown(true); this._stopSearch(); this.containerOuter.element.focus(); } }; Choices.prototype._onDirectionKey = function (event, hasActiveDropdown) { var keyCode = event.keyCode; // If up or down key is pressed, traverse through options if (hasActiveDropdown || this._isSelectOneElement) { this.showDropdown(); this._canSearch = false; var directionInt = keyCode === KeyCodeMap.DOWN_KEY || keyCode === KeyCodeMap.PAGE_DOWN_KEY ? 1 : -1; var skipKey = event.metaKey || keyCode === KeyCodeMap.PAGE_DOWN_KEY || keyCode === KeyCodeMap.PAGE_UP_KEY; var nextEl = void 0; if (skipKey) { if (directionInt > 0) { nextEl = this.dropdown.element.querySelector("".concat(selectableChoiceIdentifier, ":last-of-type")); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } else { var currentEl = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (currentEl) { nextEl = getAdjacentEl(currentEl, selectableChoiceIdentifier, directionInt); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } if (nextEl) { // We prevent default to stop the cursor moving // when pressing the arrow if (!isScrolledIntoView(nextEl, this.choiceList.element, directionInt)) { this.choiceList.scrollToChildElement(nextEl, directionInt); } this._highlightChoice(nextEl); } // Prevent default to maintain cursor position whilst // traversing dropdown options event.preventDefault(); } }; Choices.prototype._onDeleteKey = function (event, items, hasFocusedInput) { // If backspace or delete key is pressed and the input has no value if (!this._isSelectOneElement && !event.target.value && hasFocusedInput) { this._handleBackspace(items); event.preventDefault(); } }; Choices.prototype._onTouchMove = function () { if (this._wasTap) { this._wasTap = false; } }; Choices.prototype._onTouchEnd = function (event) { var target = (event || event.touches[0]).target; var touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target); if (touchWasWithinContainer) { var containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; if (containerWasExactTarget) { if (this._isTextElement) { this.input.focus(); } else if (this._isSelectMultipleElement) { this.showDropdown(); } } // Prevents focus event firing event.stopPropagation(); } this._wasTap = true; }; /** * Handles mousedown event in capture mode for containetOuter.element */ Choices.prototype._onMouseDown = function (event) { var target = event.target; if (!(target instanceof Element)) { return; } // If we have our mouse down on the scrollbar and are on IE11... if (IS_IE11 && this.choiceList.element.contains(target)) { // check if click was on a scrollbar area var firstChoice = this.choiceList.element.firstElementChild; this._isScrollingOnIe = this._direction === 'ltr' ? event.offsetX >= firstChoice.offsetWidth : event.offsetX < firstChoice.offsetLeft; } if (target === this.input.element) { return; } var item = target.closest('[data-button],[data-item],[data-choice]'); if (item instanceof HTMLElement) { if ('button' in item.dataset) { this._handleButtonAction(item); } else if ('item' in item.dataset) { this._handleItemAction(item, event.shiftKey); } else if ('choice' in item.dataset) { this._handleChoiceAction(item); } } event.preventDefault(); }; /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ Choices.prototype._onMouseOver = function (_a) { var target = _a.target; if (target instanceof HTMLElement && 'choice' in target.dataset) { this._highlightChoice(target); } }; Choices.prototype._onClick = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var clickWasWithinContainer = containerOuter.element.contains(target); if (clickWasWithinContainer) { if (!this.dropdown.isActive && !containerOuter.isDisabled) { if (this._isTextElement) { if (document.activeElement !== this.input.element) { this.input.focus(); } } else { this.showDropdown(); containerOuter.element.focus(); } } else if (this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target)) { this.hideDropdown(); } } else { containerOuter.removeFocusState(); this.hideDropdown(true); this.unhighlightAll(); } }; Choices.prototype._onFocus = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var focusWasWithinContainer = target && containerOuter.element.contains(target); if (!focusWasWithinContainer) { return; } var targetIsInput = target === this.input.element; if (this._isTextElement) { if (targetIsInput) { containerOuter.addFocusState(); } } else if (this._isSelectMultipleElement) { if (targetIsInput) { this.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown containerOuter.addFocusState(); } } else { containerOuter.addFocusState(); if (targetIsInput) { this.showDropdown(true); } } }; Choices.prototype._onBlur = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var blurWasWithinContainer = target && containerOuter.element.contains(target); if (blurWasWithinContainer && !this._isScrollingOnIe) { if (target === this.input.element) { containerOuter.removeFocusState(); this.hideDropdown(true); if (this._isTextElement || this._isSelectMultipleElement) { this.unhighlightAll(); } } else if (target === this.containerOuter.element) { // Remove the focus state when the past outerContainer was the target containerOuter.removeFocusState(); // Also close the dropdown if search is disabled if (!this.config.searchEnabled) { this.hideDropdown(true); } } } else { // On IE11, clicking the scollbar blurs our input and thus // closes the dropdown. To stop this, we refocus our input // if we know we are on IE *and* are scrolling. this._isScrollingOnIe = false; this.input.element.focus(); } }; Choices.prototype._onFormReset = function () { var _this = this; this._store.withTxn(function () { _this.clearInput(); _this.hideDropdown(); _this.refresh(false, false, true); if (_this._initialItems.length) { _this.setChoiceByValue(_this._initialItems); } }); }; Choices.prototype._onChange = function (event) { if (!event.target.checkValidity()) { return; } this.containerOuter.removeInvalidState(); }; Choices.prototype._onInvalid = function () { this.containerOuter.addInvalidState(); }; /** * Removes any highlighted choice options */ Choices.prototype._removeHighlightedChoices = function () { var highlightedState = this.config.classNames.highlightedState; var highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(getClassNamesSelector(highlightedState))); // Remove any highlighted choices highlightedChoices.forEach(function (choice) { removeClassesFromElement(choice, highlightedState); choice.setAttribute('aria-selected', 'false'); }); }; Choices.prototype._highlightChoice = function (el) { if (el === void 0) { el = null; } var choices = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)); if (!choices.length) { return; } var passedEl = el; var highlightedState = this.config.classNames.highlightedState; this._removeHighlightedChoices(); if (passedEl) { this._highlightPosition = choices.indexOf(passedEl); } else { // Highlight choice based on last known highlight location if (choices.length > this._highlightPosition) { // If we have an option to highlight passedEl = choices[this._highlightPosition]; } else { // Otherwise highlight the option before passedEl = choices[choices.length - 1]; } if (!passedEl) { passedEl = choices[0]; } } addClassesToElement(passedEl, highlightedState); passedEl.setAttribute('aria-selected', 'true'); this.passedElement.triggerEvent(EventType.highlightChoice, { el: passedEl, }); if (this.dropdown.isActive) { // IE11 ignores aria-label and blocks virtual keyboard // if aria-activedescendant is set without a dropdown this.input.setActiveDescendant(passedEl.id); this.containerOuter.setActiveDescendant(passedEl.id); } }; Choices.prototype._addItem = function (item, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (!item.id) { throw new TypeError('item.id must be set before _addItem is called for a choice/item'); } if (this.config.singleModeForMultiSelect || this._isSelectOneElement) { this.removeActiveItems(item.id); } this._store.dispatch(addItem(item)); if (withEvents) { var eventChoice = getChoiceForOutput(item); this.passedElement.triggerEvent(EventType.addItem, eventChoice); if (userTriggered) { this.passedElement.triggerEvent(EventType.choice, eventChoice); } } }; Choices.prototype._removeItem = function (item) { if (!item.id) { return; } this._store.dispatch(removeItem$1(item)); var notice = this._notice; if (notice && notice.type === NoticeTypes.noChoices) { this._clearNotice(); } this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item)); }; Choices.prototype._addChoice = function (choice, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (choice.id) { throw new TypeError('Can not re-add a choice which has already been added'); } var config = this.config; if (!config.duplicateItemsAllowed && this._store.choices.find(function (c) { return config.valueComparer(c.value, choice.value); })) { return; } // Generate unique id, in-place update is required so chaining _addItem works as expected this._lastAddedChoiceId++; choice.id = this._lastAddedChoiceId; choice.elementId = "".concat(this._baseId, "-").concat(this._idNames.itemChoice, "-").concat(choice.id); var prependValue = config.prependValue, appendValue = config.appendValue; if (prependValue) { choice.value = prependValue + choice.value; } if (appendValue) { choice.value += appendValue.toString(); } if ((prependValue || appendValue) && choice.element) { choice.element.value = choice.value; } this._clearNotice(); this._store.dispatch(addChoice(choice)); if (choice.selected) { this._addItem(choice, withEvents, userTriggered); } }; Choices.prototype._addGroup = function (group, withEvents) { var _this = this; if (withEvents === void 0) { withEvents = true; } if (group.id) { throw new TypeError('Can not re-add a group which has already been added'); } this._store.dispatch(addGroup(group)); if (!group.choices) { return; } // add unique id for the group(s), and do not store the full list of choices in this group this._lastAddedGroupId++; group.id = this._lastAddedGroupId; group.choices.forEach(function (item) { item.group = group; if (group.disabled) { item.disabled = true; } _this._addChoice(item, withEvents); }); }; Choices.prototype._createTemplates = function () { var _this = this; var callbackOnCreateTemplates = this.config.callbackOnCreateTemplates; var userTemplates = {}; if (typeof callbackOnCreateTemplates === 'function') { userTemplates = callbackOnCreateTemplates.call(this, strToEl, escapeForTemplate, getClassNames); } var templating = {}; Object.keys(this._templates).forEach(function (name) { if (name in userTemplates) { templating[name] = userTemplates[name].bind(_this); } else { templating[name] = _this._templates[name].bind(_this); } }); this._templates = templating; }; Choices.prototype._createElements = function () { var templating = this._templates; var _a = this, config = _a.config, isSelectOneElement = _a._isSelectOneElement; var position = config.position, classNames = config.classNames; var elementType = this._elementType; this.containerOuter = new Container({ element: templating.containerOuter(config, this._direction, this._isSelectElement, isSelectOneElement, config.searchEnabled, elementType, config.labelId), classNames: classNames, type: elementType, position: position, }); this.containerInner = new Container({ element: templating.containerInner(config), classNames: classNames, type: elementType, position: position, }); this.input = new Input({ element: templating.input(config, this._placeholderValue), classNames: classNames, type: elementType, preventPaste: !config.paste, }); this.choiceList = new List({ element: templating.choiceList(config, isSelectOneElement), }); this.itemList = new List({ element: templating.itemList(config, isSelectOneElement), }); this.dropdown = new Dropdown({ element: templating.dropdown(config), classNames: classNames, type: elementType, }); }; Choices.prototype._createStructure = function () { var _a = this, containerInner = _a.containerInner, containerOuter = _a.containerOuter, passedElement = _a.passedElement; var dropdownElement = this.dropdown.element; // Hide original element passedElement.conceal(); // Wrap input in container preserving DOM ordering containerInner.wrap(passedElement.element); // Wrapper inner container with outer container containerOuter.wrap(containerInner.element); containerOuter.element.appendChild(containerInner.element); containerOuter.element.appendChild(dropdownElement); containerInner.element.appendChild(this.itemList.element); dropdownElement.appendChild(this.choiceList.element); if (this._isSelectOneElement) { this.input.placeholder = this.config.searchPlaceholderValue || ''; if (this.config.searchEnabled) { dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild); } } else { if (!this._isSelectMultipleElement || this.config.searchEnabled) { containerInner.element.appendChild(this.input.element); } if (this._placeholderValue) { this.input.placeholder = this._placeholderValue; } this.input.setWidth(); } this._highlightPosition = 0; this._isSearching = false; }; Choices.prototype._initStore = function () { var _this = this; this._store.subscribe(this._render).withTxn(function () { _this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false); }); if (!this._store.choices.length || (this._isSelectOneElement && this._hasNonChoicePlaceholder)) { this._render(); } }; Choices.prototype._addPredefinedChoices = function (choices, selectFirstOption, withEvents) { var _this = this; if (selectFirstOption === void 0) { selectFirstOption = false; } if (withEvents === void 0) { withEvents = true; } if (selectFirstOption) { /** * If there is a selected choice already or the choice is not the first in * the array, add each choice normally. * * Otherwise we pre-select the first enabled choice in the array ("select-one" only) */ var noSelectedChoices = choices.findIndex(function (choice) { return choice.selected; }) === -1; if (noSelectedChoices) { choices.some(function (choice) { if (choice.disabled || 'choices' in choice) { return false; } choice.selected = true; return true; }); } } choices.forEach(function (item) { if ('choices' in item) { if (_this._isSelectElement) { _this._addGroup(item, withEvents); } } else { _this._addChoice(item, withEvents); } }); }; Choices.prototype._findAndSelectChoiceByValue = function (value, userTriggered) { var _this = this; if (userTriggered === void 0) { userTriggered = false; } // Check 'value' property exists and the choice isn't already selected var foundChoice = this._store.choices.find(function (choice) { return _this.config.valueComparer(choice.value, value); }); if (foundChoice && !foundChoice.disabled && !foundChoice.selected) { this._addItem(foundChoice, true, userTriggered); return true; } return false; }; Choices.prototype._generatePlaceholderValue = function () { var config = this.config; if (!config.placeholder) { return null; } if (this._hasNonChoicePlaceholder) { return config.placeholderValue; } if (this._isSelectElement) { var placeholderOption = this.passedElement.placeholderOption; return placeholderOption ? placeholderOption.text : null; } return null; }; Choices.prototype._warnChoicesInitFailed = function (caller) { if (this.config.silent) { return; } if (!this.initialised) { throw new TypeError("".concat(caller, " called on a non-initialised instance of Choices")); } else if (!this.initialisedOK) { throw new TypeError("".concat(caller, " called for an element which has multiple instances of Choices initialised on it")); } }; Choices.version = '11.2.1'; return Choices; }()); return Choices; })); ================================================ FILE: public/assets/scripts/choices.search-kmp.mjs ================================================ /*! choices.js v11.2.1 | © 2026 Josh Johnson | https://github.com/Choices-js/Choices#readme */ /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var ActionType = { ADD_CHOICE: 'ADD_CHOICE', REMOVE_CHOICE: 'REMOVE_CHOICE', FILTER_CHOICES: 'FILTER_CHOICES', ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', CLEAR_CHOICES: 'CLEAR_CHOICES', ADD_GROUP: 'ADD_GROUP', ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', }; var EventType = { showDropdown: 'showDropdown', hideDropdown: 'hideDropdown', change: 'change', choice: 'choice', search: 'search', addItem: 'addItem', removeItem: 'removeItem', highlightItem: 'highlightItem', highlightChoice: 'highlightChoice', unhighlightItem: 'unhighlightItem', }; var KeyCodeMap = { TAB_KEY: 9, SHIFT_KEY: 16, BACK_KEY: 46, DELETE_KEY: 8, ENTER_KEY: 13, A_KEY: 65, ESC_KEY: 27, UP_KEY: 38, DOWN_KEY: 40, PAGE_UP_KEY: 33, PAGE_DOWN_KEY: 34, }; var ObjectsInConfig = ['fuseOptions', 'classNames']; var PassedElementTypes = { Text: 'text', SelectOne: 'select-one', SelectMultiple: 'select-multiple', }; var addChoice = function (choice) { return ({ type: ActionType.ADD_CHOICE, choice: choice, }); }; var removeChoice = function (choice) { return ({ type: ActionType.REMOVE_CHOICE, choice: choice, }); }; var filterChoices = function (results) { return ({ type: ActionType.FILTER_CHOICES, results: results, }); }; var activateChoices = function (active) { return ({ type: ActionType.ACTIVATE_CHOICES, active: active, }); }; var addGroup = function (group) { return ({ type: ActionType.ADD_GROUP, group: group, }); }; var addItem = function (item) { return ({ type: ActionType.ADD_ITEM, item: item, }); }; var removeItem$1 = function (item) { return ({ type: ActionType.REMOVE_ITEM, item: item, }); }; var highlightItem = function (item, highlighted) { return ({ type: ActionType.HIGHLIGHT_ITEM, item: item, highlighted: highlighted, }); }; var getRandomNumber = function (min, max) { return Math.floor(Math.random() * (max - min) + min); }; var generateChars = function (length) { return Array.from({ length: length }, function () { return getRandomNumber(0, 36).toString(36); }).join(''); }; var generateId = function (element, prefix) { var id = element.id || (element.name && "".concat(element.name, "-").concat(generateChars(2))) || generateChars(4); id = id.replace(/(:|\.|\[|\]|,)/g, ''); id = "".concat(prefix, "-").concat(id); return id; }; var getAdjacentEl = function (startEl, selector, direction) { if (direction === void 0) { direction = 1; } var prop = "".concat(direction > 0 ? 'next' : 'previous', "ElementSibling"); var sibling = startEl[prop]; while (sibling) { if (sibling.matches(selector)) { return sibling; } sibling = sibling[prop]; } return null; }; var isScrolledIntoView = function (element, parent, direction) { if (direction === void 0) { direction = 1; } var isVisible; if (direction > 0) { // In view from bottom isVisible = parent.scrollTop + parent.offsetHeight >= element.offsetTop + element.offsetHeight; } else { // In view from top isVisible = element.offsetTop >= parent.scrollTop; } return isVisible; }; var sanitise = function (value) { if (typeof value !== 'string') { if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { if ('raw' in value) { return sanitise(value.raw); } if ('trusted' in value) { return value.trusted; } } return value; } return value .replace(/&/g, '&') .replace(/>/g, '>') .replace(/= 0 && !window.matchMedia("(min-height: ".concat(dropdownPos + 1, "px)")).matches; } else if (this.position === 'top') { shouldFlip = true; } return shouldFlip; }; Container.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Container.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Container.prototype.open = function (dropdownPos, dropdownHeight) { addClassesToElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; if (this.shouldFlip(dropdownPos, dropdownHeight)) { addClassesToElement(this.element, this.classNames.flippedState); this.isFlipped = true; } }; Container.prototype.close = function () { removeClassesFromElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'false'); this.removeActiveDescendant(); this.isOpen = false; // A dropdown flips if it does not have space within the page if (this.isFlipped) { removeClassesFromElement(this.element, this.classNames.flippedState); this.isFlipped = false; } }; Container.prototype.addFocusState = function () { addClassesToElement(this.element, this.classNames.focusState); }; Container.prototype.removeFocusState = function () { removeClassesFromElement(this.element, this.classNames.focusState); }; Container.prototype.addInvalidState = function () { addClassesToElement(this.element, this.classNames.invalidState); }; Container.prototype.removeInvalidState = function () { removeClassesFromElement(this.element, this.classNames.invalidState); }; Container.prototype.enable = function () { removeClassesFromElement(this.element, this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; }; Container.prototype.disable = function () { addClassesToElement(this.element, this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; }; Container.prototype.wrap = function (element) { var el = this.element; var parentNode = element.parentNode; if (parentNode) { if (element.nextSibling) { parentNode.insertBefore(el, element.nextSibling); } else { parentNode.appendChild(el); } } el.appendChild(element); }; Container.prototype.unwrap = function (element) { var el = this.element; var parentNode = el.parentNode; if (parentNode) { // Move passed element outside this element parentNode.insertBefore(element, el); // Remove this element parentNode.removeChild(el); } }; Container.prototype.addLoadingState = function () { addClassesToElement(this.element, this.classNames.loadingState); this.element.setAttribute('aria-busy', 'true'); this.isLoading = true; }; Container.prototype.removeLoadingState = function () { removeClassesFromElement(this.element, this.classNames.loadingState); this.element.removeAttribute('aria-busy'); this.isLoading = false; }; return Container; }()); var Input = /** @class */ (function () { function Input(_a) { var element = _a.element, type = _a.type, classNames = _a.classNames, preventPaste = _a.preventPaste; this.element = element; this.type = type; this.classNames = classNames; this.preventPaste = preventPaste; this.isFocussed = this.element.isEqualNode(document.activeElement); this.isDisabled = element.disabled; this._onPaste = this._onPaste.bind(this); this._onInput = this._onInput.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } Object.defineProperty(Input.prototype, "placeholder", { set: function (placeholder) { this.element.placeholder = placeholder; }, enumerable: false, configurable: true }); Object.defineProperty(Input.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.value = value; }, enumerable: false, configurable: true }); Input.prototype.addEventListeners = function () { var el = this.element; el.addEventListener('paste', this._onPaste); el.addEventListener('input', this._onInput, { passive: true, }); el.addEventListener('focus', this._onFocus, { passive: true, }); el.addEventListener('blur', this._onBlur, { passive: true, }); }; Input.prototype.removeEventListeners = function () { var el = this.element; el.removeEventListener('input', this._onInput); el.removeEventListener('paste', this._onPaste); el.removeEventListener('focus', this._onFocus); el.removeEventListener('blur', this._onBlur); }; Input.prototype.enable = function () { var el = this.element; el.removeAttribute('disabled'); this.isDisabled = false; }; Input.prototype.disable = function () { var el = this.element; el.setAttribute('disabled', ''); this.isDisabled = true; }; Input.prototype.focus = function () { if (!this.isFocussed) { this.element.focus(); } }; Input.prototype.blur = function () { if (this.isFocussed) { this.element.blur(); } }; Input.prototype.clear = function (setWidth) { if (setWidth === void 0) { setWidth = true; } this.element.value = ''; if (setWidth) { this.setWidth(); } return this; }; /** * Set the correct input width based on placeholder * value or input value */ Input.prototype.setWidth = function () { // Resize input to contents or placeholder var element = this.element; element.style.minWidth = "".concat(element.placeholder.length + 1, "ch"); element.style.width = "".concat(element.value.length + 1, "ch"); }; Input.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Input.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Input.prototype._onInput = function () { if (this.type !== PassedElementTypes.SelectOne) { this.setWidth(); } }; Input.prototype._onPaste = function (event) { if (this.preventPaste) { event.preventDefault(); } }; Input.prototype._onFocus = function () { this.isFocussed = true; }; Input.prototype._onBlur = function () { this.isFocussed = false; }; return Input; }()); var SCROLLING_SPEED = 4; var List = /** @class */ (function () { function List(_a) { var element = _a.element; this.element = element; this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; } List.prototype.prepend = function (node) { var child = this.element.firstElementChild; if (child) { this.element.insertBefore(node, child); } else { this.element.append(node); } }; List.prototype.scrollToTop = function () { this.element.scrollTop = 0; }; List.prototype.scrollToChildElement = function (element, direction) { var _this = this; if (!element) { return; } var listHeight = this.element.offsetHeight; // Scroll position of dropdown var listScrollPosition = this.element.scrollTop + listHeight; var elementHeight = element.offsetHeight; // Distance from bottom of element to top of parent var elementPos = element.offsetTop + elementHeight; // Difference between the element and scroll position var destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop; requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); }; List.prototype._scrollDown = function (scrollPos, strength, destination) { var easing = (destination - scrollPos) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos + distance; }; List.prototype._scrollUp = function (scrollPos, strength, destination) { var easing = (scrollPos - destination) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos - distance; }; List.prototype._animateScroll = function (destination, direction) { var _this = this; var strength = SCROLLING_SPEED; var choiceListScrollTop = this.element.scrollTop; var continueAnimation = false; if (direction > 0) { this._scrollDown(choiceListScrollTop, strength, destination); if (choiceListScrollTop < destination) { continueAnimation = true; } } else { this._scrollUp(choiceListScrollTop, strength, destination); if (choiceListScrollTop > destination) { continueAnimation = true; } } if (continueAnimation) { requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); } }; return List; }()); var WrappedElement = /** @class */ (function () { function WrappedElement(_a) { var element = _a.element, classNames = _a.classNames; this.element = element; this.classNames = classNames; this.isDisabled = false; } Object.defineProperty(WrappedElement.prototype, "isActive", { get: function () { return this.element.dataset.choice === 'active'; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "dir", { get: function () { return this.element.dir; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.setAttribute('value', value); this.element.value = value; }, enumerable: false, configurable: true }); WrappedElement.prototype.conceal = function () { var el = this.element; // Hide passed input addClassesToElement(el, this.classNames.input); el.hidden = true; // Remove element from tab index el.tabIndex = -1; // Backup original styles if any var origStyle = el.getAttribute('style'); if (origStyle) { el.setAttribute('data-choice-orig-style', origStyle); } el.setAttribute('data-choice', 'active'); }; WrappedElement.prototype.reveal = function () { var el = this.element; // Reinstate passed element removeClassesFromElement(el, this.classNames.input); el.hidden = false; el.removeAttribute('tabindex'); // Recover original styles if any var origStyle = el.getAttribute('data-choice-orig-style'); if (origStyle) { el.removeAttribute('data-choice-orig-style'); el.setAttribute('style', origStyle); } else { el.removeAttribute('style'); } el.removeAttribute('data-choice'); }; WrappedElement.prototype.enable = function () { this.element.removeAttribute('disabled'); this.element.disabled = false; this.isDisabled = false; }; WrappedElement.prototype.disable = function () { this.element.setAttribute('disabled', ''); this.element.disabled = true; this.isDisabled = true; }; WrappedElement.prototype.triggerEvent = function (eventType, data) { dispatchEvent(this.element, eventType, data || {}); }; return WrappedElement; }()); var WrappedInput = /** @class */ (function (_super) { __extends(WrappedInput, _super); function WrappedInput() { return _super !== null && _super.apply(this, arguments) || this; } return WrappedInput; }(WrappedElement)); var coerceBool = function (arg, defaultValue) { if (defaultValue === void 0) { defaultValue = true; } return typeof arg === 'undefined' ? defaultValue : !!arg; }; var stringToHtmlClass = function (input) { if (typeof input === 'string') { // eslint-disable-next-line no-param-reassign input = input.split(' ').filter(function (s) { return s.length; }); } if (Array.isArray(input) && input.length) { return input; } return undefined; }; var mapInputToChoice = function (value, allowGroup, allowRawString) { if (allowRawString === void 0) { allowRawString = true; } if (typeof value === 'string') { var sanitisedValue = sanitise(value); var userValue = allowRawString || sanitisedValue === value ? value : { escaped: sanitisedValue, raw: value }; var result_1 = mapInputToChoice({ value: value, label: userValue, selected: true, }, false); return result_1; } var groupOrChoice = value; if ('choices' in groupOrChoice) { if (!allowGroup) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup throw new TypeError("optGroup is not allowed"); } var group = groupOrChoice; var choices = group.choices.map(function (e) { return mapInputToChoice(e, false); }); var result_2 = { id: 0, // actual ID will be assigned during _addGroup label: unwrapStringForRaw(group.label) || group.value, active: !!choices.length, disabled: !!group.disabled, choices: choices, }; return result_2; } var choice = groupOrChoice; var result = { id: 0, // actual ID will be assigned during _addChoice group: null, // actual group will be assigned during _addGroup but before _addChoice score: 0, // used in search rank: 0, // used in search, stable sort order value: choice.value, label: choice.label || choice.value, active: coerceBool(choice.active), selected: coerceBool(choice.selected, false), disabled: coerceBool(choice.disabled, false), placeholder: coerceBool(choice.placeholder, false), highlighted: false, labelClass: stringToHtmlClass(choice.labelClass), labelDescription: choice.labelDescription, customProperties: choice.customProperties, }; return result; }; var isHtmlInputElement = function (e) { return e.tagName === 'INPUT'; }; var isHtmlSelectElement = function (e) { return e.tagName === 'SELECT'; }; var isHtmlOption = function (e) { return e.tagName === 'OPTION'; }; var isHtmlOptgroup = function (e) { return e.tagName === 'OPTGROUP'; }; var WrappedSelect = /** @class */ (function (_super) { __extends(WrappedSelect, _super); function WrappedSelect(_a) { var element = _a.element, classNames = _a.classNames, template = _a.template, extractPlaceholder = _a.extractPlaceholder; var _this = _super.call(this, { element: element, classNames: classNames }) || this; _this.template = template; _this.extractPlaceholder = extractPlaceholder; return _this; } Object.defineProperty(WrappedSelect.prototype, "placeholderOption", { get: function () { return (this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]')); }, enumerable: false, configurable: true }); WrappedSelect.prototype.addOptions = function (choices) { var _this = this; var fragment = document.createDocumentFragment(); choices.forEach(function (obj) { var choice = obj; if (choice.element) { return; } var option = _this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); }; WrappedSelect.prototype.optionsAsChoices = function () { var _this = this; var choices = []; this.element.querySelectorAll(':scope > option, :scope > optgroup').forEach(function (e) { if (isHtmlOption(e)) { choices.push(_this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(_this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; }; // eslint-disable-next-line class-methods-use-this WrappedSelect.prototype._optionToChoice = function (option) { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? { trusted: option.dataset.labelDescription } : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; }; WrappedSelect.prototype._optgroupToChoice = function (optgroup) { var _this = this; var options = optgroup.querySelectorAll('option'); var choices = Array.from(options).map(function (option) { return _this._optionToChoice(option); }); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices: choices, }; }; return WrappedSelect; }(WrappedElement)); var DEFAULT_CLASSNAMES = { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], }; var DEFAULT_CONFIG = { items: [], choices: [], silent: false, renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: function (value) { return !!value && value !== ''; }, removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: function (value) { return "Press Enter to add \"".concat(value, "\""); }, removeItemIconText: function () { return "Remove item"; }, removeItemLabelText: function (value, _valueRaw, i) { return "Remove item: ".concat(i ? sanitise(i.label) : value); }, maxItemText: function (maxItemCount) { return "Only ".concat(maxItemCount, " values can be added"); }, valueComparer: function (value1, value2) { return value1 === value2; }, fuseOptions: { includeScore: true, }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, appendGroupInSearch: false, }; var removeItem = function (item) { var itemEl = item.itemEl; if (itemEl) { itemEl.remove(); item.itemEl = undefined; } }; function items(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_ITEM: { action.item.selected = true; var el = action.item.element; if (el) { el.selected = true; el.setAttribute('selected', ''); } state.push(action.item); break; } case ActionType.REMOVE_ITEM: { action.item.selected = false; var el = action.item.element; if (el) { el.selected = false; el.removeAttribute('selected'); // For a select-one, if all options are deselected, the first item is selected. To set a black value, select.value needs to be set var select = el.parentElement; if (select && isHtmlSelectElement(select) && select.type === PassedElementTypes.SelectOne) { select.value = ''; } } // this is mixing concerns, but this is *so much faster* removeItem(action.item); state = state.filter(function (choice) { return choice.id !== action.item.id; }); break; } case ActionType.REMOVE_CHOICE: { removeItem(action.choice); state = state.filter(function (item) { return item.id !== action.choice.id; }); break; } case ActionType.HIGHLIGHT_ITEM: { var highlighted = action.highlighted; var item = state.find(function (obj) { return obj.id === action.item.id; }); if (item && item.highlighted !== highlighted) { item.highlighted = highlighted; if (context) { updateClassList(item, highlighted ? context.classNames.highlightedState : context.classNames.selectedState, highlighted ? context.classNames.selectedState : context.classNames.highlightedState); } } break; } default: { update = false; break; } } return { state: state, update: update }; } function groups(s, action) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_GROUP: { state.push(action.group); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } /* eslint-disable */ function choices(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_CHOICE: { state.push(action.choice); break; } case ActionType.REMOVE_CHOICE: { action.choice.choiceEl = undefined; if (action.choice.group) { action.choice.group.choices = action.choice.group.choices.filter(function (obj) { return obj.id !== action.choice.id; }); } state = state.filter(function (obj) { return obj.id !== action.choice.id; }); break; } case ActionType.ADD_ITEM: case ActionType.REMOVE_ITEM: { action.item.choiceEl = undefined; break; } case ActionType.FILTER_CHOICES: { // avoid O(n^2) algorithm complexity when searching/filtering choices var scoreLookup_1 = []; action.results.forEach(function (result) { scoreLookup_1[result.item.id] = result; }); state.forEach(function (choice) { var result = scoreLookup_1[choice.id]; if (result !== undefined) { choice.score = result.score; choice.rank = result.rank; choice.active = true; } else { choice.score = 0; choice.rank = 0; choice.active = false; } if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.ACTIVATE_CHOICES: { state.forEach(function (choice) { choice.active = action.active; if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } var reducers = { groups: groups, items: items, choices: choices, }; var Store = /** @class */ (function () { function Store(context) { this._state = this.defaultState; this._listeners = []; this._txn = 0; this._context = context; } Object.defineProperty(Store.prototype, "defaultState", { // eslint-disable-next-line class-methods-use-this get: function () { return { groups: [], items: [], choices: [], }; }, enumerable: false, configurable: true }); // eslint-disable-next-line class-methods-use-this Store.prototype.changeSet = function (init) { return { groups: init, items: init, choices: init, }; }; Store.prototype.reset = function () { this._state = this.defaultState; var changes = this.changeSet(true); if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } }; Store.prototype.subscribe = function (onChange) { this._listeners.push(onChange); return this; }; Store.prototype.dispatch = function (action) { var _this = this; var state = this._state; var hasChanges = false; var changes = this._changeSet || this.changeSet(false); Object.keys(reducers).forEach(function (key) { var stateUpdate = reducers[key](state[key], action, _this._context); if (stateUpdate.update) { hasChanges = true; changes[key] = true; state[key] = stateUpdate.state; } }); if (hasChanges) { if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } } }; Store.prototype.withTxn = function (func) { this._txn++; try { func(); } finally { this._txn = Math.max(0, this._txn - 1); if (!this._txn) { var changeSet_1 = this._changeSet; if (changeSet_1) { this._changeSet = undefined; this._listeners.forEach(function (l) { return l(changeSet_1); }); } } } }; Object.defineProperty(Store.prototype, "state", { /** * Get store object */ get: function () { return this._state; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "items", { /** * Get items from store */ get: function () { return this.state.items; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "highlightedActiveItems", { /** * Get highlighted items from store */ get: function () { return this.items.filter(function (item) { return item.active && item.highlighted; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "choices", { /** * Get choices from store */ get: function () { return this.state.choices; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeChoices", { /** * Get active choices from store */ get: function () { return this.choices.filter(function (choice) { return choice.active; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "searchableChoices", { /** * Get choices that can be searched (excluding placeholders or disabled choices) */ get: function () { var context = this._context; return this.choices.filter(function (choice) { return !choice.placeholder && (context.searchDisabledChoices || !choice.disabled); }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "groups", { /** * Get groups from store */ get: function () { return this.state.groups; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeGroups", { /** * Get active groups from store */ get: function () { var _this = this; return this.state.groups.filter(function (group) { var isActive = group.active && !group.disabled; var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; }); return isActive && hasActiveOptions; }, []); }, enumerable: false, configurable: true }); Store.prototype.inTxn = function () { return this._txn > 0; }; /** * Get single choice by it's ID */ Store.prototype.getChoiceById = function (id) { return this.activeChoices.find(function (choice) { return choice.id === id; }); }; /** * Get group by group id */ Store.prototype.getGroupById = function (id) { return this.groups.find(function (group) { return group.id === id; }); }; return Store; }()); var NoticeTypes = { noChoices: 'no-choices', noResults: 'no-results', addChoice: 'add-choice', generic: '', }; function kmpSearch(pattern, text) { if (pattern.length === 0) { return 0; // Immediate match } // Compute longest suffix-prefix table var lsp = [0]; // Base case for (var i = 1; i < pattern.length; i++) { var j_1 = lsp[i - 1]; // Start by assuming we're extending the previous LSP while (j_1 > 0 && pattern.charAt(i) !== pattern.charAt(j_1)) { j_1 = lsp[j_1 - 1]; } if (pattern.charAt(i) === pattern.charAt(j_1)) { j_1++; } lsp.push(j_1); } // Walk through text string var j = 0; // Number of chars matched in pattern for (var i = 0; i < text.length; i++) { while (j > 0 && text.charAt(i) !== pattern.charAt(j)) { j = lsp[j - 1]; // Fall back in the pattern } if (text.charAt(i) === pattern.charAt(j)) { j++; // Next char matched, increment position if (j === pattern.length) { return i - (j - 1); } } } return -1; // Not found } var SearchByKMP = /** @class */ (function () { function SearchByKMP(config) { this._haystack = []; this._fields = config.searchFields; } SearchByKMP.prototype.index = function (data) { this._haystack = data; }; SearchByKMP.prototype.reset = function () { this._haystack = []; }; SearchByKMP.prototype.isEmptyIndex = function () { return !this._haystack.length; }; SearchByKMP.prototype.search = function (_needle) { var fields = this._fields; if (!fields || !fields.length || !_needle) { return []; } var needle = _needle.toLowerCase(); var results = []; var count = 0; for (var i = 0, j = this._haystack.length; i < j; i++) { var obj = this._haystack[i]; for (var k = 0, l = this._fields.length; k < l; k++) { var field = this._fields[k]; if (field in obj && kmpSearch(needle, obj[field].toLowerCase()) !== -1) { results.push({ item: obj, score: count, rank: count + 1, }); count++; break; } } } return results; }; return SearchByKMP; }()); function getSearcher(config) { { return new SearchByKMP(config); } } /** * Helpers to create HTML elements used by Choices * Can be overridden by providing `callbackOnCreateTemplates` option. * `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates` */ var isEmptyObject = function (obj) { // eslint-disable-next-line no-restricted-syntax for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { return false; } } return true; }; var assignCustomProperties = function (el, choice, withCustomProperties) { var dataset = el.dataset; var customProperties = choice.customProperties, labelClass = choice.labelClass, labelDescription = choice.labelDescription; if (labelClass) { dataset.labelClass = getClassNames(labelClass).join(' '); } if (labelDescription) { dataset.labelDescription = unwrapStringForRaw(labelDescription); } if (withCustomProperties && customProperties) { if (typeof customProperties === 'string') { dataset.customProperties = customProperties; } else if (typeof customProperties === 'object' && !isEmptyObject(customProperties)) { dataset.customProperties = JSON.stringify(customProperties); } } }; var addAriaLabel = function (docRoot, id, element) { var label = id && docRoot.querySelector("label[for='".concat(id, "']")); var text = label && label.innerText; if (text) { element.setAttribute('aria-label', text); } }; var templates = { containerOuter: function (_a, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType, labelId) { var containerOuter = _a.classNames.containerOuter; var div = document.createElement('div'); addClassesToElement(div, containerOuter); div.dataset.type = passedElementType; if (dir) { div.dir = dir; } if (isSelectOneElement) { div.tabIndex = 0; } if (isSelectElement) { div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox'); if (searchEnabled) { div.setAttribute('aria-autocomplete', 'list'); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, div); } div.setAttribute('aria-haspopup', 'true'); div.setAttribute('aria-expanded', 'false'); } if (labelId) { div.setAttribute('aria-labelledby', labelId); } return div; }, containerInner: function (_a) { var containerInner = _a.classNames.containerInner; var div = document.createElement('div'); addClassesToElement(div, containerInner); return div; }, itemList: function (_a, isSelectOneElement) { var searchEnabled = _a.searchEnabled, _b = _a.classNames, list = _b.list, listSingle = _b.listSingle, listItems = _b.listItems; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, isSelectOneElement ? listSingle : listItems); if (this._isSelectElement && searchEnabled) { div.setAttribute('role', 'listbox'); } return div; }, placeholder: function (_a, value) { var allowHTML = _a.allowHTML, placeholder = _a.classNames.placeholder; var div = document.createElement('div'); addClassesToElement(div, placeholder); setElementHtml(div, allowHTML, value); return div; }, item: function (_a, choice, removeItemButton) { var allowHTML = _a.allowHTML, removeItemButtonAlignLeft = _a.removeItemButtonAlignLeft, removeItemIconText = _a.removeItemIconText, removeItemLabelText = _a.removeItemLabelText, _b = _a.classNames, item = _b.item, button = _b.button, highlightedState = _b.highlightedState, itemSelectable = _b.itemSelectable, placeholder = _b.placeholder; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); addClassesToElement(div, item); if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, choice.label); addClassesToElement(spanLabel, choice.labelClass); div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, choice.label); } div.dataset.item = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; assignCustomProperties(div, choice, true); if (choice.disabled || this.containerOuter.isDisabled) { div.setAttribute('aria-disabled', 'true'); } if (this._isSelectElement) { div.setAttribute('aria-selected', 'true'); div.setAttribute('role', 'option'); } if (choice.placeholder) { addClassesToElement(div, placeholder); div.dataset.placeholder = ''; } addClassesToElement(div, choice.highlighted ? highlightedState : itemSelectable); if (removeItemButton) { if (choice.disabled) { removeClassesFromElement(div, itemSelectable); } div.dataset.deletable = ''; var removeButton = document.createElement('button'); removeButton.type = 'button'; addClassesToElement(removeButton, button); var eventChoice = getChoiceForOutput(choice); setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value, eventChoice)); var REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value, eventChoice); if (REMOVE_ITEM_LABEL) { removeButton.setAttribute('aria-label', REMOVE_ITEM_LABEL); } removeButton.dataset.button = ''; if (removeItemButtonAlignLeft) { div.insertAdjacentElement('afterbegin', removeButton); } else { div.appendChild(removeButton); } } return div; }, choiceList: function (_a, isSelectOneElement) { var list = _a.classNames.list; var div = document.createElement('div'); addClassesToElement(div, list); if (!isSelectOneElement) { div.setAttribute('aria-multiselectable', 'true'); } div.setAttribute('role', 'listbox'); return div; }, choiceGroup: function (_a, _b) { var allowHTML = _a.allowHTML, _c = _a.classNames, group = _c.group, groupHeading = _c.groupHeading, itemDisabled = _c.itemDisabled; var id = _b.id, label = _b.label, disabled = _b.disabled; var rawLabel = unwrapStringForRaw(label); var div = document.createElement('div'); addClassesToElement(div, group); if (disabled) { addClassesToElement(div, itemDisabled); } div.setAttribute('role', 'group'); div.dataset.group = ''; div.dataset.id = id; div.dataset.value = rawLabel; if (disabled) { div.setAttribute('aria-disabled', 'true'); } var heading = document.createElement('div'); addClassesToElement(heading, groupHeading); setElementHtml(heading, allowHTML, label || ''); div.appendChild(heading); return div; }, choice: function (_a, choice, selectText, groupName) { var allowHTML = _a.allowHTML, _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, itemSelectable = _b.itemSelectable, selectedState = _b.selectedState, itemDisabled = _b.itemDisabled, description = _b.description, placeholder = _b.placeholder; // eslint-disable-next-line prefer-destructuring var label = choice.label; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); div.id = choice.elementId; addClassesToElement(div, item); addClassesToElement(div, itemChoice); if (groupName && typeof label === 'string') { label = escapeForTemplate(allowHTML, label); label += " (".concat(groupName, ")"); label = { trusted: label }; } var describedBy = div; if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, label); addClassesToElement(spanLabel, choice.labelClass); describedBy = spanLabel; div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, label); } if (choice.labelDescription) { var descId = "".concat(choice.elementId, "-description"); describedBy.setAttribute('aria-describedby', descId); var spanDesc = document.createElement('span'); setElementHtml(spanDesc, allowHTML, choice.labelDescription); spanDesc.id = descId; addClassesToElement(spanDesc, description); div.appendChild(spanDesc); } if (choice.selected) { addClassesToElement(div, selectedState); } if (choice.placeholder) { addClassesToElement(div, placeholder); } div.setAttribute('role', choice.group ? 'treeitem' : 'option'); div.dataset.choice = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; if (selectText) { div.dataset.selectText = selectText; } if (choice.group) { div.dataset.groupId = "".concat(choice.group.id); } assignCustomProperties(div, choice, false); if (choice.disabled) { addClassesToElement(div, itemDisabled); div.dataset.choiceDisabled = ''; div.setAttribute('aria-disabled', 'true'); } else { addClassesToElement(div, itemSelectable); div.dataset.choiceSelectable = ''; div.setAttribute('aria-selected', choice.selected ? 'true' : 'false'); } return div; }, input: function (_a, placeholderValue) { var _b = _a.classNames, input = _b.input, inputCloned = _b.inputCloned, labelId = _a.labelId; var inp = document.createElement('input'); inp.type = 'search'; addClassesToElement(inp, input); addClassesToElement(inp, inputCloned); inp.autocomplete = 'off'; inp.autocapitalize = 'off'; inp.spellcheck = false; inp.setAttribute('aria-autocomplete', 'list'); if (placeholderValue) { inp.setAttribute('aria-label', placeholderValue); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, inp); } return inp; }, dropdown: function (_a) { var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, listDropdown); div.setAttribute('aria-expanded', 'false'); return div; }, notice: function (_a, innerHTML, type) { var _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, addChoice = _b.addChoice, noResults = _b.noResults, noChoices = _b.noChoices, noticeItem = _b.notice; if (type === void 0) { type = NoticeTypes.generic; } var notice = document.createElement('div'); setElementHtml(notice, true, innerHTML); addClassesToElement(notice, item); addClassesToElement(notice, itemChoice); addClassesToElement(notice, noticeItem); // eslint-disable-next-line default-case switch (type) { case NoticeTypes.addChoice: addClassesToElement(notice, addChoice); break; case NoticeTypes.noResults: addClassesToElement(notice, noResults); break; case NoticeTypes.noChoices: addClassesToElement(notice, noChoices); break; } if (type === NoticeTypes.addChoice) { notice.dataset.choiceSelectable = ''; notice.dataset.choice = ''; } return notice; }, option: function (choice) { // HtmlOptionElement's label value does not support HTML, so the avoid double escaping unwrap the untrusted string. var labelValue = unwrapStringForRaw(choice.label); var opt = new Option(labelValue, choice.value, false, choice.selected); assignCustomProperties(opt, choice, true); opt.disabled = choice.disabled; if (choice.selected) { opt.setAttribute('selected', ''); } return opt; }, }; /** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ var IS_IE11 = '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style; var USER_DEFAULTS = {}; var parseDataSetId = function (element) { if (!element) { return undefined; } return element.dataset.id ? parseInt(element.dataset.id, 10) : undefined; }; var selectableChoiceIdentifier = '[data-choice-selectable]'; /** * Choices * @author Josh Johnson */ var Choices = /** @class */ (function () { function Choices(element, userConfig) { if (element === void 0) { element = '[data-choice]'; } if (userConfig === void 0) { userConfig = {}; } var _this = this; this.initialisedOK = undefined; this._hasNonChoicePlaceholder = false; this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; var defaults = Choices.defaults; this.config = __assign(__assign(__assign({}, defaults.allOptions), defaults.options), userConfig); ObjectsInConfig.forEach(function (key) { _this.config[key] = __assign(__assign(__assign({}, defaults.allOptions[key]), defaults.options[key]), userConfig[key]); }); var config = this.config; if (!config.silent) { this._validateConfig(); } var docRoot = config.shadowRoot || document.documentElement; this._docRoot = docRoot; var passedElement = typeof element === 'string' ? docRoot.querySelector(element) : element; if (!passedElement || typeof passedElement !== 'object' || !(isHtmlInputElement(passedElement) || isHtmlSelectElement(passedElement))) { if (!passedElement && typeof element === 'string') { throw TypeError("Selector ".concat(element, " failed to find an element")); } throw TypeError("Expected one of the following types text|select-one|select-multiple"); } var elementType = passedElement.type; var isText = elementType === PassedElementTypes.Text; if (isText || config.maxItemCount !== 1) { config.singleModeForMultiSelect = false; } if (config.singleModeForMultiSelect) { elementType = PassedElementTypes.SelectMultiple; } var isSelectOne = elementType === PassedElementTypes.SelectOne; var isSelectMultiple = elementType === PassedElementTypes.SelectMultiple; var isSelect = isSelectOne || isSelectMultiple; this._elementType = elementType; this._isTextElement = isText; this._isSelectOneElement = isSelectOne; this._isSelectMultipleElement = isSelectMultiple; this._isSelectElement = isSelectOne || isSelectMultiple; this._canAddUserChoices = (isText && config.addItems) || (isSelect && config.addChoices); if (typeof config.renderSelectedChoices !== 'boolean') { config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne; } if (config.closeDropdownOnSelect === 'auto') { config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect; } else { config.closeDropdownOnSelect = coerceBool(config.closeDropdownOnSelect); } if (config.placeholder) { if (config.placeholderValue) { this._hasNonChoicePlaceholder = true; } else if (passedElement.dataset.placeholder) { this._hasNonChoicePlaceholder = true; config.placeholderValue = passedElement.dataset.placeholder; } } if (userConfig.addItemFilter && typeof userConfig.addItemFilter !== 'function') { var re = userConfig.addItemFilter instanceof RegExp ? userConfig.addItemFilter : new RegExp(userConfig.addItemFilter); config.addItemFilter = re.test.bind(re); } if (this._isTextElement) { this.passedElement = new WrappedInput({ element: passedElement, classNames: config.classNames, }); } else { var selectEl = passedElement; this.passedElement = new WrappedSelect({ element: selectEl, classNames: config.classNames, template: function (data) { return _this._templates.option(data); }, extractPlaceholder: config.placeholder && !this._hasNonChoicePlaceholder, }); } this.initialised = false; this._store = new Store(config); this._currentValue = ''; config.searchEnabled = !isText && config.searchEnabled; this._canSearch = config.searchEnabled; this._isScrollingOnIe = false; this._highlightPosition = 0; this._wasTap = true; this._placeholderValue = this._generatePlaceholderValue(); this._baseId = generateId(passedElement, 'choices-'); /** * setting direction in cases where it's explicitly set on passedElement * or when calculated direction is different from the document */ this._direction = passedElement.dir; if (!this._direction) { var elementDirection = window.getComputedStyle(passedElement).direction; var documentDirection = window.getComputedStyle(document.documentElement).direction; if (elementDirection !== documentDirection) { this._direction = elementDirection; } } this._idNames = { itemChoice: 'item-choice', }; this._templates = defaults.templates; this._render = this._render.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onKeyUp = this._onKeyUp.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onInput = this._onInput.bind(this); this._onClick = this._onClick.bind(this); this._onTouchMove = this._onTouchMove.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseOver = this._onMouseOver.bind(this); this._onFormReset = this._onFormReset.bind(this); this._onSelectKey = this._onSelectKey.bind(this); this._onEnterKey = this._onEnterKey.bind(this); this._onEscapeKey = this._onEscapeKey.bind(this); this._onDirectionKey = this._onDirectionKey.bind(this); this._onDeleteKey = this._onDeleteKey.bind(this); this._onChange = this._onChange.bind(this); this._onInvalid = this._onInvalid.bind(this); // If element has already been initialised with Choices, fail silently if (this.passedElement.isActive) { if (!config.silent) { console.warn('Trying to initialise Choices on element already initialised', { element: element }); } this.initialised = true; this.initialisedOK = false; return; } // Let's go this.init(); // preserve the selected item list after setup for form reset this._initialItems = this._store.items.map(function (choice) { return choice.value; }); } Object.defineProperty(Choices, "defaults", { get: function () { return Object.preventExtensions({ get options() { return USER_DEFAULTS; }, get allOptions() { return DEFAULT_CONFIG; }, get templates() { return templates; }, }); }, enumerable: false, configurable: true }); Choices.prototype.init = function () { if (this.initialised || this.initialisedOK !== undefined) { return; } this._searcher = getSearcher(this.config); this._loadChoices(); this._createTemplates(); this._createElements(); this._createStructure(); if ((this._isTextElement && !this.config.addItems) || this.passedElement.element.hasAttribute('disabled') || !!this.passedElement.element.closest('fieldset:disabled')) { this.disable(); } else { this.enable(); this._addEventListeners(); } // should be triggered **after** disabled state to avoid additional re-draws this._initStore(); this.initialised = true; this.initialisedOK = true; var callbackOnInit = this.config.callbackOnInit; // Run callback if it is a function if (typeof callbackOnInit === 'function') { callbackOnInit.call(this); } }; Choices.prototype.destroy = function () { if (!this.initialised) { return; } this._removeEventListeners(); this.passedElement.reveal(); this.containerOuter.unwrap(this.passedElement.element); this._store._listeners = []; // prevents select/input value being wiped this.clearStore(false); this._stopSearch(); this._templates = Choices.defaults.templates; this.initialised = false; this.initialisedOK = undefined; }; Choices.prototype.enable = function () { if (this.passedElement.isDisabled) { this.passedElement.enable(); } if (this.containerOuter.isDisabled) { this._addEventListeners(); this.input.enable(); this.containerOuter.enable(); } return this; }; Choices.prototype.disable = function () { if (!this.passedElement.isDisabled) { this.passedElement.disable(); } if (!this.containerOuter.isDisabled) { this._removeEventListeners(); this.input.disable(); this.containerOuter.disable(); } return this; }; Choices.prototype.highlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, true)); if (runEvent) { this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.unhighlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || !choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, false)); if (runEvent) { this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.highlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (!item.highlighted) { _this._store.dispatch(highlightItem(item, true)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.unhighlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (item.highlighted) { _this._store.dispatch(highlightItem(item, false)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.removeActiveItemsByValue = function (value) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (item) { return item.value === value; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeActiveItems = function (excludedId) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (_a) { var id = _a.id; return id !== excludedId; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeHighlightedItems = function (runEvent) { var _this = this; if (runEvent === void 0) { runEvent = false; } this._store.withTxn(function () { _this._store.highlightedActiveItems.forEach(function (item) { _this._removeItem(item); // If this action was performed by the user // trigger the event if (runEvent) { _this._triggerChange(item.value); } }); }); return this; }; Choices.prototype.showDropdown = function (preventInputFocus) { var _this = this; if (this.dropdown.isActive) { return this; } if (preventInputFocus === undefined) { // eslint-disable-next-line no-param-reassign preventInputFocus = !this._canSearch; } requestAnimationFrame(function () { _this.dropdown.show(); var rect = _this.dropdown.element.getBoundingClientRect(); _this.containerOuter.open(rect.bottom, rect.height); if (!preventInputFocus) { _this.input.focus(); } _this.passedElement.triggerEvent(EventType.showDropdown); var activeElement = _this.choiceList.element.querySelector(getClassNamesSelector(_this.config.classNames.selectedState)); if (activeElement !== null && !isScrolledIntoView(activeElement, _this.choiceList.element)) { // We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll. activeElement.scrollIntoView(); } }); return this; }; Choices.prototype.hideDropdown = function (preventInputBlur) { var _this = this; if (!this.dropdown.isActive) { return this; } this._removeHighlightedChoices(); requestAnimationFrame(function () { _this.dropdown.hide(); _this.containerOuter.close(); if (!preventInputBlur && _this._canSearch) { _this.input.removeActiveDescendant(); _this.input.blur(); } _this.passedElement.triggerEvent(EventType.hideDropdown); }); return this; }; Choices.prototype.getValue = function (valueOnly) { var values = this._store.items.map(function (item) { return (valueOnly ? item.value : getChoiceForOutput(item)); }); return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values; }; Choices.prototype.setValue = function (items) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setValue'); return this; } this._store.withTxn(function () { items.forEach(function (value) { if (value) { _this._addChoice(mapInputToChoice(value, false)); } }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.setChoiceByValue = function (value) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoiceByValue'); return this; } if (this._isTextElement) { return this; } this._store.withTxn(function () { // If only one value has been passed, convert to array var choiceValue = Array.isArray(value) ? value : [value]; // Loop through each value and choiceValue.forEach(function (val) { return _this._findAndSelectChoiceByValue(val); }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ Choices.prototype.setChoices = function (choicesArrayOrFetcher, value, label, replaceChoices, clearSearchFlag, replaceItems) { var _this = this; if (choicesArrayOrFetcher === void 0) { choicesArrayOrFetcher = []; } if (value === void 0) { value = 'value'; } if (label === void 0) { label = 'label'; } if (replaceChoices === void 0) { replaceChoices = false; } if (clearSearchFlag === void 0) { clearSearchFlag = true; } if (replaceItems === void 0) { replaceItems = false; } if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoices'); return this; } if (!this._isSelectElement) { throw new TypeError("setChoices can't be used with INPUT based Choices"); } if (typeof value !== 'string' || !value) { throw new TypeError("value parameter must be a name of 'value' field in passed objects"); } if (typeof choicesArrayOrFetcher === 'function') { // it's a choices fetcher function var fetcher_1 = choicesArrayOrFetcher(this); if (typeof Promise === 'function' && fetcher_1 instanceof Promise) { // that's a promise // eslint-disable-next-line no-promise-executor-return return new Promise(function (resolve) { return requestAnimationFrame(resolve); }) .then(function () { return _this._handleLoadingState(true); }) .then(function () { return fetcher_1; }) .then(function (data) { return _this.setChoices(data, value, label, replaceChoices, clearSearchFlag, replaceItems); }) .catch(function (err) { if (!_this.config.silent) { console.error(err); } }) .then(function () { return _this._handleLoadingState(false); }) .then(function () { return _this; }); } // function returned something else than promise, let's check if it's an array of choices if (!Array.isArray(fetcher_1)) { throw new TypeError(".setChoices first argument function must return either array of choices or Promise, got: ".concat(typeof fetcher_1)); } // recursion with results, it's sync and choices were cleared already return this.setChoices(fetcher_1, value, label, false); } if (!Array.isArray(choicesArrayOrFetcher)) { throw new TypeError(".setChoices must be called either with array of choices with a function resulting into Promise of array of choices"); } this.containerOuter.removeLoadingState(); this._store.withTxn(function () { if (clearSearchFlag) { _this._isSearching = false; } // Clear choices if needed if (replaceChoices) { _this.clearChoices(true, replaceItems); } var isDefaultValue = value === 'value'; var isDefaultLabel = label === 'label'; choicesArrayOrFetcher.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { var group = groupOrChoice; if (!isDefaultLabel) { group = __assign(__assign({}, group), { label: group[label] }); } _this._addGroup(mapInputToChoice(group, true)); } else { var choice = groupOrChoice; if (!isDefaultLabel || !isDefaultValue) { choice = __assign(__assign({}, choice), { value: choice[value], label: choice[label] }); } var choiceFull = mapInputToChoice(choice, false); _this._addChoice(choiceFull); if (choiceFull.placeholder && !_this._hasNonChoicePlaceholder) { _this._placeholderValue = unwrapStringForEscaped(choiceFull.label); } } }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.refresh = function (withEvents, selectFirstOption, deselectAll) { var _this = this; if (withEvents === void 0) { withEvents = false; } if (selectFirstOption === void 0) { selectFirstOption = false; } if (deselectAll === void 0) { deselectAll = false; } if (!this._isSelectElement) { if (!this.config.silent) { console.warn('refresh method can only be used on choices backed by a element'); } return this; } this._store.withTxn(function () { var choicesFromOptions = _this.passedElement.optionsAsChoices(); // Build the list of items which require preserving var existingItems = {}; if (!deselectAll) { _this._store.items.forEach(function (choice) { if (choice.id && choice.active && choice.selected) { existingItems[choice.value] = true; } }); } _this.clearStore(false); var updateChoice = function (choice) { if (deselectAll) { _this._store.dispatch(removeItem$1(choice)); } else if (existingItems[choice.value]) { choice.selected = true; } }; choicesFromOptions.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { groupOrChoice.choices.forEach(updateChoice); return; } updateChoice(groupOrChoice); }); /* @todo only generate add events for the added options instead of all if (withEvents) { items.forEach((choice) => { if (existingItems[choice.value]) { this.passedElement.triggerEvent( EventType.removeItem, this._getChoiceForEvent(choice), ); } }); } */ // load new choices & items _this._addPredefinedChoices(choicesFromOptions, selectFirstOption, withEvents); // re-do search if required if (_this._isSearching) { _this._searchChoices(_this.input.value); } }); return this; }; Choices.prototype.removeChoice = function (value) { var choice = this._store.choices.find(function (c) { return c.value === value; }); if (!choice) { return this; } this._clearNotice(); this._store.dispatch(removeChoice(choice)); // @todo integrate with Store this._searcher.reset(); if (choice.selected) { this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.clearChoices = function (clearOptions, clearItems) { var _this = this; if (clearOptions === void 0) { clearOptions = true; } if (clearItems === void 0) { clearItems = false; } if (clearOptions) { if (clearItems) { this.passedElement.element.replaceChildren(''); } else { this.passedElement.element.querySelectorAll(':not([selected])').forEach(function (el) { el.remove(); }); } } this.itemList.element.replaceChildren(''); this.choiceList.element.replaceChildren(''); this._clearNotice(); this._store.withTxn(function () { var items = clearItems ? [] : _this._store.items; _this._store.reset(); items.forEach(function (item) { _this._store.dispatch(addChoice(item)); _this._store.dispatch(addItem(item)); }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.clearStore = function (clearOptions) { if (clearOptions === void 0) { clearOptions = true; } this.clearChoices(clearOptions, true); this._stopSearch(); this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; return this; }; Choices.prototype.clearInput = function () { var shouldSetInputWidth = !this._isSelectOneElement; this.input.clear(shouldSetInputWidth); this._stopSearch(); return this; }; Choices.prototype._validateConfig = function () { var config = this.config; var invalidConfigOptions = diff(config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); } if (config.allowHTML && config.allowHtmlUserInput) { if (config.addItems) { console.warn('Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks'); } if (config.addChoices) { console.warn('Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks'); } } }; Choices.prototype._render = function (changes) { if (changes === void 0) { changes = { choices: true, groups: true, items: true }; } if (this._store.inTxn()) { return; } if (this._isSelectElement) { if (changes.choices || changes.groups) { this._renderChoices(); } } if (changes.items) { this._renderItems(); } }; Choices.prototype._renderChoices = function () { var _this = this; if (!this._canAddItems()) { return; // block rendering choices if the input limit is reached. } var _a = this, config = _a.config, isSearching = _a._isSearching; var _b = this._store, activeGroups = _b.activeGroups, activeChoices = _b.activeChoices; var renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit; if (this._isSelectElement) { var backingOptions = activeChoices.filter(function (choice) { return !choice.element; }); if (backingOptions.length) { this.passedElement.addOptions(backingOptions); } } var fragment = document.createDocumentFragment(); var renderableChoices = function (choices) { return choices.filter(function (choice) { return !choice.placeholder && (isSearching ? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank : config.renderSelectedChoices || !choice.selected); }); }; var showLabel = config.appendGroupInSearch && isSearching; var selectableChoices = false; var highlightedEl = null; var renderChoices = function (choices, withinGroup) { if (isSearching) { // sortByRank is used to ensure stable sorting, as scores are non-unique // this additionally ensures fuseOptions.sortFn is not ignored choices.sort(sortByRank); } else if (config.shouldSort) { choices.sort(config.sorter); } var choiceLimit = choices.length; choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit; choiceLimit--; choices.every(function (choice, index) { // choiceEl being empty signals the contents has probably significantly changed var dropdownItem = choice.choiceEl || _this._templates.choice(config, choice, config.itemSelectText, showLabel && choice.group ? choice.group.label : undefined); choice.choiceEl = dropdownItem; fragment.appendChild(dropdownItem); if (isSearching || !choice.selected) { selectableChoices = true; } else if (!highlightedEl) { highlightedEl = dropdownItem; } return index < choiceLimit; }); }; if (activeChoices.length) { if (config.resetScrollPosition) { requestAnimationFrame(function () { return _this.choiceList.scrollToTop(); }); } if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) { // If we have a placeholder choice along with groups renderChoices(activeChoices.filter(function (choice) { return choice.placeholder && !choice.group; }), false); } // If we have grouped options if (activeGroups.length && !isSearching) { if (config.shouldSort) { activeGroups.sort(config.sorter); } // render Choices without group first, regardless of sort, otherwise they won't be distinguishable // from the last group renderChoices(activeChoices.filter(function (choice) { return !choice.placeholder && !choice.group; }), false); activeGroups.forEach(function (group) { var groupChoices = renderableChoices(group.choices); if (groupChoices.length) { if (group.label) { var dropdownGroup = group.groupEl || _this._templates.choiceGroup(_this.config, group); group.groupEl = dropdownGroup; dropdownGroup.remove(); fragment.appendChild(dropdownGroup); } renderChoices(groupChoices, true); } }); } else { renderChoices(renderableChoices(activeChoices), false); } } if (!selectableChoices && (isSearching || !fragment.children.length || !config.renderSelectedChoices)) { if (!this._notice) { this._notice = { text: resolveStringFunction(isSearching ? config.noResultsText : config.noChoicesText), type: isSearching ? NoticeTypes.noResults : NoticeTypes.noChoices, }; } fragment.replaceChildren(''); } this._renderNotice(fragment); this.choiceList.element.replaceChildren(fragment); this._highlightChoice(highlightedEl); }; Choices.prototype._renderItems = function () { var _this = this; var items = this._store.items || []; var itemList = this.itemList.element; var config = this.config; var fragment = document.createDocumentFragment(); var itemFromList = function (item) { return itemList.querySelector("[data-item][data-id=\"".concat(item.id, "\"]")); }; var addItemToFragment = function (item) { var el = item.itemEl; if (el && el.parentElement) { return; } el = itemFromList(item) || _this._templates.item(config, item, config.removeItemButton); item.itemEl = el; fragment.appendChild(el); }; // new items items.forEach(addItemToFragment); var addedItems = !!fragment.childNodes.length; if (this._isSelectOneElement) { var existingItems = itemList.children.length; if (addedItems || existingItems > 1) { var placeholder = itemList.querySelector(getClassNamesSelector(config.classNames.placeholder)); if (placeholder) { placeholder.remove(); } } else if (!addedItems && !existingItems && this._placeholderValue) { addedItems = true; addItemToFragment(mapInputToChoice({ selected: true, value: '', label: this._placeholderValue, placeholder: true, }, false)); } } if (addedItems) { itemList.append(fragment); if (config.shouldSortItems && !this._isSelectOneElement) { items.sort(config.sorter); // push sorting into the DOM items.forEach(function (item) { var el = itemFromList(item); if (el) { el.remove(); fragment.append(el); } }); itemList.append(fragment); } } if (this._isTextElement) { // Update the value of the hidden input this.passedElement.value = items.map(function (_a) { var value = _a.value; return value; }).join(config.delimiter); } }; Choices.prototype._displayNotice = function (text, type, openDropdown) { if (openDropdown === void 0) { openDropdown = true; } var oldNotice = this._notice; if (oldNotice && ((oldNotice.type === type && oldNotice.text === text) || (oldNotice.type === NoticeTypes.addChoice && (type === NoticeTypes.noResults || type === NoticeTypes.noChoices)))) { if (openDropdown) { this.showDropdown(true); } return; } this._clearNotice(); this._notice = text ? { text: text, type: type, } : undefined; this._renderNotice(); if (openDropdown && text) { this.showDropdown(true); } }; Choices.prototype._clearNotice = function () { if (!this._notice) { return; } var noticeElement = this.choiceList.element.querySelector(getClassNamesSelector(this.config.classNames.notice)); if (noticeElement) { noticeElement.remove(); } this._notice = undefined; }; Choices.prototype._renderNotice = function (fragment) { var noticeConf = this._notice; if (noticeConf) { var notice = this._templates.notice(this.config, noticeConf.text, noticeConf.type); if (fragment) { fragment.append(notice); } else { this.choiceList.prepend(notice); } } }; /** * @deprecated Use utils.getChoiceForOutput */ // eslint-disable-next-line class-methods-use-this Choices.prototype._getChoiceForOutput = function (choice, keyCode) { return getChoiceForOutput(choice, keyCode); }; Choices.prototype._triggerChange = function (value) { if (value === undefined || value === null) { return; } this.passedElement.triggerEvent(EventType.change, { value: value, }); }; Choices.prototype._handleButtonAction = function (element) { var _this = this; var items = this._store.items; if (!items.length || !this.config.removeItems || !this.config.removeItemButton) { return; } var id = element && parseDataSetId(element.closest('[data-id]')); var itemToRemove = id && items.find(function (item) { return item.id === id; }); if (!itemToRemove) { return; } this._store.withTxn(function () { // Remove item associated with button _this._removeItem(itemToRemove); _this._triggerChange(itemToRemove.value); if (_this._isSelectOneElement && !_this._hasNonChoicePlaceholder) { var placeholderChoice = (_this.config.shouldSort ? _this._store.choices.reverse() : _this._store.choices).find(function (choice) { return choice.placeholder; }); if (placeholderChoice) { _this._addItem(placeholderChoice); _this.unhighlightAll(); if (placeholderChoice.value) { _this._triggerChange(placeholderChoice.value); } } } }); }; Choices.prototype._handleItemAction = function (element, hasShiftKey) { var _this = this; if (hasShiftKey === void 0) { hasShiftKey = false; } var items = this._store.items; if (!items.length || !this.config.removeItems || this._isSelectOneElement) { return; } var id = parseDataSetId(element); if (!id) { return; } // We only want to select one item with a click // so we deselect any items that aren't the target // unless shift is being pressed items.forEach(function (item) { if (item.id === id && !item.highlighted) { _this.highlightItem(item); } else if (!hasShiftKey && item.highlighted) { _this.unhighlightItem(item); } }); // Focus input as without focus, a user cannot do anything with a // highlighted item this.input.focus(); }; Choices.prototype._handleChoiceAction = function (element) { var _this = this; // If we are clicking on an option var id = parseDataSetId(element); var choice = id && this._store.getChoiceById(id); if (!choice || choice.disabled) { return false; } var hasActiveDropdown = this.dropdown.isActive; if (!choice.selected) { if (!this._canAddItems()) { return true; // causes _onEnterKey to early out } this._store.withTxn(function () { _this._addItem(choice, true, true); _this.clearInput(); _this.unhighlightAll(); }); this._triggerChange(choice.value); } // We want to close the dropdown if we are dealing with a single select box if (hasActiveDropdown && this.config.closeDropdownOnSelect) { this.hideDropdown(true); this.containerOuter.element.focus(); } return true; }; Choices.prototype._handleBackspace = function (items) { var config = this.config; if (!config.removeItems || !items.length) { return; } var lastItem = items[items.length - 1]; var hasHighlightedItems = items.some(function (item) { return item.highlighted; }); // If editing the last item is allowed and there are not other selected items, // we can edit the item value. Otherwise if we can remove items, remove all selected items if (config.editItems && !hasHighlightedItems && lastItem) { this.input.value = lastItem.value; this.input.setWidth(); this._removeItem(lastItem); this._triggerChange(lastItem.value); } else { if (!hasHighlightedItems) { // Highlight last item if none already highlighted this.highlightItem(lastItem, false); } this.removeHighlightedItems(true); } }; Choices.prototype._loadChoices = function () { var _a; var _this = this; var config = this.config; if (this._isTextElement) { // Assign preset items from passed object first this._presetChoices = config.items.map(function (e) { return mapInputToChoice(e, false); }); // Add any values passed from attribute if (this.passedElement.value) { var elementItems = this.passedElement.value .split(config.delimiter) .map(function (e) { return mapInputToChoice(e, false, _this.config.allowHtmlUserInput); }); this._presetChoices = this._presetChoices.concat(elementItems); } this._presetChoices.forEach(function (choice) { choice.selected = true; }); } else if (this._isSelectElement) { // Assign preset choices from passed object this._presetChoices = config.choices.map(function (e) { return mapInputToChoice(e, true); }); // Create array of choices from option elements var choicesFromOptions = this.passedElement.optionsAsChoices(); if (choicesFromOptions) { (_a = this._presetChoices).push.apply(_a, choicesFromOptions); } } }; Choices.prototype._handleLoadingState = function (setLoading) { if (setLoading === void 0) { setLoading = true; } var el = this.itemList.element; if (setLoading) { this.disable(); this.containerOuter.addLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(this._templates.placeholder(this.config, this.config.loadingText)); } else { this.input.placeholder = this.config.loadingText; } } else { this.enable(); this.containerOuter.removeLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(''); this._render(); } else { this.input.placeholder = this._placeholderValue || ''; } } }; Choices.prototype._handleSearch = function (value) { if (!this.input.isFocussed) { return; } // Check that we have a value to search and the input was an alphanumeric character if (value !== null && typeof value !== 'undefined' && value.length >= this.config.searchFloor) { var resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; if (resultCount !== null) { // Trigger search event this.passedElement.triggerEvent(EventType.search, { value: value, resultCount: resultCount, }); } } else if (this._store.choices.some(function (option) { return !option.active; })) { this._stopSearch(); } }; Choices.prototype._canAddItems = function () { var config = this.config; var maxItemCount = config.maxItemCount, maxItemText = config.maxItemText; if (!config.singleModeForMultiSelect && maxItemCount > 0 && maxItemCount <= this._store.items.length) { this.choiceList.element.replaceChildren(''); this._notice = undefined; this._displayNotice(typeof maxItemText === 'function' ? maxItemText(maxItemCount) : maxItemText, NoticeTypes.addChoice); return false; } if (this._notice && this._notice.type === NoticeTypes.addChoice) { this._clearNotice(); } return true; }; Choices.prototype._canCreateItem = function (value) { var config = this.config; var canAddItem = true; var notice = ''; if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) { canAddItem = false; notice = resolveNoticeFunction(config.customAddItemText, value, undefined); } if (canAddItem) { var foundChoice = this._store.choices.find(function (choice) { return config.valueComparer(choice.value, value); }); if (foundChoice) { if (this._isSelectElement) { // for exact matches, do not prompt to add it as a custom choice this._displayNotice('', NoticeTypes.addChoice); return false; } if (!config.duplicateItemsAllowed) { canAddItem = false; notice = resolveNoticeFunction(config.uniqueItemText, value, undefined); } } } if (canAddItem) { notice = resolveNoticeFunction(config.addItemText, value, undefined); } if (notice) { this._displayNotice(notice, NoticeTypes.addChoice); } return canAddItem; }; Choices.prototype._searchChoices = function (value) { var newValue = value.trim().replace(/\s{2,}/, ' '); // signal input didn't change search if (!newValue.length || newValue === this._currentValue) { return null; } var searcher = this._searcher; if (searcher.isEmptyIndex()) { searcher.index(this._store.searchableChoices); } // If new value matches the desired length and is not the same as the current value with a space var results = searcher.search(newValue); this._currentValue = newValue; this._highlightPosition = 0; this._isSearching = true; var notice = this._notice; var noticeType = notice && notice.type; if (noticeType !== NoticeTypes.addChoice) { if (!results.length) { this._displayNotice(resolveStringFunction(this.config.noResultsText), NoticeTypes.noResults); } else { this._clearNotice(); } } this._store.dispatch(filterChoices(results)); return results.length; }; Choices.prototype._stopSearch = function () { if (this._isSearching) { this._currentValue = ''; this._isSearching = false; this._clearNotice(); this._store.dispatch(activateChoices(true)); this.passedElement.triggerEvent(EventType.search, { value: '', resultCount: 0, }); } }; Choices.prototype._addEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; // capture events - can cancel event processing or propagation documentElement.addEventListener('touchend', this._onTouchEnd, true); outerElement.addEventListener('keydown', this._onKeyDown, true); outerElement.addEventListener('mousedown', this._onMouseDown, true); // passive events - doesn't call `preventDefault` or `stopPropagation` documentElement.addEventListener('click', this._onClick, { passive: true }); documentElement.addEventListener('touchmove', this._onTouchMove, { passive: true, }); this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { passive: true, }); if (this._isSelectOneElement) { outerElement.addEventListener('focus', this._onFocus, { passive: true, }); outerElement.addEventListener('blur', this._onBlur, { passive: true, }); } inputElement.addEventListener('keyup', this._onKeyUp, { passive: true, }); inputElement.addEventListener('input', this._onInput, { passive: true, }); inputElement.addEventListener('focus', this._onFocus, { passive: true, }); inputElement.addEventListener('blur', this._onBlur, { passive: true, }); if (inputElement.form) { inputElement.form.addEventListener('reset', this._onFormReset, { passive: true, }); } if (passedElement.hasAttribute('required')) { passedElement.addEventListener('change', this._onChange, { passive: true, }); passedElement.addEventListener('invalid', this._onInvalid, { passive: true, }); } this.input.addEventListeners(); }; Choices.prototype._removeEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; documentElement.removeEventListener('touchend', this._onTouchEnd, true); outerElement.removeEventListener('keydown', this._onKeyDown, true); outerElement.removeEventListener('mousedown', this._onMouseDown, true); documentElement.removeEventListener('click', this._onClick); documentElement.removeEventListener('touchmove', this._onTouchMove); this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); if (this._isSelectOneElement) { outerElement.removeEventListener('focus', this._onFocus); outerElement.removeEventListener('blur', this._onBlur); } inputElement.removeEventListener('keyup', this._onKeyUp); inputElement.removeEventListener('input', this._onInput); inputElement.removeEventListener('focus', this._onFocus); inputElement.removeEventListener('blur', this._onBlur); if (inputElement.form) { inputElement.form.removeEventListener('reset', this._onFormReset); } if (passedElement.hasAttribute('required')) { passedElement.removeEventListener('change', this._onChange); passedElement.removeEventListener('invalid', this._onInvalid); } this.input.removeEventListeners(); }; Choices.prototype._onKeyDown = function (event) { var keyCode = event.keyCode; var hasActiveDropdown = this.dropdown.isActive; /* See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs https://stackoverflow.com/a/70866532 - "Unidentified" for mobile http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7) Logic: when a key event is sent, `event.key` represents its printable value _or_ one of a large list of special values indicating meta keys/functionality. In addition, key events for compose functionality contain a value of `Dead` when mid-composition. I can't quite verify it, but non-English IMEs may also be able to generate key codes for code points in the surrogate-pair range, which could potentially be seen as having key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that alone. Here, key.length === 1 means we know for sure the input was printable and not a special `key` value. When the length is greater than 1, it could be either a printable surrogate pair or a special `key` value. We can tell the difference by checking if the _character code_ value (not code point!) is in the "surrogate pair" range or not. We don't use .codePointAt because an invalid code point would return 65535, which wouldn't pass the >= 0x10000 check we would otherwise use. > ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points > of each plane are noncharacters: U+FFFE and U+FFFF on the BMP... */ var wasPrintableChar = event.key.length === 1 || (event.key.length === 2 && event.key.charCodeAt(0) >= 0xd800) || event.key === 'Unidentified'; /* We do not show the dropdown if focusing out with esc or navigating through input fields. An activated search can still be opened with any other key. */ if (!this._isTextElement && !hasActiveDropdown && keyCode !== KeyCodeMap.ESC_KEY && keyCode !== KeyCodeMap.TAB_KEY && keyCode !== KeyCodeMap.SHIFT_KEY) { this.showDropdown(); if (!this.input.isFocussed && wasPrintableChar) { /* We update the input value with the pressed key as the input was not focussed at the time of key press therefore does not have the value of the key. */ this.input.value += event.key; // browsers interpret a space as pagedown if (event.key === ' ') { event.preventDefault(); } } } switch (keyCode) { case KeyCodeMap.A_KEY: return this._onSelectKey(event, this.itemList.element.hasChildNodes()); case KeyCodeMap.ENTER_KEY: return this._onEnterKey(event, hasActiveDropdown); case KeyCodeMap.ESC_KEY: return this._onEscapeKey(event, hasActiveDropdown); case KeyCodeMap.UP_KEY: case KeyCodeMap.PAGE_UP_KEY: case KeyCodeMap.DOWN_KEY: case KeyCodeMap.PAGE_DOWN_KEY: return this._onDirectionKey(event, hasActiveDropdown); case KeyCodeMap.DELETE_KEY: case KeyCodeMap.BACK_KEY: return this._onDeleteKey(event, this._store.items, this.input.isFocussed); } }; Choices.prototype._onKeyUp = function ( /* event: KeyboardEvent */) { this._canSearch = this.config.searchEnabled; }; Choices.prototype._onInput = function ( /* event: InputEvent */) { var value = this.input.value; if (!value) { if (this._isTextElement) { this.hideDropdown(true); } else { this._stopSearch(); } return; } if (!this._canAddItems()) { return; } if (this._canSearch) { // do the search even if the entered text can not be added this._handleSearch(value); } if (!this._canAddUserChoices) { return; } // determine if a notice needs to be displayed for why a search result can't be added this._canCreateItem(value); if (this._isSelectElement) { this._highlightPosition = 0; // reset to select the notice and/or exact match this._highlightChoice(); } }; Choices.prototype._onSelectKey = function (event, hasItems) { // If CTRL + A or CMD + A have been pressed and there are items to select if ((event.ctrlKey || event.metaKey) && hasItems) { this._canSearch = false; var shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; if (shouldHightlightAll) { this.highlightAll(); } } }; Choices.prototype._onEnterKey = function (event, hasActiveDropdown) { var _this = this; var value = this.input.value; var target = event.target; event.preventDefault(); if (target && target.hasAttribute('data-button')) { this._handleButtonAction(target); return; } if (!hasActiveDropdown) { if (this._isSelectElement || this._notice) { this.showDropdown(); } return; } var highlightedChoice = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (highlightedChoice && this._handleChoiceAction(highlightedChoice)) { return; } if (!target || !value) { this.hideDropdown(true); return; } if (!this._canAddItems()) { return; } var addedItem = false; this._store.withTxn(function () { addedItem = _this._findAndSelectChoiceByValue(value, true); if (!addedItem) { if (!_this._canAddUserChoices) { return; } if (!_this._canCreateItem(value)) { return; } _this._addChoice(mapInputToChoice(value, false, _this.config.allowHtmlUserInput), true, true); addedItem = true; } _this.clearInput(); _this.unhighlightAll(); }); if (!addedItem) { return; } this._triggerChange(value); if (this.config.closeDropdownOnSelect) { this.hideDropdown(true); } }; Choices.prototype._onEscapeKey = function (event, hasActiveDropdown) { if (hasActiveDropdown) { event.stopPropagation(); this.hideDropdown(true); this._stopSearch(); this.containerOuter.element.focus(); } }; Choices.prototype._onDirectionKey = function (event, hasActiveDropdown) { var keyCode = event.keyCode; // If up or down key is pressed, traverse through options if (hasActiveDropdown || this._isSelectOneElement) { this.showDropdown(); this._canSearch = false; var directionInt = keyCode === KeyCodeMap.DOWN_KEY || keyCode === KeyCodeMap.PAGE_DOWN_KEY ? 1 : -1; var skipKey = event.metaKey || keyCode === KeyCodeMap.PAGE_DOWN_KEY || keyCode === KeyCodeMap.PAGE_UP_KEY; var nextEl = void 0; if (skipKey) { if (directionInt > 0) { nextEl = this.dropdown.element.querySelector("".concat(selectableChoiceIdentifier, ":last-of-type")); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } else { var currentEl = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (currentEl) { nextEl = getAdjacentEl(currentEl, selectableChoiceIdentifier, directionInt); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } if (nextEl) { // We prevent default to stop the cursor moving // when pressing the arrow if (!isScrolledIntoView(nextEl, this.choiceList.element, directionInt)) { this.choiceList.scrollToChildElement(nextEl, directionInt); } this._highlightChoice(nextEl); } // Prevent default to maintain cursor position whilst // traversing dropdown options event.preventDefault(); } }; Choices.prototype._onDeleteKey = function (event, items, hasFocusedInput) { // If backspace or delete key is pressed and the input has no value if (!this._isSelectOneElement && !event.target.value && hasFocusedInput) { this._handleBackspace(items); event.preventDefault(); } }; Choices.prototype._onTouchMove = function () { if (this._wasTap) { this._wasTap = false; } }; Choices.prototype._onTouchEnd = function (event) { var target = (event || event.touches[0]).target; var touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target); if (touchWasWithinContainer) { var containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; if (containerWasExactTarget) { if (this._isTextElement) { this.input.focus(); } else if (this._isSelectMultipleElement) { this.showDropdown(); } } // Prevents focus event firing event.stopPropagation(); } this._wasTap = true; }; /** * Handles mousedown event in capture mode for containetOuter.element */ Choices.prototype._onMouseDown = function (event) { var target = event.target; if (!(target instanceof Element)) { return; } // If we have our mouse down on the scrollbar and are on IE11... if (IS_IE11 && this.choiceList.element.contains(target)) { // check if click was on a scrollbar area var firstChoice = this.choiceList.element.firstElementChild; this._isScrollingOnIe = this._direction === 'ltr' ? event.offsetX >= firstChoice.offsetWidth : event.offsetX < firstChoice.offsetLeft; } if (target === this.input.element) { return; } var item = target.closest('[data-button],[data-item],[data-choice]'); if (item instanceof HTMLElement) { if ('button' in item.dataset) { this._handleButtonAction(item); } else if ('item' in item.dataset) { this._handleItemAction(item, event.shiftKey); } else if ('choice' in item.dataset) { this._handleChoiceAction(item); } } event.preventDefault(); }; /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ Choices.prototype._onMouseOver = function (_a) { var target = _a.target; if (target instanceof HTMLElement && 'choice' in target.dataset) { this._highlightChoice(target); } }; Choices.prototype._onClick = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var clickWasWithinContainer = containerOuter.element.contains(target); if (clickWasWithinContainer) { if (!this.dropdown.isActive && !containerOuter.isDisabled) { if (this._isTextElement) { if (document.activeElement !== this.input.element) { this.input.focus(); } } else { this.showDropdown(); containerOuter.element.focus(); } } else if (this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target)) { this.hideDropdown(); } } else { containerOuter.removeFocusState(); this.hideDropdown(true); this.unhighlightAll(); } }; Choices.prototype._onFocus = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var focusWasWithinContainer = target && containerOuter.element.contains(target); if (!focusWasWithinContainer) { return; } var targetIsInput = target === this.input.element; if (this._isTextElement) { if (targetIsInput) { containerOuter.addFocusState(); } } else if (this._isSelectMultipleElement) { if (targetIsInput) { this.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown containerOuter.addFocusState(); } } else { containerOuter.addFocusState(); if (targetIsInput) { this.showDropdown(true); } } }; Choices.prototype._onBlur = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var blurWasWithinContainer = target && containerOuter.element.contains(target); if (blurWasWithinContainer && !this._isScrollingOnIe) { if (target === this.input.element) { containerOuter.removeFocusState(); this.hideDropdown(true); if (this._isTextElement || this._isSelectMultipleElement) { this.unhighlightAll(); } } else if (target === this.containerOuter.element) { // Remove the focus state when the past outerContainer was the target containerOuter.removeFocusState(); // Also close the dropdown if search is disabled if (!this.config.searchEnabled) { this.hideDropdown(true); } } } else { // On IE11, clicking the scollbar blurs our input and thus // closes the dropdown. To stop this, we refocus our input // if we know we are on IE *and* are scrolling. this._isScrollingOnIe = false; this.input.element.focus(); } }; Choices.prototype._onFormReset = function () { var _this = this; this._store.withTxn(function () { _this.clearInput(); _this.hideDropdown(); _this.refresh(false, false, true); if (_this._initialItems.length) { _this.setChoiceByValue(_this._initialItems); } }); }; Choices.prototype._onChange = function (event) { if (!event.target.checkValidity()) { return; } this.containerOuter.removeInvalidState(); }; Choices.prototype._onInvalid = function () { this.containerOuter.addInvalidState(); }; /** * Removes any highlighted choice options */ Choices.prototype._removeHighlightedChoices = function () { var highlightedState = this.config.classNames.highlightedState; var highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(getClassNamesSelector(highlightedState))); // Remove any highlighted choices highlightedChoices.forEach(function (choice) { removeClassesFromElement(choice, highlightedState); choice.setAttribute('aria-selected', 'false'); }); }; Choices.prototype._highlightChoice = function (el) { if (el === void 0) { el = null; } var choices = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)); if (!choices.length) { return; } var passedEl = el; var highlightedState = this.config.classNames.highlightedState; this._removeHighlightedChoices(); if (passedEl) { this._highlightPosition = choices.indexOf(passedEl); } else { // Highlight choice based on last known highlight location if (choices.length > this._highlightPosition) { // If we have an option to highlight passedEl = choices[this._highlightPosition]; } else { // Otherwise highlight the option before passedEl = choices[choices.length - 1]; } if (!passedEl) { passedEl = choices[0]; } } addClassesToElement(passedEl, highlightedState); passedEl.setAttribute('aria-selected', 'true'); this.passedElement.triggerEvent(EventType.highlightChoice, { el: passedEl, }); if (this.dropdown.isActive) { // IE11 ignores aria-label and blocks virtual keyboard // if aria-activedescendant is set without a dropdown this.input.setActiveDescendant(passedEl.id); this.containerOuter.setActiveDescendant(passedEl.id); } }; Choices.prototype._addItem = function (item, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (!item.id) { throw new TypeError('item.id must be set before _addItem is called for a choice/item'); } if (this.config.singleModeForMultiSelect || this._isSelectOneElement) { this.removeActiveItems(item.id); } this._store.dispatch(addItem(item)); if (withEvents) { var eventChoice = getChoiceForOutput(item); this.passedElement.triggerEvent(EventType.addItem, eventChoice); if (userTriggered) { this.passedElement.triggerEvent(EventType.choice, eventChoice); } } }; Choices.prototype._removeItem = function (item) { if (!item.id) { return; } this._store.dispatch(removeItem$1(item)); var notice = this._notice; if (notice && notice.type === NoticeTypes.noChoices) { this._clearNotice(); } this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item)); }; Choices.prototype._addChoice = function (choice, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (choice.id) { throw new TypeError('Can not re-add a choice which has already been added'); } var config = this.config; if (!config.duplicateItemsAllowed && this._store.choices.find(function (c) { return config.valueComparer(c.value, choice.value); })) { return; } // Generate unique id, in-place update is required so chaining _addItem works as expected this._lastAddedChoiceId++; choice.id = this._lastAddedChoiceId; choice.elementId = "".concat(this._baseId, "-").concat(this._idNames.itemChoice, "-").concat(choice.id); var prependValue = config.prependValue, appendValue = config.appendValue; if (prependValue) { choice.value = prependValue + choice.value; } if (appendValue) { choice.value += appendValue.toString(); } if ((prependValue || appendValue) && choice.element) { choice.element.value = choice.value; } this._clearNotice(); this._store.dispatch(addChoice(choice)); if (choice.selected) { this._addItem(choice, withEvents, userTriggered); } }; Choices.prototype._addGroup = function (group, withEvents) { var _this = this; if (withEvents === void 0) { withEvents = true; } if (group.id) { throw new TypeError('Can not re-add a group which has already been added'); } this._store.dispatch(addGroup(group)); if (!group.choices) { return; } // add unique id for the group(s), and do not store the full list of choices in this group this._lastAddedGroupId++; group.id = this._lastAddedGroupId; group.choices.forEach(function (item) { item.group = group; if (group.disabled) { item.disabled = true; } _this._addChoice(item, withEvents); }); }; Choices.prototype._createTemplates = function () { var _this = this; var callbackOnCreateTemplates = this.config.callbackOnCreateTemplates; var userTemplates = {}; if (typeof callbackOnCreateTemplates === 'function') { userTemplates = callbackOnCreateTemplates.call(this, strToEl, escapeForTemplate, getClassNames); } var templating = {}; Object.keys(this._templates).forEach(function (name) { if (name in userTemplates) { templating[name] = userTemplates[name].bind(_this); } else { templating[name] = _this._templates[name].bind(_this); } }); this._templates = templating; }; Choices.prototype._createElements = function () { var templating = this._templates; var _a = this, config = _a.config, isSelectOneElement = _a._isSelectOneElement; var position = config.position, classNames = config.classNames; var elementType = this._elementType; this.containerOuter = new Container({ element: templating.containerOuter(config, this._direction, this._isSelectElement, isSelectOneElement, config.searchEnabled, elementType, config.labelId), classNames: classNames, type: elementType, position: position, }); this.containerInner = new Container({ element: templating.containerInner(config), classNames: classNames, type: elementType, position: position, }); this.input = new Input({ element: templating.input(config, this._placeholderValue), classNames: classNames, type: elementType, preventPaste: !config.paste, }); this.choiceList = new List({ element: templating.choiceList(config, isSelectOneElement), }); this.itemList = new List({ element: templating.itemList(config, isSelectOneElement), }); this.dropdown = new Dropdown({ element: templating.dropdown(config), classNames: classNames, type: elementType, }); }; Choices.prototype._createStructure = function () { var _a = this, containerInner = _a.containerInner, containerOuter = _a.containerOuter, passedElement = _a.passedElement; var dropdownElement = this.dropdown.element; // Hide original element passedElement.conceal(); // Wrap input in container preserving DOM ordering containerInner.wrap(passedElement.element); // Wrapper inner container with outer container containerOuter.wrap(containerInner.element); containerOuter.element.appendChild(containerInner.element); containerOuter.element.appendChild(dropdownElement); containerInner.element.appendChild(this.itemList.element); dropdownElement.appendChild(this.choiceList.element); if (this._isSelectOneElement) { this.input.placeholder = this.config.searchPlaceholderValue || ''; if (this.config.searchEnabled) { dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild); } } else { if (!this._isSelectMultipleElement || this.config.searchEnabled) { containerInner.element.appendChild(this.input.element); } if (this._placeholderValue) { this.input.placeholder = this._placeholderValue; } this.input.setWidth(); } this._highlightPosition = 0; this._isSearching = false; }; Choices.prototype._initStore = function () { var _this = this; this._store.subscribe(this._render).withTxn(function () { _this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false); }); if (!this._store.choices.length || (this._isSelectOneElement && this._hasNonChoicePlaceholder)) { this._render(); } }; Choices.prototype._addPredefinedChoices = function (choices, selectFirstOption, withEvents) { var _this = this; if (selectFirstOption === void 0) { selectFirstOption = false; } if (withEvents === void 0) { withEvents = true; } if (selectFirstOption) { /** * If there is a selected choice already or the choice is not the first in * the array, add each choice normally. * * Otherwise we pre-select the first enabled choice in the array ("select-one" only) */ var noSelectedChoices = choices.findIndex(function (choice) { return choice.selected; }) === -1; if (noSelectedChoices) { choices.some(function (choice) { if (choice.disabled || 'choices' in choice) { return false; } choice.selected = true; return true; }); } } choices.forEach(function (item) { if ('choices' in item) { if (_this._isSelectElement) { _this._addGroup(item, withEvents); } } else { _this._addChoice(item, withEvents); } }); }; Choices.prototype._findAndSelectChoiceByValue = function (value, userTriggered) { var _this = this; if (userTriggered === void 0) { userTriggered = false; } // Check 'value' property exists and the choice isn't already selected var foundChoice = this._store.choices.find(function (choice) { return _this.config.valueComparer(choice.value, value); }); if (foundChoice && !foundChoice.disabled && !foundChoice.selected) { this._addItem(foundChoice, true, userTriggered); return true; } return false; }; Choices.prototype._generatePlaceholderValue = function () { var config = this.config; if (!config.placeholder) { return null; } if (this._hasNonChoicePlaceholder) { return config.placeholderValue; } if (this._isSelectElement) { var placeholderOption = this.passedElement.placeholderOption; return placeholderOption ? placeholderOption.text : null; } return null; }; Choices.prototype._warnChoicesInitFailed = function (caller) { if (this.config.silent) { return; } if (!this.initialised) { throw new TypeError("".concat(caller, " called on a non-initialised instance of Choices")); } else if (!this.initialisedOK) { throw new TypeError("".concat(caller, " called for an element which has multiple instances of Choices initialised on it")); } }; Choices.version = '11.2.1'; return Choices; }()); export { Choices as default }; ================================================ FILE: public/assets/scripts/choices.search-prefix.js ================================================ /*! choices.js v11.2.1 | © 2026 Josh Johnson | https://github.com/Choices-js/Choices#readme */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Choices = factory()); })(this, (function () { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var ActionType = { ADD_CHOICE: 'ADD_CHOICE', REMOVE_CHOICE: 'REMOVE_CHOICE', FILTER_CHOICES: 'FILTER_CHOICES', ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', CLEAR_CHOICES: 'CLEAR_CHOICES', ADD_GROUP: 'ADD_GROUP', ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', }; var EventType = { showDropdown: 'showDropdown', hideDropdown: 'hideDropdown', change: 'change', choice: 'choice', search: 'search', addItem: 'addItem', removeItem: 'removeItem', highlightItem: 'highlightItem', highlightChoice: 'highlightChoice', unhighlightItem: 'unhighlightItem', }; var KeyCodeMap = { TAB_KEY: 9, SHIFT_KEY: 16, BACK_KEY: 46, DELETE_KEY: 8, ENTER_KEY: 13, A_KEY: 65, ESC_KEY: 27, UP_KEY: 38, DOWN_KEY: 40, PAGE_UP_KEY: 33, PAGE_DOWN_KEY: 34, }; var ObjectsInConfig = ['fuseOptions', 'classNames']; var PassedElementTypes = { Text: 'text', SelectOne: 'select-one', SelectMultiple: 'select-multiple', }; var addChoice = function (choice) { return ({ type: ActionType.ADD_CHOICE, choice: choice, }); }; var removeChoice = function (choice) { return ({ type: ActionType.REMOVE_CHOICE, choice: choice, }); }; var filterChoices = function (results) { return ({ type: ActionType.FILTER_CHOICES, results: results, }); }; var activateChoices = function (active) { return ({ type: ActionType.ACTIVATE_CHOICES, active: active, }); }; var addGroup = function (group) { return ({ type: ActionType.ADD_GROUP, group: group, }); }; var addItem = function (item) { return ({ type: ActionType.ADD_ITEM, item: item, }); }; var removeItem$1 = function (item) { return ({ type: ActionType.REMOVE_ITEM, item: item, }); }; var highlightItem = function (item, highlighted) { return ({ type: ActionType.HIGHLIGHT_ITEM, item: item, highlighted: highlighted, }); }; var getRandomNumber = function (min, max) { return Math.floor(Math.random() * (max - min) + min); }; var generateChars = function (length) { return Array.from({ length: length }, function () { return getRandomNumber(0, 36).toString(36); }).join(''); }; var generateId = function (element, prefix) { var id = element.id || (element.name && "".concat(element.name, "-").concat(generateChars(2))) || generateChars(4); id = id.replace(/(:|\.|\[|\]|,)/g, ''); id = "".concat(prefix, "-").concat(id); return id; }; var getAdjacentEl = function (startEl, selector, direction) { if (direction === void 0) { direction = 1; } var prop = "".concat(direction > 0 ? 'next' : 'previous', "ElementSibling"); var sibling = startEl[prop]; while (sibling) { if (sibling.matches(selector)) { return sibling; } sibling = sibling[prop]; } return null; }; var isScrolledIntoView = function (element, parent, direction) { if (direction === void 0) { direction = 1; } var isVisible; if (direction > 0) { // In view from bottom isVisible = parent.scrollTop + parent.offsetHeight >= element.offsetTop + element.offsetHeight; } else { // In view from top isVisible = element.offsetTop >= parent.scrollTop; } return isVisible; }; var sanitise = function (value) { if (typeof value !== 'string') { if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { if ('raw' in value) { return sanitise(value.raw); } if ('trusted' in value) { return value.trusted; } } return value; } return value .replace(/&/g, '&') .replace(/>/g, '>') .replace(/= 0 && !window.matchMedia("(min-height: ".concat(dropdownPos + 1, "px)")).matches; } else if (this.position === 'top') { shouldFlip = true; } return shouldFlip; }; Container.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Container.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Container.prototype.open = function (dropdownPos, dropdownHeight) { addClassesToElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; if (this.shouldFlip(dropdownPos, dropdownHeight)) { addClassesToElement(this.element, this.classNames.flippedState); this.isFlipped = true; } }; Container.prototype.close = function () { removeClassesFromElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'false'); this.removeActiveDescendant(); this.isOpen = false; // A dropdown flips if it does not have space within the page if (this.isFlipped) { removeClassesFromElement(this.element, this.classNames.flippedState); this.isFlipped = false; } }; Container.prototype.addFocusState = function () { addClassesToElement(this.element, this.classNames.focusState); }; Container.prototype.removeFocusState = function () { removeClassesFromElement(this.element, this.classNames.focusState); }; Container.prototype.addInvalidState = function () { addClassesToElement(this.element, this.classNames.invalidState); }; Container.prototype.removeInvalidState = function () { removeClassesFromElement(this.element, this.classNames.invalidState); }; Container.prototype.enable = function () { removeClassesFromElement(this.element, this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; }; Container.prototype.disable = function () { addClassesToElement(this.element, this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; }; Container.prototype.wrap = function (element) { var el = this.element; var parentNode = element.parentNode; if (parentNode) { if (element.nextSibling) { parentNode.insertBefore(el, element.nextSibling); } else { parentNode.appendChild(el); } } el.appendChild(element); }; Container.prototype.unwrap = function (element) { var el = this.element; var parentNode = el.parentNode; if (parentNode) { // Move passed element outside this element parentNode.insertBefore(element, el); // Remove this element parentNode.removeChild(el); } }; Container.prototype.addLoadingState = function () { addClassesToElement(this.element, this.classNames.loadingState); this.element.setAttribute('aria-busy', 'true'); this.isLoading = true; }; Container.prototype.removeLoadingState = function () { removeClassesFromElement(this.element, this.classNames.loadingState); this.element.removeAttribute('aria-busy'); this.isLoading = false; }; return Container; }()); var Input = /** @class */ (function () { function Input(_a) { var element = _a.element, type = _a.type, classNames = _a.classNames, preventPaste = _a.preventPaste; this.element = element; this.type = type; this.classNames = classNames; this.preventPaste = preventPaste; this.isFocussed = this.element.isEqualNode(document.activeElement); this.isDisabled = element.disabled; this._onPaste = this._onPaste.bind(this); this._onInput = this._onInput.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } Object.defineProperty(Input.prototype, "placeholder", { set: function (placeholder) { this.element.placeholder = placeholder; }, enumerable: false, configurable: true }); Object.defineProperty(Input.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.value = value; }, enumerable: false, configurable: true }); Input.prototype.addEventListeners = function () { var el = this.element; el.addEventListener('paste', this._onPaste); el.addEventListener('input', this._onInput, { passive: true, }); el.addEventListener('focus', this._onFocus, { passive: true, }); el.addEventListener('blur', this._onBlur, { passive: true, }); }; Input.prototype.removeEventListeners = function () { var el = this.element; el.removeEventListener('input', this._onInput); el.removeEventListener('paste', this._onPaste); el.removeEventListener('focus', this._onFocus); el.removeEventListener('blur', this._onBlur); }; Input.prototype.enable = function () { var el = this.element; el.removeAttribute('disabled'); this.isDisabled = false; }; Input.prototype.disable = function () { var el = this.element; el.setAttribute('disabled', ''); this.isDisabled = true; }; Input.prototype.focus = function () { if (!this.isFocussed) { this.element.focus(); } }; Input.prototype.blur = function () { if (this.isFocussed) { this.element.blur(); } }; Input.prototype.clear = function (setWidth) { if (setWidth === void 0) { setWidth = true; } this.element.value = ''; if (setWidth) { this.setWidth(); } return this; }; /** * Set the correct input width based on placeholder * value or input value */ Input.prototype.setWidth = function () { // Resize input to contents or placeholder var element = this.element; element.style.minWidth = "".concat(element.placeholder.length + 1, "ch"); element.style.width = "".concat(element.value.length + 1, "ch"); }; Input.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Input.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Input.prototype._onInput = function () { if (this.type !== PassedElementTypes.SelectOne) { this.setWidth(); } }; Input.prototype._onPaste = function (event) { if (this.preventPaste) { event.preventDefault(); } }; Input.prototype._onFocus = function () { this.isFocussed = true; }; Input.prototype._onBlur = function () { this.isFocussed = false; }; return Input; }()); var SCROLLING_SPEED = 4; var List = /** @class */ (function () { function List(_a) { var element = _a.element; this.element = element; this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; } List.prototype.prepend = function (node) { var child = this.element.firstElementChild; if (child) { this.element.insertBefore(node, child); } else { this.element.append(node); } }; List.prototype.scrollToTop = function () { this.element.scrollTop = 0; }; List.prototype.scrollToChildElement = function (element, direction) { var _this = this; if (!element) { return; } var listHeight = this.element.offsetHeight; // Scroll position of dropdown var listScrollPosition = this.element.scrollTop + listHeight; var elementHeight = element.offsetHeight; // Distance from bottom of element to top of parent var elementPos = element.offsetTop + elementHeight; // Difference between the element and scroll position var destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop; requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); }; List.prototype._scrollDown = function (scrollPos, strength, destination) { var easing = (destination - scrollPos) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos + distance; }; List.prototype._scrollUp = function (scrollPos, strength, destination) { var easing = (scrollPos - destination) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos - distance; }; List.prototype._animateScroll = function (destination, direction) { var _this = this; var strength = SCROLLING_SPEED; var choiceListScrollTop = this.element.scrollTop; var continueAnimation = false; if (direction > 0) { this._scrollDown(choiceListScrollTop, strength, destination); if (choiceListScrollTop < destination) { continueAnimation = true; } } else { this._scrollUp(choiceListScrollTop, strength, destination); if (choiceListScrollTop > destination) { continueAnimation = true; } } if (continueAnimation) { requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); } }; return List; }()); var WrappedElement = /** @class */ (function () { function WrappedElement(_a) { var element = _a.element, classNames = _a.classNames; this.element = element; this.classNames = classNames; this.isDisabled = false; } Object.defineProperty(WrappedElement.prototype, "isActive", { get: function () { return this.element.dataset.choice === 'active'; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "dir", { get: function () { return this.element.dir; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.setAttribute('value', value); this.element.value = value; }, enumerable: false, configurable: true }); WrappedElement.prototype.conceal = function () { var el = this.element; // Hide passed input addClassesToElement(el, this.classNames.input); el.hidden = true; // Remove element from tab index el.tabIndex = -1; // Backup original styles if any var origStyle = el.getAttribute('style'); if (origStyle) { el.setAttribute('data-choice-orig-style', origStyle); } el.setAttribute('data-choice', 'active'); }; WrappedElement.prototype.reveal = function () { var el = this.element; // Reinstate passed element removeClassesFromElement(el, this.classNames.input); el.hidden = false; el.removeAttribute('tabindex'); // Recover original styles if any var origStyle = el.getAttribute('data-choice-orig-style'); if (origStyle) { el.removeAttribute('data-choice-orig-style'); el.setAttribute('style', origStyle); } else { el.removeAttribute('style'); } el.removeAttribute('data-choice'); }; WrappedElement.prototype.enable = function () { this.element.removeAttribute('disabled'); this.element.disabled = false; this.isDisabled = false; }; WrappedElement.prototype.disable = function () { this.element.setAttribute('disabled', ''); this.element.disabled = true; this.isDisabled = true; }; WrappedElement.prototype.triggerEvent = function (eventType, data) { dispatchEvent(this.element, eventType, data || {}); }; return WrappedElement; }()); var WrappedInput = /** @class */ (function (_super) { __extends(WrappedInput, _super); function WrappedInput() { return _super !== null && _super.apply(this, arguments) || this; } return WrappedInput; }(WrappedElement)); var coerceBool = function (arg, defaultValue) { if (defaultValue === void 0) { defaultValue = true; } return typeof arg === 'undefined' ? defaultValue : !!arg; }; var stringToHtmlClass = function (input) { if (typeof input === 'string') { // eslint-disable-next-line no-param-reassign input = input.split(' ').filter(function (s) { return s.length; }); } if (Array.isArray(input) && input.length) { return input; } return undefined; }; var mapInputToChoice = function (value, allowGroup, allowRawString) { if (allowRawString === void 0) { allowRawString = true; } if (typeof value === 'string') { var sanitisedValue = sanitise(value); var userValue = allowRawString || sanitisedValue === value ? value : { escaped: sanitisedValue, raw: value }; var result_1 = mapInputToChoice({ value: value, label: userValue, selected: true, }, false); return result_1; } var groupOrChoice = value; if ('choices' in groupOrChoice) { if (!allowGroup) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup throw new TypeError("optGroup is not allowed"); } var group = groupOrChoice; var choices = group.choices.map(function (e) { return mapInputToChoice(e, false); }); var result_2 = { id: 0, // actual ID will be assigned during _addGroup label: unwrapStringForRaw(group.label) || group.value, active: !!choices.length, disabled: !!group.disabled, choices: choices, }; return result_2; } var choice = groupOrChoice; var result = { id: 0, // actual ID will be assigned during _addChoice group: null, // actual group will be assigned during _addGroup but before _addChoice score: 0, // used in search rank: 0, // used in search, stable sort order value: choice.value, label: choice.label || choice.value, active: coerceBool(choice.active), selected: coerceBool(choice.selected, false), disabled: coerceBool(choice.disabled, false), placeholder: coerceBool(choice.placeholder, false), highlighted: false, labelClass: stringToHtmlClass(choice.labelClass), labelDescription: choice.labelDescription, customProperties: choice.customProperties, }; return result; }; var isHtmlInputElement = function (e) { return e.tagName === 'INPUT'; }; var isHtmlSelectElement = function (e) { return e.tagName === 'SELECT'; }; var isHtmlOption = function (e) { return e.tagName === 'OPTION'; }; var isHtmlOptgroup = function (e) { return e.tagName === 'OPTGROUP'; }; var WrappedSelect = /** @class */ (function (_super) { __extends(WrappedSelect, _super); function WrappedSelect(_a) { var element = _a.element, classNames = _a.classNames, template = _a.template, extractPlaceholder = _a.extractPlaceholder; var _this = _super.call(this, { element: element, classNames: classNames }) || this; _this.template = template; _this.extractPlaceholder = extractPlaceholder; return _this; } Object.defineProperty(WrappedSelect.prototype, "placeholderOption", { get: function () { return (this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]')); }, enumerable: false, configurable: true }); WrappedSelect.prototype.addOptions = function (choices) { var _this = this; var fragment = document.createDocumentFragment(); choices.forEach(function (obj) { var choice = obj; if (choice.element) { return; } var option = _this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); }; WrappedSelect.prototype.optionsAsChoices = function () { var _this = this; var choices = []; this.element.querySelectorAll(':scope > option, :scope > optgroup').forEach(function (e) { if (isHtmlOption(e)) { choices.push(_this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(_this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; }; // eslint-disable-next-line class-methods-use-this WrappedSelect.prototype._optionToChoice = function (option) { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? { trusted: option.dataset.labelDescription } : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; }; WrappedSelect.prototype._optgroupToChoice = function (optgroup) { var _this = this; var options = optgroup.querySelectorAll('option'); var choices = Array.from(options).map(function (option) { return _this._optionToChoice(option); }); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices: choices, }; }; return WrappedSelect; }(WrappedElement)); var DEFAULT_CLASSNAMES = { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], }; var DEFAULT_CONFIG = { items: [], choices: [], silent: false, renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: function (value) { return !!value && value !== ''; }, removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: function (value) { return "Press Enter to add \"".concat(value, "\""); }, removeItemIconText: function () { return "Remove item"; }, removeItemLabelText: function (value, _valueRaw, i) { return "Remove item: ".concat(i ? sanitise(i.label) : value); }, maxItemText: function (maxItemCount) { return "Only ".concat(maxItemCount, " values can be added"); }, valueComparer: function (value1, value2) { return value1 === value2; }, fuseOptions: { includeScore: true, }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, appendGroupInSearch: false, }; var removeItem = function (item) { var itemEl = item.itemEl; if (itemEl) { itemEl.remove(); item.itemEl = undefined; } }; function items(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_ITEM: { action.item.selected = true; var el = action.item.element; if (el) { el.selected = true; el.setAttribute('selected', ''); } state.push(action.item); break; } case ActionType.REMOVE_ITEM: { action.item.selected = false; var el = action.item.element; if (el) { el.selected = false; el.removeAttribute('selected'); // For a select-one, if all options are deselected, the first item is selected. To set a black value, select.value needs to be set var select = el.parentElement; if (select && isHtmlSelectElement(select) && select.type === PassedElementTypes.SelectOne) { select.value = ''; } } // this is mixing concerns, but this is *so much faster* removeItem(action.item); state = state.filter(function (choice) { return choice.id !== action.item.id; }); break; } case ActionType.REMOVE_CHOICE: { removeItem(action.choice); state = state.filter(function (item) { return item.id !== action.choice.id; }); break; } case ActionType.HIGHLIGHT_ITEM: { var highlighted = action.highlighted; var item = state.find(function (obj) { return obj.id === action.item.id; }); if (item && item.highlighted !== highlighted) { item.highlighted = highlighted; if (context) { updateClassList(item, highlighted ? context.classNames.highlightedState : context.classNames.selectedState, highlighted ? context.classNames.selectedState : context.classNames.highlightedState); } } break; } default: { update = false; break; } } return { state: state, update: update }; } function groups(s, action) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_GROUP: { state.push(action.group); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } /* eslint-disable */ function choices(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_CHOICE: { state.push(action.choice); break; } case ActionType.REMOVE_CHOICE: { action.choice.choiceEl = undefined; if (action.choice.group) { action.choice.group.choices = action.choice.group.choices.filter(function (obj) { return obj.id !== action.choice.id; }); } state = state.filter(function (obj) { return obj.id !== action.choice.id; }); break; } case ActionType.ADD_ITEM: case ActionType.REMOVE_ITEM: { action.item.choiceEl = undefined; break; } case ActionType.FILTER_CHOICES: { // avoid O(n^2) algorithm complexity when searching/filtering choices var scoreLookup_1 = []; action.results.forEach(function (result) { scoreLookup_1[result.item.id] = result; }); state.forEach(function (choice) { var result = scoreLookup_1[choice.id]; if (result !== undefined) { choice.score = result.score; choice.rank = result.rank; choice.active = true; } else { choice.score = 0; choice.rank = 0; choice.active = false; } if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.ACTIVATE_CHOICES: { state.forEach(function (choice) { choice.active = action.active; if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } var reducers = { groups: groups, items: items, choices: choices, }; var Store = /** @class */ (function () { function Store(context) { this._state = this.defaultState; this._listeners = []; this._txn = 0; this._context = context; } Object.defineProperty(Store.prototype, "defaultState", { // eslint-disable-next-line class-methods-use-this get: function () { return { groups: [], items: [], choices: [], }; }, enumerable: false, configurable: true }); // eslint-disable-next-line class-methods-use-this Store.prototype.changeSet = function (init) { return { groups: init, items: init, choices: init, }; }; Store.prototype.reset = function () { this._state = this.defaultState; var changes = this.changeSet(true); if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } }; Store.prototype.subscribe = function (onChange) { this._listeners.push(onChange); return this; }; Store.prototype.dispatch = function (action) { var _this = this; var state = this._state; var hasChanges = false; var changes = this._changeSet || this.changeSet(false); Object.keys(reducers).forEach(function (key) { var stateUpdate = reducers[key](state[key], action, _this._context); if (stateUpdate.update) { hasChanges = true; changes[key] = true; state[key] = stateUpdate.state; } }); if (hasChanges) { if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } } }; Store.prototype.withTxn = function (func) { this._txn++; try { func(); } finally { this._txn = Math.max(0, this._txn - 1); if (!this._txn) { var changeSet_1 = this._changeSet; if (changeSet_1) { this._changeSet = undefined; this._listeners.forEach(function (l) { return l(changeSet_1); }); } } } }; Object.defineProperty(Store.prototype, "state", { /** * Get store object */ get: function () { return this._state; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "items", { /** * Get items from store */ get: function () { return this.state.items; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "highlightedActiveItems", { /** * Get highlighted items from store */ get: function () { return this.items.filter(function (item) { return item.active && item.highlighted; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "choices", { /** * Get choices from store */ get: function () { return this.state.choices; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeChoices", { /** * Get active choices from store */ get: function () { return this.choices.filter(function (choice) { return choice.active; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "searchableChoices", { /** * Get choices that can be searched (excluding placeholders or disabled choices) */ get: function () { var context = this._context; return this.choices.filter(function (choice) { return !choice.placeholder && (context.searchDisabledChoices || !choice.disabled); }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "groups", { /** * Get groups from store */ get: function () { return this.state.groups; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeGroups", { /** * Get active groups from store */ get: function () { var _this = this; return this.state.groups.filter(function (group) { var isActive = group.active && !group.disabled; var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; }); return isActive && hasActiveOptions; }, []); }, enumerable: false, configurable: true }); Store.prototype.inTxn = function () { return this._txn > 0; }; /** * Get single choice by it's ID */ Store.prototype.getChoiceById = function (id) { return this.activeChoices.find(function (choice) { return choice.id === id; }); }; /** * Get group by group id */ Store.prototype.getGroupById = function (id) { return this.groups.find(function (group) { return group.id === id; }); }; return Store; }()); var NoticeTypes = { noChoices: 'no-choices', noResults: 'no-results', addChoice: 'add-choice', generic: '', }; var SearchByPrefixFilter = /** @class */ (function () { function SearchByPrefixFilter(config) { this._haystack = []; this._fields = config.searchFields; } SearchByPrefixFilter.prototype.index = function (data) { this._haystack = data; }; SearchByPrefixFilter.prototype.reset = function () { this._haystack = []; }; SearchByPrefixFilter.prototype.isEmptyIndex = function () { return !this._haystack.length; }; SearchByPrefixFilter.prototype.search = function (_needle) { var fields = this._fields; if (!fields || !fields.length || !_needle) { return []; } var needle = _needle.toLowerCase(); return this._haystack .filter(function (obj) { return fields.some(function (field) { return field in obj && obj[field].toLowerCase().startsWith(needle); }); }) .map(function (value, index) { return { item: value, score: index, rank: index + 1, }; }); }; return SearchByPrefixFilter; }()); function getSearcher(config) { return new SearchByPrefixFilter(config); } /** * Helpers to create HTML elements used by Choices * Can be overridden by providing `callbackOnCreateTemplates` option. * `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates` */ var isEmptyObject = function (obj) { // eslint-disable-next-line no-restricted-syntax for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { return false; } } return true; }; var assignCustomProperties = function (el, choice, withCustomProperties) { var dataset = el.dataset; var customProperties = choice.customProperties, labelClass = choice.labelClass, labelDescription = choice.labelDescription; if (labelClass) { dataset.labelClass = getClassNames(labelClass).join(' '); } if (labelDescription) { dataset.labelDescription = unwrapStringForRaw(labelDescription); } if (withCustomProperties && customProperties) { if (typeof customProperties === 'string') { dataset.customProperties = customProperties; } else if (typeof customProperties === 'object' && !isEmptyObject(customProperties)) { dataset.customProperties = JSON.stringify(customProperties); } } }; var addAriaLabel = function (docRoot, id, element) { var label = id && docRoot.querySelector("label[for='".concat(id, "']")); var text = label && label.innerText; if (text) { element.setAttribute('aria-label', text); } }; var templates = { containerOuter: function (_a, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType, labelId) { var containerOuter = _a.classNames.containerOuter; var div = document.createElement('div'); addClassesToElement(div, containerOuter); div.dataset.type = passedElementType; if (dir) { div.dir = dir; } if (isSelectOneElement) { div.tabIndex = 0; } if (isSelectElement) { div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox'); if (searchEnabled) { div.setAttribute('aria-autocomplete', 'list'); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, div); } div.setAttribute('aria-haspopup', 'true'); div.setAttribute('aria-expanded', 'false'); } if (labelId) { div.setAttribute('aria-labelledby', labelId); } return div; }, containerInner: function (_a) { var containerInner = _a.classNames.containerInner; var div = document.createElement('div'); addClassesToElement(div, containerInner); return div; }, itemList: function (_a, isSelectOneElement) { var searchEnabled = _a.searchEnabled, _b = _a.classNames, list = _b.list, listSingle = _b.listSingle, listItems = _b.listItems; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, isSelectOneElement ? listSingle : listItems); if (this._isSelectElement && searchEnabled) { div.setAttribute('role', 'listbox'); } return div; }, placeholder: function (_a, value) { var allowHTML = _a.allowHTML, placeholder = _a.classNames.placeholder; var div = document.createElement('div'); addClassesToElement(div, placeholder); setElementHtml(div, allowHTML, value); return div; }, item: function (_a, choice, removeItemButton) { var allowHTML = _a.allowHTML, removeItemButtonAlignLeft = _a.removeItemButtonAlignLeft, removeItemIconText = _a.removeItemIconText, removeItemLabelText = _a.removeItemLabelText, _b = _a.classNames, item = _b.item, button = _b.button, highlightedState = _b.highlightedState, itemSelectable = _b.itemSelectable, placeholder = _b.placeholder; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); addClassesToElement(div, item); if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, choice.label); addClassesToElement(spanLabel, choice.labelClass); div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, choice.label); } div.dataset.item = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; assignCustomProperties(div, choice, true); if (choice.disabled || this.containerOuter.isDisabled) { div.setAttribute('aria-disabled', 'true'); } if (this._isSelectElement) { div.setAttribute('aria-selected', 'true'); div.setAttribute('role', 'option'); } if (choice.placeholder) { addClassesToElement(div, placeholder); div.dataset.placeholder = ''; } addClassesToElement(div, choice.highlighted ? highlightedState : itemSelectable); if (removeItemButton) { if (choice.disabled) { removeClassesFromElement(div, itemSelectable); } div.dataset.deletable = ''; var removeButton = document.createElement('button'); removeButton.type = 'button'; addClassesToElement(removeButton, button); var eventChoice = getChoiceForOutput(choice); setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value, eventChoice)); var REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value, eventChoice); if (REMOVE_ITEM_LABEL) { removeButton.setAttribute('aria-label', REMOVE_ITEM_LABEL); } removeButton.dataset.button = ''; if (removeItemButtonAlignLeft) { div.insertAdjacentElement('afterbegin', removeButton); } else { div.appendChild(removeButton); } } return div; }, choiceList: function (_a, isSelectOneElement) { var list = _a.classNames.list; var div = document.createElement('div'); addClassesToElement(div, list); if (!isSelectOneElement) { div.setAttribute('aria-multiselectable', 'true'); } div.setAttribute('role', 'listbox'); return div; }, choiceGroup: function (_a, _b) { var allowHTML = _a.allowHTML, _c = _a.classNames, group = _c.group, groupHeading = _c.groupHeading, itemDisabled = _c.itemDisabled; var id = _b.id, label = _b.label, disabled = _b.disabled; var rawLabel = unwrapStringForRaw(label); var div = document.createElement('div'); addClassesToElement(div, group); if (disabled) { addClassesToElement(div, itemDisabled); } div.setAttribute('role', 'group'); div.dataset.group = ''; div.dataset.id = id; div.dataset.value = rawLabel; if (disabled) { div.setAttribute('aria-disabled', 'true'); } var heading = document.createElement('div'); addClassesToElement(heading, groupHeading); setElementHtml(heading, allowHTML, label || ''); div.appendChild(heading); return div; }, choice: function (_a, choice, selectText, groupName) { var allowHTML = _a.allowHTML, _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, itemSelectable = _b.itemSelectable, selectedState = _b.selectedState, itemDisabled = _b.itemDisabled, description = _b.description, placeholder = _b.placeholder; // eslint-disable-next-line prefer-destructuring var label = choice.label; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); div.id = choice.elementId; addClassesToElement(div, item); addClassesToElement(div, itemChoice); if (groupName && typeof label === 'string') { label = escapeForTemplate(allowHTML, label); label += " (".concat(groupName, ")"); label = { trusted: label }; } var describedBy = div; if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, label); addClassesToElement(spanLabel, choice.labelClass); describedBy = spanLabel; div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, label); } if (choice.labelDescription) { var descId = "".concat(choice.elementId, "-description"); describedBy.setAttribute('aria-describedby', descId); var spanDesc = document.createElement('span'); setElementHtml(spanDesc, allowHTML, choice.labelDescription); spanDesc.id = descId; addClassesToElement(spanDesc, description); div.appendChild(spanDesc); } if (choice.selected) { addClassesToElement(div, selectedState); } if (choice.placeholder) { addClassesToElement(div, placeholder); } div.setAttribute('role', choice.group ? 'treeitem' : 'option'); div.dataset.choice = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; if (selectText) { div.dataset.selectText = selectText; } if (choice.group) { div.dataset.groupId = "".concat(choice.group.id); } assignCustomProperties(div, choice, false); if (choice.disabled) { addClassesToElement(div, itemDisabled); div.dataset.choiceDisabled = ''; div.setAttribute('aria-disabled', 'true'); } else { addClassesToElement(div, itemSelectable); div.dataset.choiceSelectable = ''; div.setAttribute('aria-selected', choice.selected ? 'true' : 'false'); } return div; }, input: function (_a, placeholderValue) { var _b = _a.classNames, input = _b.input, inputCloned = _b.inputCloned, labelId = _a.labelId; var inp = document.createElement('input'); inp.type = 'search'; addClassesToElement(inp, input); addClassesToElement(inp, inputCloned); inp.autocomplete = 'off'; inp.autocapitalize = 'off'; inp.spellcheck = false; inp.setAttribute('aria-autocomplete', 'list'); if (placeholderValue) { inp.setAttribute('aria-label', placeholderValue); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, inp); } return inp; }, dropdown: function (_a) { var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, listDropdown); div.setAttribute('aria-expanded', 'false'); return div; }, notice: function (_a, innerHTML, type) { var _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, addChoice = _b.addChoice, noResults = _b.noResults, noChoices = _b.noChoices, noticeItem = _b.notice; if (type === void 0) { type = NoticeTypes.generic; } var notice = document.createElement('div'); setElementHtml(notice, true, innerHTML); addClassesToElement(notice, item); addClassesToElement(notice, itemChoice); addClassesToElement(notice, noticeItem); // eslint-disable-next-line default-case switch (type) { case NoticeTypes.addChoice: addClassesToElement(notice, addChoice); break; case NoticeTypes.noResults: addClassesToElement(notice, noResults); break; case NoticeTypes.noChoices: addClassesToElement(notice, noChoices); break; } if (type === NoticeTypes.addChoice) { notice.dataset.choiceSelectable = ''; notice.dataset.choice = ''; } return notice; }, option: function (choice) { // HtmlOptionElement's label value does not support HTML, so the avoid double escaping unwrap the untrusted string. var labelValue = unwrapStringForRaw(choice.label); var opt = new Option(labelValue, choice.value, false, choice.selected); assignCustomProperties(opt, choice, true); opt.disabled = choice.disabled; if (choice.selected) { opt.setAttribute('selected', ''); } return opt; }, }; /** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ var IS_IE11 = '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style; var USER_DEFAULTS = {}; var parseDataSetId = function (element) { if (!element) { return undefined; } return element.dataset.id ? parseInt(element.dataset.id, 10) : undefined; }; var selectableChoiceIdentifier = '[data-choice-selectable]'; /** * Choices * @author Josh Johnson */ var Choices = /** @class */ (function () { function Choices(element, userConfig) { if (element === void 0) { element = '[data-choice]'; } if (userConfig === void 0) { userConfig = {}; } var _this = this; this.initialisedOK = undefined; this._hasNonChoicePlaceholder = false; this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; var defaults = Choices.defaults; this.config = __assign(__assign(__assign({}, defaults.allOptions), defaults.options), userConfig); ObjectsInConfig.forEach(function (key) { _this.config[key] = __assign(__assign(__assign({}, defaults.allOptions[key]), defaults.options[key]), userConfig[key]); }); var config = this.config; if (!config.silent) { this._validateConfig(); } var docRoot = config.shadowRoot || document.documentElement; this._docRoot = docRoot; var passedElement = typeof element === 'string' ? docRoot.querySelector(element) : element; if (!passedElement || typeof passedElement !== 'object' || !(isHtmlInputElement(passedElement) || isHtmlSelectElement(passedElement))) { if (!passedElement && typeof element === 'string') { throw TypeError("Selector ".concat(element, " failed to find an element")); } throw TypeError("Expected one of the following types text|select-one|select-multiple"); } var elementType = passedElement.type; var isText = elementType === PassedElementTypes.Text; if (isText || config.maxItemCount !== 1) { config.singleModeForMultiSelect = false; } if (config.singleModeForMultiSelect) { elementType = PassedElementTypes.SelectMultiple; } var isSelectOne = elementType === PassedElementTypes.SelectOne; var isSelectMultiple = elementType === PassedElementTypes.SelectMultiple; var isSelect = isSelectOne || isSelectMultiple; this._elementType = elementType; this._isTextElement = isText; this._isSelectOneElement = isSelectOne; this._isSelectMultipleElement = isSelectMultiple; this._isSelectElement = isSelectOne || isSelectMultiple; this._canAddUserChoices = (isText && config.addItems) || (isSelect && config.addChoices); if (typeof config.renderSelectedChoices !== 'boolean') { config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne; } if (config.closeDropdownOnSelect === 'auto') { config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect; } else { config.closeDropdownOnSelect = coerceBool(config.closeDropdownOnSelect); } if (config.placeholder) { if (config.placeholderValue) { this._hasNonChoicePlaceholder = true; } else if (passedElement.dataset.placeholder) { this._hasNonChoicePlaceholder = true; config.placeholderValue = passedElement.dataset.placeholder; } } if (userConfig.addItemFilter && typeof userConfig.addItemFilter !== 'function') { var re = userConfig.addItemFilter instanceof RegExp ? userConfig.addItemFilter : new RegExp(userConfig.addItemFilter); config.addItemFilter = re.test.bind(re); } if (this._isTextElement) { this.passedElement = new WrappedInput({ element: passedElement, classNames: config.classNames, }); } else { var selectEl = passedElement; this.passedElement = new WrappedSelect({ element: selectEl, classNames: config.classNames, template: function (data) { return _this._templates.option(data); }, extractPlaceholder: config.placeholder && !this._hasNonChoicePlaceholder, }); } this.initialised = false; this._store = new Store(config); this._currentValue = ''; config.searchEnabled = !isText && config.searchEnabled; this._canSearch = config.searchEnabled; this._isScrollingOnIe = false; this._highlightPosition = 0; this._wasTap = true; this._placeholderValue = this._generatePlaceholderValue(); this._baseId = generateId(passedElement, 'choices-'); /** * setting direction in cases where it's explicitly set on passedElement * or when calculated direction is different from the document */ this._direction = passedElement.dir; if (!this._direction) { var elementDirection = window.getComputedStyle(passedElement).direction; var documentDirection = window.getComputedStyle(document.documentElement).direction; if (elementDirection !== documentDirection) { this._direction = elementDirection; } } this._idNames = { itemChoice: 'item-choice', }; this._templates = defaults.templates; this._render = this._render.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onKeyUp = this._onKeyUp.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onInput = this._onInput.bind(this); this._onClick = this._onClick.bind(this); this._onTouchMove = this._onTouchMove.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseOver = this._onMouseOver.bind(this); this._onFormReset = this._onFormReset.bind(this); this._onSelectKey = this._onSelectKey.bind(this); this._onEnterKey = this._onEnterKey.bind(this); this._onEscapeKey = this._onEscapeKey.bind(this); this._onDirectionKey = this._onDirectionKey.bind(this); this._onDeleteKey = this._onDeleteKey.bind(this); this._onChange = this._onChange.bind(this); this._onInvalid = this._onInvalid.bind(this); // If element has already been initialised with Choices, fail silently if (this.passedElement.isActive) { if (!config.silent) { console.warn('Trying to initialise Choices on element already initialised', { element: element }); } this.initialised = true; this.initialisedOK = false; return; } // Let's go this.init(); // preserve the selected item list after setup for form reset this._initialItems = this._store.items.map(function (choice) { return choice.value; }); } Object.defineProperty(Choices, "defaults", { get: function () { return Object.preventExtensions({ get options() { return USER_DEFAULTS; }, get allOptions() { return DEFAULT_CONFIG; }, get templates() { return templates; }, }); }, enumerable: false, configurable: true }); Choices.prototype.init = function () { if (this.initialised || this.initialisedOK !== undefined) { return; } this._searcher = getSearcher(this.config); this._loadChoices(); this._createTemplates(); this._createElements(); this._createStructure(); if ((this._isTextElement && !this.config.addItems) || this.passedElement.element.hasAttribute('disabled') || !!this.passedElement.element.closest('fieldset:disabled')) { this.disable(); } else { this.enable(); this._addEventListeners(); } // should be triggered **after** disabled state to avoid additional re-draws this._initStore(); this.initialised = true; this.initialisedOK = true; var callbackOnInit = this.config.callbackOnInit; // Run callback if it is a function if (typeof callbackOnInit === 'function') { callbackOnInit.call(this); } }; Choices.prototype.destroy = function () { if (!this.initialised) { return; } this._removeEventListeners(); this.passedElement.reveal(); this.containerOuter.unwrap(this.passedElement.element); this._store._listeners = []; // prevents select/input value being wiped this.clearStore(false); this._stopSearch(); this._templates = Choices.defaults.templates; this.initialised = false; this.initialisedOK = undefined; }; Choices.prototype.enable = function () { if (this.passedElement.isDisabled) { this.passedElement.enable(); } if (this.containerOuter.isDisabled) { this._addEventListeners(); this.input.enable(); this.containerOuter.enable(); } return this; }; Choices.prototype.disable = function () { if (!this.passedElement.isDisabled) { this.passedElement.disable(); } if (!this.containerOuter.isDisabled) { this._removeEventListeners(); this.input.disable(); this.containerOuter.disable(); } return this; }; Choices.prototype.highlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, true)); if (runEvent) { this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.unhighlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || !choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, false)); if (runEvent) { this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.highlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (!item.highlighted) { _this._store.dispatch(highlightItem(item, true)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.unhighlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (item.highlighted) { _this._store.dispatch(highlightItem(item, false)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.removeActiveItemsByValue = function (value) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (item) { return item.value === value; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeActiveItems = function (excludedId) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (_a) { var id = _a.id; return id !== excludedId; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeHighlightedItems = function (runEvent) { var _this = this; if (runEvent === void 0) { runEvent = false; } this._store.withTxn(function () { _this._store.highlightedActiveItems.forEach(function (item) { _this._removeItem(item); // If this action was performed by the user // trigger the event if (runEvent) { _this._triggerChange(item.value); } }); }); return this; }; Choices.prototype.showDropdown = function (preventInputFocus) { var _this = this; if (this.dropdown.isActive) { return this; } if (preventInputFocus === undefined) { // eslint-disable-next-line no-param-reassign preventInputFocus = !this._canSearch; } requestAnimationFrame(function () { _this.dropdown.show(); var rect = _this.dropdown.element.getBoundingClientRect(); _this.containerOuter.open(rect.bottom, rect.height); if (!preventInputFocus) { _this.input.focus(); } _this.passedElement.triggerEvent(EventType.showDropdown); var activeElement = _this.choiceList.element.querySelector(getClassNamesSelector(_this.config.classNames.selectedState)); if (activeElement !== null && !isScrolledIntoView(activeElement, _this.choiceList.element)) { // We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll. activeElement.scrollIntoView(); } }); return this; }; Choices.prototype.hideDropdown = function (preventInputBlur) { var _this = this; if (!this.dropdown.isActive) { return this; } this._removeHighlightedChoices(); requestAnimationFrame(function () { _this.dropdown.hide(); _this.containerOuter.close(); if (!preventInputBlur && _this._canSearch) { _this.input.removeActiveDescendant(); _this.input.blur(); } _this.passedElement.triggerEvent(EventType.hideDropdown); }); return this; }; Choices.prototype.getValue = function (valueOnly) { var values = this._store.items.map(function (item) { return (valueOnly ? item.value : getChoiceForOutput(item)); }); return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values; }; Choices.prototype.setValue = function (items) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setValue'); return this; } this._store.withTxn(function () { items.forEach(function (value) { if (value) { _this._addChoice(mapInputToChoice(value, false)); } }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.setChoiceByValue = function (value) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoiceByValue'); return this; } if (this._isTextElement) { return this; } this._store.withTxn(function () { // If only one value has been passed, convert to array var choiceValue = Array.isArray(value) ? value : [value]; // Loop through each value and choiceValue.forEach(function (val) { return _this._findAndSelectChoiceByValue(val); }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ Choices.prototype.setChoices = function (choicesArrayOrFetcher, value, label, replaceChoices, clearSearchFlag, replaceItems) { var _this = this; if (choicesArrayOrFetcher === void 0) { choicesArrayOrFetcher = []; } if (value === void 0) { value = 'value'; } if (label === void 0) { label = 'label'; } if (replaceChoices === void 0) { replaceChoices = false; } if (clearSearchFlag === void 0) { clearSearchFlag = true; } if (replaceItems === void 0) { replaceItems = false; } if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoices'); return this; } if (!this._isSelectElement) { throw new TypeError("setChoices can't be used with INPUT based Choices"); } if (typeof value !== 'string' || !value) { throw new TypeError("value parameter must be a name of 'value' field in passed objects"); } if (typeof choicesArrayOrFetcher === 'function') { // it's a choices fetcher function var fetcher_1 = choicesArrayOrFetcher(this); if (typeof Promise === 'function' && fetcher_1 instanceof Promise) { // that's a promise // eslint-disable-next-line no-promise-executor-return return new Promise(function (resolve) { return requestAnimationFrame(resolve); }) .then(function () { return _this._handleLoadingState(true); }) .then(function () { return fetcher_1; }) .then(function (data) { return _this.setChoices(data, value, label, replaceChoices, clearSearchFlag, replaceItems); }) .catch(function (err) { if (!_this.config.silent) { console.error(err); } }) .then(function () { return _this._handleLoadingState(false); }) .then(function () { return _this; }); } // function returned something else than promise, let's check if it's an array of choices if (!Array.isArray(fetcher_1)) { throw new TypeError(".setChoices first argument function must return either array of choices or Promise, got: ".concat(typeof fetcher_1)); } // recursion with results, it's sync and choices were cleared already return this.setChoices(fetcher_1, value, label, false); } if (!Array.isArray(choicesArrayOrFetcher)) { throw new TypeError(".setChoices must be called either with array of choices with a function resulting into Promise of array of choices"); } this.containerOuter.removeLoadingState(); this._store.withTxn(function () { if (clearSearchFlag) { _this._isSearching = false; } // Clear choices if needed if (replaceChoices) { _this.clearChoices(true, replaceItems); } var isDefaultValue = value === 'value'; var isDefaultLabel = label === 'label'; choicesArrayOrFetcher.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { var group = groupOrChoice; if (!isDefaultLabel) { group = __assign(__assign({}, group), { label: group[label] }); } _this._addGroup(mapInputToChoice(group, true)); } else { var choice = groupOrChoice; if (!isDefaultLabel || !isDefaultValue) { choice = __assign(__assign({}, choice), { value: choice[value], label: choice[label] }); } var choiceFull = mapInputToChoice(choice, false); _this._addChoice(choiceFull); if (choiceFull.placeholder && !_this._hasNonChoicePlaceholder) { _this._placeholderValue = unwrapStringForEscaped(choiceFull.label); } } }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.refresh = function (withEvents, selectFirstOption, deselectAll) { var _this = this; if (withEvents === void 0) { withEvents = false; } if (selectFirstOption === void 0) { selectFirstOption = false; } if (deselectAll === void 0) { deselectAll = false; } if (!this._isSelectElement) { if (!this.config.silent) { console.warn('refresh method can only be used on choices backed by a element'); } return this; } this._store.withTxn(function () { var choicesFromOptions = _this.passedElement.optionsAsChoices(); // Build the list of items which require preserving var existingItems = {}; if (!deselectAll) { _this._store.items.forEach(function (choice) { if (choice.id && choice.active && choice.selected) { existingItems[choice.value] = true; } }); } _this.clearStore(false); var updateChoice = function (choice) { if (deselectAll) { _this._store.dispatch(removeItem$1(choice)); } else if (existingItems[choice.value]) { choice.selected = true; } }; choicesFromOptions.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { groupOrChoice.choices.forEach(updateChoice); return; } updateChoice(groupOrChoice); }); /* @todo only generate add events for the added options instead of all if (withEvents) { items.forEach((choice) => { if (existingItems[choice.value]) { this.passedElement.triggerEvent( EventType.removeItem, this._getChoiceForEvent(choice), ); } }); } */ // load new choices & items _this._addPredefinedChoices(choicesFromOptions, selectFirstOption, withEvents); // re-do search if required if (_this._isSearching) { _this._searchChoices(_this.input.value); } }); return this; }; Choices.prototype.removeChoice = function (value) { var choice = this._store.choices.find(function (c) { return c.value === value; }); if (!choice) { return this; } this._clearNotice(); this._store.dispatch(removeChoice(choice)); // @todo integrate with Store this._searcher.reset(); if (choice.selected) { this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.clearChoices = function (clearOptions, clearItems) { var _this = this; if (clearOptions === void 0) { clearOptions = true; } if (clearItems === void 0) { clearItems = false; } if (clearOptions) { if (clearItems) { this.passedElement.element.replaceChildren(''); } else { this.passedElement.element.querySelectorAll(':not([selected])').forEach(function (el) { el.remove(); }); } } this.itemList.element.replaceChildren(''); this.choiceList.element.replaceChildren(''); this._clearNotice(); this._store.withTxn(function () { var items = clearItems ? [] : _this._store.items; _this._store.reset(); items.forEach(function (item) { _this._store.dispatch(addChoice(item)); _this._store.dispatch(addItem(item)); }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.clearStore = function (clearOptions) { if (clearOptions === void 0) { clearOptions = true; } this.clearChoices(clearOptions, true); this._stopSearch(); this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; return this; }; Choices.prototype.clearInput = function () { var shouldSetInputWidth = !this._isSelectOneElement; this.input.clear(shouldSetInputWidth); this._stopSearch(); return this; }; Choices.prototype._validateConfig = function () { var config = this.config; var invalidConfigOptions = diff(config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); } if (config.allowHTML && config.allowHtmlUserInput) { if (config.addItems) { console.warn('Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks'); } if (config.addChoices) { console.warn('Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks'); } } }; Choices.prototype._render = function (changes) { if (changes === void 0) { changes = { choices: true, groups: true, items: true }; } if (this._store.inTxn()) { return; } if (this._isSelectElement) { if (changes.choices || changes.groups) { this._renderChoices(); } } if (changes.items) { this._renderItems(); } }; Choices.prototype._renderChoices = function () { var _this = this; if (!this._canAddItems()) { return; // block rendering choices if the input limit is reached. } var _a = this, config = _a.config, isSearching = _a._isSearching; var _b = this._store, activeGroups = _b.activeGroups, activeChoices = _b.activeChoices; var renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit; if (this._isSelectElement) { var backingOptions = activeChoices.filter(function (choice) { return !choice.element; }); if (backingOptions.length) { this.passedElement.addOptions(backingOptions); } } var fragment = document.createDocumentFragment(); var renderableChoices = function (choices) { return choices.filter(function (choice) { return !choice.placeholder && (isSearching ? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank : config.renderSelectedChoices || !choice.selected); }); }; var showLabel = config.appendGroupInSearch && isSearching; var selectableChoices = false; var highlightedEl = null; var renderChoices = function (choices, withinGroup) { if (isSearching) { // sortByRank is used to ensure stable sorting, as scores are non-unique // this additionally ensures fuseOptions.sortFn is not ignored choices.sort(sortByRank); } else if (config.shouldSort) { choices.sort(config.sorter); } var choiceLimit = choices.length; choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit; choiceLimit--; choices.every(function (choice, index) { // choiceEl being empty signals the contents has probably significantly changed var dropdownItem = choice.choiceEl || _this._templates.choice(config, choice, config.itemSelectText, showLabel && choice.group ? choice.group.label : undefined); choice.choiceEl = dropdownItem; fragment.appendChild(dropdownItem); if (isSearching || !choice.selected) { selectableChoices = true; } else if (!highlightedEl) { highlightedEl = dropdownItem; } return index < choiceLimit; }); }; if (activeChoices.length) { if (config.resetScrollPosition) { requestAnimationFrame(function () { return _this.choiceList.scrollToTop(); }); } if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) { // If we have a placeholder choice along with groups renderChoices(activeChoices.filter(function (choice) { return choice.placeholder && !choice.group; }), false); } // If we have grouped options if (activeGroups.length && !isSearching) { if (config.shouldSort) { activeGroups.sort(config.sorter); } // render Choices without group first, regardless of sort, otherwise they won't be distinguishable // from the last group renderChoices(activeChoices.filter(function (choice) { return !choice.placeholder && !choice.group; }), false); activeGroups.forEach(function (group) { var groupChoices = renderableChoices(group.choices); if (groupChoices.length) { if (group.label) { var dropdownGroup = group.groupEl || _this._templates.choiceGroup(_this.config, group); group.groupEl = dropdownGroup; dropdownGroup.remove(); fragment.appendChild(dropdownGroup); } renderChoices(groupChoices, true); } }); } else { renderChoices(renderableChoices(activeChoices), false); } } if (!selectableChoices && (isSearching || !fragment.children.length || !config.renderSelectedChoices)) { if (!this._notice) { this._notice = { text: resolveStringFunction(isSearching ? config.noResultsText : config.noChoicesText), type: isSearching ? NoticeTypes.noResults : NoticeTypes.noChoices, }; } fragment.replaceChildren(''); } this._renderNotice(fragment); this.choiceList.element.replaceChildren(fragment); this._highlightChoice(highlightedEl); }; Choices.prototype._renderItems = function () { var _this = this; var items = this._store.items || []; var itemList = this.itemList.element; var config = this.config; var fragment = document.createDocumentFragment(); var itemFromList = function (item) { return itemList.querySelector("[data-item][data-id=\"".concat(item.id, "\"]")); }; var addItemToFragment = function (item) { var el = item.itemEl; if (el && el.parentElement) { return; } el = itemFromList(item) || _this._templates.item(config, item, config.removeItemButton); item.itemEl = el; fragment.appendChild(el); }; // new items items.forEach(addItemToFragment); var addedItems = !!fragment.childNodes.length; if (this._isSelectOneElement) { var existingItems = itemList.children.length; if (addedItems || existingItems > 1) { var placeholder = itemList.querySelector(getClassNamesSelector(config.classNames.placeholder)); if (placeholder) { placeholder.remove(); } } else if (!addedItems && !existingItems && this._placeholderValue) { addedItems = true; addItemToFragment(mapInputToChoice({ selected: true, value: '', label: this._placeholderValue, placeholder: true, }, false)); } } if (addedItems) { itemList.append(fragment); if (config.shouldSortItems && !this._isSelectOneElement) { items.sort(config.sorter); // push sorting into the DOM items.forEach(function (item) { var el = itemFromList(item); if (el) { el.remove(); fragment.append(el); } }); itemList.append(fragment); } } if (this._isTextElement) { // Update the value of the hidden input this.passedElement.value = items.map(function (_a) { var value = _a.value; return value; }).join(config.delimiter); } }; Choices.prototype._displayNotice = function (text, type, openDropdown) { if (openDropdown === void 0) { openDropdown = true; } var oldNotice = this._notice; if (oldNotice && ((oldNotice.type === type && oldNotice.text === text) || (oldNotice.type === NoticeTypes.addChoice && (type === NoticeTypes.noResults || type === NoticeTypes.noChoices)))) { if (openDropdown) { this.showDropdown(true); } return; } this._clearNotice(); this._notice = text ? { text: text, type: type, } : undefined; this._renderNotice(); if (openDropdown && text) { this.showDropdown(true); } }; Choices.prototype._clearNotice = function () { if (!this._notice) { return; } var noticeElement = this.choiceList.element.querySelector(getClassNamesSelector(this.config.classNames.notice)); if (noticeElement) { noticeElement.remove(); } this._notice = undefined; }; Choices.prototype._renderNotice = function (fragment) { var noticeConf = this._notice; if (noticeConf) { var notice = this._templates.notice(this.config, noticeConf.text, noticeConf.type); if (fragment) { fragment.append(notice); } else { this.choiceList.prepend(notice); } } }; /** * @deprecated Use utils.getChoiceForOutput */ // eslint-disable-next-line class-methods-use-this Choices.prototype._getChoiceForOutput = function (choice, keyCode) { return getChoiceForOutput(choice, keyCode); }; Choices.prototype._triggerChange = function (value) { if (value === undefined || value === null) { return; } this.passedElement.triggerEvent(EventType.change, { value: value, }); }; Choices.prototype._handleButtonAction = function (element) { var _this = this; var items = this._store.items; if (!items.length || !this.config.removeItems || !this.config.removeItemButton) { return; } var id = element && parseDataSetId(element.closest('[data-id]')); var itemToRemove = id && items.find(function (item) { return item.id === id; }); if (!itemToRemove) { return; } this._store.withTxn(function () { // Remove item associated with button _this._removeItem(itemToRemove); _this._triggerChange(itemToRemove.value); if (_this._isSelectOneElement && !_this._hasNonChoicePlaceholder) { var placeholderChoice = (_this.config.shouldSort ? _this._store.choices.reverse() : _this._store.choices).find(function (choice) { return choice.placeholder; }); if (placeholderChoice) { _this._addItem(placeholderChoice); _this.unhighlightAll(); if (placeholderChoice.value) { _this._triggerChange(placeholderChoice.value); } } } }); }; Choices.prototype._handleItemAction = function (element, hasShiftKey) { var _this = this; if (hasShiftKey === void 0) { hasShiftKey = false; } var items = this._store.items; if (!items.length || !this.config.removeItems || this._isSelectOneElement) { return; } var id = parseDataSetId(element); if (!id) { return; } // We only want to select one item with a click // so we deselect any items that aren't the target // unless shift is being pressed items.forEach(function (item) { if (item.id === id && !item.highlighted) { _this.highlightItem(item); } else if (!hasShiftKey && item.highlighted) { _this.unhighlightItem(item); } }); // Focus input as without focus, a user cannot do anything with a // highlighted item this.input.focus(); }; Choices.prototype._handleChoiceAction = function (element) { var _this = this; // If we are clicking on an option var id = parseDataSetId(element); var choice = id && this._store.getChoiceById(id); if (!choice || choice.disabled) { return false; } var hasActiveDropdown = this.dropdown.isActive; if (!choice.selected) { if (!this._canAddItems()) { return true; // causes _onEnterKey to early out } this._store.withTxn(function () { _this._addItem(choice, true, true); _this.clearInput(); _this.unhighlightAll(); }); this._triggerChange(choice.value); } // We want to close the dropdown if we are dealing with a single select box if (hasActiveDropdown && this.config.closeDropdownOnSelect) { this.hideDropdown(true); this.containerOuter.element.focus(); } return true; }; Choices.prototype._handleBackspace = function (items) { var config = this.config; if (!config.removeItems || !items.length) { return; } var lastItem = items[items.length - 1]; var hasHighlightedItems = items.some(function (item) { return item.highlighted; }); // If editing the last item is allowed and there are not other selected items, // we can edit the item value. Otherwise if we can remove items, remove all selected items if (config.editItems && !hasHighlightedItems && lastItem) { this.input.value = lastItem.value; this.input.setWidth(); this._removeItem(lastItem); this._triggerChange(lastItem.value); } else { if (!hasHighlightedItems) { // Highlight last item if none already highlighted this.highlightItem(lastItem, false); } this.removeHighlightedItems(true); } }; Choices.prototype._loadChoices = function () { var _a; var _this = this; var config = this.config; if (this._isTextElement) { // Assign preset items from passed object first this._presetChoices = config.items.map(function (e) { return mapInputToChoice(e, false); }); // Add any values passed from attribute if (this.passedElement.value) { var elementItems = this.passedElement.value .split(config.delimiter) .map(function (e) { return mapInputToChoice(e, false, _this.config.allowHtmlUserInput); }); this._presetChoices = this._presetChoices.concat(elementItems); } this._presetChoices.forEach(function (choice) { choice.selected = true; }); } else if (this._isSelectElement) { // Assign preset choices from passed object this._presetChoices = config.choices.map(function (e) { return mapInputToChoice(e, true); }); // Create array of choices from option elements var choicesFromOptions = this.passedElement.optionsAsChoices(); if (choicesFromOptions) { (_a = this._presetChoices).push.apply(_a, choicesFromOptions); } } }; Choices.prototype._handleLoadingState = function (setLoading) { if (setLoading === void 0) { setLoading = true; } var el = this.itemList.element; if (setLoading) { this.disable(); this.containerOuter.addLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(this._templates.placeholder(this.config, this.config.loadingText)); } else { this.input.placeholder = this.config.loadingText; } } else { this.enable(); this.containerOuter.removeLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(''); this._render(); } else { this.input.placeholder = this._placeholderValue || ''; } } }; Choices.prototype._handleSearch = function (value) { if (!this.input.isFocussed) { return; } // Check that we have a value to search and the input was an alphanumeric character if (value !== null && typeof value !== 'undefined' && value.length >= this.config.searchFloor) { var resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; if (resultCount !== null) { // Trigger search event this.passedElement.triggerEvent(EventType.search, { value: value, resultCount: resultCount, }); } } else if (this._store.choices.some(function (option) { return !option.active; })) { this._stopSearch(); } }; Choices.prototype._canAddItems = function () { var config = this.config; var maxItemCount = config.maxItemCount, maxItemText = config.maxItemText; if (!config.singleModeForMultiSelect && maxItemCount > 0 && maxItemCount <= this._store.items.length) { this.choiceList.element.replaceChildren(''); this._notice = undefined; this._displayNotice(typeof maxItemText === 'function' ? maxItemText(maxItemCount) : maxItemText, NoticeTypes.addChoice); return false; } if (this._notice && this._notice.type === NoticeTypes.addChoice) { this._clearNotice(); } return true; }; Choices.prototype._canCreateItem = function (value) { var config = this.config; var canAddItem = true; var notice = ''; if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) { canAddItem = false; notice = resolveNoticeFunction(config.customAddItemText, value, undefined); } if (canAddItem) { var foundChoice = this._store.choices.find(function (choice) { return config.valueComparer(choice.value, value); }); if (foundChoice) { if (this._isSelectElement) { // for exact matches, do not prompt to add it as a custom choice this._displayNotice('', NoticeTypes.addChoice); return false; } if (!config.duplicateItemsAllowed) { canAddItem = false; notice = resolveNoticeFunction(config.uniqueItemText, value, undefined); } } } if (canAddItem) { notice = resolveNoticeFunction(config.addItemText, value, undefined); } if (notice) { this._displayNotice(notice, NoticeTypes.addChoice); } return canAddItem; }; Choices.prototype._searchChoices = function (value) { var newValue = value.trim().replace(/\s{2,}/, ' '); // signal input didn't change search if (!newValue.length || newValue === this._currentValue) { return null; } var searcher = this._searcher; if (searcher.isEmptyIndex()) { searcher.index(this._store.searchableChoices); } // If new value matches the desired length and is not the same as the current value with a space var results = searcher.search(newValue); this._currentValue = newValue; this._highlightPosition = 0; this._isSearching = true; var notice = this._notice; var noticeType = notice && notice.type; if (noticeType !== NoticeTypes.addChoice) { if (!results.length) { this._displayNotice(resolveStringFunction(this.config.noResultsText), NoticeTypes.noResults); } else { this._clearNotice(); } } this._store.dispatch(filterChoices(results)); return results.length; }; Choices.prototype._stopSearch = function () { if (this._isSearching) { this._currentValue = ''; this._isSearching = false; this._clearNotice(); this._store.dispatch(activateChoices(true)); this.passedElement.triggerEvent(EventType.search, { value: '', resultCount: 0, }); } }; Choices.prototype._addEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; // capture events - can cancel event processing or propagation documentElement.addEventListener('touchend', this._onTouchEnd, true); outerElement.addEventListener('keydown', this._onKeyDown, true); outerElement.addEventListener('mousedown', this._onMouseDown, true); // passive events - doesn't call `preventDefault` or `stopPropagation` documentElement.addEventListener('click', this._onClick, { passive: true }); documentElement.addEventListener('touchmove', this._onTouchMove, { passive: true, }); this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { passive: true, }); if (this._isSelectOneElement) { outerElement.addEventListener('focus', this._onFocus, { passive: true, }); outerElement.addEventListener('blur', this._onBlur, { passive: true, }); } inputElement.addEventListener('keyup', this._onKeyUp, { passive: true, }); inputElement.addEventListener('input', this._onInput, { passive: true, }); inputElement.addEventListener('focus', this._onFocus, { passive: true, }); inputElement.addEventListener('blur', this._onBlur, { passive: true, }); if (inputElement.form) { inputElement.form.addEventListener('reset', this._onFormReset, { passive: true, }); } if (passedElement.hasAttribute('required')) { passedElement.addEventListener('change', this._onChange, { passive: true, }); passedElement.addEventListener('invalid', this._onInvalid, { passive: true, }); } this.input.addEventListeners(); }; Choices.prototype._removeEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; documentElement.removeEventListener('touchend', this._onTouchEnd, true); outerElement.removeEventListener('keydown', this._onKeyDown, true); outerElement.removeEventListener('mousedown', this._onMouseDown, true); documentElement.removeEventListener('click', this._onClick); documentElement.removeEventListener('touchmove', this._onTouchMove); this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); if (this._isSelectOneElement) { outerElement.removeEventListener('focus', this._onFocus); outerElement.removeEventListener('blur', this._onBlur); } inputElement.removeEventListener('keyup', this._onKeyUp); inputElement.removeEventListener('input', this._onInput); inputElement.removeEventListener('focus', this._onFocus); inputElement.removeEventListener('blur', this._onBlur); if (inputElement.form) { inputElement.form.removeEventListener('reset', this._onFormReset); } if (passedElement.hasAttribute('required')) { passedElement.removeEventListener('change', this._onChange); passedElement.removeEventListener('invalid', this._onInvalid); } this.input.removeEventListeners(); }; Choices.prototype._onKeyDown = function (event) { var keyCode = event.keyCode; var hasActiveDropdown = this.dropdown.isActive; /* See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs https://stackoverflow.com/a/70866532 - "Unidentified" for mobile http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7) Logic: when a key event is sent, `event.key` represents its printable value _or_ one of a large list of special values indicating meta keys/functionality. In addition, key events for compose functionality contain a value of `Dead` when mid-composition. I can't quite verify it, but non-English IMEs may also be able to generate key codes for code points in the surrogate-pair range, which could potentially be seen as having key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that alone. Here, key.length === 1 means we know for sure the input was printable and not a special `key` value. When the length is greater than 1, it could be either a printable surrogate pair or a special `key` value. We can tell the difference by checking if the _character code_ value (not code point!) is in the "surrogate pair" range or not. We don't use .codePointAt because an invalid code point would return 65535, which wouldn't pass the >= 0x10000 check we would otherwise use. > ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points > of each plane are noncharacters: U+FFFE and U+FFFF on the BMP... */ var wasPrintableChar = event.key.length === 1 || (event.key.length === 2 && event.key.charCodeAt(0) >= 0xd800) || event.key === 'Unidentified'; /* We do not show the dropdown if focusing out with esc or navigating through input fields. An activated search can still be opened with any other key. */ if (!this._isTextElement && !hasActiveDropdown && keyCode !== KeyCodeMap.ESC_KEY && keyCode !== KeyCodeMap.TAB_KEY && keyCode !== KeyCodeMap.SHIFT_KEY) { this.showDropdown(); if (!this.input.isFocussed && wasPrintableChar) { /* We update the input value with the pressed key as the input was not focussed at the time of key press therefore does not have the value of the key. */ this.input.value += event.key; // browsers interpret a space as pagedown if (event.key === ' ') { event.preventDefault(); } } } switch (keyCode) { case KeyCodeMap.A_KEY: return this._onSelectKey(event, this.itemList.element.hasChildNodes()); case KeyCodeMap.ENTER_KEY: return this._onEnterKey(event, hasActiveDropdown); case KeyCodeMap.ESC_KEY: return this._onEscapeKey(event, hasActiveDropdown); case KeyCodeMap.UP_KEY: case KeyCodeMap.PAGE_UP_KEY: case KeyCodeMap.DOWN_KEY: case KeyCodeMap.PAGE_DOWN_KEY: return this._onDirectionKey(event, hasActiveDropdown); case KeyCodeMap.DELETE_KEY: case KeyCodeMap.BACK_KEY: return this._onDeleteKey(event, this._store.items, this.input.isFocussed); } }; Choices.prototype._onKeyUp = function ( /* event: KeyboardEvent */) { this._canSearch = this.config.searchEnabled; }; Choices.prototype._onInput = function ( /* event: InputEvent */) { var value = this.input.value; if (!value) { if (this._isTextElement) { this.hideDropdown(true); } else { this._stopSearch(); } return; } if (!this._canAddItems()) { return; } if (this._canSearch) { // do the search even if the entered text can not be added this._handleSearch(value); } if (!this._canAddUserChoices) { return; } // determine if a notice needs to be displayed for why a search result can't be added this._canCreateItem(value); if (this._isSelectElement) { this._highlightPosition = 0; // reset to select the notice and/or exact match this._highlightChoice(); } }; Choices.prototype._onSelectKey = function (event, hasItems) { // If CTRL + A or CMD + A have been pressed and there are items to select if ((event.ctrlKey || event.metaKey) && hasItems) { this._canSearch = false; var shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; if (shouldHightlightAll) { this.highlightAll(); } } }; Choices.prototype._onEnterKey = function (event, hasActiveDropdown) { var _this = this; var value = this.input.value; var target = event.target; event.preventDefault(); if (target && target.hasAttribute('data-button')) { this._handleButtonAction(target); return; } if (!hasActiveDropdown) { if (this._isSelectElement || this._notice) { this.showDropdown(); } return; } var highlightedChoice = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (highlightedChoice && this._handleChoiceAction(highlightedChoice)) { return; } if (!target || !value) { this.hideDropdown(true); return; } if (!this._canAddItems()) { return; } var addedItem = false; this._store.withTxn(function () { addedItem = _this._findAndSelectChoiceByValue(value, true); if (!addedItem) { if (!_this._canAddUserChoices) { return; } if (!_this._canCreateItem(value)) { return; } _this._addChoice(mapInputToChoice(value, false, _this.config.allowHtmlUserInput), true, true); addedItem = true; } _this.clearInput(); _this.unhighlightAll(); }); if (!addedItem) { return; } this._triggerChange(value); if (this.config.closeDropdownOnSelect) { this.hideDropdown(true); } }; Choices.prototype._onEscapeKey = function (event, hasActiveDropdown) { if (hasActiveDropdown) { event.stopPropagation(); this.hideDropdown(true); this._stopSearch(); this.containerOuter.element.focus(); } }; Choices.prototype._onDirectionKey = function (event, hasActiveDropdown) { var keyCode = event.keyCode; // If up or down key is pressed, traverse through options if (hasActiveDropdown || this._isSelectOneElement) { this.showDropdown(); this._canSearch = false; var directionInt = keyCode === KeyCodeMap.DOWN_KEY || keyCode === KeyCodeMap.PAGE_DOWN_KEY ? 1 : -1; var skipKey = event.metaKey || keyCode === KeyCodeMap.PAGE_DOWN_KEY || keyCode === KeyCodeMap.PAGE_UP_KEY; var nextEl = void 0; if (skipKey) { if (directionInt > 0) { nextEl = this.dropdown.element.querySelector("".concat(selectableChoiceIdentifier, ":last-of-type")); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } else { var currentEl = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (currentEl) { nextEl = getAdjacentEl(currentEl, selectableChoiceIdentifier, directionInt); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } if (nextEl) { // We prevent default to stop the cursor moving // when pressing the arrow if (!isScrolledIntoView(nextEl, this.choiceList.element, directionInt)) { this.choiceList.scrollToChildElement(nextEl, directionInt); } this._highlightChoice(nextEl); } // Prevent default to maintain cursor position whilst // traversing dropdown options event.preventDefault(); } }; Choices.prototype._onDeleteKey = function (event, items, hasFocusedInput) { // If backspace or delete key is pressed and the input has no value if (!this._isSelectOneElement && !event.target.value && hasFocusedInput) { this._handleBackspace(items); event.preventDefault(); } }; Choices.prototype._onTouchMove = function () { if (this._wasTap) { this._wasTap = false; } }; Choices.prototype._onTouchEnd = function (event) { var target = (event || event.touches[0]).target; var touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target); if (touchWasWithinContainer) { var containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; if (containerWasExactTarget) { if (this._isTextElement) { this.input.focus(); } else if (this._isSelectMultipleElement) { this.showDropdown(); } } // Prevents focus event firing event.stopPropagation(); } this._wasTap = true; }; /** * Handles mousedown event in capture mode for containetOuter.element */ Choices.prototype._onMouseDown = function (event) { var target = event.target; if (!(target instanceof Element)) { return; } // If we have our mouse down on the scrollbar and are on IE11... if (IS_IE11 && this.choiceList.element.contains(target)) { // check if click was on a scrollbar area var firstChoice = this.choiceList.element.firstElementChild; this._isScrollingOnIe = this._direction === 'ltr' ? event.offsetX >= firstChoice.offsetWidth : event.offsetX < firstChoice.offsetLeft; } if (target === this.input.element) { return; } var item = target.closest('[data-button],[data-item],[data-choice]'); if (item instanceof HTMLElement) { if ('button' in item.dataset) { this._handleButtonAction(item); } else if ('item' in item.dataset) { this._handleItemAction(item, event.shiftKey); } else if ('choice' in item.dataset) { this._handleChoiceAction(item); } } event.preventDefault(); }; /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ Choices.prototype._onMouseOver = function (_a) { var target = _a.target; if (target instanceof HTMLElement && 'choice' in target.dataset) { this._highlightChoice(target); } }; Choices.prototype._onClick = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var clickWasWithinContainer = containerOuter.element.contains(target); if (clickWasWithinContainer) { if (!this.dropdown.isActive && !containerOuter.isDisabled) { if (this._isTextElement) { if (document.activeElement !== this.input.element) { this.input.focus(); } } else { this.showDropdown(); containerOuter.element.focus(); } } else if (this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target)) { this.hideDropdown(); } } else { containerOuter.removeFocusState(); this.hideDropdown(true); this.unhighlightAll(); } }; Choices.prototype._onFocus = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var focusWasWithinContainer = target && containerOuter.element.contains(target); if (!focusWasWithinContainer) { return; } var targetIsInput = target === this.input.element; if (this._isTextElement) { if (targetIsInput) { containerOuter.addFocusState(); } } else if (this._isSelectMultipleElement) { if (targetIsInput) { this.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown containerOuter.addFocusState(); } } else { containerOuter.addFocusState(); if (targetIsInput) { this.showDropdown(true); } } }; Choices.prototype._onBlur = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var blurWasWithinContainer = target && containerOuter.element.contains(target); if (blurWasWithinContainer && !this._isScrollingOnIe) { if (target === this.input.element) { containerOuter.removeFocusState(); this.hideDropdown(true); if (this._isTextElement || this._isSelectMultipleElement) { this.unhighlightAll(); } } else if (target === this.containerOuter.element) { // Remove the focus state when the past outerContainer was the target containerOuter.removeFocusState(); // Also close the dropdown if search is disabled if (!this.config.searchEnabled) { this.hideDropdown(true); } } } else { // On IE11, clicking the scollbar blurs our input and thus // closes the dropdown. To stop this, we refocus our input // if we know we are on IE *and* are scrolling. this._isScrollingOnIe = false; this.input.element.focus(); } }; Choices.prototype._onFormReset = function () { var _this = this; this._store.withTxn(function () { _this.clearInput(); _this.hideDropdown(); _this.refresh(false, false, true); if (_this._initialItems.length) { _this.setChoiceByValue(_this._initialItems); } }); }; Choices.prototype._onChange = function (event) { if (!event.target.checkValidity()) { return; } this.containerOuter.removeInvalidState(); }; Choices.prototype._onInvalid = function () { this.containerOuter.addInvalidState(); }; /** * Removes any highlighted choice options */ Choices.prototype._removeHighlightedChoices = function () { var highlightedState = this.config.classNames.highlightedState; var highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(getClassNamesSelector(highlightedState))); // Remove any highlighted choices highlightedChoices.forEach(function (choice) { removeClassesFromElement(choice, highlightedState); choice.setAttribute('aria-selected', 'false'); }); }; Choices.prototype._highlightChoice = function (el) { if (el === void 0) { el = null; } var choices = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)); if (!choices.length) { return; } var passedEl = el; var highlightedState = this.config.classNames.highlightedState; this._removeHighlightedChoices(); if (passedEl) { this._highlightPosition = choices.indexOf(passedEl); } else { // Highlight choice based on last known highlight location if (choices.length > this._highlightPosition) { // If we have an option to highlight passedEl = choices[this._highlightPosition]; } else { // Otherwise highlight the option before passedEl = choices[choices.length - 1]; } if (!passedEl) { passedEl = choices[0]; } } addClassesToElement(passedEl, highlightedState); passedEl.setAttribute('aria-selected', 'true'); this.passedElement.triggerEvent(EventType.highlightChoice, { el: passedEl, }); if (this.dropdown.isActive) { // IE11 ignores aria-label and blocks virtual keyboard // if aria-activedescendant is set without a dropdown this.input.setActiveDescendant(passedEl.id); this.containerOuter.setActiveDescendant(passedEl.id); } }; Choices.prototype._addItem = function (item, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (!item.id) { throw new TypeError('item.id must be set before _addItem is called for a choice/item'); } if (this.config.singleModeForMultiSelect || this._isSelectOneElement) { this.removeActiveItems(item.id); } this._store.dispatch(addItem(item)); if (withEvents) { var eventChoice = getChoiceForOutput(item); this.passedElement.triggerEvent(EventType.addItem, eventChoice); if (userTriggered) { this.passedElement.triggerEvent(EventType.choice, eventChoice); } } }; Choices.prototype._removeItem = function (item) { if (!item.id) { return; } this._store.dispatch(removeItem$1(item)); var notice = this._notice; if (notice && notice.type === NoticeTypes.noChoices) { this._clearNotice(); } this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item)); }; Choices.prototype._addChoice = function (choice, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (choice.id) { throw new TypeError('Can not re-add a choice which has already been added'); } var config = this.config; if (!config.duplicateItemsAllowed && this._store.choices.find(function (c) { return config.valueComparer(c.value, choice.value); })) { return; } // Generate unique id, in-place update is required so chaining _addItem works as expected this._lastAddedChoiceId++; choice.id = this._lastAddedChoiceId; choice.elementId = "".concat(this._baseId, "-").concat(this._idNames.itemChoice, "-").concat(choice.id); var prependValue = config.prependValue, appendValue = config.appendValue; if (prependValue) { choice.value = prependValue + choice.value; } if (appendValue) { choice.value += appendValue.toString(); } if ((prependValue || appendValue) && choice.element) { choice.element.value = choice.value; } this._clearNotice(); this._store.dispatch(addChoice(choice)); if (choice.selected) { this._addItem(choice, withEvents, userTriggered); } }; Choices.prototype._addGroup = function (group, withEvents) { var _this = this; if (withEvents === void 0) { withEvents = true; } if (group.id) { throw new TypeError('Can not re-add a group which has already been added'); } this._store.dispatch(addGroup(group)); if (!group.choices) { return; } // add unique id for the group(s), and do not store the full list of choices in this group this._lastAddedGroupId++; group.id = this._lastAddedGroupId; group.choices.forEach(function (item) { item.group = group; if (group.disabled) { item.disabled = true; } _this._addChoice(item, withEvents); }); }; Choices.prototype._createTemplates = function () { var _this = this; var callbackOnCreateTemplates = this.config.callbackOnCreateTemplates; var userTemplates = {}; if (typeof callbackOnCreateTemplates === 'function') { userTemplates = callbackOnCreateTemplates.call(this, strToEl, escapeForTemplate, getClassNames); } var templating = {}; Object.keys(this._templates).forEach(function (name) { if (name in userTemplates) { templating[name] = userTemplates[name].bind(_this); } else { templating[name] = _this._templates[name].bind(_this); } }); this._templates = templating; }; Choices.prototype._createElements = function () { var templating = this._templates; var _a = this, config = _a.config, isSelectOneElement = _a._isSelectOneElement; var position = config.position, classNames = config.classNames; var elementType = this._elementType; this.containerOuter = new Container({ element: templating.containerOuter(config, this._direction, this._isSelectElement, isSelectOneElement, config.searchEnabled, elementType, config.labelId), classNames: classNames, type: elementType, position: position, }); this.containerInner = new Container({ element: templating.containerInner(config), classNames: classNames, type: elementType, position: position, }); this.input = new Input({ element: templating.input(config, this._placeholderValue), classNames: classNames, type: elementType, preventPaste: !config.paste, }); this.choiceList = new List({ element: templating.choiceList(config, isSelectOneElement), }); this.itemList = new List({ element: templating.itemList(config, isSelectOneElement), }); this.dropdown = new Dropdown({ element: templating.dropdown(config), classNames: classNames, type: elementType, }); }; Choices.prototype._createStructure = function () { var _a = this, containerInner = _a.containerInner, containerOuter = _a.containerOuter, passedElement = _a.passedElement; var dropdownElement = this.dropdown.element; // Hide original element passedElement.conceal(); // Wrap input in container preserving DOM ordering containerInner.wrap(passedElement.element); // Wrapper inner container with outer container containerOuter.wrap(containerInner.element); containerOuter.element.appendChild(containerInner.element); containerOuter.element.appendChild(dropdownElement); containerInner.element.appendChild(this.itemList.element); dropdownElement.appendChild(this.choiceList.element); if (this._isSelectOneElement) { this.input.placeholder = this.config.searchPlaceholderValue || ''; if (this.config.searchEnabled) { dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild); } } else { if (!this._isSelectMultipleElement || this.config.searchEnabled) { containerInner.element.appendChild(this.input.element); } if (this._placeholderValue) { this.input.placeholder = this._placeholderValue; } this.input.setWidth(); } this._highlightPosition = 0; this._isSearching = false; }; Choices.prototype._initStore = function () { var _this = this; this._store.subscribe(this._render).withTxn(function () { _this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false); }); if (!this._store.choices.length || (this._isSelectOneElement && this._hasNonChoicePlaceholder)) { this._render(); } }; Choices.prototype._addPredefinedChoices = function (choices, selectFirstOption, withEvents) { var _this = this; if (selectFirstOption === void 0) { selectFirstOption = false; } if (withEvents === void 0) { withEvents = true; } if (selectFirstOption) { /** * If there is a selected choice already or the choice is not the first in * the array, add each choice normally. * * Otherwise we pre-select the first enabled choice in the array ("select-one" only) */ var noSelectedChoices = choices.findIndex(function (choice) { return choice.selected; }) === -1; if (noSelectedChoices) { choices.some(function (choice) { if (choice.disabled || 'choices' in choice) { return false; } choice.selected = true; return true; }); } } choices.forEach(function (item) { if ('choices' in item) { if (_this._isSelectElement) { _this._addGroup(item, withEvents); } } else { _this._addChoice(item, withEvents); } }); }; Choices.prototype._findAndSelectChoiceByValue = function (value, userTriggered) { var _this = this; if (userTriggered === void 0) { userTriggered = false; } // Check 'value' property exists and the choice isn't already selected var foundChoice = this._store.choices.find(function (choice) { return _this.config.valueComparer(choice.value, value); }); if (foundChoice && !foundChoice.disabled && !foundChoice.selected) { this._addItem(foundChoice, true, userTriggered); return true; } return false; }; Choices.prototype._generatePlaceholderValue = function () { var config = this.config; if (!config.placeholder) { return null; } if (this._hasNonChoicePlaceholder) { return config.placeholderValue; } if (this._isSelectElement) { var placeholderOption = this.passedElement.placeholderOption; return placeholderOption ? placeholderOption.text : null; } return null; }; Choices.prototype._warnChoicesInitFailed = function (caller) { if (this.config.silent) { return; } if (!this.initialised) { throw new TypeError("".concat(caller, " called on a non-initialised instance of Choices")); } else if (!this.initialisedOK) { throw new TypeError("".concat(caller, " called for an element which has multiple instances of Choices initialised on it")); } }; Choices.version = '11.2.1'; return Choices; }()); return Choices; })); ================================================ FILE: public/assets/scripts/choices.search-prefix.mjs ================================================ /*! choices.js v11.2.1 | © 2026 Josh Johnson | https://github.com/Choices-js/Choices#readme */ /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var ActionType = { ADD_CHOICE: 'ADD_CHOICE', REMOVE_CHOICE: 'REMOVE_CHOICE', FILTER_CHOICES: 'FILTER_CHOICES', ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', CLEAR_CHOICES: 'CLEAR_CHOICES', ADD_GROUP: 'ADD_GROUP', ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', }; var EventType = { showDropdown: 'showDropdown', hideDropdown: 'hideDropdown', change: 'change', choice: 'choice', search: 'search', addItem: 'addItem', removeItem: 'removeItem', highlightItem: 'highlightItem', highlightChoice: 'highlightChoice', unhighlightItem: 'unhighlightItem', }; var KeyCodeMap = { TAB_KEY: 9, SHIFT_KEY: 16, BACK_KEY: 46, DELETE_KEY: 8, ENTER_KEY: 13, A_KEY: 65, ESC_KEY: 27, UP_KEY: 38, DOWN_KEY: 40, PAGE_UP_KEY: 33, PAGE_DOWN_KEY: 34, }; var ObjectsInConfig = ['fuseOptions', 'classNames']; var PassedElementTypes = { Text: 'text', SelectOne: 'select-one', SelectMultiple: 'select-multiple', }; var addChoice = function (choice) { return ({ type: ActionType.ADD_CHOICE, choice: choice, }); }; var removeChoice = function (choice) { return ({ type: ActionType.REMOVE_CHOICE, choice: choice, }); }; var filterChoices = function (results) { return ({ type: ActionType.FILTER_CHOICES, results: results, }); }; var activateChoices = function (active) { return ({ type: ActionType.ACTIVATE_CHOICES, active: active, }); }; var addGroup = function (group) { return ({ type: ActionType.ADD_GROUP, group: group, }); }; var addItem = function (item) { return ({ type: ActionType.ADD_ITEM, item: item, }); }; var removeItem$1 = function (item) { return ({ type: ActionType.REMOVE_ITEM, item: item, }); }; var highlightItem = function (item, highlighted) { return ({ type: ActionType.HIGHLIGHT_ITEM, item: item, highlighted: highlighted, }); }; var getRandomNumber = function (min, max) { return Math.floor(Math.random() * (max - min) + min); }; var generateChars = function (length) { return Array.from({ length: length }, function () { return getRandomNumber(0, 36).toString(36); }).join(''); }; var generateId = function (element, prefix) { var id = element.id || (element.name && "".concat(element.name, "-").concat(generateChars(2))) || generateChars(4); id = id.replace(/(:|\.|\[|\]|,)/g, ''); id = "".concat(prefix, "-").concat(id); return id; }; var getAdjacentEl = function (startEl, selector, direction) { if (direction === void 0) { direction = 1; } var prop = "".concat(direction > 0 ? 'next' : 'previous', "ElementSibling"); var sibling = startEl[prop]; while (sibling) { if (sibling.matches(selector)) { return sibling; } sibling = sibling[prop]; } return null; }; var isScrolledIntoView = function (element, parent, direction) { if (direction === void 0) { direction = 1; } var isVisible; if (direction > 0) { // In view from bottom isVisible = parent.scrollTop + parent.offsetHeight >= element.offsetTop + element.offsetHeight; } else { // In view from top isVisible = element.offsetTop >= parent.scrollTop; } return isVisible; }; var sanitise = function (value) { if (typeof value !== 'string') { if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { if ('raw' in value) { return sanitise(value.raw); } if ('trusted' in value) { return value.trusted; } } return value; } return value .replace(/&/g, '&') .replace(/>/g, '>') .replace(/= 0 && !window.matchMedia("(min-height: ".concat(dropdownPos + 1, "px)")).matches; } else if (this.position === 'top') { shouldFlip = true; } return shouldFlip; }; Container.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Container.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Container.prototype.open = function (dropdownPos, dropdownHeight) { addClassesToElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; if (this.shouldFlip(dropdownPos, dropdownHeight)) { addClassesToElement(this.element, this.classNames.flippedState); this.isFlipped = true; } }; Container.prototype.close = function () { removeClassesFromElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'false'); this.removeActiveDescendant(); this.isOpen = false; // A dropdown flips if it does not have space within the page if (this.isFlipped) { removeClassesFromElement(this.element, this.classNames.flippedState); this.isFlipped = false; } }; Container.prototype.addFocusState = function () { addClassesToElement(this.element, this.classNames.focusState); }; Container.prototype.removeFocusState = function () { removeClassesFromElement(this.element, this.classNames.focusState); }; Container.prototype.addInvalidState = function () { addClassesToElement(this.element, this.classNames.invalidState); }; Container.prototype.removeInvalidState = function () { removeClassesFromElement(this.element, this.classNames.invalidState); }; Container.prototype.enable = function () { removeClassesFromElement(this.element, this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; }; Container.prototype.disable = function () { addClassesToElement(this.element, this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; }; Container.prototype.wrap = function (element) { var el = this.element; var parentNode = element.parentNode; if (parentNode) { if (element.nextSibling) { parentNode.insertBefore(el, element.nextSibling); } else { parentNode.appendChild(el); } } el.appendChild(element); }; Container.prototype.unwrap = function (element) { var el = this.element; var parentNode = el.parentNode; if (parentNode) { // Move passed element outside this element parentNode.insertBefore(element, el); // Remove this element parentNode.removeChild(el); } }; Container.prototype.addLoadingState = function () { addClassesToElement(this.element, this.classNames.loadingState); this.element.setAttribute('aria-busy', 'true'); this.isLoading = true; }; Container.prototype.removeLoadingState = function () { removeClassesFromElement(this.element, this.classNames.loadingState); this.element.removeAttribute('aria-busy'); this.isLoading = false; }; return Container; }()); var Input = /** @class */ (function () { function Input(_a) { var element = _a.element, type = _a.type, classNames = _a.classNames, preventPaste = _a.preventPaste; this.element = element; this.type = type; this.classNames = classNames; this.preventPaste = preventPaste; this.isFocussed = this.element.isEqualNode(document.activeElement); this.isDisabled = element.disabled; this._onPaste = this._onPaste.bind(this); this._onInput = this._onInput.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } Object.defineProperty(Input.prototype, "placeholder", { set: function (placeholder) { this.element.placeholder = placeholder; }, enumerable: false, configurable: true }); Object.defineProperty(Input.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.value = value; }, enumerable: false, configurable: true }); Input.prototype.addEventListeners = function () { var el = this.element; el.addEventListener('paste', this._onPaste); el.addEventListener('input', this._onInput, { passive: true, }); el.addEventListener('focus', this._onFocus, { passive: true, }); el.addEventListener('blur', this._onBlur, { passive: true, }); }; Input.prototype.removeEventListeners = function () { var el = this.element; el.removeEventListener('input', this._onInput); el.removeEventListener('paste', this._onPaste); el.removeEventListener('focus', this._onFocus); el.removeEventListener('blur', this._onBlur); }; Input.prototype.enable = function () { var el = this.element; el.removeAttribute('disabled'); this.isDisabled = false; }; Input.prototype.disable = function () { var el = this.element; el.setAttribute('disabled', ''); this.isDisabled = true; }; Input.prototype.focus = function () { if (!this.isFocussed) { this.element.focus(); } }; Input.prototype.blur = function () { if (this.isFocussed) { this.element.blur(); } }; Input.prototype.clear = function (setWidth) { if (setWidth === void 0) { setWidth = true; } this.element.value = ''; if (setWidth) { this.setWidth(); } return this; }; /** * Set the correct input width based on placeholder * value or input value */ Input.prototype.setWidth = function () { // Resize input to contents or placeholder var element = this.element; element.style.minWidth = "".concat(element.placeholder.length + 1, "ch"); element.style.width = "".concat(element.value.length + 1, "ch"); }; Input.prototype.setActiveDescendant = function (activeDescendantID) { this.element.setAttribute('aria-activedescendant', activeDescendantID); }; Input.prototype.removeActiveDescendant = function () { this.element.removeAttribute('aria-activedescendant'); }; Input.prototype._onInput = function () { if (this.type !== PassedElementTypes.SelectOne) { this.setWidth(); } }; Input.prototype._onPaste = function (event) { if (this.preventPaste) { event.preventDefault(); } }; Input.prototype._onFocus = function () { this.isFocussed = true; }; Input.prototype._onBlur = function () { this.isFocussed = false; }; return Input; }()); var SCROLLING_SPEED = 4; var List = /** @class */ (function () { function List(_a) { var element = _a.element; this.element = element; this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; } List.prototype.prepend = function (node) { var child = this.element.firstElementChild; if (child) { this.element.insertBefore(node, child); } else { this.element.append(node); } }; List.prototype.scrollToTop = function () { this.element.scrollTop = 0; }; List.prototype.scrollToChildElement = function (element, direction) { var _this = this; if (!element) { return; } var listHeight = this.element.offsetHeight; // Scroll position of dropdown var listScrollPosition = this.element.scrollTop + listHeight; var elementHeight = element.offsetHeight; // Distance from bottom of element to top of parent var elementPos = element.offsetTop + elementHeight; // Difference between the element and scroll position var destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop; requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); }; List.prototype._scrollDown = function (scrollPos, strength, destination) { var easing = (destination - scrollPos) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos + distance; }; List.prototype._scrollUp = function (scrollPos, strength, destination) { var easing = (scrollPos - destination) / strength; var distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos - distance; }; List.prototype._animateScroll = function (destination, direction) { var _this = this; var strength = SCROLLING_SPEED; var choiceListScrollTop = this.element.scrollTop; var continueAnimation = false; if (direction > 0) { this._scrollDown(choiceListScrollTop, strength, destination); if (choiceListScrollTop < destination) { continueAnimation = true; } } else { this._scrollUp(choiceListScrollTop, strength, destination); if (choiceListScrollTop > destination) { continueAnimation = true; } } if (continueAnimation) { requestAnimationFrame(function () { _this._animateScroll(destination, direction); }); } }; return List; }()); var WrappedElement = /** @class */ (function () { function WrappedElement(_a) { var element = _a.element, classNames = _a.classNames; this.element = element; this.classNames = classNames; this.isDisabled = false; } Object.defineProperty(WrappedElement.prototype, "isActive", { get: function () { return this.element.dataset.choice === 'active'; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "dir", { get: function () { return this.element.dir; }, enumerable: false, configurable: true }); Object.defineProperty(WrappedElement.prototype, "value", { get: function () { return this.element.value; }, set: function (value) { this.element.setAttribute('value', value); this.element.value = value; }, enumerable: false, configurable: true }); WrappedElement.prototype.conceal = function () { var el = this.element; // Hide passed input addClassesToElement(el, this.classNames.input); el.hidden = true; // Remove element from tab index el.tabIndex = -1; // Backup original styles if any var origStyle = el.getAttribute('style'); if (origStyle) { el.setAttribute('data-choice-orig-style', origStyle); } el.setAttribute('data-choice', 'active'); }; WrappedElement.prototype.reveal = function () { var el = this.element; // Reinstate passed element removeClassesFromElement(el, this.classNames.input); el.hidden = false; el.removeAttribute('tabindex'); // Recover original styles if any var origStyle = el.getAttribute('data-choice-orig-style'); if (origStyle) { el.removeAttribute('data-choice-orig-style'); el.setAttribute('style', origStyle); } else { el.removeAttribute('style'); } el.removeAttribute('data-choice'); }; WrappedElement.prototype.enable = function () { this.element.removeAttribute('disabled'); this.element.disabled = false; this.isDisabled = false; }; WrappedElement.prototype.disable = function () { this.element.setAttribute('disabled', ''); this.element.disabled = true; this.isDisabled = true; }; WrappedElement.prototype.triggerEvent = function (eventType, data) { dispatchEvent(this.element, eventType, data || {}); }; return WrappedElement; }()); var WrappedInput = /** @class */ (function (_super) { __extends(WrappedInput, _super); function WrappedInput() { return _super !== null && _super.apply(this, arguments) || this; } return WrappedInput; }(WrappedElement)); var coerceBool = function (arg, defaultValue) { if (defaultValue === void 0) { defaultValue = true; } return typeof arg === 'undefined' ? defaultValue : !!arg; }; var stringToHtmlClass = function (input) { if (typeof input === 'string') { // eslint-disable-next-line no-param-reassign input = input.split(' ').filter(function (s) { return s.length; }); } if (Array.isArray(input) && input.length) { return input; } return undefined; }; var mapInputToChoice = function (value, allowGroup, allowRawString) { if (allowRawString === void 0) { allowRawString = true; } if (typeof value === 'string') { var sanitisedValue = sanitise(value); var userValue = allowRawString || sanitisedValue === value ? value : { escaped: sanitisedValue, raw: value }; var result_1 = mapInputToChoice({ value: value, label: userValue, selected: true, }, false); return result_1; } var groupOrChoice = value; if ('choices' in groupOrChoice) { if (!allowGroup) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup throw new TypeError("optGroup is not allowed"); } var group = groupOrChoice; var choices = group.choices.map(function (e) { return mapInputToChoice(e, false); }); var result_2 = { id: 0, // actual ID will be assigned during _addGroup label: unwrapStringForRaw(group.label) || group.value, active: !!choices.length, disabled: !!group.disabled, choices: choices, }; return result_2; } var choice = groupOrChoice; var result = { id: 0, // actual ID will be assigned during _addChoice group: null, // actual group will be assigned during _addGroup but before _addChoice score: 0, // used in search rank: 0, // used in search, stable sort order value: choice.value, label: choice.label || choice.value, active: coerceBool(choice.active), selected: coerceBool(choice.selected, false), disabled: coerceBool(choice.disabled, false), placeholder: coerceBool(choice.placeholder, false), highlighted: false, labelClass: stringToHtmlClass(choice.labelClass), labelDescription: choice.labelDescription, customProperties: choice.customProperties, }; return result; }; var isHtmlInputElement = function (e) { return e.tagName === 'INPUT'; }; var isHtmlSelectElement = function (e) { return e.tagName === 'SELECT'; }; var isHtmlOption = function (e) { return e.tagName === 'OPTION'; }; var isHtmlOptgroup = function (e) { return e.tagName === 'OPTGROUP'; }; var WrappedSelect = /** @class */ (function (_super) { __extends(WrappedSelect, _super); function WrappedSelect(_a) { var element = _a.element, classNames = _a.classNames, template = _a.template, extractPlaceholder = _a.extractPlaceholder; var _this = _super.call(this, { element: element, classNames: classNames }) || this; _this.template = template; _this.extractPlaceholder = extractPlaceholder; return _this; } Object.defineProperty(WrappedSelect.prototype, "placeholderOption", { get: function () { return (this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]')); }, enumerable: false, configurable: true }); WrappedSelect.prototype.addOptions = function (choices) { var _this = this; var fragment = document.createDocumentFragment(); choices.forEach(function (obj) { var choice = obj; if (choice.element) { return; } var option = _this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); }; WrappedSelect.prototype.optionsAsChoices = function () { var _this = this; var choices = []; this.element.querySelectorAll(':scope > option, :scope > optgroup').forEach(function (e) { if (isHtmlOption(e)) { choices.push(_this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(_this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; }; // eslint-disable-next-line class-methods-use-this WrappedSelect.prototype._optionToChoice = function (option) { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? { trusted: option.dataset.labelDescription } : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; }; WrappedSelect.prototype._optgroupToChoice = function (optgroup) { var _this = this; var options = optgroup.querySelectorAll('option'); var choices = Array.from(options).map(function (option) { return _this._optionToChoice(option); }); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices: choices, }; }; return WrappedSelect; }(WrappedElement)); var DEFAULT_CLASSNAMES = { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], }; var DEFAULT_CONFIG = { items: [], choices: [], silent: false, renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: function (value) { return !!value && value !== ''; }, removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: function (value) { return "Press Enter to add \"".concat(value, "\""); }, removeItemIconText: function () { return "Remove item"; }, removeItemLabelText: function (value, _valueRaw, i) { return "Remove item: ".concat(i ? sanitise(i.label) : value); }, maxItemText: function (maxItemCount) { return "Only ".concat(maxItemCount, " values can be added"); }, valueComparer: function (value1, value2) { return value1 === value2; }, fuseOptions: { includeScore: true, }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, appendGroupInSearch: false, }; var removeItem = function (item) { var itemEl = item.itemEl; if (itemEl) { itemEl.remove(); item.itemEl = undefined; } }; function items(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_ITEM: { action.item.selected = true; var el = action.item.element; if (el) { el.selected = true; el.setAttribute('selected', ''); } state.push(action.item); break; } case ActionType.REMOVE_ITEM: { action.item.selected = false; var el = action.item.element; if (el) { el.selected = false; el.removeAttribute('selected'); // For a select-one, if all options are deselected, the first item is selected. To set a black value, select.value needs to be set var select = el.parentElement; if (select && isHtmlSelectElement(select) && select.type === PassedElementTypes.SelectOne) { select.value = ''; } } // this is mixing concerns, but this is *so much faster* removeItem(action.item); state = state.filter(function (choice) { return choice.id !== action.item.id; }); break; } case ActionType.REMOVE_CHOICE: { removeItem(action.choice); state = state.filter(function (item) { return item.id !== action.choice.id; }); break; } case ActionType.HIGHLIGHT_ITEM: { var highlighted = action.highlighted; var item = state.find(function (obj) { return obj.id === action.item.id; }); if (item && item.highlighted !== highlighted) { item.highlighted = highlighted; if (context) { updateClassList(item, highlighted ? context.classNames.highlightedState : context.classNames.selectedState, highlighted ? context.classNames.selectedState : context.classNames.highlightedState); } } break; } default: { update = false; break; } } return { state: state, update: update }; } function groups(s, action) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_GROUP: { state.push(action.group); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } /* eslint-disable */ function choices(s, action, context) { var state = s; var update = true; switch (action.type) { case ActionType.ADD_CHOICE: { state.push(action.choice); break; } case ActionType.REMOVE_CHOICE: { action.choice.choiceEl = undefined; if (action.choice.group) { action.choice.group.choices = action.choice.group.choices.filter(function (obj) { return obj.id !== action.choice.id; }); } state = state.filter(function (obj) { return obj.id !== action.choice.id; }); break; } case ActionType.ADD_ITEM: case ActionType.REMOVE_ITEM: { action.item.choiceEl = undefined; break; } case ActionType.FILTER_CHOICES: { // avoid O(n^2) algorithm complexity when searching/filtering choices var scoreLookup_1 = []; action.results.forEach(function (result) { scoreLookup_1[result.item.id] = result; }); state.forEach(function (choice) { var result = scoreLookup_1[choice.id]; if (result !== undefined) { choice.score = result.score; choice.rank = result.rank; choice.active = true; } else { choice.score = 0; choice.rank = 0; choice.active = false; } if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.ACTIVATE_CHOICES: { state.forEach(function (choice) { choice.active = action.active; if (context && context.appendGroupInSearch) { choice.choiceEl = undefined; } }); break; } case ActionType.CLEAR_CHOICES: { state = []; break; } default: { update = false; break; } } return { state: state, update: update }; } var reducers = { groups: groups, items: items, choices: choices, }; var Store = /** @class */ (function () { function Store(context) { this._state = this.defaultState; this._listeners = []; this._txn = 0; this._context = context; } Object.defineProperty(Store.prototype, "defaultState", { // eslint-disable-next-line class-methods-use-this get: function () { return { groups: [], items: [], choices: [], }; }, enumerable: false, configurable: true }); // eslint-disable-next-line class-methods-use-this Store.prototype.changeSet = function (init) { return { groups: init, items: init, choices: init, }; }; Store.prototype.reset = function () { this._state = this.defaultState; var changes = this.changeSet(true); if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } }; Store.prototype.subscribe = function (onChange) { this._listeners.push(onChange); return this; }; Store.prototype.dispatch = function (action) { var _this = this; var state = this._state; var hasChanges = false; var changes = this._changeSet || this.changeSet(false); Object.keys(reducers).forEach(function (key) { var stateUpdate = reducers[key](state[key], action, _this._context); if (stateUpdate.update) { hasChanges = true; changes[key] = true; state[key] = stateUpdate.state; } }); if (hasChanges) { if (this._txn) { this._changeSet = changes; } else { this._listeners.forEach(function (l) { return l(changes); }); } } }; Store.prototype.withTxn = function (func) { this._txn++; try { func(); } finally { this._txn = Math.max(0, this._txn - 1); if (!this._txn) { var changeSet_1 = this._changeSet; if (changeSet_1) { this._changeSet = undefined; this._listeners.forEach(function (l) { return l(changeSet_1); }); } } } }; Object.defineProperty(Store.prototype, "state", { /** * Get store object */ get: function () { return this._state; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "items", { /** * Get items from store */ get: function () { return this.state.items; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "highlightedActiveItems", { /** * Get highlighted items from store */ get: function () { return this.items.filter(function (item) { return item.active && item.highlighted; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "choices", { /** * Get choices from store */ get: function () { return this.state.choices; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeChoices", { /** * Get active choices from store */ get: function () { return this.choices.filter(function (choice) { return choice.active; }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "searchableChoices", { /** * Get choices that can be searched (excluding placeholders or disabled choices) */ get: function () { var context = this._context; return this.choices.filter(function (choice) { return !choice.placeholder && (context.searchDisabledChoices || !choice.disabled); }); }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "groups", { /** * Get groups from store */ get: function () { return this.state.groups; }, enumerable: false, configurable: true }); Object.defineProperty(Store.prototype, "activeGroups", { /** * Get active groups from store */ get: function () { var _this = this; return this.state.groups.filter(function (group) { var isActive = group.active && !group.disabled; var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; }); return isActive && hasActiveOptions; }, []); }, enumerable: false, configurable: true }); Store.prototype.inTxn = function () { return this._txn > 0; }; /** * Get single choice by it's ID */ Store.prototype.getChoiceById = function (id) { return this.activeChoices.find(function (choice) { return choice.id === id; }); }; /** * Get group by group id */ Store.prototype.getGroupById = function (id) { return this.groups.find(function (group) { return group.id === id; }); }; return Store; }()); var NoticeTypes = { noChoices: 'no-choices', noResults: 'no-results', addChoice: 'add-choice', generic: '', }; var SearchByPrefixFilter = /** @class */ (function () { function SearchByPrefixFilter(config) { this._haystack = []; this._fields = config.searchFields; } SearchByPrefixFilter.prototype.index = function (data) { this._haystack = data; }; SearchByPrefixFilter.prototype.reset = function () { this._haystack = []; }; SearchByPrefixFilter.prototype.isEmptyIndex = function () { return !this._haystack.length; }; SearchByPrefixFilter.prototype.search = function (_needle) { var fields = this._fields; if (!fields || !fields.length || !_needle) { return []; } var needle = _needle.toLowerCase(); return this._haystack .filter(function (obj) { return fields.some(function (field) { return field in obj && obj[field].toLowerCase().startsWith(needle); }); }) .map(function (value, index) { return { item: value, score: index, rank: index + 1, }; }); }; return SearchByPrefixFilter; }()); function getSearcher(config) { return new SearchByPrefixFilter(config); } /** * Helpers to create HTML elements used by Choices * Can be overridden by providing `callbackOnCreateTemplates` option. * `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates` */ var isEmptyObject = function (obj) { // eslint-disable-next-line no-restricted-syntax for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { return false; } } return true; }; var assignCustomProperties = function (el, choice, withCustomProperties) { var dataset = el.dataset; var customProperties = choice.customProperties, labelClass = choice.labelClass, labelDescription = choice.labelDescription; if (labelClass) { dataset.labelClass = getClassNames(labelClass).join(' '); } if (labelDescription) { dataset.labelDescription = unwrapStringForRaw(labelDescription); } if (withCustomProperties && customProperties) { if (typeof customProperties === 'string') { dataset.customProperties = customProperties; } else if (typeof customProperties === 'object' && !isEmptyObject(customProperties)) { dataset.customProperties = JSON.stringify(customProperties); } } }; var addAriaLabel = function (docRoot, id, element) { var label = id && docRoot.querySelector("label[for='".concat(id, "']")); var text = label && label.innerText; if (text) { element.setAttribute('aria-label', text); } }; var templates = { containerOuter: function (_a, dir, isSelectElement, isSelectOneElement, searchEnabled, passedElementType, labelId) { var containerOuter = _a.classNames.containerOuter; var div = document.createElement('div'); addClassesToElement(div, containerOuter); div.dataset.type = passedElementType; if (dir) { div.dir = dir; } if (isSelectOneElement) { div.tabIndex = 0; } if (isSelectElement) { div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox'); if (searchEnabled) { div.setAttribute('aria-autocomplete', 'list'); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, div); } div.setAttribute('aria-haspopup', 'true'); div.setAttribute('aria-expanded', 'false'); } if (labelId) { div.setAttribute('aria-labelledby', labelId); } return div; }, containerInner: function (_a) { var containerInner = _a.classNames.containerInner; var div = document.createElement('div'); addClassesToElement(div, containerInner); return div; }, itemList: function (_a, isSelectOneElement) { var searchEnabled = _a.searchEnabled, _b = _a.classNames, list = _b.list, listSingle = _b.listSingle, listItems = _b.listItems; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, isSelectOneElement ? listSingle : listItems); if (this._isSelectElement && searchEnabled) { div.setAttribute('role', 'listbox'); } return div; }, placeholder: function (_a, value) { var allowHTML = _a.allowHTML, placeholder = _a.classNames.placeholder; var div = document.createElement('div'); addClassesToElement(div, placeholder); setElementHtml(div, allowHTML, value); return div; }, item: function (_a, choice, removeItemButton) { var allowHTML = _a.allowHTML, removeItemButtonAlignLeft = _a.removeItemButtonAlignLeft, removeItemIconText = _a.removeItemIconText, removeItemLabelText = _a.removeItemLabelText, _b = _a.classNames, item = _b.item, button = _b.button, highlightedState = _b.highlightedState, itemSelectable = _b.itemSelectable, placeholder = _b.placeholder; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); addClassesToElement(div, item); if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, choice.label); addClassesToElement(spanLabel, choice.labelClass); div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, choice.label); } div.dataset.item = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; assignCustomProperties(div, choice, true); if (choice.disabled || this.containerOuter.isDisabled) { div.setAttribute('aria-disabled', 'true'); } if (this._isSelectElement) { div.setAttribute('aria-selected', 'true'); div.setAttribute('role', 'option'); } if (choice.placeholder) { addClassesToElement(div, placeholder); div.dataset.placeholder = ''; } addClassesToElement(div, choice.highlighted ? highlightedState : itemSelectable); if (removeItemButton) { if (choice.disabled) { removeClassesFromElement(div, itemSelectable); } div.dataset.deletable = ''; var removeButton = document.createElement('button'); removeButton.type = 'button'; addClassesToElement(removeButton, button); var eventChoice = getChoiceForOutput(choice); setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value, eventChoice)); var REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value, eventChoice); if (REMOVE_ITEM_LABEL) { removeButton.setAttribute('aria-label', REMOVE_ITEM_LABEL); } removeButton.dataset.button = ''; if (removeItemButtonAlignLeft) { div.insertAdjacentElement('afterbegin', removeButton); } else { div.appendChild(removeButton); } } return div; }, choiceList: function (_a, isSelectOneElement) { var list = _a.classNames.list; var div = document.createElement('div'); addClassesToElement(div, list); if (!isSelectOneElement) { div.setAttribute('aria-multiselectable', 'true'); } div.setAttribute('role', 'listbox'); return div; }, choiceGroup: function (_a, _b) { var allowHTML = _a.allowHTML, _c = _a.classNames, group = _c.group, groupHeading = _c.groupHeading, itemDisabled = _c.itemDisabled; var id = _b.id, label = _b.label, disabled = _b.disabled; var rawLabel = unwrapStringForRaw(label); var div = document.createElement('div'); addClassesToElement(div, group); if (disabled) { addClassesToElement(div, itemDisabled); } div.setAttribute('role', 'group'); div.dataset.group = ''; div.dataset.id = id; div.dataset.value = rawLabel; if (disabled) { div.setAttribute('aria-disabled', 'true'); } var heading = document.createElement('div'); addClassesToElement(heading, groupHeading); setElementHtml(heading, allowHTML, label || ''); div.appendChild(heading); return div; }, choice: function (_a, choice, selectText, groupName) { var allowHTML = _a.allowHTML, _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, itemSelectable = _b.itemSelectable, selectedState = _b.selectedState, itemDisabled = _b.itemDisabled, description = _b.description, placeholder = _b.placeholder; // eslint-disable-next-line prefer-destructuring var label = choice.label; var rawValue = unwrapStringForRaw(choice.value); var div = document.createElement('div'); div.id = choice.elementId; addClassesToElement(div, item); addClassesToElement(div, itemChoice); if (groupName && typeof label === 'string') { label = escapeForTemplate(allowHTML, label); label += " (".concat(groupName, ")"); label = { trusted: label }; } var describedBy = div; if (choice.labelClass) { var spanLabel = document.createElement('span'); setElementHtml(spanLabel, allowHTML, label); addClassesToElement(spanLabel, choice.labelClass); describedBy = spanLabel; div.appendChild(spanLabel); } else { setElementHtml(div, allowHTML, label); } if (choice.labelDescription) { var descId = "".concat(choice.elementId, "-description"); describedBy.setAttribute('aria-describedby', descId); var spanDesc = document.createElement('span'); setElementHtml(spanDesc, allowHTML, choice.labelDescription); spanDesc.id = descId; addClassesToElement(spanDesc, description); div.appendChild(spanDesc); } if (choice.selected) { addClassesToElement(div, selectedState); } if (choice.placeholder) { addClassesToElement(div, placeholder); } div.setAttribute('role', choice.group ? 'treeitem' : 'option'); div.dataset.choice = ''; div.dataset.id = choice.id; div.dataset.value = rawValue; if (selectText) { div.dataset.selectText = selectText; } if (choice.group) { div.dataset.groupId = "".concat(choice.group.id); } assignCustomProperties(div, choice, false); if (choice.disabled) { addClassesToElement(div, itemDisabled); div.dataset.choiceDisabled = ''; div.setAttribute('aria-disabled', 'true'); } else { addClassesToElement(div, itemSelectable); div.dataset.choiceSelectable = ''; div.setAttribute('aria-selected', choice.selected ? 'true' : 'false'); } return div; }, input: function (_a, placeholderValue) { var _b = _a.classNames, input = _b.input, inputCloned = _b.inputCloned, labelId = _a.labelId; var inp = document.createElement('input'); inp.type = 'search'; addClassesToElement(inp, input); addClassesToElement(inp, inputCloned); inp.autocomplete = 'off'; inp.autocapitalize = 'off'; inp.spellcheck = false; inp.setAttribute('aria-autocomplete', 'list'); if (placeholderValue) { inp.setAttribute('aria-label', placeholderValue); } else if (!labelId) { addAriaLabel(this._docRoot, this.passedElement.element.id, inp); } return inp; }, dropdown: function (_a) { var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown; var div = document.createElement('div'); addClassesToElement(div, list); addClassesToElement(div, listDropdown); div.setAttribute('aria-expanded', 'false'); return div; }, notice: function (_a, innerHTML, type) { var _b = _a.classNames, item = _b.item, itemChoice = _b.itemChoice, addChoice = _b.addChoice, noResults = _b.noResults, noChoices = _b.noChoices, noticeItem = _b.notice; if (type === void 0) { type = NoticeTypes.generic; } var notice = document.createElement('div'); setElementHtml(notice, true, innerHTML); addClassesToElement(notice, item); addClassesToElement(notice, itemChoice); addClassesToElement(notice, noticeItem); // eslint-disable-next-line default-case switch (type) { case NoticeTypes.addChoice: addClassesToElement(notice, addChoice); break; case NoticeTypes.noResults: addClassesToElement(notice, noResults); break; case NoticeTypes.noChoices: addClassesToElement(notice, noChoices); break; } if (type === NoticeTypes.addChoice) { notice.dataset.choiceSelectable = ''; notice.dataset.choice = ''; } return notice; }, option: function (choice) { // HtmlOptionElement's label value does not support HTML, so the avoid double escaping unwrap the untrusted string. var labelValue = unwrapStringForRaw(choice.label); var opt = new Option(labelValue, choice.value, false, choice.selected); assignCustomProperties(opt, choice, true); opt.disabled = choice.disabled; if (choice.selected) { opt.setAttribute('selected', ''); } return opt; }, }; /** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ var IS_IE11 = '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style; var USER_DEFAULTS = {}; var parseDataSetId = function (element) { if (!element) { return undefined; } return element.dataset.id ? parseInt(element.dataset.id, 10) : undefined; }; var selectableChoiceIdentifier = '[data-choice-selectable]'; /** * Choices * @author Josh Johnson */ var Choices = /** @class */ (function () { function Choices(element, userConfig) { if (element === void 0) { element = '[data-choice]'; } if (userConfig === void 0) { userConfig = {}; } var _this = this; this.initialisedOK = undefined; this._hasNonChoicePlaceholder = false; this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; var defaults = Choices.defaults; this.config = __assign(__assign(__assign({}, defaults.allOptions), defaults.options), userConfig); ObjectsInConfig.forEach(function (key) { _this.config[key] = __assign(__assign(__assign({}, defaults.allOptions[key]), defaults.options[key]), userConfig[key]); }); var config = this.config; if (!config.silent) { this._validateConfig(); } var docRoot = config.shadowRoot || document.documentElement; this._docRoot = docRoot; var passedElement = typeof element === 'string' ? docRoot.querySelector(element) : element; if (!passedElement || typeof passedElement !== 'object' || !(isHtmlInputElement(passedElement) || isHtmlSelectElement(passedElement))) { if (!passedElement && typeof element === 'string') { throw TypeError("Selector ".concat(element, " failed to find an element")); } throw TypeError("Expected one of the following types text|select-one|select-multiple"); } var elementType = passedElement.type; var isText = elementType === PassedElementTypes.Text; if (isText || config.maxItemCount !== 1) { config.singleModeForMultiSelect = false; } if (config.singleModeForMultiSelect) { elementType = PassedElementTypes.SelectMultiple; } var isSelectOne = elementType === PassedElementTypes.SelectOne; var isSelectMultiple = elementType === PassedElementTypes.SelectMultiple; var isSelect = isSelectOne || isSelectMultiple; this._elementType = elementType; this._isTextElement = isText; this._isSelectOneElement = isSelectOne; this._isSelectMultipleElement = isSelectMultiple; this._isSelectElement = isSelectOne || isSelectMultiple; this._canAddUserChoices = (isText && config.addItems) || (isSelect && config.addChoices); if (typeof config.renderSelectedChoices !== 'boolean') { config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne; } if (config.closeDropdownOnSelect === 'auto') { config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect; } else { config.closeDropdownOnSelect = coerceBool(config.closeDropdownOnSelect); } if (config.placeholder) { if (config.placeholderValue) { this._hasNonChoicePlaceholder = true; } else if (passedElement.dataset.placeholder) { this._hasNonChoicePlaceholder = true; config.placeholderValue = passedElement.dataset.placeholder; } } if (userConfig.addItemFilter && typeof userConfig.addItemFilter !== 'function') { var re = userConfig.addItemFilter instanceof RegExp ? userConfig.addItemFilter : new RegExp(userConfig.addItemFilter); config.addItemFilter = re.test.bind(re); } if (this._isTextElement) { this.passedElement = new WrappedInput({ element: passedElement, classNames: config.classNames, }); } else { var selectEl = passedElement; this.passedElement = new WrappedSelect({ element: selectEl, classNames: config.classNames, template: function (data) { return _this._templates.option(data); }, extractPlaceholder: config.placeholder && !this._hasNonChoicePlaceholder, }); } this.initialised = false; this._store = new Store(config); this._currentValue = ''; config.searchEnabled = !isText && config.searchEnabled; this._canSearch = config.searchEnabled; this._isScrollingOnIe = false; this._highlightPosition = 0; this._wasTap = true; this._placeholderValue = this._generatePlaceholderValue(); this._baseId = generateId(passedElement, 'choices-'); /** * setting direction in cases where it's explicitly set on passedElement * or when calculated direction is different from the document */ this._direction = passedElement.dir; if (!this._direction) { var elementDirection = window.getComputedStyle(passedElement).direction; var documentDirection = window.getComputedStyle(document.documentElement).direction; if (elementDirection !== documentDirection) { this._direction = elementDirection; } } this._idNames = { itemChoice: 'item-choice', }; this._templates = defaults.templates; this._render = this._render.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onKeyUp = this._onKeyUp.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onInput = this._onInput.bind(this); this._onClick = this._onClick.bind(this); this._onTouchMove = this._onTouchMove.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseOver = this._onMouseOver.bind(this); this._onFormReset = this._onFormReset.bind(this); this._onSelectKey = this._onSelectKey.bind(this); this._onEnterKey = this._onEnterKey.bind(this); this._onEscapeKey = this._onEscapeKey.bind(this); this._onDirectionKey = this._onDirectionKey.bind(this); this._onDeleteKey = this._onDeleteKey.bind(this); this._onChange = this._onChange.bind(this); this._onInvalid = this._onInvalid.bind(this); // If element has already been initialised with Choices, fail silently if (this.passedElement.isActive) { if (!config.silent) { console.warn('Trying to initialise Choices on element already initialised', { element: element }); } this.initialised = true; this.initialisedOK = false; return; } // Let's go this.init(); // preserve the selected item list after setup for form reset this._initialItems = this._store.items.map(function (choice) { return choice.value; }); } Object.defineProperty(Choices, "defaults", { get: function () { return Object.preventExtensions({ get options() { return USER_DEFAULTS; }, get allOptions() { return DEFAULT_CONFIG; }, get templates() { return templates; }, }); }, enumerable: false, configurable: true }); Choices.prototype.init = function () { if (this.initialised || this.initialisedOK !== undefined) { return; } this._searcher = getSearcher(this.config); this._loadChoices(); this._createTemplates(); this._createElements(); this._createStructure(); if ((this._isTextElement && !this.config.addItems) || this.passedElement.element.hasAttribute('disabled') || !!this.passedElement.element.closest('fieldset:disabled')) { this.disable(); } else { this.enable(); this._addEventListeners(); } // should be triggered **after** disabled state to avoid additional re-draws this._initStore(); this.initialised = true; this.initialisedOK = true; var callbackOnInit = this.config.callbackOnInit; // Run callback if it is a function if (typeof callbackOnInit === 'function') { callbackOnInit.call(this); } }; Choices.prototype.destroy = function () { if (!this.initialised) { return; } this._removeEventListeners(); this.passedElement.reveal(); this.containerOuter.unwrap(this.passedElement.element); this._store._listeners = []; // prevents select/input value being wiped this.clearStore(false); this._stopSearch(); this._templates = Choices.defaults.templates; this.initialised = false; this.initialisedOK = undefined; }; Choices.prototype.enable = function () { if (this.passedElement.isDisabled) { this.passedElement.enable(); } if (this.containerOuter.isDisabled) { this._addEventListeners(); this.input.enable(); this.containerOuter.enable(); } return this; }; Choices.prototype.disable = function () { if (!this.passedElement.isDisabled) { this.passedElement.disable(); } if (!this.containerOuter.isDisabled) { this._removeEventListeners(); this.input.disable(); this.containerOuter.disable(); } return this; }; Choices.prototype.highlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, true)); if (runEvent) { this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.unhighlightItem = function (item, runEvent) { if (runEvent === void 0) { runEvent = true; } if (!item || !item.id) { return this; } var choice = this._store.items.find(function (c) { return c.id === item.id; }); if (!choice || !choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, false)); if (runEvent) { this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.highlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (!item.highlighted) { _this._store.dispatch(highlightItem(item, true)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.unhighlightAll = function () { var _this = this; this._store.withTxn(function () { _this._store.items.forEach(function (item) { if (item.highlighted) { _this._store.dispatch(highlightItem(item, false)); _this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; }; Choices.prototype.removeActiveItemsByValue = function (value) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (item) { return item.value === value; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeActiveItems = function (excludedId) { var _this = this; this._store.withTxn(function () { _this._store.items.filter(function (_a) { var id = _a.id; return id !== excludedId; }).forEach(function (item) { return _this._removeItem(item); }); }); return this; }; Choices.prototype.removeHighlightedItems = function (runEvent) { var _this = this; if (runEvent === void 0) { runEvent = false; } this._store.withTxn(function () { _this._store.highlightedActiveItems.forEach(function (item) { _this._removeItem(item); // If this action was performed by the user // trigger the event if (runEvent) { _this._triggerChange(item.value); } }); }); return this; }; Choices.prototype.showDropdown = function (preventInputFocus) { var _this = this; if (this.dropdown.isActive) { return this; } if (preventInputFocus === undefined) { // eslint-disable-next-line no-param-reassign preventInputFocus = !this._canSearch; } requestAnimationFrame(function () { _this.dropdown.show(); var rect = _this.dropdown.element.getBoundingClientRect(); _this.containerOuter.open(rect.bottom, rect.height); if (!preventInputFocus) { _this.input.focus(); } _this.passedElement.triggerEvent(EventType.showDropdown); var activeElement = _this.choiceList.element.querySelector(getClassNamesSelector(_this.config.classNames.selectedState)); if (activeElement !== null && !isScrolledIntoView(activeElement, _this.choiceList.element)) { // We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll. activeElement.scrollIntoView(); } }); return this; }; Choices.prototype.hideDropdown = function (preventInputBlur) { var _this = this; if (!this.dropdown.isActive) { return this; } this._removeHighlightedChoices(); requestAnimationFrame(function () { _this.dropdown.hide(); _this.containerOuter.close(); if (!preventInputBlur && _this._canSearch) { _this.input.removeActiveDescendant(); _this.input.blur(); } _this.passedElement.triggerEvent(EventType.hideDropdown); }); return this; }; Choices.prototype.getValue = function (valueOnly) { var values = this._store.items.map(function (item) { return (valueOnly ? item.value : getChoiceForOutput(item)); }); return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values; }; Choices.prototype.setValue = function (items) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setValue'); return this; } this._store.withTxn(function () { items.forEach(function (value) { if (value) { _this._addChoice(mapInputToChoice(value, false)); } }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.setChoiceByValue = function (value) { var _this = this; if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoiceByValue'); return this; } if (this._isTextElement) { return this; } this._store.withTxn(function () { // If only one value has been passed, convert to array var choiceValue = Array.isArray(value) ? value : [value]; // Loop through each value and choiceValue.forEach(function (val) { return _this._findAndSelectChoiceByValue(val); }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ Choices.prototype.setChoices = function (choicesArrayOrFetcher, value, label, replaceChoices, clearSearchFlag, replaceItems) { var _this = this; if (choicesArrayOrFetcher === void 0) { choicesArrayOrFetcher = []; } if (value === void 0) { value = 'value'; } if (label === void 0) { label = 'label'; } if (replaceChoices === void 0) { replaceChoices = false; } if (clearSearchFlag === void 0) { clearSearchFlag = true; } if (replaceItems === void 0) { replaceItems = false; } if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoices'); return this; } if (!this._isSelectElement) { throw new TypeError("setChoices can't be used with INPUT based Choices"); } if (typeof value !== 'string' || !value) { throw new TypeError("value parameter must be a name of 'value' field in passed objects"); } if (typeof choicesArrayOrFetcher === 'function') { // it's a choices fetcher function var fetcher_1 = choicesArrayOrFetcher(this); if (typeof Promise === 'function' && fetcher_1 instanceof Promise) { // that's a promise // eslint-disable-next-line no-promise-executor-return return new Promise(function (resolve) { return requestAnimationFrame(resolve); }) .then(function () { return _this._handleLoadingState(true); }) .then(function () { return fetcher_1; }) .then(function (data) { return _this.setChoices(data, value, label, replaceChoices, clearSearchFlag, replaceItems); }) .catch(function (err) { if (!_this.config.silent) { console.error(err); } }) .then(function () { return _this._handleLoadingState(false); }) .then(function () { return _this; }); } // function returned something else than promise, let's check if it's an array of choices if (!Array.isArray(fetcher_1)) { throw new TypeError(".setChoices first argument function must return either array of choices or Promise, got: ".concat(typeof fetcher_1)); } // recursion with results, it's sync and choices were cleared already return this.setChoices(fetcher_1, value, label, false); } if (!Array.isArray(choicesArrayOrFetcher)) { throw new TypeError(".setChoices must be called either with array of choices with a function resulting into Promise of array of choices"); } this.containerOuter.removeLoadingState(); this._store.withTxn(function () { if (clearSearchFlag) { _this._isSearching = false; } // Clear choices if needed if (replaceChoices) { _this.clearChoices(true, replaceItems); } var isDefaultValue = value === 'value'; var isDefaultLabel = label === 'label'; choicesArrayOrFetcher.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { var group = groupOrChoice; if (!isDefaultLabel) { group = __assign(__assign({}, group), { label: group[label] }); } _this._addGroup(mapInputToChoice(group, true)); } else { var choice = groupOrChoice; if (!isDefaultLabel || !isDefaultValue) { choice = __assign(__assign({}, choice), { value: choice[value], label: choice[label] }); } var choiceFull = mapInputToChoice(choice, false); _this._addChoice(choiceFull); if (choiceFull.placeholder && !_this._hasNonChoicePlaceholder) { _this._placeholderValue = unwrapStringForEscaped(choiceFull.label); } } }); _this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.refresh = function (withEvents, selectFirstOption, deselectAll) { var _this = this; if (withEvents === void 0) { withEvents = false; } if (selectFirstOption === void 0) { selectFirstOption = false; } if (deselectAll === void 0) { deselectAll = false; } if (!this._isSelectElement) { if (!this.config.silent) { console.warn('refresh method can only be used on choices backed by a element'); } return this; } this._store.withTxn(function () { var choicesFromOptions = _this.passedElement.optionsAsChoices(); // Build the list of items which require preserving var existingItems = {}; if (!deselectAll) { _this._store.items.forEach(function (choice) { if (choice.id && choice.active && choice.selected) { existingItems[choice.value] = true; } }); } _this.clearStore(false); var updateChoice = function (choice) { if (deselectAll) { _this._store.dispatch(removeItem$1(choice)); } else if (existingItems[choice.value]) { choice.selected = true; } }; choicesFromOptions.forEach(function (groupOrChoice) { if ('choices' in groupOrChoice) { groupOrChoice.choices.forEach(updateChoice); return; } updateChoice(groupOrChoice); }); /* @todo only generate add events for the added options instead of all if (withEvents) { items.forEach((choice) => { if (existingItems[choice.value]) { this.passedElement.triggerEvent( EventType.removeItem, this._getChoiceForEvent(choice), ); } }); } */ // load new choices & items _this._addPredefinedChoices(choicesFromOptions, selectFirstOption, withEvents); // re-do search if required if (_this._isSearching) { _this._searchChoices(_this.input.value); } }); return this; }; Choices.prototype.removeChoice = function (value) { var choice = this._store.choices.find(function (c) { return c.value === value; }); if (!choice) { return this; } this._clearNotice(); this._store.dispatch(removeChoice(choice)); // @todo integrate with Store this._searcher.reset(); if (choice.selected) { this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice)); } return this; }; Choices.prototype.clearChoices = function (clearOptions, clearItems) { var _this = this; if (clearOptions === void 0) { clearOptions = true; } if (clearItems === void 0) { clearItems = false; } if (clearOptions) { if (clearItems) { this.passedElement.element.replaceChildren(''); } else { this.passedElement.element.querySelectorAll(':not([selected])').forEach(function (el) { el.remove(); }); } } this.itemList.element.replaceChildren(''); this.choiceList.element.replaceChildren(''); this._clearNotice(); this._store.withTxn(function () { var items = clearItems ? [] : _this._store.items; _this._store.reset(); items.forEach(function (item) { _this._store.dispatch(addChoice(item)); _this._store.dispatch(addItem(item)); }); }); // @todo integrate with Store this._searcher.reset(); return this; }; Choices.prototype.clearStore = function (clearOptions) { if (clearOptions === void 0) { clearOptions = true; } this.clearChoices(clearOptions, true); this._stopSearch(); this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; return this; }; Choices.prototype.clearInput = function () { var shouldSetInputWidth = !this._isSelectOneElement; this.input.clear(shouldSetInputWidth); this._stopSearch(); return this; }; Choices.prototype._validateConfig = function () { var config = this.config; var invalidConfigOptions = diff(config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); } if (config.allowHTML && config.allowHtmlUserInput) { if (config.addItems) { console.warn('Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks'); } if (config.addChoices) { console.warn('Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks'); } } }; Choices.prototype._render = function (changes) { if (changes === void 0) { changes = { choices: true, groups: true, items: true }; } if (this._store.inTxn()) { return; } if (this._isSelectElement) { if (changes.choices || changes.groups) { this._renderChoices(); } } if (changes.items) { this._renderItems(); } }; Choices.prototype._renderChoices = function () { var _this = this; if (!this._canAddItems()) { return; // block rendering choices if the input limit is reached. } var _a = this, config = _a.config, isSearching = _a._isSearching; var _b = this._store, activeGroups = _b.activeGroups, activeChoices = _b.activeChoices; var renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit; if (this._isSelectElement) { var backingOptions = activeChoices.filter(function (choice) { return !choice.element; }); if (backingOptions.length) { this.passedElement.addOptions(backingOptions); } } var fragment = document.createDocumentFragment(); var renderableChoices = function (choices) { return choices.filter(function (choice) { return !choice.placeholder && (isSearching ? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank : config.renderSelectedChoices || !choice.selected); }); }; var showLabel = config.appendGroupInSearch && isSearching; var selectableChoices = false; var highlightedEl = null; var renderChoices = function (choices, withinGroup) { if (isSearching) { // sortByRank is used to ensure stable sorting, as scores are non-unique // this additionally ensures fuseOptions.sortFn is not ignored choices.sort(sortByRank); } else if (config.shouldSort) { choices.sort(config.sorter); } var choiceLimit = choices.length; choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit; choiceLimit--; choices.every(function (choice, index) { // choiceEl being empty signals the contents has probably significantly changed var dropdownItem = choice.choiceEl || _this._templates.choice(config, choice, config.itemSelectText, showLabel && choice.group ? choice.group.label : undefined); choice.choiceEl = dropdownItem; fragment.appendChild(dropdownItem); if (isSearching || !choice.selected) { selectableChoices = true; } else if (!highlightedEl) { highlightedEl = dropdownItem; } return index < choiceLimit; }); }; if (activeChoices.length) { if (config.resetScrollPosition) { requestAnimationFrame(function () { return _this.choiceList.scrollToTop(); }); } if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) { // If we have a placeholder choice along with groups renderChoices(activeChoices.filter(function (choice) { return choice.placeholder && !choice.group; }), false); } // If we have grouped options if (activeGroups.length && !isSearching) { if (config.shouldSort) { activeGroups.sort(config.sorter); } // render Choices without group first, regardless of sort, otherwise they won't be distinguishable // from the last group renderChoices(activeChoices.filter(function (choice) { return !choice.placeholder && !choice.group; }), false); activeGroups.forEach(function (group) { var groupChoices = renderableChoices(group.choices); if (groupChoices.length) { if (group.label) { var dropdownGroup = group.groupEl || _this._templates.choiceGroup(_this.config, group); group.groupEl = dropdownGroup; dropdownGroup.remove(); fragment.appendChild(dropdownGroup); } renderChoices(groupChoices, true); } }); } else { renderChoices(renderableChoices(activeChoices), false); } } if (!selectableChoices && (isSearching || !fragment.children.length || !config.renderSelectedChoices)) { if (!this._notice) { this._notice = { text: resolveStringFunction(isSearching ? config.noResultsText : config.noChoicesText), type: isSearching ? NoticeTypes.noResults : NoticeTypes.noChoices, }; } fragment.replaceChildren(''); } this._renderNotice(fragment); this.choiceList.element.replaceChildren(fragment); this._highlightChoice(highlightedEl); }; Choices.prototype._renderItems = function () { var _this = this; var items = this._store.items || []; var itemList = this.itemList.element; var config = this.config; var fragment = document.createDocumentFragment(); var itemFromList = function (item) { return itemList.querySelector("[data-item][data-id=\"".concat(item.id, "\"]")); }; var addItemToFragment = function (item) { var el = item.itemEl; if (el && el.parentElement) { return; } el = itemFromList(item) || _this._templates.item(config, item, config.removeItemButton); item.itemEl = el; fragment.appendChild(el); }; // new items items.forEach(addItemToFragment); var addedItems = !!fragment.childNodes.length; if (this._isSelectOneElement) { var existingItems = itemList.children.length; if (addedItems || existingItems > 1) { var placeholder = itemList.querySelector(getClassNamesSelector(config.classNames.placeholder)); if (placeholder) { placeholder.remove(); } } else if (!addedItems && !existingItems && this._placeholderValue) { addedItems = true; addItemToFragment(mapInputToChoice({ selected: true, value: '', label: this._placeholderValue, placeholder: true, }, false)); } } if (addedItems) { itemList.append(fragment); if (config.shouldSortItems && !this._isSelectOneElement) { items.sort(config.sorter); // push sorting into the DOM items.forEach(function (item) { var el = itemFromList(item); if (el) { el.remove(); fragment.append(el); } }); itemList.append(fragment); } } if (this._isTextElement) { // Update the value of the hidden input this.passedElement.value = items.map(function (_a) { var value = _a.value; return value; }).join(config.delimiter); } }; Choices.prototype._displayNotice = function (text, type, openDropdown) { if (openDropdown === void 0) { openDropdown = true; } var oldNotice = this._notice; if (oldNotice && ((oldNotice.type === type && oldNotice.text === text) || (oldNotice.type === NoticeTypes.addChoice && (type === NoticeTypes.noResults || type === NoticeTypes.noChoices)))) { if (openDropdown) { this.showDropdown(true); } return; } this._clearNotice(); this._notice = text ? { text: text, type: type, } : undefined; this._renderNotice(); if (openDropdown && text) { this.showDropdown(true); } }; Choices.prototype._clearNotice = function () { if (!this._notice) { return; } var noticeElement = this.choiceList.element.querySelector(getClassNamesSelector(this.config.classNames.notice)); if (noticeElement) { noticeElement.remove(); } this._notice = undefined; }; Choices.prototype._renderNotice = function (fragment) { var noticeConf = this._notice; if (noticeConf) { var notice = this._templates.notice(this.config, noticeConf.text, noticeConf.type); if (fragment) { fragment.append(notice); } else { this.choiceList.prepend(notice); } } }; /** * @deprecated Use utils.getChoiceForOutput */ // eslint-disable-next-line class-methods-use-this Choices.prototype._getChoiceForOutput = function (choice, keyCode) { return getChoiceForOutput(choice, keyCode); }; Choices.prototype._triggerChange = function (value) { if (value === undefined || value === null) { return; } this.passedElement.triggerEvent(EventType.change, { value: value, }); }; Choices.prototype._handleButtonAction = function (element) { var _this = this; var items = this._store.items; if (!items.length || !this.config.removeItems || !this.config.removeItemButton) { return; } var id = element && parseDataSetId(element.closest('[data-id]')); var itemToRemove = id && items.find(function (item) { return item.id === id; }); if (!itemToRemove) { return; } this._store.withTxn(function () { // Remove item associated with button _this._removeItem(itemToRemove); _this._triggerChange(itemToRemove.value); if (_this._isSelectOneElement && !_this._hasNonChoicePlaceholder) { var placeholderChoice = (_this.config.shouldSort ? _this._store.choices.reverse() : _this._store.choices).find(function (choice) { return choice.placeholder; }); if (placeholderChoice) { _this._addItem(placeholderChoice); _this.unhighlightAll(); if (placeholderChoice.value) { _this._triggerChange(placeholderChoice.value); } } } }); }; Choices.prototype._handleItemAction = function (element, hasShiftKey) { var _this = this; if (hasShiftKey === void 0) { hasShiftKey = false; } var items = this._store.items; if (!items.length || !this.config.removeItems || this._isSelectOneElement) { return; } var id = parseDataSetId(element); if (!id) { return; } // We only want to select one item with a click // so we deselect any items that aren't the target // unless shift is being pressed items.forEach(function (item) { if (item.id === id && !item.highlighted) { _this.highlightItem(item); } else if (!hasShiftKey && item.highlighted) { _this.unhighlightItem(item); } }); // Focus input as without focus, a user cannot do anything with a // highlighted item this.input.focus(); }; Choices.prototype._handleChoiceAction = function (element) { var _this = this; // If we are clicking on an option var id = parseDataSetId(element); var choice = id && this._store.getChoiceById(id); if (!choice || choice.disabled) { return false; } var hasActiveDropdown = this.dropdown.isActive; if (!choice.selected) { if (!this._canAddItems()) { return true; // causes _onEnterKey to early out } this._store.withTxn(function () { _this._addItem(choice, true, true); _this.clearInput(); _this.unhighlightAll(); }); this._triggerChange(choice.value); } // We want to close the dropdown if we are dealing with a single select box if (hasActiveDropdown && this.config.closeDropdownOnSelect) { this.hideDropdown(true); this.containerOuter.element.focus(); } return true; }; Choices.prototype._handleBackspace = function (items) { var config = this.config; if (!config.removeItems || !items.length) { return; } var lastItem = items[items.length - 1]; var hasHighlightedItems = items.some(function (item) { return item.highlighted; }); // If editing the last item is allowed and there are not other selected items, // we can edit the item value. Otherwise if we can remove items, remove all selected items if (config.editItems && !hasHighlightedItems && lastItem) { this.input.value = lastItem.value; this.input.setWidth(); this._removeItem(lastItem); this._triggerChange(lastItem.value); } else { if (!hasHighlightedItems) { // Highlight last item if none already highlighted this.highlightItem(lastItem, false); } this.removeHighlightedItems(true); } }; Choices.prototype._loadChoices = function () { var _a; var _this = this; var config = this.config; if (this._isTextElement) { // Assign preset items from passed object first this._presetChoices = config.items.map(function (e) { return mapInputToChoice(e, false); }); // Add any values passed from attribute if (this.passedElement.value) { var elementItems = this.passedElement.value .split(config.delimiter) .map(function (e) { return mapInputToChoice(e, false, _this.config.allowHtmlUserInput); }); this._presetChoices = this._presetChoices.concat(elementItems); } this._presetChoices.forEach(function (choice) { choice.selected = true; }); } else if (this._isSelectElement) { // Assign preset choices from passed object this._presetChoices = config.choices.map(function (e) { return mapInputToChoice(e, true); }); // Create array of choices from option elements var choicesFromOptions = this.passedElement.optionsAsChoices(); if (choicesFromOptions) { (_a = this._presetChoices).push.apply(_a, choicesFromOptions); } } }; Choices.prototype._handleLoadingState = function (setLoading) { if (setLoading === void 0) { setLoading = true; } var el = this.itemList.element; if (setLoading) { this.disable(); this.containerOuter.addLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(this._templates.placeholder(this.config, this.config.loadingText)); } else { this.input.placeholder = this.config.loadingText; } } else { this.enable(); this.containerOuter.removeLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(''); this._render(); } else { this.input.placeholder = this._placeholderValue || ''; } } }; Choices.prototype._handleSearch = function (value) { if (!this.input.isFocussed) { return; } // Check that we have a value to search and the input was an alphanumeric character if (value !== null && typeof value !== 'undefined' && value.length >= this.config.searchFloor) { var resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; if (resultCount !== null) { // Trigger search event this.passedElement.triggerEvent(EventType.search, { value: value, resultCount: resultCount, }); } } else if (this._store.choices.some(function (option) { return !option.active; })) { this._stopSearch(); } }; Choices.prototype._canAddItems = function () { var config = this.config; var maxItemCount = config.maxItemCount, maxItemText = config.maxItemText; if (!config.singleModeForMultiSelect && maxItemCount > 0 && maxItemCount <= this._store.items.length) { this.choiceList.element.replaceChildren(''); this._notice = undefined; this._displayNotice(typeof maxItemText === 'function' ? maxItemText(maxItemCount) : maxItemText, NoticeTypes.addChoice); return false; } if (this._notice && this._notice.type === NoticeTypes.addChoice) { this._clearNotice(); } return true; }; Choices.prototype._canCreateItem = function (value) { var config = this.config; var canAddItem = true; var notice = ''; if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) { canAddItem = false; notice = resolveNoticeFunction(config.customAddItemText, value, undefined); } if (canAddItem) { var foundChoice = this._store.choices.find(function (choice) { return config.valueComparer(choice.value, value); }); if (foundChoice) { if (this._isSelectElement) { // for exact matches, do not prompt to add it as a custom choice this._displayNotice('', NoticeTypes.addChoice); return false; } if (!config.duplicateItemsAllowed) { canAddItem = false; notice = resolveNoticeFunction(config.uniqueItemText, value, undefined); } } } if (canAddItem) { notice = resolveNoticeFunction(config.addItemText, value, undefined); } if (notice) { this._displayNotice(notice, NoticeTypes.addChoice); } return canAddItem; }; Choices.prototype._searchChoices = function (value) { var newValue = value.trim().replace(/\s{2,}/, ' '); // signal input didn't change search if (!newValue.length || newValue === this._currentValue) { return null; } var searcher = this._searcher; if (searcher.isEmptyIndex()) { searcher.index(this._store.searchableChoices); } // If new value matches the desired length and is not the same as the current value with a space var results = searcher.search(newValue); this._currentValue = newValue; this._highlightPosition = 0; this._isSearching = true; var notice = this._notice; var noticeType = notice && notice.type; if (noticeType !== NoticeTypes.addChoice) { if (!results.length) { this._displayNotice(resolveStringFunction(this.config.noResultsText), NoticeTypes.noResults); } else { this._clearNotice(); } } this._store.dispatch(filterChoices(results)); return results.length; }; Choices.prototype._stopSearch = function () { if (this._isSearching) { this._currentValue = ''; this._isSearching = false; this._clearNotice(); this._store.dispatch(activateChoices(true)); this.passedElement.triggerEvent(EventType.search, { value: '', resultCount: 0, }); } }; Choices.prototype._addEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; // capture events - can cancel event processing or propagation documentElement.addEventListener('touchend', this._onTouchEnd, true); outerElement.addEventListener('keydown', this._onKeyDown, true); outerElement.addEventListener('mousedown', this._onMouseDown, true); // passive events - doesn't call `preventDefault` or `stopPropagation` documentElement.addEventListener('click', this._onClick, { passive: true }); documentElement.addEventListener('touchmove', this._onTouchMove, { passive: true, }); this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { passive: true, }); if (this._isSelectOneElement) { outerElement.addEventListener('focus', this._onFocus, { passive: true, }); outerElement.addEventListener('blur', this._onBlur, { passive: true, }); } inputElement.addEventListener('keyup', this._onKeyUp, { passive: true, }); inputElement.addEventListener('input', this._onInput, { passive: true, }); inputElement.addEventListener('focus', this._onFocus, { passive: true, }); inputElement.addEventListener('blur', this._onBlur, { passive: true, }); if (inputElement.form) { inputElement.form.addEventListener('reset', this._onFormReset, { passive: true, }); } if (passedElement.hasAttribute('required')) { passedElement.addEventListener('change', this._onChange, { passive: true, }); passedElement.addEventListener('invalid', this._onInvalid, { passive: true, }); } this.input.addEventListeners(); }; Choices.prototype._removeEventListeners = function () { var documentElement = this._docRoot; var outerElement = this.containerOuter.element; var inputElement = this.input.element; var passedElement = this.passedElement.element; documentElement.removeEventListener('touchend', this._onTouchEnd, true); outerElement.removeEventListener('keydown', this._onKeyDown, true); outerElement.removeEventListener('mousedown', this._onMouseDown, true); documentElement.removeEventListener('click', this._onClick); documentElement.removeEventListener('touchmove', this._onTouchMove); this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); if (this._isSelectOneElement) { outerElement.removeEventListener('focus', this._onFocus); outerElement.removeEventListener('blur', this._onBlur); } inputElement.removeEventListener('keyup', this._onKeyUp); inputElement.removeEventListener('input', this._onInput); inputElement.removeEventListener('focus', this._onFocus); inputElement.removeEventListener('blur', this._onBlur); if (inputElement.form) { inputElement.form.removeEventListener('reset', this._onFormReset); } if (passedElement.hasAttribute('required')) { passedElement.removeEventListener('change', this._onChange); passedElement.removeEventListener('invalid', this._onInvalid); } this.input.removeEventListeners(); }; Choices.prototype._onKeyDown = function (event) { var keyCode = event.keyCode; var hasActiveDropdown = this.dropdown.isActive; /* See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs https://stackoverflow.com/a/70866532 - "Unidentified" for mobile http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7) Logic: when a key event is sent, `event.key` represents its printable value _or_ one of a large list of special values indicating meta keys/functionality. In addition, key events for compose functionality contain a value of `Dead` when mid-composition. I can't quite verify it, but non-English IMEs may also be able to generate key codes for code points in the surrogate-pair range, which could potentially be seen as having key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that alone. Here, key.length === 1 means we know for sure the input was printable and not a special `key` value. When the length is greater than 1, it could be either a printable surrogate pair or a special `key` value. We can tell the difference by checking if the _character code_ value (not code point!) is in the "surrogate pair" range or not. We don't use .codePointAt because an invalid code point would return 65535, which wouldn't pass the >= 0x10000 check we would otherwise use. > ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points > of each plane are noncharacters: U+FFFE and U+FFFF on the BMP... */ var wasPrintableChar = event.key.length === 1 || (event.key.length === 2 && event.key.charCodeAt(0) >= 0xd800) || event.key === 'Unidentified'; /* We do not show the dropdown if focusing out with esc or navigating through input fields. An activated search can still be opened with any other key. */ if (!this._isTextElement && !hasActiveDropdown && keyCode !== KeyCodeMap.ESC_KEY && keyCode !== KeyCodeMap.TAB_KEY && keyCode !== KeyCodeMap.SHIFT_KEY) { this.showDropdown(); if (!this.input.isFocussed && wasPrintableChar) { /* We update the input value with the pressed key as the input was not focussed at the time of key press therefore does not have the value of the key. */ this.input.value += event.key; // browsers interpret a space as pagedown if (event.key === ' ') { event.preventDefault(); } } } switch (keyCode) { case KeyCodeMap.A_KEY: return this._onSelectKey(event, this.itemList.element.hasChildNodes()); case KeyCodeMap.ENTER_KEY: return this._onEnterKey(event, hasActiveDropdown); case KeyCodeMap.ESC_KEY: return this._onEscapeKey(event, hasActiveDropdown); case KeyCodeMap.UP_KEY: case KeyCodeMap.PAGE_UP_KEY: case KeyCodeMap.DOWN_KEY: case KeyCodeMap.PAGE_DOWN_KEY: return this._onDirectionKey(event, hasActiveDropdown); case KeyCodeMap.DELETE_KEY: case KeyCodeMap.BACK_KEY: return this._onDeleteKey(event, this._store.items, this.input.isFocussed); } }; Choices.prototype._onKeyUp = function ( /* event: KeyboardEvent */) { this._canSearch = this.config.searchEnabled; }; Choices.prototype._onInput = function ( /* event: InputEvent */) { var value = this.input.value; if (!value) { if (this._isTextElement) { this.hideDropdown(true); } else { this._stopSearch(); } return; } if (!this._canAddItems()) { return; } if (this._canSearch) { // do the search even if the entered text can not be added this._handleSearch(value); } if (!this._canAddUserChoices) { return; } // determine if a notice needs to be displayed for why a search result can't be added this._canCreateItem(value); if (this._isSelectElement) { this._highlightPosition = 0; // reset to select the notice and/or exact match this._highlightChoice(); } }; Choices.prototype._onSelectKey = function (event, hasItems) { // If CTRL + A or CMD + A have been pressed and there are items to select if ((event.ctrlKey || event.metaKey) && hasItems) { this._canSearch = false; var shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; if (shouldHightlightAll) { this.highlightAll(); } } }; Choices.prototype._onEnterKey = function (event, hasActiveDropdown) { var _this = this; var value = this.input.value; var target = event.target; event.preventDefault(); if (target && target.hasAttribute('data-button')) { this._handleButtonAction(target); return; } if (!hasActiveDropdown) { if (this._isSelectElement || this._notice) { this.showDropdown(); } return; } var highlightedChoice = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (highlightedChoice && this._handleChoiceAction(highlightedChoice)) { return; } if (!target || !value) { this.hideDropdown(true); return; } if (!this._canAddItems()) { return; } var addedItem = false; this._store.withTxn(function () { addedItem = _this._findAndSelectChoiceByValue(value, true); if (!addedItem) { if (!_this._canAddUserChoices) { return; } if (!_this._canCreateItem(value)) { return; } _this._addChoice(mapInputToChoice(value, false, _this.config.allowHtmlUserInput), true, true); addedItem = true; } _this.clearInput(); _this.unhighlightAll(); }); if (!addedItem) { return; } this._triggerChange(value); if (this.config.closeDropdownOnSelect) { this.hideDropdown(true); } }; Choices.prototype._onEscapeKey = function (event, hasActiveDropdown) { if (hasActiveDropdown) { event.stopPropagation(); this.hideDropdown(true); this._stopSearch(); this.containerOuter.element.focus(); } }; Choices.prototype._onDirectionKey = function (event, hasActiveDropdown) { var keyCode = event.keyCode; // If up or down key is pressed, traverse through options if (hasActiveDropdown || this._isSelectOneElement) { this.showDropdown(); this._canSearch = false; var directionInt = keyCode === KeyCodeMap.DOWN_KEY || keyCode === KeyCodeMap.PAGE_DOWN_KEY ? 1 : -1; var skipKey = event.metaKey || keyCode === KeyCodeMap.PAGE_DOWN_KEY || keyCode === KeyCodeMap.PAGE_UP_KEY; var nextEl = void 0; if (skipKey) { if (directionInt > 0) { nextEl = this.dropdown.element.querySelector("".concat(selectableChoiceIdentifier, ":last-of-type")); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } else { var currentEl = this.dropdown.element.querySelector(getClassNamesSelector(this.config.classNames.highlightedState)); if (currentEl) { nextEl = getAdjacentEl(currentEl, selectableChoiceIdentifier, directionInt); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } if (nextEl) { // We prevent default to stop the cursor moving // when pressing the arrow if (!isScrolledIntoView(nextEl, this.choiceList.element, directionInt)) { this.choiceList.scrollToChildElement(nextEl, directionInt); } this._highlightChoice(nextEl); } // Prevent default to maintain cursor position whilst // traversing dropdown options event.preventDefault(); } }; Choices.prototype._onDeleteKey = function (event, items, hasFocusedInput) { // If backspace or delete key is pressed and the input has no value if (!this._isSelectOneElement && !event.target.value && hasFocusedInput) { this._handleBackspace(items); event.preventDefault(); } }; Choices.prototype._onTouchMove = function () { if (this._wasTap) { this._wasTap = false; } }; Choices.prototype._onTouchEnd = function (event) { var target = (event || event.touches[0]).target; var touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target); if (touchWasWithinContainer) { var containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; if (containerWasExactTarget) { if (this._isTextElement) { this.input.focus(); } else if (this._isSelectMultipleElement) { this.showDropdown(); } } // Prevents focus event firing event.stopPropagation(); } this._wasTap = true; }; /** * Handles mousedown event in capture mode for containetOuter.element */ Choices.prototype._onMouseDown = function (event) { var target = event.target; if (!(target instanceof Element)) { return; } // If we have our mouse down on the scrollbar and are on IE11... if (IS_IE11 && this.choiceList.element.contains(target)) { // check if click was on a scrollbar area var firstChoice = this.choiceList.element.firstElementChild; this._isScrollingOnIe = this._direction === 'ltr' ? event.offsetX >= firstChoice.offsetWidth : event.offsetX < firstChoice.offsetLeft; } if (target === this.input.element) { return; } var item = target.closest('[data-button],[data-item],[data-choice]'); if (item instanceof HTMLElement) { if ('button' in item.dataset) { this._handleButtonAction(item); } else if ('item' in item.dataset) { this._handleItemAction(item, event.shiftKey); } else if ('choice' in item.dataset) { this._handleChoiceAction(item); } } event.preventDefault(); }; /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ Choices.prototype._onMouseOver = function (_a) { var target = _a.target; if (target instanceof HTMLElement && 'choice' in target.dataset) { this._highlightChoice(target); } }; Choices.prototype._onClick = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var clickWasWithinContainer = containerOuter.element.contains(target); if (clickWasWithinContainer) { if (!this.dropdown.isActive && !containerOuter.isDisabled) { if (this._isTextElement) { if (document.activeElement !== this.input.element) { this.input.focus(); } } else { this.showDropdown(); containerOuter.element.focus(); } } else if (this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target)) { this.hideDropdown(); } } else { containerOuter.removeFocusState(); this.hideDropdown(true); this.unhighlightAll(); } }; Choices.prototype._onFocus = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var focusWasWithinContainer = target && containerOuter.element.contains(target); if (!focusWasWithinContainer) { return; } var targetIsInput = target === this.input.element; if (this._isTextElement) { if (targetIsInput) { containerOuter.addFocusState(); } } else if (this._isSelectMultipleElement) { if (targetIsInput) { this.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown containerOuter.addFocusState(); } } else { containerOuter.addFocusState(); if (targetIsInput) { this.showDropdown(true); } } }; Choices.prototype._onBlur = function (_a) { var target = _a.target; var containerOuter = this.containerOuter; var blurWasWithinContainer = target && containerOuter.element.contains(target); if (blurWasWithinContainer && !this._isScrollingOnIe) { if (target === this.input.element) { containerOuter.removeFocusState(); this.hideDropdown(true); if (this._isTextElement || this._isSelectMultipleElement) { this.unhighlightAll(); } } else if (target === this.containerOuter.element) { // Remove the focus state when the past outerContainer was the target containerOuter.removeFocusState(); // Also close the dropdown if search is disabled if (!this.config.searchEnabled) { this.hideDropdown(true); } } } else { // On IE11, clicking the scollbar blurs our input and thus // closes the dropdown. To stop this, we refocus our input // if we know we are on IE *and* are scrolling. this._isScrollingOnIe = false; this.input.element.focus(); } }; Choices.prototype._onFormReset = function () { var _this = this; this._store.withTxn(function () { _this.clearInput(); _this.hideDropdown(); _this.refresh(false, false, true); if (_this._initialItems.length) { _this.setChoiceByValue(_this._initialItems); } }); }; Choices.prototype._onChange = function (event) { if (!event.target.checkValidity()) { return; } this.containerOuter.removeInvalidState(); }; Choices.prototype._onInvalid = function () { this.containerOuter.addInvalidState(); }; /** * Removes any highlighted choice options */ Choices.prototype._removeHighlightedChoices = function () { var highlightedState = this.config.classNames.highlightedState; var highlightedChoices = Array.from(this.dropdown.element.querySelectorAll(getClassNamesSelector(highlightedState))); // Remove any highlighted choices highlightedChoices.forEach(function (choice) { removeClassesFromElement(choice, highlightedState); choice.setAttribute('aria-selected', 'false'); }); }; Choices.prototype._highlightChoice = function (el) { if (el === void 0) { el = null; } var choices = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)); if (!choices.length) { return; } var passedEl = el; var highlightedState = this.config.classNames.highlightedState; this._removeHighlightedChoices(); if (passedEl) { this._highlightPosition = choices.indexOf(passedEl); } else { // Highlight choice based on last known highlight location if (choices.length > this._highlightPosition) { // If we have an option to highlight passedEl = choices[this._highlightPosition]; } else { // Otherwise highlight the option before passedEl = choices[choices.length - 1]; } if (!passedEl) { passedEl = choices[0]; } } addClassesToElement(passedEl, highlightedState); passedEl.setAttribute('aria-selected', 'true'); this.passedElement.triggerEvent(EventType.highlightChoice, { el: passedEl, }); if (this.dropdown.isActive) { // IE11 ignores aria-label and blocks virtual keyboard // if aria-activedescendant is set without a dropdown this.input.setActiveDescendant(passedEl.id); this.containerOuter.setActiveDescendant(passedEl.id); } }; Choices.prototype._addItem = function (item, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (!item.id) { throw new TypeError('item.id must be set before _addItem is called for a choice/item'); } if (this.config.singleModeForMultiSelect || this._isSelectOneElement) { this.removeActiveItems(item.id); } this._store.dispatch(addItem(item)); if (withEvents) { var eventChoice = getChoiceForOutput(item); this.passedElement.triggerEvent(EventType.addItem, eventChoice); if (userTriggered) { this.passedElement.triggerEvent(EventType.choice, eventChoice); } } }; Choices.prototype._removeItem = function (item) { if (!item.id) { return; } this._store.dispatch(removeItem$1(item)); var notice = this._notice; if (notice && notice.type === NoticeTypes.noChoices) { this._clearNotice(); } this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item)); }; Choices.prototype._addChoice = function (choice, withEvents, userTriggered) { if (withEvents === void 0) { withEvents = true; } if (userTriggered === void 0) { userTriggered = false; } if (choice.id) { throw new TypeError('Can not re-add a choice which has already been added'); } var config = this.config; if (!config.duplicateItemsAllowed && this._store.choices.find(function (c) { return config.valueComparer(c.value, choice.value); })) { return; } // Generate unique id, in-place update is required so chaining _addItem works as expected this._lastAddedChoiceId++; choice.id = this._lastAddedChoiceId; choice.elementId = "".concat(this._baseId, "-").concat(this._idNames.itemChoice, "-").concat(choice.id); var prependValue = config.prependValue, appendValue = config.appendValue; if (prependValue) { choice.value = prependValue + choice.value; } if (appendValue) { choice.value += appendValue.toString(); } if ((prependValue || appendValue) && choice.element) { choice.element.value = choice.value; } this._clearNotice(); this._store.dispatch(addChoice(choice)); if (choice.selected) { this._addItem(choice, withEvents, userTriggered); } }; Choices.prototype._addGroup = function (group, withEvents) { var _this = this; if (withEvents === void 0) { withEvents = true; } if (group.id) { throw new TypeError('Can not re-add a group which has already been added'); } this._store.dispatch(addGroup(group)); if (!group.choices) { return; } // add unique id for the group(s), and do not store the full list of choices in this group this._lastAddedGroupId++; group.id = this._lastAddedGroupId; group.choices.forEach(function (item) { item.group = group; if (group.disabled) { item.disabled = true; } _this._addChoice(item, withEvents); }); }; Choices.prototype._createTemplates = function () { var _this = this; var callbackOnCreateTemplates = this.config.callbackOnCreateTemplates; var userTemplates = {}; if (typeof callbackOnCreateTemplates === 'function') { userTemplates = callbackOnCreateTemplates.call(this, strToEl, escapeForTemplate, getClassNames); } var templating = {}; Object.keys(this._templates).forEach(function (name) { if (name in userTemplates) { templating[name] = userTemplates[name].bind(_this); } else { templating[name] = _this._templates[name].bind(_this); } }); this._templates = templating; }; Choices.prototype._createElements = function () { var templating = this._templates; var _a = this, config = _a.config, isSelectOneElement = _a._isSelectOneElement; var position = config.position, classNames = config.classNames; var elementType = this._elementType; this.containerOuter = new Container({ element: templating.containerOuter(config, this._direction, this._isSelectElement, isSelectOneElement, config.searchEnabled, elementType, config.labelId), classNames: classNames, type: elementType, position: position, }); this.containerInner = new Container({ element: templating.containerInner(config), classNames: classNames, type: elementType, position: position, }); this.input = new Input({ element: templating.input(config, this._placeholderValue), classNames: classNames, type: elementType, preventPaste: !config.paste, }); this.choiceList = new List({ element: templating.choiceList(config, isSelectOneElement), }); this.itemList = new List({ element: templating.itemList(config, isSelectOneElement), }); this.dropdown = new Dropdown({ element: templating.dropdown(config), classNames: classNames, type: elementType, }); }; Choices.prototype._createStructure = function () { var _a = this, containerInner = _a.containerInner, containerOuter = _a.containerOuter, passedElement = _a.passedElement; var dropdownElement = this.dropdown.element; // Hide original element passedElement.conceal(); // Wrap input in container preserving DOM ordering containerInner.wrap(passedElement.element); // Wrapper inner container with outer container containerOuter.wrap(containerInner.element); containerOuter.element.appendChild(containerInner.element); containerOuter.element.appendChild(dropdownElement); containerInner.element.appendChild(this.itemList.element); dropdownElement.appendChild(this.choiceList.element); if (this._isSelectOneElement) { this.input.placeholder = this.config.searchPlaceholderValue || ''; if (this.config.searchEnabled) { dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild); } } else { if (!this._isSelectMultipleElement || this.config.searchEnabled) { containerInner.element.appendChild(this.input.element); } if (this._placeholderValue) { this.input.placeholder = this._placeholderValue; } this.input.setWidth(); } this._highlightPosition = 0; this._isSearching = false; }; Choices.prototype._initStore = function () { var _this = this; this._store.subscribe(this._render).withTxn(function () { _this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false); }); if (!this._store.choices.length || (this._isSelectOneElement && this._hasNonChoicePlaceholder)) { this._render(); } }; Choices.prototype._addPredefinedChoices = function (choices, selectFirstOption, withEvents) { var _this = this; if (selectFirstOption === void 0) { selectFirstOption = false; } if (withEvents === void 0) { withEvents = true; } if (selectFirstOption) { /** * If there is a selected choice already or the choice is not the first in * the array, add each choice normally. * * Otherwise we pre-select the first enabled choice in the array ("select-one" only) */ var noSelectedChoices = choices.findIndex(function (choice) { return choice.selected; }) === -1; if (noSelectedChoices) { choices.some(function (choice) { if (choice.disabled || 'choices' in choice) { return false; } choice.selected = true; return true; }); } } choices.forEach(function (item) { if ('choices' in item) { if (_this._isSelectElement) { _this._addGroup(item, withEvents); } } else { _this._addChoice(item, withEvents); } }); }; Choices.prototype._findAndSelectChoiceByValue = function (value, userTriggered) { var _this = this; if (userTriggered === void 0) { userTriggered = false; } // Check 'value' property exists and the choice isn't already selected var foundChoice = this._store.choices.find(function (choice) { return _this.config.valueComparer(choice.value, value); }); if (foundChoice && !foundChoice.disabled && !foundChoice.selected) { this._addItem(foundChoice, true, userTriggered); return true; } return false; }; Choices.prototype._generatePlaceholderValue = function () { var config = this.config; if (!config.placeholder) { return null; } if (this._hasNonChoicePlaceholder) { return config.placeholderValue; } if (this._isSelectElement) { var placeholderOption = this.passedElement.placeholderOption; return placeholderOption ? placeholderOption.text : null; } return null; }; Choices.prototype._warnChoicesInitFailed = function (caller) { if (this.config.silent) { return; } if (!this.initialised) { throw new TypeError("".concat(caller, " called on a non-initialised instance of Choices")); } else if (!this.initialisedOK) { throw new TypeError("".concat(caller, " called for an element which has multiple instances of Choices initialised on it")); } }; Choices.version = '11.2.1'; return Choices; }()); export { Choices as default }; ================================================ FILE: public/assets/styles/base.css ================================================ /* ============================================= = Generic styling = ============================================= */ :root { --color-primary: #005F75; } @media (prefers-color-scheme: dark) { :root { /* Demo defaults */ --body-bg: #272a2b; --text-color: #cacaca; --color-primary: #38daff; --section-bg: #181a1b; --section-color: #cacaca; --hr-border: #373a3d; --choices-primary-color: #38daff; --choices-item-color: black; --choices-bg-color: #101010; --choices-bg-color-dropdown: #101010; --choices-keyline-color: #3b3e40; --choices-bg-color-disabled: #181a1b; --choices-item-disabled-color: #eee; --choices-disabled-color: #2d2d2d; --choices-highlighted-color: #16292d; --choices-icon-cross: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg=="); --choices-icon-cross-inverse: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg=="); color-scheme: dark; } input, select { color: #fff; } } * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } *, *::before, *::after { box-sizing: border-box; } html, body { position: relative; margin: 0; width: 100%; height: 100%; } body { font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 16px; line-height: 1.4; color: var(--text-color, #fff); background-color: var(--body-bg, #333); overflow-x: hidden; } label { display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; cursor: pointer; } p { margin-top: 0; margin-bottom: 8px; } hr { display: block; margin: 30px 0; border: 0; border-bottom: 1px solid var(--hr-border, #eaeaea); height: 1px; } h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 12px; font-weight: 400; line-height: 1.2; } a, a:visited, a:focus { color: var(--link-color, #fff); text-decoration: none; font-weight: 600; } .form-control { display: block; width: 100%; background-color: var(--form-bg, #f9f9f9); padding: 12px; border: 1px solid var(--form-boder, #ddd); border-radius: 2.5px; font-size: 14px; appearance: none; margin-bottom: 24px; } h1, .h1 { font-size: 32px; } h2, .h2 { font-size: 24px; } h3, .h3 { font-size: 20px; } h4, .h4 { font-size: 18px; } h5, .h5 { font-size: 16px; } h6, .h6 { font-size: 14px; } label + p { margin-top: -4px; } .container { display: block; margin: auto; max-width: 40em; padding: 48px; } @media (max-width: 620px) { .container { padding: 0; } } .section { background-color: var(--section-bg, #fff); padding: 24px; color: var(--section-color, #333); } .section a, .section a:visited, .section a:focus { color: var(--link-color-section, var(--color-primary)); } .logo { display: block; margin-bottom: 12px; } .logo-img { width: 100%; height: auto; display: inline-block; max-width: 100%; vertical-align: top; padding: 6px 0; } .visible-ie { display: none; } .push-bottom { margin-bottom: 24px; } .zero-bottom { margin-bottom: 0; } .zero-top { margin-top: 0; } .text-center { text-align: center; } [data-test-hook] { margin-bottom: 24px; } /* ===== End of Section comment block ====== */ ================================================ FILE: public/assets/styles/choices.css ================================================ /* =============================== = Choices = =============================== */ .choices { position: relative; overflow: hidden; margin-bottom: var(--choices-guttering, 24px); font-size: var(--choices-font-size-lg, 16px); } .choices:focus { outline: none; } .choices:last-child { margin-bottom: 0; } .choices.is-open { overflow: visible; } .choices.is-disabled :is(.choices__inner, .choices__input) { background-color: var(--choices-bg-color-disabled, #eaeaea); cursor: not-allowed !important; -webkit-user-select: none; user-select: none; } .choices.is-disabled .choices__item { cursor: not-allowed; color: var(--choices-item-disabled-color, #fff); } .choices [hidden] { position: absolute; inset: 0; pointer-events: none; opacity: 0; } .choices[data-type*=select-one] { cursor: pointer; } .choices[data-type*=select-one] .choices__inner { padding-bottom: var(--choices-inner-one-padding, 7.5px); } .choices[data-type*=select-one] .choices__input { display: block; width: var(--choices-width, 100%); padding: var(--choices-dropdown-item-padding, 10px); border-bottom: var(--choices-base-border, 1px solid) var(--choices-keyline-color, #ddd); background-color: var(--choices-bg-color-dropdown, #fff); margin: 0; } .choices[data-type*=select-one] .choices__button { background-image: var(--choices-icon-cross-inverse, url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==")); padding: 0; background-size: 8px; position: absolute; top: 50%; right: 0; margin-top: -10px; margin-right: 25px; height: 20px; width: 20px; border-radius: 10em; opacity: 0.25; } .choices[data-type*=select-one] .choices__button:is(:hover, :focus) { opacity: var(--choices-button-opacity-hover, 1); } .choices[data-type*=select-one] .choices__button:focus { box-shadow: 0 0 0 2px var(--choices-highlight-color, #005F75); } .choices[data-type*=select-one] .choices__item[data-placeholder] .choices__button { display: none; } .choices[data-type*=select-one]::after { content: ""; height: 0; width: 0; border-style: solid; border-color: var(--choices-text-color, #333) transparent transparent transparent; border-width: var(--choices-arrow-size, 5px); position: absolute; right: var(--choices-arrow-right, 11.5px); top: 50%; margin-top: var(--choices-arrow-margin-top, -2.5px); pointer-events: none; } .choices[data-type*=select-one].is-open::after { border-color: transparent transparent var(--choices-text-color, #333); margin-top: var(--choices-arrow-margin-top-open, -7.5px); } .choices[data-type*=select-one][dir=rtl]::after { left: 11.5px; right: auto; } .choices[data-type*=select-one][dir=rtl] .choices__button { right: auto; left: 0; margin-left: 25px; margin-right: 0; } .choices:is([data-type*=select-multiple], [data-type*=text]) .choices__inner { cursor: text; } .choices:is([data-type*=select-multiple], [data-type*=text]) .choices__button { position: relative; display: inline-block; margin: 0 calc(var(--choices-button-offset, 8px) * -0.5) 0 var(--choices-button-offset, 8px); padding-left: calc(var(--choices-button-offset, 8px) * 2); border-left: 1px solid color-mix(in srgb, var(--choices-primary-color, #005F75) 90%, var(--choices-darken, black)); background-image: var(--choices-icon-cross, url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==")); background-size: var(--choices-button-dimension, 8px); width: var(--choices-button-dimension, 8px); line-height: var(--choices-button-line-height, 1); border-radius: var(--choices-button-border-radius, 0); opacity: var(--choices-button-opacity, 0.75); } .choices:is([data-type*=select-multiple], [data-type*=text]) .choices__button:is(:hover, :focus) { --choices-button-opacity: var(--choices-button-opacity-hover, 1); } .choices__inner { display: inline-block; vertical-align: top; width: var(--choices-width, 100%); background-color: var(--choices-bg-color, #f9f9f9); padding: var(--choices-inner-padding, 7.5px 7.5px 3.75px); border: var(--choices-base-border, 1px solid) var(--choices-keyline-color, #ddd); border-radius: var(--choices-border-radius, 2.5px); font-size: var(--choices-font-size-md, 14px); min-height: var(--choices-input-height, 44px); overflow: hidden; } .is-focused .choices__inner, .is-open .choices__inner { border-color: color-mix(in srgb, var(--choices-keyline-color, #ddd) 85%, var(--choices-darken, black)); } .is-open .choices__inner { border-radius: var(--choices-border-radius, 2.5px) var(--choices-border-radius, 2.5px) 0 0; } .is-invalid .choices__inner { border-color: var(--choices-invalid-color, #d33141); } .is-flipped.is-open .choices__inner { border-radius: 0 0 var(--choices-border-radius, 2.5px) var(--choices-border-radius, 2.5px); } .choices__list { margin: 0; padding-left: 0; list-style: none; } .choices__list--single { display: inline-block; padding: var(--choices-list-single-padding, 4px 16px 4px 4px); width: var(--choices-width, 100%); } [dir=rtl] .choices__list--single { padding-right: 4px; padding-left: 16px; } .choices__list--single .choices__item { width: var(--choices-width, 100%); } .choices__list--multiple { display: inline; } .choices__list--multiple .choices__item { display: inline-block; vertical-align: middle; border-radius: var(--choices-border-radius-item, 20px); padding: var(--choices-multiple-item-padding, 4px 10px); font-size: var(--choices-font-size-sm, 12px); font-weight: 500; margin-right: var(--choices-multiple-item-margin, 3.75px); margin-bottom: var(--choices-multiple-item-margin, 3.75px); background-color: var(--choices-primary-color, #005F75); border: 1px solid color-mix(in srgb, var(--choices-primary-color, #005F75) 95%, var(--choices-darken, black)); color: var(--choices-item-color, #fff); word-break: break-all; box-sizing: border-box; } .choices__list--multiple .choices__item[data-deletable] { padding-right: 5px; } [dir=rtl] .choices__list--multiple .choices__item { margin-right: 0; margin-left: var(--choices-multiple-item-margin, 3.75px); } .choices__list--multiple .choices__item.is-highlighted { background-color: color-mix(in srgb, var(--choices-primary-color, #005F75) 95%, var(--choices-darken, black)); border: 1px solid color-mix(in srgb, var(--choices-primary-color, #005F75) 90%, var(--choices-darken, black)); } .is-disabled .choices__list--multiple .choices__item { background-color: color-mix(in srgb, var(--choices-disabled-color, #eaeaea) 75%, var(--choices-darken, black)); border: 1px solid color-mix(in srgb, var(--choices-disabled-color, #eaeaea) 65%, var(--choices-darken, black)); } .choices__list--dropdown, .choices__list[aria-expanded] { display: none; z-index: var(--choices-z-index, 1); position: absolute; width: var(--choices-width, 100%); background-color: var(--choices-bg-color-dropdown, #fff); border: var(--choices-base-border, 1px solid) var(--choices-keyline-color, #ddd); top: 100%; margin-top: -1px; border-bottom-left-radius: var(--choices-border-radius, 2.5px); border-bottom-right-radius: var(--choices-border-radius, 2.5px); overflow: hidden; word-break: break-all; } .is-active.choices__list--dropdown, .is-active.choices__list[aria-expanded] { display: block; } .is-open .choices__list--dropdown, .is-open .choices__list[aria-expanded] { border-color: color-mix(in srgb, var(--choices-keyline-color, #ddd) 85%, var(--choices-darken, black)); } .is-flipped .choices__list--dropdown, .is-flipped .choices__list[aria-expanded] { top: auto; bottom: 100%; margin-top: 0; margin-bottom: -1px; border-radius: 0.25rem 0.25rem 0 0; } .choices__list--dropdown .choices__list, .choices__list[aria-expanded] .choices__list { position: relative; max-height: 300px; overflow: auto; -webkit-overflow-scrolling: touch; will-change: scroll-position; } .choices__list--dropdown .choices__item, .choices__list[aria-expanded] .choices__item { position: relative; padding: var(--choices-dropdown-item-padding, 10px); font-size: var(--choices-font-size-md, 14px); } [dir=rtl] .choices__list--dropdown .choices__item, [dir=rtl] .choices__list[aria-expanded] .choices__item { text-align: right; } @media (min-width: 640px) { .choices__list--dropdown .choices__item--selectable.is-highlighted[data-select-text], .choices__list[aria-expanded] .choices__item--selectable.is-highlighted[data-select-text] { padding-right: 100px; } .choices__list--dropdown .choices__item--selectable.is-highlighted[data-select-text]::after, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted[data-select-text]::after { content: attr(data-select-text); font-size: var(--choices-font-size-sm, 12px); position: absolute; right: 10px; top: 50%; transform: translateY(-50%); } [dir=rtl] .choices__list--dropdown .choices__item--selectable.is-highlighted[data-select-text], [dir=rtl] .choices__list[aria-expanded] .choices__item--selectable.is-highlighted[data-select-text] { text-align: right; padding-left: 100px; padding-right: 10px; } [dir=rtl] .choices__list--dropdown .choices__item--selectable.is-highlighted[data-select-text]::after, [dir=rtl] .choices__list[aria-expanded] .choices__item--selectable.is-highlighted[data-select-text]::after { right: auto; left: 10px; } } .choices__list--dropdown .choices__item--selectable.is-selected::after, .choices__list[aria-expanded] .choices__item--selectable.is-selected::after { content: none !important; } .choices__list--dropdown .choices__item--selectable.is-selected, .choices__list[aria-expanded] .choices__item--selectable.is-selected, .choices__list--dropdown .choices__item--selectable.is-highlighted, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted { background-color: var(--choices-highlighted-color, #f2f2f2); } .choices__item { cursor: default; } .choices__item--selectable { cursor: pointer; } .choices__item--disabled { cursor: not-allowed; -webkit-user-select: none; user-select: none; opacity: 0.5; } .choices__heading { font-weight: 600; font-size: 12px; padding: 10px; border-bottom: 1px solid color-mix(in srgb, var(--choices-keyline-color, #ddd) 90%, var(--choices-lighten, white)); color: gray; } .choices__button { text-indent: -9999px; appearance: none; border: 0; background-color: transparent; background-repeat: no-repeat; background-position: center; cursor: pointer; } .choices__button:focus { outline: none; } .choices__input { display: inline-block; vertical-align: baseline; background-color: var(--choices-bg-color, #f9f9f9); font-size: var(--choices-font-size-md, 14px); margin-bottom: var(--choices-input-margin-bottom, 5px); border: 0; border-radius: 0; max-width: var(--choices-width, 100%); padding: var(--choices-input-padding, 4px 0 4px 2px); } .choices__input:focus { outline: 0; } .choices__input::-webkit-search-decoration, .choices__input::-webkit-search-cancel-button, .choices__input::-webkit-search-results-button, .choices__input::-webkit-search-results-decoration { display: none; } .choices__input::-ms-clear, .choices__input::-ms-reveal { display: none; width: 0; height: 0; } [dir=rtl] .choices__input { padding-right: 2px; padding-left: 0; } .choices__placeholder { opacity: var(--choices-placeholder-opacity, 0.5); } /* ===== End of Choices ====== */ ================================================ FILE: public/index.html ================================================ Choices Choices.js Choices.js is a lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency. For all config options, visit the GitHub repo. Interested in writing your own ES6 JavaScript plugins? Check out ES6.io for great tutorials! 💪🏼 Sponsored by: Text inputs Limited to 5 values with remove button Unique values only, no pasting Email addresses only Disabled Prepends and appends a value to each items return value Preset values passed through options I18N labels Right-to-left Multiple select input Default Choice 1 Choice 2 Choice 3 Choice 4 With remove button Choice 1 Choice 2 Choice 3 Choice 4 Option groups Choose a city London Manchester Liverpool Paris Lyon Marseille Hamburg Munich Berlin New York Washington Michigan Madrid Barcelona Malaga Montreal Toronto Vancouver If the following example do not load, the Discogs rate limit has probably been reached. Try again later! Options from remote source (Fetch API) & limited to 5 Right-to-left Choice 1 Choice 2 Choice 3 Choice 4 Use label in event (add/remove) Single select input Default This is a placeholder Choice 1 Choice 2 Choice 3 If the following two examples do not load, the Discogs rate limit has probably been reached. Try again later! Options from remote source (Fetch API) Pick an Arctic Monkeys' record Options from remote source (Fetch API) & remove button Pick a Smiths' record Option groups Choose a city London Manchester Liverpool Paris Lyon Marseille Hamburg Munich Berlin New York Washington Michigan Madrid Barcelona Malaga Montreal Toronto Vancouver Right-to-left Choice 1 Choice 2 Choice 3 Options added via config with no search Zero Option and option groups added via config Option selected via config with custom properties Try searching for 'fantastic', "Label 3" should display Option searchable by custom properties via data-custom-properties attribute Try searching for 'fantastic', "Label 3" should display Label One Label Two Label Three Label Four Options without sorting Madrid Toronto Vancouver London Manchester Liverpool Paris Malaga Washington Lyon Marseille Hamburg Munich Barcelona Berlin Montreal New York Michigan Custom templates React Angular Ember Vue Below is an example of how you could have two select inputs depend on eachother. 'Tube stations' will only be enabled if the value of 'Cities' is 'London' Cities Choose a city Leeds Manchester London Sheffield Newcastle Tube stations Choose a tube station Moorgate St Pauls Old Street Liverpool Street Kings Cross St. Pancras Form interaction Change the values and press reset to restore to initial state. Change me! Option 1 Option 2 Option 3 Option 4 Option 5 And me! Choice 1 Choice 2 Choice 3 Choice 4 Reset Form validation Try submitting the form first. Then try setting the values as they are required! Change me! - Option 1 Option 2 Option 3 Option 4 Option 5 And me! Submit ================================================ FILE: public/robots.txt ================================================ Disallow: /test/* ================================================ FILE: public/test/data.json ================================================ [ { "label": "Label 1", "value": "Value 1" }, { "label": "Label 2", "value": "Value 2" }, { "label": "Label 3", "value": "Value 3" }, { "label": "Label 4", "value": "Value 4" }, { "label": "Label 5", "value": "Value 5" }, { "label": "Label 6", "value": "Value 6" }, { "label": "Label 7", "value": "Value 7" }, { "label": "Label 8", "value": "Value 8" }, { "label": "Label 9", "value": "Value 9" }, { "label": "Label 10", "value": "Value 10" } ] ================================================ FILE: public/test/disabled-data.json ================================================ [ { "label": "Disabled Label 1", "value": "Disabled Value 1", "disabled": true }, { "label": "Disabled Label 2", "value": "Disabled Value 2", "disabled": true }, { "label": "Disabled Label 3", "value": "Disabled Value 3", "disabled": true }, { "label": "Disabled Label 4", "value": "Disabled Value 4", "disabled": true }, { "label": "Disabled Label 5", "value": "Disabled Value 5", "disabled": true }, { "label": "Disabled Label 6", "value": "Disabled Value 6", "disabled": true }, { "label": "Disabled Label 7", "value": "Disabled Value 7", "disabled": true }, { "label": "Disabled Label 8", "value": "Disabled Value 8", "disabled": true }, { "label": "Disabled Label 9", "value": "Disabled Value 9", "disabled": true }, { "label": "Disabled Label 10", "value": "Disabled Value 10", "disabled": true } ] ================================================ FILE: public/test/select-multiple/index-performance.html ================================================ Choices Select multiple inputs Basic Disable Enable Enable ================================================ FILE: public/test/select-multiple/index.html ================================================ Choices Select multiple inputs Basic Disable Enable Choice 1 Choice 3 Choice 4 Single-item Choice 1 Render selected choices Choice 1 Choice 2 Choice 3 Choice 4 Selected choice in dropdown Choice 1 Choice 2 Choice 3 Choice 4 Remove button Choice 1 Choice 2 Choice 3 Choice 4 No press to select Choice 1 Choice 2 Choice 3 Choice 4 Duplicate items allowed Service 1 Service 2 Service 3 Service 4 Service 5 Duplicate items disallowed Service 1 Service 2 Service 3 Service 4 Service 5 Render selected choices (single option) Choice 1 No choices No choices (besides selected) Choice 1 Disabled choice Choice 1 Choice 2 Choice 3 Choice 4 Disabled first choice by options Disabled via fieldset Choice 1 Choice 2 Choice 3 Disabled via attribute Choice 1 Choice 2 Choice 3 Input limit Choice 1 Choice 2 Choice 3 Choice 4 Choice 5 Choice 6 Input limit note after unselecting choice Choice 1 Choice 2 Choice 3 Choice 4 Choice 5 Prepend/append Choice 1 Choice 2 Choice 3 Render choice limit Choice 1 Choice 2 Choice 3 Search disabled Choice 1 Choice 2 Choice 3 Search floor Choice 1 Choice 2 Choice 3 Search hide selected Choice 1 Choice 2 Choice 3 Choice 4 Placeholder via empty option value I am a placeholder Choice 1 Choice 2 Choice 3 Placeholder via option attribute I am a placeholder Choice 1 Choice 2 Choice 3 Placeholder via data attribute I am a not placeholder Choice 1 Choice 2 Choice 3 Remote data I am a placeholder Scrolling dropdown Choice 1 Choice 2 Choice 3 Choice 4 Choice 5 Choice 6 Choice 7 Choice 8 Choice 9 Choice 10 Choice 11 Choice 12 Choice 13 Choice 14 Choice 15 Choice groups London Manchester Liverpool Paris Lyon Marseille Choice groups with some elements without a group London Paris Lyon Custom properties Custom properties Label One Label Two Label Three Label Four Non-string values Within form Choice 1 Choice 2 Dynamically set choice by value Choice 1 Choice 2 Choice 3 Search by label label1 label2 HTML disabled by default (adds choices) HTML allowed (adds choices) HTML allowed (except user input, adds choices) HTML disabled (adds choices) New, Destroy, Init Choice 1 Choice 2 Choice 3 Destroy Init Shadow DOM - Basic Disable Enable Choice 1 Choice 2 Choice 3 Choice 4 Autocomplete example Clear choices on add item Choice 1 Choice 2 setChoices example Choice 1 Choice 2 setChoices example (no duplicates) Choice 1 Choice 2 ================================================ FILE: public/test/select-one/index.html ================================================ Choices Select one inputs Basic Disable Enable Choice 1 Choice 3 Choice 4 Single-item Choice 1 Selected choice in dropdown Choice 1 Choice 2 Choice 3 Choice 4 Remove button Choice 1 Choice 2 Choice 3 Choice 4 Remove button with should sort On Choice 4 Choice 3 Choice 2 Choice 1 Remove button with should sort off Choice 4 Choice 3 Choice 2 Choice 1 No press to select Choice 1 Choice 2 Choice 3 Choice 4 Duplicate items allowed Service 1 Service 2 Service 3 Service 4 Service 5 Duplicate items disallowed Service 1 Service 2 Service 3 Service 4 Service 5 Render selected choices (single option) Choice 1 No choices No choices (besides selected) Choice 1 Disabled choice Choice 1 Choice 2 Choice 3 Choice 4 Disabled first choice by options Disabled via fieldset Choice 1 Choice 2 Choice 3 Disabled via attribute Choice 1 Choice 2 Choice 3 Prepend/append Choice 1 Choice 2 Choice 3 Render choice limit Choice 1 Choice 2 Choice 3 Search disabled Choice 1 Choice 2 Choice 3 Search floor Choice 1 Choice 2 Choice 3 Placeholder via empty option value I am a placeholder Choice 1 Choice 2 Choice 3 Placeholder via option attribute I am a placeholder Choice 1 Choice 2 Choice 3 Placeholder via data attribute I am a not placeholder Choice 1 Choice 2 Choice 3 Remote data I am a placeholder Remote disabled data I am a placeholder Scrolling dropdown Choice 1 Choice 2 Choice 3 Choice 4 Choice 5 Choice 6 Choice 7 Choice 8 Choice 9 Choice 10 Choice 11 Choice 12 Choice 13 Choice 14 Choice 15 Choice groups London Manchester Liverpool Paris Lyon Marseille Choice groups with some elements without a group London Paris Lyon Parent Parent choice 1 Parent choice 2 Parent choice 3 Child Child choice 1 Child choice 2 Child choice 3 Custom properties Custom properties Label One Label Two Label Three Label Four Non-string values Within form Choice 1 Choice 2 Dynamically set choice by value Choice 1 Choice 2 Choice 3 Search by label label1 label2 HTML disabled by default (adds choices) HTML allowed (adds choices) HTML allowed (except user input, adds choices) HTML disabled (adds choices) New, Destroy, Init Choice 1 Choice 2 Choice 3 Destroy Init Shadow DOM - Basic Disable Enable Choice 1 Choice 2 Choice 3 Choice 4 Autocomplete example Clear choices on add item Choice 1 Choice 2 setChoices example Choice 1 Choice 2 setChoices example (no duplicates) Choice 1 Choice 2 ================================================ FILE: public/test/text/index.html ================================================ Choices Text inputs Basic Edit items Remove button Unique values HTML disabled by default HTML allowed (adds choices) HTML allowed (except user input, adds choices) HTML disabled (adds choices) Input limit Add item filter Add items disabled Disabled via fieldset Disabled via attribute Prepend/append Pre-populated choices Placeholder Within form New, Destroy, Init Destroy Init Shadow DOM - Basic ================================================ FILE: public/types/src/index.d.ts ================================================ import Choices from './scripts/choices'; export * from './scripts/interfaces'; export * from './scripts/constants'; export * from './scripts/defaults'; export { default as templates } from './scripts/templates'; export default Choices; ================================================ FILE: public/types/src/scripts/actions/choices.d.ts ================================================ import { ChoiceFull } from '../interfaces/choice-full'; import { ActionType } from '../interfaces'; import { SearchResult } from '../interfaces/search'; import { AnyAction } from '../interfaces/store'; export type ChoiceActions = AddChoiceAction | RemoveChoiceAction | FilterChoicesAction | ActivateChoicesAction | ClearChoicesAction; export interface AddChoiceAction extends AnyAction { choice: ChoiceFull; } export interface RemoveChoiceAction extends AnyAction { choice: ChoiceFull; } export interface FilterChoicesAction extends AnyAction { results: SearchResult[]; } export interface ActivateChoicesAction extends AnyAction { active: boolean; } /** * @deprecated use clearStore() or clearChoices() instead. */ export interface ClearChoicesAction extends AnyAction { } export declare const addChoice: (choice: ChoiceFull) => AddChoiceAction; export declare const removeChoice: (choice: ChoiceFull) => RemoveChoiceAction; export declare const filterChoices: (results: SearchResult[]) => FilterChoicesAction; export declare const activateChoices: (active?: boolean) => ActivateChoicesAction; /** * @deprecated use clearStore() or clearChoices() instead. */ export declare const clearChoices: () => ClearChoicesAction; ================================================ FILE: public/types/src/scripts/actions/groups.d.ts ================================================ import { GroupFull } from '../interfaces/group-full'; import { ActionType } from '../interfaces'; import { AnyAction } from '../interfaces/store'; export type GroupActions = AddGroupAction; export interface AddGroupAction extends AnyAction { group: GroupFull; } export declare const addGroup: (group: GroupFull) => AddGroupAction; ================================================ FILE: public/types/src/scripts/actions/items.d.ts ================================================ import { ChoiceFull } from '../interfaces/choice-full'; import { ActionType } from '../interfaces'; import { AnyAction } from '../interfaces/store'; export type ItemActions = AddItemAction | RemoveItemAction | HighlightItemAction; export interface AddItemAction extends AnyAction { item: ChoiceFull; } export interface RemoveItemAction extends AnyAction { item: ChoiceFull; } export interface HighlightItemAction extends AnyAction { item: ChoiceFull; highlighted: boolean; } export declare const addItem: (item: ChoiceFull) => AddItemAction; export declare const removeItem: (item: ChoiceFull) => RemoveItemAction; export declare const highlightItem: (item: ChoiceFull, highlighted: boolean) => HighlightItemAction; ================================================ FILE: public/types/src/scripts/choices.d.ts ================================================ import { Container, Dropdown, Input, List, WrappedInput, WrappedSelect } from './components'; import { InputChoice } from './interfaces/input-choice'; import { InputGroup } from './interfaces/input-group'; import { Options } from './interfaces/options'; import { StateChangeSet } from './interfaces/state'; import Store from './store/store'; import { ChoiceFull } from './interfaces/choice-full'; import { GroupFull } from './interfaces/group-full'; import { EventChoiceValueType, PassedElementType } from './interfaces'; import { EventChoice } from './interfaces/event-choice'; import { NoticeType, Templates } from './interfaces/templates'; import { Searcher } from './interfaces/search'; /** * Choices * @author Josh Johnson */ declare class Choices { static version: string; static get defaults(): { options: Partial; allOptions: Options; templates: Templates; }; initialised: boolean; initialisedOK?: boolean; config: Options; passedElement: WrappedInput | WrappedSelect; containerOuter: Container; containerInner: Container; choiceList: List; itemList: List; input: Input; dropdown: Dropdown; _elementType: PassedElementType; _isTextElement: boolean; _isSelectOneElement: boolean; _isSelectMultipleElement: boolean; _isSelectElement: boolean; _hasNonChoicePlaceholder: boolean; _canAddUserChoices: boolean; _store: Store; _templates: Templates; _lastAddedChoiceId: number; _lastAddedGroupId: number; _currentValue: string; _canSearch: boolean; _isScrollingOnIe: boolean; _highlightPosition: number; _wasTap: boolean; _isSearching: boolean; _placeholderValue: string | null; _baseId: string; _direction: HTMLElement['dir']; _idNames: { itemChoice: string; }; _presetChoices: (ChoiceFull | GroupFull)[]; _initialItems: string[]; _searcher: Searcher; _notice?: { type: NoticeType; text: string; }; _docRoot: ShadowRoot | HTMLElement; constructor(element?: string | Element | HTMLInputElement | HTMLSelectElement, userConfig?: Partial); init(): void; destroy(): void; enable(): this; disable(): this; highlightItem(item: InputChoice, runEvent?: boolean): this; unhighlightItem(item: InputChoice, runEvent?: boolean): this; highlightAll(): this; unhighlightAll(): this; removeActiveItemsByValue(value: string): this; removeActiveItems(excludedId?: number): this; removeHighlightedItems(runEvent?: boolean): this; showDropdown(preventInputFocus?: boolean): this; hideDropdown(preventInputBlur?: boolean): this; getValue(valueOnly?: B): EventChoiceValueType | EventChoiceValueType[]; setValue(items: string[] | InputChoice[]): this; setChoiceByValue(value: string | string[]): this; /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ setChoices(choicesArrayOrFetcher?: (InputChoice | InputGroup)[] | ((instance: Choices) => (InputChoice | InputGroup)[] | Promise<(InputChoice | InputGroup)[]>), value?: string | null, label?: string, replaceChoices?: boolean, clearSearchFlag?: boolean, replaceItems?: boolean): this | Promise; refresh(withEvents?: boolean, selectFirstOption?: boolean, deselectAll?: boolean): this; removeChoice(value: string): this; clearChoices(clearOptions?: boolean, clearItems?: boolean): this; clearStore(clearOptions?: boolean): this; clearInput(): this; _validateConfig(): void; _render(changes?: StateChangeSet): void; _renderChoices(): void; _renderItems(): void; _displayNotice(text: string, type: NoticeType, openDropdown?: boolean): void; _clearNotice(): void; _renderNotice(fragment?: DocumentFragment): void; /** * @deprecated Use utils.getChoiceForOutput */ _getChoiceForOutput(choice: ChoiceFull, keyCode?: number): EventChoice; _triggerChange(value: any): void; _handleButtonAction(element: HTMLElement): void; _handleItemAction(element: HTMLElement, hasShiftKey?: boolean): void; _handleChoiceAction(element: HTMLElement): boolean; _handleBackspace(items: ChoiceFull[]): void; _loadChoices(): void; _handleLoadingState(setLoading?: boolean): void; _handleSearch(value?: string): void; _canAddItems(): boolean; _canCreateItem(value: string): boolean; _searchChoices(value: string): number | null; _stopSearch(): void; _addEventListeners(): void; _removeEventListeners(): void; _onKeyDown(event: KeyboardEvent): void; _onKeyUp(): void; _onInput(): void; _onSelectKey(event: KeyboardEvent, hasItems: boolean): void; _onEnterKey(event: KeyboardEvent, hasActiveDropdown: boolean): void; _onEscapeKey(event: KeyboardEvent, hasActiveDropdown: boolean): void; _onDirectionKey(event: KeyboardEvent, hasActiveDropdown: boolean): void; _onDeleteKey(event: KeyboardEvent, items: ChoiceFull[], hasFocusedInput: boolean): void; _onTouchMove(): void; _onTouchEnd(event: TouchEvent): void; /** * Handles mousedown event in capture mode for containetOuter.element */ _onMouseDown(event: MouseEvent): void; /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ _onMouseOver({ target }: Pick): void; _onClick({ target }: Pick): void; _onFocus({ target }: Pick): void; _onBlur({ target }: Pick): void; _onFormReset(): void; _onChange(event: Event & { target: HTMLInputElement | HTMLSelectElement; }): void; _onInvalid(): void; /** * Removes any highlighted choice options */ _removeHighlightedChoices(): void; _highlightChoice(el?: HTMLElement | null): void; _addItem(item: ChoiceFull, withEvents?: boolean, userTriggered?: boolean): void; _removeItem(item: ChoiceFull): void; _addChoice(choice: ChoiceFull, withEvents?: boolean, userTriggered?: boolean): void; _addGroup(group: GroupFull, withEvents?: boolean): void; _createTemplates(): void; _createElements(): void; _createStructure(): void; _initStore(): void; _addPredefinedChoices(choices: (ChoiceFull | GroupFull)[], selectFirstOption?: boolean, withEvents?: boolean): void; _findAndSelectChoiceByValue(value: string, userTriggered?: boolean): boolean; _generatePlaceholderValue(): string | null; _warnChoicesInitFailed(caller: string): void; } export default Choices; ================================================ FILE: public/types/src/scripts/components/container.d.ts ================================================ import { ClassNames } from '../interfaces/class-names'; import { PositionOptionsType } from '../interfaces/position-options-type'; import { PassedElementType } from '../interfaces/passed-element-type'; export default class Container { element: HTMLElement; type: PassedElementType; classNames: ClassNames; position: PositionOptionsType; isOpen: boolean; isFlipped: boolean; isDisabled: boolean; isLoading: boolean; constructor({ element, type, classNames, position, }: { element: HTMLElement; type: PassedElementType; classNames: ClassNames; position: PositionOptionsType; }); /** * Determine whether container should be flipped based on passed * dropdown position */ shouldFlip(dropdownPos: number, dropdownHeight: number): boolean; setActiveDescendant(activeDescendantID: string): void; removeActiveDescendant(): void; open(dropdownPos: number, dropdownHeight: number): void; close(): void; addFocusState(): void; removeFocusState(): void; addInvalidState(): void; removeInvalidState(): void; enable(): void; disable(): void; wrap(element: HTMLElement): void; unwrap(element: HTMLElement): void; addLoadingState(): void; removeLoadingState(): void; } ================================================ FILE: public/types/src/scripts/components/dropdown.d.ts ================================================ import { ClassNames } from '../interfaces/class-names'; import { PassedElementType } from '../interfaces/passed-element-type'; export default class Dropdown { element: HTMLElement; type: PassedElementType; classNames: ClassNames; isActive: boolean; constructor({ element, type, classNames, }: { element: HTMLElement; type: PassedElementType; classNames: ClassNames; }); /** * Show dropdown to user by adding active state class */ show(): this; /** * Hide dropdown from user */ hide(): this; } ================================================ FILE: public/types/src/scripts/components/index.d.ts ================================================ import Dropdown from './dropdown'; import Container from './container'; import Input from './input'; import List from './list'; import WrappedInput from './wrapped-input'; import WrappedSelect from './wrapped-select'; export { Dropdown, Container, Input, List, WrappedInput, WrappedSelect }; ================================================ FILE: public/types/src/scripts/components/input.d.ts ================================================ import { ClassNames } from '../interfaces/class-names'; import { PassedElementType } from '../interfaces/passed-element-type'; export default class Input { element: HTMLInputElement; type: PassedElementType; classNames: ClassNames; preventPaste: boolean; isFocussed: boolean; isDisabled: boolean; constructor({ element, type, classNames, preventPaste, }: { element: HTMLInputElement; type: PassedElementType; classNames: ClassNames; preventPaste: boolean; }); set placeholder(placeholder: string); get value(): string; set value(value: string); addEventListeners(): void; removeEventListeners(): void; enable(): void; disable(): void; focus(): void; blur(): void; clear(setWidth?: boolean): this; /** * Set the correct input width based on placeholder * value or input value */ setWidth(): void; setActiveDescendant(activeDescendantID: string): void; removeActiveDescendant(): void; _onInput(): void; _onPaste(event: ClipboardEvent): void; _onFocus(): void; _onBlur(): void; } ================================================ FILE: public/types/src/scripts/components/list.d.ts ================================================ export default class List { element: HTMLElement; scrollPos: number; height: number; constructor({ element }: { element: HTMLElement; }); prepend(node: Element | DocumentFragment): void; scrollToTop(): void; scrollToChildElement(element: HTMLElement, direction: 1 | -1): void; _scrollDown(scrollPos: number, strength: number, destination: number): void; _scrollUp(scrollPos: number, strength: number, destination: number): void; _animateScroll(destination: number, direction: number): void; } ================================================ FILE: public/types/src/scripts/components/wrapped-element.d.ts ================================================ import { ClassNames } from '../interfaces/class-names'; import { EventTypes } from '../interfaces/event-type'; import { EventMap } from '../interfaces'; export default class WrappedElement { element: T; classNames: ClassNames; isDisabled: boolean; constructor({ element, classNames }: { element: any; classNames: any; }); get isActive(): boolean; get dir(): string; get value(): string; set value(value: string); conceal(): void; reveal(): void; enable(): void; disable(): void; triggerEvent(eventType: EventTypes, data?: EventMap[K]['detail']): void; } ================================================ FILE: public/types/src/scripts/components/wrapped-input.d.ts ================================================ import WrappedElement from './wrapped-element'; export default class WrappedInput extends WrappedElement { } ================================================ FILE: public/types/src/scripts/components/wrapped-select.d.ts ================================================ import { ClassNames } from '../interfaces/class-names'; import WrappedElement from './wrapped-element'; import { GroupFull } from '../interfaces/group-full'; import { ChoiceFull } from '../interfaces/choice-full'; export default class WrappedSelect extends WrappedElement { classNames: ClassNames; template: (data: object) => HTMLOptionElement; extractPlaceholder: boolean; constructor({ element, classNames, template, extractPlaceholder, }: { element: HTMLSelectElement; classNames: ClassNames; template: (data: object) => HTMLOptionElement; extractPlaceholder: boolean; }); get placeholderOption(): HTMLOptionElement | null; addOptions(choices: ChoiceFull[]): void; optionsAsChoices(): (ChoiceFull | GroupFull)[]; _optionToChoice(option: HTMLOptionElement): ChoiceFull; _optgroupToChoice(optgroup: HTMLOptGroupElement): GroupFull; } ================================================ FILE: public/types/src/scripts/constants.d.ts ================================================ export declare const SCROLLING_SPEED: number; ================================================ FILE: public/types/src/scripts/defaults.d.ts ================================================ import { ClassNames } from './interfaces/class-names'; import { Options } from './interfaces/options'; export declare const DEFAULT_CLASSNAMES: ClassNames; export declare const DEFAULT_CONFIG: Options; ================================================ FILE: public/types/src/scripts/interfaces/action-type.d.ts ================================================ import { Types } from './types'; export declare const ActionType: { readonly ADD_CHOICE: "ADD_CHOICE"; readonly REMOVE_CHOICE: "REMOVE_CHOICE"; readonly FILTER_CHOICES: "FILTER_CHOICES"; readonly ACTIVATE_CHOICES: "ACTIVATE_CHOICES"; readonly CLEAR_CHOICES: "CLEAR_CHOICES"; readonly ADD_GROUP: "ADD_GROUP"; readonly ADD_ITEM: "ADD_ITEM"; readonly REMOVE_ITEM: "REMOVE_ITEM"; readonly HIGHLIGHT_ITEM: "HIGHLIGHT_ITEM"; }; export type ActionTypes = Types.ValueOf; ================================================ FILE: public/types/src/scripts/interfaces/build-flags.d.ts ================================================ export declare const canUseDom: boolean; export declare const searchFuse: string | undefined; export declare const searchKMP: boolean; /** * These are not directly used, as an exported object (even as const) will prevent tree-shake away code paths */ export declare const BuildFlags: { readonly searchFuse: string | undefined; readonly searchKMP: boolean; readonly canUseDom: boolean; }; ================================================ FILE: public/types/src/scripts/interfaces/choice-full.d.ts ================================================ import { StringUntrusted } from './string-untrusted'; import { StringPreEscaped } from './string-pre-escaped'; import { Types } from './types'; import { GroupFull } from './group-full'; export interface ChoiceFull { id: number; highlighted: boolean; element?: HTMLOptionElement | HTMLOptGroupElement; itemEl?: HTMLElement; choiceEl?: HTMLElement; labelClass?: Array; labelDescription?: StringPreEscaped | StringUntrusted | string; customProperties?: Types.CustomProperties; disabled: boolean; active: boolean; elementId?: string; group: GroupFull | null; label: StringUntrusted | string; placeholder: boolean; selected: boolean; value: string; score: number; rank: number; } ================================================ FILE: public/types/src/scripts/interfaces/class-names.d.ts ================================================ /** Classes added to HTML generated by By default classnames follow the BEM notation. */ export interface ClassNames { /** @default ['choices'] */ containerOuter: string | Array; /** @default ['choices__inner'] */ containerInner: string | Array; /** @default ['choices__input'] */ input: string | Array; /** @default ['choices__input--cloned'] */ inputCloned: string | Array; /** @default ['choices__list'] */ list: string | Array; /** @default ['choices__list--multiple'] */ listItems: string | Array; /** @default ['choices__list--single'] */ listSingle: string | Array; /** @default ['choices__list--dropdown'] */ listDropdown: string | Array; /** @default ['choices__item'] */ item: string | Array; /** @default ['choices__item--selectable'] */ itemSelectable: string | Array; /** @default ['choices__item--disabled'] */ itemDisabled: string | Array; /** @default ['choices__item--choice'] */ itemChoice: string | Array; /** @default ['choices__description'] */ description: string | Array; /** @default ['choices__placeholder'] */ placeholder: string | Array; /** @default ['choices__group'] */ group: string | Array; /** @default ['choices__heading'] */ groupHeading: string | Array; /** @default ['choices__button'] */ button: string | Array; /** @default ['is-active'] */ activeState: string | Array; /** @default ['is-focused'] */ focusState: string | Array; /** @default ['is-open'] */ openState: string | Array; /** @default ['is-disabled'] */ disabledState: string | Array; /** @default ['is-highlighted'] */ highlightedState: string | Array; /** @default ['is-selected'] */ selectedState: string | Array; /** @default ['is-flipped'] */ flippedState: string | Array; /** @default ['is-loading'] */ loadingState: string | Array; /** @default ['is-invalid'] */ invalidState: string | Array; /** @default ['choices__notice'] */ notice: string | Array; /** @default ['choices__item--selectable', 'add-choice'] */ addChoice: string | Array; /** @default ['has-no-results'] */ noResults: string | Array; /** @default ['has-no-choices'] */ noChoices: string | Array; } ================================================ FILE: public/types/src/scripts/interfaces/event-choice.d.ts ================================================ import { InputChoice } from './input-choice'; export type EventChoiceValueType = B extends true ? string : EventChoice; export interface EventChoice extends InputChoice { element?: HTMLOptionElement | HTMLOptGroupElement; groupValue?: string; keyCode?: number; } ================================================ FILE: public/types/src/scripts/interfaces/event-type.d.ts ================================================ import { Types } from './types'; export declare const EventType: { readonly showDropdown: "showDropdown"; readonly hideDropdown: "hideDropdown"; readonly change: "change"; readonly choice: "choice"; readonly search: "search"; readonly addItem: "addItem"; readonly removeItem: "removeItem"; readonly highlightItem: "highlightItem"; readonly highlightChoice: "highlightChoice"; readonly unhighlightItem: "unhighlightItem"; }; export type EventTypes = Types.ValueOf; ================================================ FILE: public/types/src/scripts/interfaces/group-full.d.ts ================================================ import { ChoiceFull } from './choice-full'; export interface GroupFull { id: number; active: boolean; disabled: boolean; label?: string; element?: HTMLOptGroupElement; groupEl?: HTMLElement; choices: ChoiceFull[]; } ================================================ FILE: public/types/src/scripts/interfaces/index.d.ts ================================================ export * from './action-type'; export * from './input-choice'; export * from './input-group'; export * from './event-choice'; export * from './class-names'; export * from './event-type'; export * from './item'; export * from './keycode-map'; export * from './options'; export * from './passed-element'; export * from './passed-element-type'; export * from './position-options-type'; export * from './state'; export * from './types'; ================================================ FILE: public/types/src/scripts/interfaces/input-choice.d.ts ================================================ import { StringUntrusted } from './string-untrusted'; import { StringPreEscaped } from './string-pre-escaped'; import { Types } from './types'; export interface InputChoice { id?: number; highlighted?: boolean; labelClass?: string | Array; labelDescription?: StringPreEscaped | StringUntrusted | string; customProperties?: Types.CustomProperties; disabled?: boolean; active?: boolean; label: StringUntrusted | string; placeholder?: boolean; selected?: boolean; value: any; } ================================================ FILE: public/types/src/scripts/interfaces/input-group.d.ts ================================================ import { InputChoice } from './input-choice'; import { StringUntrusted } from './string-untrusted'; export interface InputGroup { id?: number; active?: boolean; disabled?: boolean; label?: StringUntrusted | string; value: string; choices: InputChoice[]; } ================================================ FILE: public/types/src/scripts/interfaces/item.d.ts ================================================ import { InputChoice } from './input-choice'; import { InputGroup } from './input-group'; /** * @deprecated Use InputChoice instead */ export interface Item extends InputChoice { } /** * @deprecated Use InputChoice instead */ export interface Choice extends InputChoice { } /** * @deprecated Use InputGroup instead */ export interface Group extends InputGroup { } ================================================ FILE: public/types/src/scripts/interfaces/keycode-map.d.ts ================================================ export declare const KeyCodeMap: { readonly TAB_KEY: 9; readonly SHIFT_KEY: 16; readonly BACK_KEY: 46; readonly DELETE_KEY: 8; readonly ENTER_KEY: 13; readonly A_KEY: 65; readonly ESC_KEY: 27; readonly UP_KEY: 38; readonly DOWN_KEY: 40; readonly PAGE_UP_KEY: 33; readonly PAGE_DOWN_KEY: 34; }; ================================================ FILE: public/types/src/scripts/interfaces/options.d.ts ================================================ import { IFuseOptions } from 'fuse.js'; import { InputChoice } from './input-choice'; import { ClassNames } from './class-names'; import { PositionOptionsType } from './position-options-type'; import { Types } from './types'; import { CallbackOnCreateTemplatesFn } from './templates'; export declare const ObjectsInConfig: string[]; /** * Choices options interface * * **Terminology** * * - **Choice:** A choice is a value a user can select. A choice would be equivalent to the `` element within a select input. * - **Group:** A group is a collection of choices. A group should be seen as equivalent to a `` element within a select input. * - **Item:** An item is an inputted value **_(text input)_** or a selected choice **_(select element)_**. In the context of a select element, an item is equivelent to a selected option element: `` whereas in the context of a text input an item is equivelant to `` */ export interface Options { /** * Optionally suppress console errors and warnings. * * **Input types affected:** text, select-single, select-multiple * * @default false */ silent: boolean; /** * Add pre-selected items (see terminology) to text input. * * **Input types affected:** text * * @example * ``` * ['value 1', 'value 2', 'value 3'] * ``` * * @example * ``` * [{ * value: 'Value 1', * label: 'Label 1', * id: 1 * }, * { * value: 'Value 2', * label: 'Label 2', * id: 2, * customProperties: { * random: 'I am a custom property' * } * }] * ``` * * @default [] */ items: string[] | InputChoice[]; /** * Add choices (see terminology) to select input. * * **Input types affected:** select-one, select-multiple * * @example * ``` * [{ * value: 'Option 1', * label: 'Option 1', * selected: true, * disabled: false, * }, * { * value: 'Option 2', * label: 'Option 2', * selected: false, * disabled: true, * customProperties: { * description: 'Custom description about Option 2', * random: 'Another random custom property' * }, * }, * { * label: 'Group 1', * choices: [{ * value: 'Option 3', * label: 'Option 4', * selected: true, * disabled: false, * }, * { * value: 'Option 2', * label: 'Option 2', * selected: false, * disabled: true, * customProperties: { * description: 'Custom description about Option 2', * random: 'Another random custom property' * } * }] * }] * ``` * * @default [] */ choices: InputChoice[]; /** * The amount of choices to be rendered within the dropdown list `("-1" indicates no limit)`. This is useful if you have a lot of choices where it is easier for a user to use the search area to find a choice. * * **Input types affected:** select-one, select-multiple * * @default -1 */ renderChoiceLimit: number; /** * The amount of items a user can input/select `("-1" indicates no limit)`. * * **Input types affected:** text, select-multiple * * @default -1 */ maxItemCount: number; /** * Control how the dropdown closes after making a selection for select-one or select-multiple * * 'auto' defaults based on backing-element type: * select-one: true * select-multiple: false * * **Input types affected:** select-one, select-multiple * * @default 'auto' */ closeDropdownOnSelect: boolean | 'auto'; /** * Make select-multiple with a max item count of 1 work similar to select-one does. * Selecting an item will auto-close the dropdown and swap any existing item for the just selected choice. * If applied to a select-one, it functions as above and not the standard select-one. * * **Input types affected:** select-one, select-multiple * * @default false */ singleModeForMultiSelect: boolean; /** * Whether a user can add choices dynamically. * * **Input types affected:** select-one, select-multiple * * @default false */ addChoices: boolean; /** * Whether a user can add items. * * **Input types affected:** text * * @default true */ addItems: boolean; /** * A filter that will need to pass for a user to successfully add an item. * * **Input types affected:** text, select-one, select-multiple * * @default (value) => !!value && value !== '' */ addItemFilter: string | RegExp | Types.FilterFunction | null; /** * The text that is shown when a user has inputted a new item but has not pressed the enter key. To access the current input value, pass a function with a `value` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string. * The raw non-sanitised value is passed as a 2nd argument. * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * **Input types affected:** text, one-select, select-one, select-multiple * * @default * ``` * (value, valueRaw) => `Press Enter to add "${value}"`; * ``` */ addItemText: string | Types.NoticeStringFunction; /** * The text/icon for the remove button. To access the item's value, pass a function with a `value` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string. * The raw non-sanitised value is passed as a 2nd argument. * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * **Input types affected:** text, select-one, select-multiple * * @default * ``` * (value, valueRaw, item) => `Remove item`; * ``` */ removeItemIconText: string | Types.NoticeStringFunction; /** * The text for the remove button's aria label. To access the item's value, pass a function with a `value` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string. * The raw non-sanitised value is passed as a 2nd argument. * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * **Input types affected:** text, select-one, select-multiple * * @default * ``` * (value, valueRaw, item) => `Remove item: ${value}`; * ``` */ removeItemLabelText: string | Types.NoticeStringFunction; /** * Whether a user can remove items. * * **Input types affected:** text, select-multiple * * @default true */ removeItems: boolean; /** * Whether each item should have a remove button. * * **Input types affected:** text, select-one, select-multiple * * @default false */ removeItemButton: boolean; /** * Align item remove button left vs right. * * **Input types affected:** text, select-one, select-multiple * * @default false */ removeItemButtonAlignLeft: boolean; /** * Whether a user can edit items. An item's value can be edited by pressing the backspace. * * **Input types affected:** text * * @default false */ editItems: boolean; /** * Whether HTML should be rendered in all Choices elements. * If `false`, all elements (placeholder, items, etc.) will be treated as plain text. * If `true`, this can be used to perform XSS scripting attacks if you load choices from a remote source. * * **Input types affected:** text, select-one, select-multiple * * @default false */ allowHTML: boolean; /** * Whether HTML should be escaped on input when `addItems` or `addChoices` is true. * If `false`, user input will be treated as plain text. * If `true`, this can be used to perform XSS scripting attacks if you load previously submitted choices from a remote source. * * **Input types affected:** text, select-one, select-multiple * * @default false */ allowHtmlUserInput: boolean; /** * Whether each inputted/chosen item should be unique. * * **Input types affected:** text, `select-multiple`, `select-one` * * @default true */ duplicateItemsAllowed: boolean; /** * What divides each value. The default delimiter separates each value with a comma: `"Value 1, Value 2, Value 3"`. * * **Input types affected:** text * * @default ',' */ delimiter: string; /** * Whether a user can paste into the input. * * **Input types affected:** text, select-multiple * * @default true */ paste: boolean; /** * Whether a search area should be shown. * * **Input types affected:** select-one, select-multiple * * @default true */ searchEnabled: boolean; /** * Whether choices should be filtered by input or not. If `false`, the search event will still emit, but choices will not be filtered. * * **Input types affected:** select-one * * @default true */ searchChoices: boolean; /** * Whether disabled choices should be included in search results. If `true`, disabled choices will appear in search results but still cannot be selected. * * **Input types affected:** select-one, select-multiple * * @default false */ searchDisabledChoices: boolean; /** * The minimum length a search value should be before choices are searched. * * **Input types affected:** select-one, select-multiple * * @default 1 */ searchFloor: number; /** * The maximum amount of search results to show. `("-1" indicates no limit)` * * **Input types affected:** select-one, select-multiple * * @default 4 */ searchResultLimit: number; /** * Specify which fields should be used when a user is searching. If you have added custom properties to your choices, you can add these values thus: `['label', 'value', 'customProperties.example']`. * * Input types affected:select-one, select-multiple * * @default ['label', 'value'] */ searchFields: string[]; /** * Whether the dropdown should appear above `(top)` or below `(bottom)` the input. By default, if there is not enough space within the window the dropdown will appear above the input, otherwise below it. * * **Input types affected:** select-one, select-multiple * * @default 'auto' */ position: PositionOptionsType; /** * Whether the scroll position should reset after adding an item. * * **Input types affected:** select-multiple * * @default true */ resetScrollPosition: boolean; /** * The shadow root for use within ShadowDom */ shadowRoot: ShadowRoot | null; /** * Whether choices and groups should be sorted. If false, choices/groups will appear in the order they were given. * * **Input types affected:** select-one, select-multiple * * @default true */ shouldSort: boolean; /** * Whether items should be sorted. If false, items will appear in the order they were selected. * * **Input types affected:** text, select-multiple * * @default false */ shouldSortItems: boolean; /** * The function that will sort choices and items before they are displayed (unless a user is searching). By default choices and items are sorted by alphabetical order. * * **Input types affected:** select-one, select-multiple * * @example * ``` * // Sorting via length of label from largest to smallest * const example = new Choices(element, { * sorter: function(a, b) { * return b.label.length - a.label.length; * }, * }; * ``` * * @default sortByAlpha */ sorter: (current: Types.RecordToCompare, next: Types.RecordToCompare) => number; /** * Whether the input should show a placeholder. Used in conjunction with `placeholderValue`. If `placeholder` is set to true and no value is passed to `placeholderValue`, the passed input's placeholder attribute will be used as the placeholder value. * * **Input types affected:** text, select-multiple * * @note For single select boxes, the recommended way of adding a placeholder is as follows: * ``` * * ... * ... * ... * * ``` * * @default true */ placeholder: boolean; /** * The value of the inputs placeholder. * * **Input types affected:** text, select-multiple * * @default null */ placeholderValue: string | null; /** * The value of the search inputs placeholder. * * **Input types affected:** select-one * * @default null */ searchPlaceholderValue: string | null; /** * Prepend a value to each item added/selected. * * **Input types affected:** text, select-one, select-multiple * * @default null */ prependValue: string | null; /** * Append a value to each item added/selected. * * **Input types affected:** text, select-one, select-multiple * * @default null */ appendValue: string | null; /** * Whether selected choices should be removed from the list. By default choices are removed when they are selected in multiple select box. To always render choices pass `always`. * * **Input types affected:** select-one, select-multiple * * @default 'auto'; */ renderSelectedChoices: 'auto' | 'always' | boolean; /** * Whether selected choices should be removed from the list during search. * * **Input types affected:** select-multiple * * @default false; */ searchRenderSelectedChoices: boolean; /** * The text that is shown whilst choices are being populated via AJAX. * * **Input types affected:** select-one, select-multiple * * @default 'Loading...' */ loadingText: string; /** * The text that is shown when a user's search has returned no results. Optionally pass a function returning a string. * * **Input types affected:** select-one, select-multiple * * @default 'No results found' */ noResultsText: string | Types.StringFunction; /** * The text that is shown when a user has selected all possible choices, or no choices exist. Optionally pass a function returning a string. * * **Input types affected:** select-multiple, select-one * * @default 'No choices to choose from' */ noChoicesText: string | Types.StringFunction; /** * The text that is shown when a user hovers over a selectable choice. Set to empty to not reserve space for this text. * * **Input types affected:** select-multiple, select-one * * @default 'Press to select' */ itemSelectText: string; /** * The text that is shown when a user has focus on the input but has already reached the **max item count** [https://github.com/jshjohnson/Choices#maxitemcount]. To access the max item count, pass a function with a `maxItemCount` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string. * * **Input types affected:** text * * @default * ``` * (maxItemCount) => `Only ${maxItemCount} values can be added.`; * ``` */ maxItemText: string | Types.NoticeLimitFunction; /** * If no duplicates are allowed, and the value already exists in the array. * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * @default 'Only unique values can be added' */ uniqueItemText: string | Types.NoticeStringFunction; /** * The text that is shown when addItemFilter is passed and it returns false * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * **Input types affected:** text * * @default 'Only values matching specific conditions can be added' */ customAddItemText: string | Types.NoticeStringFunction; /** * Compare choice and value in appropriate way (e.g. deep equality for objects). To compare choice and value, pass a function with a `valueComparer` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example). * * **Input types affected:** select-one, select-multiple * * @default * ``` * (choice, item) => choice === item; * ``` */ valueComparer: Types.ValueCompareFunction; /** * Classes added to HTML generated by By default classnames follow the BEM notation. * * **Input types affected:** text, select-one, select-multiple */ classNames: ClassNames; /** * Choices uses the great Fuse library for searching. You can find more options here: https://fusejs.io/api/options.html */ fuseOptions: IFuseOptions; /** * ID of the connected label to improve a11y. If set, aria-labelledby will be added. */ labelId: string; /** * Function to run once Choices initialises. * * **Input types affected:** text, select-one, select-multiple * * @note For each callback, this refers to the current instance of This can be useful if you need access to methods `(this.disable())` or the config object `(this.config)`. * * @default null */ callbackOnInit: (() => void) | null; /** * Function to run on template creation. Through this callback it is possible to provide custom templates for the various components of Choices (see terminology). For Choices to work with custom templates, it is important you maintain the various data attributes defined here [https://github.com/jshjohnson/Choices/blob/67f29c286aa21d88847adfcd6304dc7d068dc01f/assets/scripts/src/choices.js#L1993-L2067]. * * **Input types affected:** text, select-one, select-multiple * * @note For each callback, `this` refers to the current instance of Choices. This can be useful if you need access to methods `(this.disable())`. * * @example * ``` * const example = new Choices(element, { * callbackOnCreateTemplates: function (template, originalTemplates, getClassNames) { * var classNames = this.config.classNames; * return { * item: (data) => { * return template(` * * ★ ${data.label} * * `); * }, * choice: (data) => { * return template(` * * ★ ${data.label} * * `); * }, * }; * } * }); * ``` * * @default null */ callbackOnCreateTemplates: CallbackOnCreateTemplatesFn | null; appendGroupInSearch: boolean; } ================================================ FILE: public/types/src/scripts/interfaces/passed-element-type.d.ts ================================================ import { Types } from './types'; export declare const PassedElementTypes: { readonly Text: "text"; readonly SelectOne: "select-one"; readonly SelectMultiple: "select-multiple"; }; export type PassedElementType = Types.ValueOf; ================================================ FILE: public/types/src/scripts/interfaces/passed-element.d.ts ================================================ import { InputChoice } from './input-choice'; import { EventChoice } from './event-choice'; /** * Events fired by Choices behave the same as standard events. Each event is triggered on the element passed to Choices (accessible via `this.passedElement`. Arguments are accessible within the `event.detail` object. */ export interface EventMap { /** * Triggered each time an item is added (programmatically or by the user). * * **Input types affected:** text, select-one, select-multiple * * Arguments: id, value, label, groupValue */ addItem: CustomEvent; /** * Triggered each time an item is removed (programmatically or by the user). * * **Input types affected:** text, select-one, select-multiple * * Arguments: id, value, label, groupValue */ removeItem: CustomEvent; /** * Triggered each time an item is highlighted. * * **Input types affected:** text, select-multiple * * Arguments: id, value, label, groupValue */ highlightItem: CustomEvent; /** * Triggered each time an item is unhighlighted. * * **Input types affected:** text, select-multiple * * Arguments: id, value, label, groupValue */ unhighlightItem: CustomEvent; /** * Triggered each time a choice is selected **by a user**, regardless if it changes the value of the input. * * **Input types affected:** select-one, select-multiple * * Arguments: choice: Choice */ choice: CustomEvent<{ choice: InputChoice; }>; /** * Triggered each time an item is added/removed **by a user**. * * **Input types affected:** text, select-one, select-multiple * * Arguments: value */ change: CustomEvent<{ value: string; }>; /** * Triggered when a user types into an input to search choices. When a search is ended, a search event with an empty value with no resultCount is triggered. * * **Input types affected:** select-one, select-multiple * * Arguments: value, resultCount */ search: CustomEvent<{ value: string; resultCount: number; }>; /** * Triggered when the dropdown is shown. * * **Input types affected:** select-one, select-multiple * * Arguments: - */ showDropdown: CustomEvent; /** * Triggered when the dropdown is hidden. * * **Input types affected:** select-one, select-multiple * * Arguments: - */ hideDropdown: CustomEvent; /** * Triggered when a choice from the dropdown is highlighted. * * Input types affected: select-one, select-multiple * Arguments: el is the choice.passedElement that was affected. */ highlightChoice: CustomEvent<{ el: HTMLElement; }>; } ================================================ FILE: public/types/src/scripts/interfaces/position-options-type.d.ts ================================================ export type PositionOptionsType = 'auto' | 'top' | 'bottom'; ================================================ FILE: public/types/src/scripts/interfaces/search.d.ts ================================================ export interface SearchResult { item: T; score: number; rank: number; } export interface Searcher { reset(): void; isEmptyIndex(): boolean; index(data: T[]): void; search(needle: string): SearchResult[]; } ================================================ FILE: public/types/src/scripts/interfaces/state.d.ts ================================================ import { ChoiceFull } from './choice-full'; import { GroupFull } from './group-full'; export interface State { choices: ChoiceFull[]; groups: GroupFull[]; items: ChoiceFull[]; } export type StateChangeSet = { [K in keyof State]: boolean; }; ================================================ FILE: public/types/src/scripts/interfaces/store.d.ts ================================================ import { StateChangeSet, State } from './state'; import { ChoiceFull } from './choice-full'; import { GroupFull } from './group-full'; import { ActionTypes } from './action-type'; export interface AnyAction { type: A; } export interface StateUpdate { update: boolean; state: T; } export type Reducer = (state: T, action: AnyAction, context?: unknown) => StateUpdate; export type StoreListener = (changes: StateChangeSet) => void; export interface Store { dispatch(action: AnyAction): void; subscribe(onChange: StoreListener): void; withTxn(func: () => void): void; reset(): void; get defaultState(): State; /** * Get store object */ get state(): State; /** * Get items from store */ get items(): ChoiceFull[]; /** * Get highlighted items from store */ get highlightedActiveItems(): ChoiceFull[]; /** * Get choices from store */ get choices(): ChoiceFull[]; /** * Get active choices from store */ get activeChoices(): ChoiceFull[]; /** * Get choices that can be searched (excluding placeholders, * optionally excluding disabled based on config) */ get searchableChoices(): ChoiceFull[]; /** * Get groups from store */ get groups(): GroupFull[]; /** * Get active groups from store */ get activeGroups(): GroupFull[]; /** * Get loading state from store */ inTxn(): boolean; /** * Get single choice by it's ID */ getChoiceById(id: number): ChoiceFull | undefined; /** * Get group by group id */ getGroupById(id: number): GroupFull | undefined; } ================================================ FILE: public/types/src/scripts/interfaces/string-pre-escaped.d.ts ================================================ export interface StringPreEscaped { readonly trusted: string; } ================================================ FILE: public/types/src/scripts/interfaces/string-untrusted.d.ts ================================================ export interface StringUntrusted { readonly escaped: string; readonly raw: string; } ================================================ FILE: public/types/src/scripts/interfaces/templates.d.ts ================================================ import { PassedElementType } from './passed-element-type'; import { StringPreEscaped } from './string-pre-escaped'; import { ChoiceFull } from './choice-full'; import { GroupFull } from './group-full'; import { Options } from './options'; import { Types } from './types'; export type TemplateOptions = Pick; export declare const NoticeTypes: { readonly noChoices: "no-choices"; readonly noResults: "no-results"; readonly addChoice: "add-choice"; readonly generic: ""; }; export type NoticeType = Types.ValueOf; export type CallbackOnCreateTemplatesFn = (template: Types.StrToEl, escapeForTemplate: Types.EscapeForTemplateFn, getClassNames: Types.GetClassNamesFn) => Partial; export interface Templates { containerOuter(options: TemplateOptions, dir: HTMLElement['dir'], isSelectElement: boolean, isSelectOneElement: boolean, searchEnabled: boolean, passedElementType: PassedElementType, labelId: string): HTMLDivElement; containerInner({ classNames: { containerInner } }: TemplateOptions): HTMLDivElement; itemList(options: TemplateOptions, isSelectOneElement: boolean): HTMLDivElement; placeholder(options: TemplateOptions, value: StringPreEscaped | string): HTMLDivElement; item(options: TemplateOptions, choice: ChoiceFull, removeItemButton: boolean): HTMLDivElement; choiceList(options: TemplateOptions, isSelectOneElement: boolean): HTMLDivElement; choiceGroup(options: TemplateOptions, group: GroupFull): HTMLDivElement; choice(options: TemplateOptions, choice: ChoiceFull, selectText: string, groupText?: string): HTMLDivElement; input(options: TemplateOptions, placeholderValue: string | null): HTMLInputElement; dropdown(options: TemplateOptions): HTMLDivElement; notice(options: TemplateOptions, innerText: string, type: NoticeType): HTMLDivElement; option(choice: ChoiceFull): HTMLOptionElement; } ================================================ FILE: public/types/src/scripts/interfaces/types.d.ts ================================================ import { StringUntrusted } from './string-untrusted'; import { StringPreEscaped } from './string-pre-escaped'; import { EventChoice } from './event-choice'; export declare namespace Types { type StrToEl = (str: string) => HTMLElement | HTMLInputElement | HTMLOptionElement; type EscapeForTemplateFn = (allowHTML: boolean, s: StringUntrusted | StringPreEscaped | string) => string; type GetClassNamesFn = (s: string | Array) => string; type StringFunction = () => string; type NoticeStringFunction = (value: string, valueRaw: string, item?: EventChoice) => string; type NoticeLimitFunction = (maxItemCount: number) => string; type FilterFunction = (value: string) => boolean; type ValueCompareFunction = (value1: string, value2: string) => boolean; interface RecordToCompare { value?: StringUntrusted | string; label?: StringUntrusted | string; } type ValueOf = T[keyof T]; type CustomProperties = Record | string; } ================================================ FILE: public/types/src/scripts/lib/choice-input.d.ts ================================================ import { InputChoice } from '../interfaces/input-choice'; import { InputGroup } from '../interfaces/input-group'; import { GroupFull } from '../interfaces/group-full'; import { ChoiceFull } from '../interfaces/choice-full'; type MappedInputTypeToChoiceType = T extends InputGroup ? GroupFull : ChoiceFull; export declare const coerceBool: (arg: unknown, defaultValue?: boolean) => boolean; export declare const stringToHtmlClass: (input: string | string[] | undefined) => string[] | undefined; export declare const mapInputToChoice: (value: T, allowGroup: boolean, allowRawString?: boolean) => MappedInputTypeToChoiceType; export {}; ================================================ FILE: public/types/src/scripts/lib/html-guard-statements.d.ts ================================================ export declare const isHtmlInputElement: (e: Element) => e is HTMLInputElement; export declare const isHtmlSelectElement: (e: Element) => e is HTMLSelectElement; export declare const isHtmlOption: (e: Element) => e is HTMLOptionElement; export declare const isHtmlOptgroup: (e: Element) => e is HTMLOptGroupElement; ================================================ FILE: public/types/src/scripts/lib/utils.d.ts ================================================ import { EventTypes } from '../interfaces/event-type'; import { StringUntrusted } from '../interfaces/string-untrusted'; import { StringPreEscaped } from '../interfaces/string-pre-escaped'; import { ChoiceFull } from '../interfaces/choice-full'; import { Types } from '../interfaces/types'; import { EventChoice } from '../interfaces'; export declare const generateId: (element: HTMLInputElement | HTMLSelectElement, prefix: string) => string; export declare const getAdjacentEl: (startEl: HTMLElement, selector: string, direction?: number) => HTMLElement | null; export declare const isScrolledIntoView: (element: HTMLElement, parent: HTMLElement, direction?: number) => boolean; export declare const sanitise: (value: T | StringUntrusted | StringPreEscaped | string) => T | string; export declare const strToEl: (str: string) => Element; export declare const resolveStringFunction: (fn: Types.StringFunction | string) => string; export declare const unwrapStringForRaw: (s?: StringUntrusted | StringPreEscaped | string) => string; export declare const unwrapStringForEscaped: (s?: StringUntrusted | StringPreEscaped | string) => string; export declare const getChoiceForOutput: (choice: ChoiceFull, keyCode?: number) => EventChoice; export declare const resolveNoticeFunction: (fn: Types.NoticeStringFunction | string, value: StringUntrusted | StringPreEscaped | string, item?: EventChoice) => string; export declare const escapeForTemplate: (allowHTML: boolean, s: StringUntrusted | StringPreEscaped | string) => string; export declare const setElementHtml: (el: HTMLElement, allowHtml: boolean, html: StringUntrusted | StringPreEscaped | string) => void; export declare const sortByAlpha: ({ value, label }: Types.RecordToCompare, { value: value2, label: label2 }: Types.RecordToCompare) => number; export declare const sortByScore: (a: Pick, b: Pick) => number; export declare const sortByRank: (a: Pick, b: Pick) => number; export declare const dispatchEvent: (element: HTMLElement, type: EventTypes, customArgs?: object | null) => boolean; export declare const cloneObject: (obj: T) => T; /** * Returns an array of keys present on the first but missing on the second object */ export declare const diff: (a: Record, b: Record) => string[]; export declare const getClassNames: (ClassNames: Array | string) => Array; export declare const getClassNamesSelector: (option: string | Array | null) => string; export declare const addClassesToElement: (element: HTMLElement, className: Array | string) => void; export declare const removeClassesFromElement: (element: HTMLElement, className: Array | string) => void; export declare const parseCustomProperties: (customProperties?: string) => object | string; export declare const updateClassList: (item: ChoiceFull, add: string | string[], remove: string | string[]) => void; ================================================ FILE: public/types/src/scripts/reducers/choices.d.ts ================================================ import { Options, State } from '../interfaces'; import { StateUpdate } from '../interfaces/store'; import { ChoiceActions } from '../actions/choices'; import { ItemActions } from '../actions/items'; type ActionTypes = ChoiceActions | ItemActions; type StateType = State['choices']; export default function choices(s: StateType, action: ActionTypes, context?: Options): StateUpdate; export {}; ================================================ FILE: public/types/src/scripts/reducers/groups.d.ts ================================================ import { GroupActions } from '../actions/groups'; import { State } from '../interfaces/state'; import { StateUpdate } from '../interfaces/store'; import { ChoiceActions } from '../actions/choices'; type ActionTypes = ChoiceActions | GroupActions; type StateType = State['groups']; export default function groups(s: StateType, action: ActionTypes): StateUpdate; export {}; ================================================ FILE: public/types/src/scripts/reducers/items.d.ts ================================================ import { ItemActions } from '../actions/items'; import { State } from '../interfaces/state'; import { ChoiceActions } from '../actions/choices'; import { Options } from '../interfaces'; import { StateUpdate } from '../interfaces/store'; type ActionTypes = ChoiceActions | ItemActions; type StateType = State['items']; export default function items(s: StateType, action: ActionTypes, context?: Options): StateUpdate; export {}; ================================================ FILE: public/types/src/scripts/search/fuse.d.ts ================================================ import { default as FuseFull, IFuseOptions } from 'fuse.js'; import { default as FuseBasic } from 'fuse.js/basic'; import { Options } from '../interfaces/options'; import { Searcher, SearchResult } from '../interfaces/search'; export declare class SearchByFuse implements Searcher { _fuseOptions: IFuseOptions; _haystack: T[]; _fuse: FuseFull | FuseBasic | undefined; constructor(config: Options); index(data: T[]): void; reset(): void; isEmptyIndex(): boolean; search(needle: string): SearchResult[]; } ================================================ FILE: public/types/src/scripts/search/index.d.ts ================================================ import { Options } from '../interfaces'; import { Searcher } from '../interfaces/search'; export declare function getSearcher(config: Options): Searcher; ================================================ FILE: public/types/src/scripts/search/kmp.d.ts ================================================ import { Options } from '../interfaces'; import { Searcher, SearchResult } from '../interfaces/search'; export declare class SearchByKMP implements Searcher { _fields: string[]; _haystack: T[]; constructor(config: Options); index(data: T[]): void; reset(): void; isEmptyIndex(): boolean; search(_needle: string): SearchResult[]; } ================================================ FILE: public/types/src/scripts/search/prefix-filter.d.ts ================================================ import { Options } from '../interfaces'; import { Searcher, SearchResult } from '../interfaces/search'; export declare class SearchByPrefixFilter implements Searcher { _fields: string[]; _haystack: T[]; constructor(config: Options); index(data: T[]): void; reset(): void; isEmptyIndex(): boolean; search(_needle: string): SearchResult[]; } ================================================ FILE: public/types/src/scripts/store/store.d.ts ================================================ import { AnyAction, Store as IStore, StoreListener } from '../interfaces/store'; import { StateChangeSet, State } from '../interfaces/state'; import { ChoiceFull } from '../interfaces/choice-full'; import { GroupFull } from '../interfaces/group-full'; export default class Store implements IStore { _state: State; _listeners: StoreListener[]; _txn: number; _changeSet?: StateChangeSet; _context: T; constructor(context: T); get defaultState(): State; changeSet(init: boolean): StateChangeSet; reset(): void; subscribe(onChange: StoreListener): this; dispatch(action: AnyAction): void; withTxn(func: () => void): void; /** * Get store object */ get state(): State; /** * Get items from store */ get items(): ChoiceFull[]; /** * Get highlighted items from store */ get highlightedActiveItems(): ChoiceFull[]; /** * Get choices from store */ get choices(): ChoiceFull[]; /** * Get active choices from store */ get activeChoices(): ChoiceFull[]; /** * Get choices that can be searched (excluding placeholders or disabled choices) */ get searchableChoices(): ChoiceFull[]; /** * Get groups from store */ get groups(): GroupFull[]; /** * Get active groups from store */ get activeGroups(): GroupFull[]; inTxn(): boolean; /** * Get single choice by it's ID */ getChoiceById(id: number): ChoiceFull | undefined; /** * Get group by group id */ getGroupById(id: number): GroupFull | undefined; } ================================================ FILE: public/types/src/scripts/templates.d.ts ================================================ /** * Helpers to create HTML elements used by Choices * Can be overridden by providing `callbackOnCreateTemplates` option. * `Choices.defaults.templates` allows access to the default template methods from `callbackOnCreateTemplates` */ import { Templates as TemplatesInterface } from './interfaces/templates'; declare const templates: TemplatesInterface; export default templates; ================================================ FILE: scripts/lint-staged.config.js ================================================ module.exports = { '*.js': ['eslint --fix --quiet -f visualstudio', 'git add'], '*.{ts,scss,yaml,yml,md,html,json,babelrc,eslintrc}': [ 'prettier --write', 'git add', ], 'src/icons/*.svg': [ 'prettier --write --parser=html --html-whitespace-sensitivity=ignore', 'git add', ], '.codecov.yml': () => 'curl -f --silent --data-binary @.codecov.yml https://codecov.io/validate', 'src/scripts/**/*.js': () => 'npm run test:unit', }; ================================================ FILE: scripts/rollup.config.mjs ================================================ import replace from '@rollup/plugin-replace'; import nodeResolve from'@rollup/plugin-node-resolve'; import babel from '@rollup/plugin-babel'; import terser from '@rollup/plugin-terser'; import typescript from '@rollup/plugin-typescript'; import * as fs from 'node:fs'; import server from './server.mjs'; // @ts-ignore const pckg = require('../package.json'); const buildFeatures = { CHOICES_SEARCH_FUSE: "full", // "basic" / "null" CHOICES_SEARCH_KMP: "0", // "1" CHOICES_CAN_USE_DOM: "1", // "0" } // Allow the following to manual set feature flags; // npm run js:build-dev -- --environment SEARCH_FUSE:null let defaultBuild = {}; Object.keys(buildFeatures).forEach((k) => { defaultBuild[k] = process.env[k] || buildFeatures[k]; }) const builds = [ { name: ".", features: defaultBuild }, { name: "search-basic", features: { ...buildFeatures, CHOICES_SEARCH_FUSE: "basic" } }, { name: "search-prefix", features: { ...buildFeatures, CHOICES_SEARCH_FUSE: "null" } }, { name: "search-kmp", features: { ...buildFeatures, CHOICES_SEARCH_KMP: "1" } }, ]; const outputTypes = { js : { prefix: 'iife', ext: 'js', format: 'iife', default: false, }, umd : { ext: 'js', format: 'umd', default: true, }, cjs : { ext: 'cjs', format: 'cjs', default: false, }, mjs : { ext: 'mjs', format: 'es', default: true, }, }; const OUTPUT_TYPES = (process.env.OUTPUT_TYPES || Object.keys(outputTypes).filter(k => outputTypes[k].default).join(',')).split(',') const FILENAME = 'choices' const VERSION = process.env.VERSION || pckg.version const AUTHOR = pckg.author const HOMEPAGE = pckg.homepage const banner = `/*! choices.js v${VERSION} | © ${new Date().getFullYear()} ${AUTHOR} | ${HOMEPAGE} */\n` const withDeclarations = !!process.env.WITH_D_TS_FILES; if (withDeclarations) { [ 'public/types/src', 'public/assets/scripts', ].forEach((p) => { if (fs.existsSync(p)) { fs.rmSync(p, { recursive: true }); } fs.mkdirSync(p, { recursive: true }); }); } const candidateBuilds = process.env.TARGET ? builds.filter((build) => build.name === process.env.TARGET) : builds; const suffix = (s, suffix) => { return s + (suffix ? '.' + suffix : '') } function genConfig(buildConfig) { // built-in vars const vars = { __VERSION__: VERSION, preventAssignment: true, 'process.env.NODE_ENV': JSON.stringify('production') } if ('features' in buildConfig) { Object.keys(buildConfig.features).forEach((key) => { if (buildConfig.features[key] === 'undefined' || buildConfig.features[key] === 'null') { vars[`process.env.${key}`] = 'false'; } else { vars[`process.env.${key}`] = JSON.stringify(buildConfig.features[key]) } }) } const config = { input: 'src/entry.js', plugins: [ nodeResolve(), typescript({ tsconfig: 'src/tsconfig.json', // https://github.com/rollup/plugins/issues/1495 // @rollup/plugin-typescript just doesn't want to reliably generate .d.ts files when "composite" is true, so just copy the tsconfig.json definition around // Additionally tsc no longer accepts relative directories which escape declarationDir "declaration": withDeclarations, // declarationDir in src/tsconfig.json with a value of "./" magically maps to "./src" here... "declarationDir": withDeclarations ? "./src" : undefined, "declarationMap": false, }), replace(vars) ], output: [], } const output = [false, true]; output.forEach((minify) => { OUTPUT_TYPES.forEach((t) => { const type = outputTypes[t]; if (!type) { return; } if (minify && type.format === 'es') { return; } let f = `public/assets/scripts/${FILENAME}`; f = suffix(f, buildConfig.name !== '.' ? buildConfig.name : ''); f = suffix(f, type.prefix ? type.prefix : ''); f = suffix(f, minify ? 'min' : ''); f = suffix(f, type.ext); const output = { banner, file: f, format: type.format, name: 'Choices', exports: 'default', plugins: [] }; if (type.format !== 'es') { config.plugins.push(babel({ babelHelpers: 'bundled' })) } if (minify && type.format !== 'es') { output.plugins.push(terser({ compress: { passes: 2, pure_getters: true, pure_new: true, // unsafe: true, } })) } config.output.push(output); }) }); if (config.output.length === 0) { return false; } return config } let buildConfig = []; candidateBuilds.forEach((build) => { buildConfig.push(genConfig(build)); }); buildConfig = buildConfig.filter((b) => !!b); if (buildConfig.length === 0) { console.log('No valid build targets or feature combinations.'); } else { const localServer = server(); if (localServer) { buildConfig[0].plugins.push(localServer) } } // noinspection JSUnusedGlobalSymbols export default buildConfig; ================================================ FILE: scripts/server.mjs ================================================ import dev from 'rollup-plugin-dev'; export default function server() { const WATCH_HOST = process.env.WATCH_HOST; if (!WATCH_HOST) { return void 0; } const WATCH_PORT = process.env.WATCH_PORT || 3001; return dev({ dirs: ['public'], host: WATCH_HOST, port: WATCH_PORT, force: !!process.env.CI, // silent: !!process.env.CI }); }; ================================================ FILE: src/entry.js ================================================ import Choices from './scripts/choices'; export default Choices; ================================================ FILE: src/index.ts ================================================ import Choices from './scripts/choices'; export * from './scripts/interfaces'; export * from './scripts/constants'; export * from './scripts/defaults'; export { default as templates } from './scripts/templates'; export default Choices; ================================================ FILE: src/scripts/actions/choices.ts ================================================ import { ChoiceFull } from '../interfaces/choice-full'; import { ActionType } from '../interfaces'; import { SearchResult } from '../interfaces/search'; import { AnyAction } from '../interfaces/store'; export type ChoiceActions = | AddChoiceAction | RemoveChoiceAction | FilterChoicesAction | ActivateChoicesAction | ClearChoicesAction; export interface AddChoiceAction extends AnyAction { choice: ChoiceFull; } export interface RemoveChoiceAction extends AnyAction { choice: ChoiceFull; } export interface FilterChoicesAction extends AnyAction { results: SearchResult[]; } export interface ActivateChoicesAction extends AnyAction { active: boolean; } /** * @deprecated use clearStore() or clearChoices() instead. */ export interface ClearChoicesAction extends AnyAction {} export const addChoice = (choice: ChoiceFull): AddChoiceAction => ({ type: ActionType.ADD_CHOICE, choice, }); export const removeChoice = (choice: ChoiceFull): RemoveChoiceAction => ({ type: ActionType.REMOVE_CHOICE, choice, }); export const filterChoices = (results: SearchResult[]): FilterChoicesAction => ({ type: ActionType.FILTER_CHOICES, results, }); export const activateChoices = (active = true): ActivateChoicesAction => ({ type: ActionType.ACTIVATE_CHOICES, active, }); /** * @deprecated use clearStore() or clearChoices() instead. */ export const clearChoices = (): ClearChoicesAction => ({ type: ActionType.CLEAR_CHOICES, }); ================================================ FILE: src/scripts/actions/groups.ts ================================================ import { GroupFull } from '../interfaces/group-full'; import { ActionType } from '../interfaces'; import { AnyAction } from '../interfaces/store'; export type GroupActions = AddGroupAction; export interface AddGroupAction extends AnyAction { group: GroupFull; } export const addGroup = (group: GroupFull): AddGroupAction => ({ type: ActionType.ADD_GROUP, group, }); ================================================ FILE: src/scripts/actions/items.ts ================================================ import { ChoiceFull } from '../interfaces/choice-full'; import { ActionType } from '../interfaces'; import { AnyAction } from '../interfaces/store'; export type ItemActions = AddItemAction | RemoveItemAction | HighlightItemAction; export interface AddItemAction extends AnyAction { item: ChoiceFull; } export interface RemoveItemAction extends AnyAction { item: ChoiceFull; } export interface HighlightItemAction extends AnyAction { item: ChoiceFull; highlighted: boolean; } export const addItem = (item: ChoiceFull): AddItemAction => ({ type: ActionType.ADD_ITEM, item, }); export const removeItem = (item: ChoiceFull): RemoveItemAction => ({ type: ActionType.REMOVE_ITEM, item, }); export const highlightItem = (item: ChoiceFull, highlighted: boolean): HighlightItemAction => ({ type: ActionType.HIGHLIGHT_ITEM, item, highlighted, }); ================================================ FILE: src/scripts/choices.ts ================================================ import { activateChoices, addChoice, removeChoice, filterChoices } from './actions/choices'; import { addGroup } from './actions/groups'; import { addItem, highlightItem, removeItem } from './actions/items'; import { Container, Dropdown, Input, List, WrappedInput, WrappedSelect } from './components'; import { DEFAULT_CONFIG } from './defaults'; import { InputChoice } from './interfaces/input-choice'; import { InputGroup } from './interfaces/input-group'; import { Options, ObjectsInConfig } from './interfaces/options'; import { StateChangeSet } from './interfaces/state'; import { addClassesToElement, diff, escapeForTemplate, generateId, getAdjacentEl, getChoiceForOutput, getClassNames, getClassNamesSelector, isScrolledIntoView, removeClassesFromElement, resolveNoticeFunction, resolveStringFunction, sortByRank, strToEl, unwrapStringForEscaped, } from './lib/utils'; import Store from './store/store'; import { coerceBool, mapInputToChoice } from './lib/choice-input'; import { ChoiceFull } from './interfaces/choice-full'; import { GroupFull } from './interfaces/group-full'; import { EventChoiceValueType, EventType, KeyCodeMap, PassedElementType, PassedElementTypes } from './interfaces'; import { EventChoice } from './interfaces/event-choice'; import { NoticeType, NoticeTypes, Templates } from './interfaces/templates'; import { isHtmlInputElement, isHtmlSelectElement } from './lib/html-guard-statements'; import { Searcher } from './interfaces/search'; import { getSearcher } from './search'; // eslint-disable-next-line import/no-named-default import { default as defaultTemplates } from './templates'; import { canUseDom } from './interfaces/build-flags'; /** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */ const IS_IE11 = canUseDom && '-ms-scroll-limit' in document.documentElement.style && '-ms-ime-align' in document.documentElement.style; const USER_DEFAULTS: Partial = {}; const parseDataSetId = (element: HTMLElement | null): number | undefined => { if (!element) { return undefined; } return element.dataset.id ? parseInt(element.dataset.id, 10) : undefined; }; const selectableChoiceIdentifier = '[data-choice-selectable]'; /** * Choices * @author Josh Johnson */ class Choices { static version: string = '__VERSION__'; static get defaults(): { options: Partial; allOptions: Options; templates: Templates; } { return Object.preventExtensions({ get options(): Partial { return USER_DEFAULTS; }, get allOptions(): Options { return DEFAULT_CONFIG; }, get templates(): Templates { return defaultTemplates; }, }); } initialised: boolean; initialisedOK?: boolean = undefined; config: Options; passedElement: WrappedInput | WrappedSelect; containerOuter: Container; containerInner: Container; choiceList: List; itemList: List; input: Input; dropdown: Dropdown; _elementType: PassedElementType; _isTextElement: boolean; _isSelectOneElement: boolean; _isSelectMultipleElement: boolean; _isSelectElement: boolean; _hasNonChoicePlaceholder: boolean = false; _canAddUserChoices: boolean; _store: Store; _templates: Templates; _lastAddedChoiceId: number = 0; _lastAddedGroupId: number = 0; _currentValue: string; _canSearch: boolean; _isScrollingOnIe: boolean; _highlightPosition: number; _wasTap: boolean; _isSearching: boolean; _placeholderValue: string | null; _baseId: string; _direction: HTMLElement['dir']; _idNames: { itemChoice: string; }; _presetChoices: (ChoiceFull | GroupFull)[]; _initialItems: string[]; _searcher: Searcher; _notice?: { type: NoticeType; text: string; }; _docRoot: ShadowRoot | HTMLElement; constructor( element: string | Element | HTMLInputElement | HTMLSelectElement = '[data-choice]', userConfig: Partial = {}, ) { const { defaults } = Choices; this.config = { ...defaults.allOptions, ...defaults.options, ...userConfig, } as Options; ObjectsInConfig.forEach((key) => { this.config[key] = { ...defaults.allOptions[key], ...defaults.options[key], ...userConfig[key], }; }); const { config } = this; if (!config.silent) { this._validateConfig(); } const docRoot = config.shadowRoot || document.documentElement; this._docRoot = docRoot; const passedElement = typeof element === 'string' ? docRoot.querySelector(element) : element; if ( !passedElement || typeof passedElement !== 'object' || !(isHtmlInputElement(passedElement) || isHtmlSelectElement(passedElement)) ) { if (!passedElement && typeof element === 'string') { throw TypeError(`Selector ${element} failed to find an element`); } throw TypeError(`Expected one of the following types text|select-one|select-multiple`); } let elementType = passedElement.type as PassedElementType; const isText = elementType === PassedElementTypes.Text; if (isText || config.maxItemCount !== 1) { config.singleModeForMultiSelect = false; } if (config.singleModeForMultiSelect) { elementType = PassedElementTypes.SelectMultiple; } const isSelectOne = elementType === PassedElementTypes.SelectOne; const isSelectMultiple = elementType === PassedElementTypes.SelectMultiple; const isSelect = isSelectOne || isSelectMultiple; this._elementType = elementType; this._isTextElement = isText; this._isSelectOneElement = isSelectOne; this._isSelectMultipleElement = isSelectMultiple; this._isSelectElement = isSelectOne || isSelectMultiple; this._canAddUserChoices = (isText && config.addItems) || (isSelect && config.addChoices); if (typeof config.renderSelectedChoices !== 'boolean') { config.renderSelectedChoices = config.renderSelectedChoices === 'always' || isSelectOne; } if (config.closeDropdownOnSelect === 'auto') { config.closeDropdownOnSelect = isText || isSelectOne || config.singleModeForMultiSelect; } else { config.closeDropdownOnSelect = coerceBool(config.closeDropdownOnSelect); } if (config.placeholder) { if (config.placeholderValue) { this._hasNonChoicePlaceholder = true; } else if (passedElement.dataset.placeholder) { this._hasNonChoicePlaceholder = true; config.placeholderValue = passedElement.dataset.placeholder; } } if (userConfig.addItemFilter && typeof userConfig.addItemFilter !== 'function') { const re = userConfig.addItemFilter instanceof RegExp ? userConfig.addItemFilter : new RegExp(userConfig.addItemFilter); config.addItemFilter = re.test.bind(re); } if (this._isTextElement) { this.passedElement = new WrappedInput({ element: passedElement as HTMLInputElement, classNames: config.classNames, }); } else { const selectEl = passedElement as HTMLSelectElement; this.passedElement = new WrappedSelect({ element: selectEl, classNames: config.classNames, template: (data: ChoiceFull): HTMLOptionElement => this._templates.option(data), extractPlaceholder: config.placeholder && !this._hasNonChoicePlaceholder, }); } this.initialised = false; this._store = new Store(config); this._currentValue = ''; config.searchEnabled = !isText && config.searchEnabled; this._canSearch = config.searchEnabled; this._isScrollingOnIe = false; this._highlightPosition = 0; this._wasTap = true; this._placeholderValue = this._generatePlaceholderValue(); this._baseId = generateId(passedElement, 'choices-'); /** * setting direction in cases where it's explicitly set on passedElement * or when calculated direction is different from the document */ this._direction = passedElement.dir; if (canUseDom && !this._direction) { const { direction: elementDirection } = window.getComputedStyle(passedElement); const { direction: documentDirection } = window.getComputedStyle(document.documentElement); if (elementDirection !== documentDirection) { this._direction = elementDirection; } } this._idNames = { itemChoice: 'item-choice', }; this._templates = defaults.templates; this._render = this._render.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onKeyUp = this._onKeyUp.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onInput = this._onInput.bind(this); this._onClick = this._onClick.bind(this); this._onTouchMove = this._onTouchMove.bind(this); this._onTouchEnd = this._onTouchEnd.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseOver = this._onMouseOver.bind(this); this._onFormReset = this._onFormReset.bind(this); this._onSelectKey = this._onSelectKey.bind(this); this._onEnterKey = this._onEnterKey.bind(this); this._onEscapeKey = this._onEscapeKey.bind(this); this._onDirectionKey = this._onDirectionKey.bind(this); this._onDeleteKey = this._onDeleteKey.bind(this); this._onChange = this._onChange.bind(this); this._onInvalid = this._onInvalid.bind(this); // If element has already been initialised with Choices, fail silently if (this.passedElement.isActive) { if (!config.silent) { console.warn('Trying to initialise Choices on element already initialised', { element }); } this.initialised = true; this.initialisedOK = false; return; } // Let's go this.init(); // preserve the selected item list after setup for form reset this._initialItems = this._store.items.map((choice) => choice.value); } init(): void { if (this.initialised || this.initialisedOK !== undefined) { return; } this._searcher = getSearcher(this.config); this._loadChoices(); this._createTemplates(); this._createElements(); this._createStructure(); if ( (this._isTextElement && !this.config.addItems) || this.passedElement.element.hasAttribute('disabled') || !!this.passedElement.element.closest('fieldset:disabled') ) { this.disable(); } else { this.enable(); this._addEventListeners(); } // should be triggered **after** disabled state to avoid additional re-draws this._initStore(); this.initialised = true; this.initialisedOK = true; const { callbackOnInit } = this.config; // Run callback if it is a function if (typeof callbackOnInit === 'function') { callbackOnInit.call(this); } } destroy(): void { if (!this.initialised) { return; } this._removeEventListeners(); this.passedElement.reveal(); this.containerOuter.unwrap(this.passedElement.element); this._store._listeners = []; // prevents select/input value being wiped this.clearStore(false); this._stopSearch(); this._templates = Choices.defaults.templates; this.initialised = false; this.initialisedOK = undefined; } enable(): this { if (this.passedElement.isDisabled) { this.passedElement.enable(); } if (this.containerOuter.isDisabled) { this._addEventListeners(); this.input.enable(); this.containerOuter.enable(); } return this; } disable(): this { if (!this.passedElement.isDisabled) { this.passedElement.disable(); } if (!this.containerOuter.isDisabled) { this._removeEventListeners(); this.input.disable(); this.containerOuter.disable(); } return this; } highlightItem(item: InputChoice, runEvent = true): this { if (!item || !item.id) { return this; } const choice = this._store.items.find((c) => c.id === item.id); if (!choice || choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, true)); if (runEvent) { this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice)); } return this; } unhighlightItem(item: InputChoice, runEvent = true): this { if (!item || !item.id) { return this; } const choice = this._store.items.find((c) => c.id === item.id); if (!choice || !choice.highlighted) { return this; } this._store.dispatch(highlightItem(choice, false)); if (runEvent) { this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice)); } return this; } highlightAll(): this { this._store.withTxn(() => { this._store.items.forEach((item) => { if (!item.highlighted) { this._store.dispatch(highlightItem(item, true)); this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; } unhighlightAll(): this { this._store.withTxn(() => { this._store.items.forEach((item) => { if (item.highlighted) { this._store.dispatch(highlightItem(item, false)); this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item)); } }); }); return this; } removeActiveItemsByValue(value: string): this { this._store.withTxn(() => { this._store.items.filter((item) => item.value === value).forEach((item) => this._removeItem(item)); }); return this; } removeActiveItems(excludedId?: number): this { this._store.withTxn(() => { this._store.items.filter(({ id }) => id !== excludedId).forEach((item) => this._removeItem(item)); }); return this; } removeHighlightedItems(runEvent = false): this { this._store.withTxn(() => { this._store.highlightedActiveItems.forEach((item) => { this._removeItem(item); // If this action was performed by the user // trigger the event if (runEvent) { this._triggerChange(item.value); } }); }); return this; } showDropdown(preventInputFocus?: boolean): this { if (this.dropdown.isActive) { return this; } if (preventInputFocus === undefined) { // eslint-disable-next-line no-param-reassign preventInputFocus = !this._canSearch; } requestAnimationFrame(() => { this.dropdown.show(); const rect = this.dropdown.element.getBoundingClientRect(); this.containerOuter.open(rect.bottom, rect.height); if (!preventInputFocus) { this.input.focus(); } this.passedElement.triggerEvent(EventType.showDropdown); const activeElement = this.choiceList.element.querySelector( getClassNamesSelector(this.config.classNames.selectedState), ); if (activeElement !== null && !isScrolledIntoView(activeElement, this.choiceList.element)) { // scrollIntoView can cause entire page scrolling, scrollToChildElement causes undesired animation this.choiceList.element.scrollTop = activeElement.offsetTop; } }); return this; } hideDropdown(preventInputBlur?: boolean): this { if (!this.dropdown.isActive) { return this; } this._removeHighlightedChoices(); requestAnimationFrame(() => { this.dropdown.hide(); this.containerOuter.close(); if (!preventInputBlur && this._canSearch) { this.input.removeActiveDescendant(); this.input.blur(); } this.passedElement.triggerEvent(EventType.hideDropdown); }); return this; } getValue(valueOnly?: B): EventChoiceValueType | EventChoiceValueType[] { const values = this._store.items.map((item) => { return (valueOnly ? item.value : getChoiceForOutput(item)) as EventChoiceValueType; }); return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values; } setValue(items: string[] | InputChoice[]): this { if (!this.initialisedOK) { this._warnChoicesInitFailed('setValue'); return this; } this._store.withTxn(() => { items.forEach((value: string | InputChoice) => { if (value) { this._addChoice(mapInputToChoice(value, false)); } }); }); // @todo integrate with Store this._searcher.reset(); return this; } setChoiceByValue(value: string | string[]): this { if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoiceByValue'); return this; } if (this._isTextElement) { return this; } this._store.withTxn(() => { // If only one value has been passed, convert to array const choiceValue = Array.isArray(value) ? value : [value]; // Loop through each value and choiceValue.forEach((val) => this._findAndSelectChoiceByValue(val)); this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; } /** * Set choices of select input via an array of objects (or function that returns array of object or promise of it), * a value field name and a label field name. * This behaves the same as passing items via the choices option but can be called after initialising Choices. * This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). * * **Input types affected:** select-one, select-multiple * * @example * ```js * const example = new Choices(element); * * example.setChoices([ * {value: 'One', label: 'Label One', disabled: true}, * {value: 'Two', label: 'Label Two', selected: true}, * {value: 'Three', label: 'Label Three'}, * ], 'value', 'label', false); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices(async () => { * try { * const items = await fetch('/items'); * return items.json() * } catch(err) { * console.error(err) * } * }); * ``` * * @example * ```js * const example = new Choices(element); * * example.setChoices([{ * label: 'Group one', * id: 1, * disabled: false, * choices: [ * {value: 'Child One', label: 'Child One', selected: true}, * {value: 'Child Two', label: 'Child Two', disabled: true}, * {value: 'Child Three', label: 'Child Three'}, * ] * }, * { * label: 'Group two', * id: 2, * disabled: false, * choices: [ * {value: 'Child Four', label: 'Child Four', disabled: true}, * {value: 'Child Five', label: 'Child Five'}, * {value: 'Child Six', label: 'Child Six', customProperties: { * description: 'Custom description about child six', * random: 'Another random custom property' * }}, * ] * }], 'value', 'label', false); * ``` */ setChoices( choicesArrayOrFetcher: | (InputChoice | InputGroup)[] | ((instance: Choices) => (InputChoice | InputGroup)[] | Promise<(InputChoice | InputGroup)[]>) = [], value: string | null = 'value', label: string = 'label', replaceChoices: boolean = false, clearSearchFlag: boolean = true, replaceItems: boolean = false, ): this | Promise { if (!this.initialisedOK) { this._warnChoicesInitFailed('setChoices'); return this; } if (!this._isSelectElement) { throw new TypeError(`setChoices can't be used with INPUT based Choices`); } if (typeof value !== 'string' || !value) { throw new TypeError(`value parameter must be a name of 'value' field in passed objects`); } if (typeof choicesArrayOrFetcher === 'function') { // it's a choices fetcher function const fetcher = choicesArrayOrFetcher(this); if (typeof Promise === 'function' && fetcher instanceof Promise) { // that's a promise // eslint-disable-next-line no-promise-executor-return return new Promise((resolve) => requestAnimationFrame(resolve)) .then(() => this._handleLoadingState(true)) .then(() => fetcher) .then((data: InputChoice[]) => this.setChoices(data, value, label, replaceChoices, clearSearchFlag, replaceItems), ) .catch((err) => { if (!this.config.silent) { console.error(err); } }) .then(() => this._handleLoadingState(false)) .then(() => this); } // function returned something else than promise, let's check if it's an array of choices if (!Array.isArray(fetcher)) { throw new TypeError( `.setChoices first argument function must return either array of choices or Promise, got: ${typeof fetcher}`, ); } // recursion with results, it's sync and choices were cleared already return this.setChoices(fetcher, value, label, false); } if (!Array.isArray(choicesArrayOrFetcher)) { throw new TypeError( `.setChoices must be called either with array of choices with a function resulting into Promise of array of choices`, ); } this.containerOuter.removeLoadingState(); this._store.withTxn(() => { if (clearSearchFlag) { this._isSearching = false; } // Clear choices if needed if (replaceChoices) { this.clearChoices(true, replaceItems); } const isDefaultValue = value === 'value'; const isDefaultLabel = label === 'label'; choicesArrayOrFetcher.forEach((groupOrChoice: InputGroup | InputChoice) => { if ('choices' in groupOrChoice) { let group = groupOrChoice; if (!isDefaultLabel) { group = { ...group, label: group[label], } as InputGroup; } this._addGroup(mapInputToChoice(group, true)); } else { let choice = groupOrChoice; if (!isDefaultLabel || !isDefaultValue) { choice = { ...choice, value: choice[value], label: choice[label], } as InputChoice; } const choiceFull = mapInputToChoice(choice, false); this._addChoice(choiceFull); if (choiceFull.placeholder && !this._hasNonChoicePlaceholder) { this._placeholderValue = unwrapStringForEscaped(choiceFull.label); } } }); this.unhighlightAll(); }); // @todo integrate with Store this._searcher.reset(); return this; } refresh(withEvents: boolean = false, selectFirstOption: boolean = false, deselectAll: boolean = false): this { if (!this._isSelectElement) { if (!this.config.silent) { console.warn('refresh method can only be used on choices backed by a element'); } return this; } this._store.withTxn(() => { const choicesFromOptions = (this.passedElement as WrappedSelect).optionsAsChoices(); // Build the list of items which require preserving const existingItems = {}; if (!deselectAll) { this._store.items.forEach((choice) => { if (choice.id && choice.active && choice.selected) { existingItems[choice.value] = true; } }); } this.clearStore(false); const updateChoice = (choice: ChoiceFull): void => { if (deselectAll) { this._store.dispatch(removeItem(choice)); } else if (existingItems[choice.value]) { choice.selected = true; } }; choicesFromOptions.forEach((groupOrChoice) => { if ('choices' in groupOrChoice) { groupOrChoice.choices.forEach(updateChoice); return; } updateChoice(groupOrChoice); }); /* @todo only generate add events for the added options instead of all if (withEvents) { items.forEach((choice) => { if (existingItems[choice.value]) { this.passedElement.triggerEvent( EventType.removeItem, this._getChoiceForEvent(choice), ); } }); } */ // load new choices & items this._addPredefinedChoices(choicesFromOptions, selectFirstOption, withEvents); // re-do search if required if (this._isSearching) { this._searchChoices(this.input.value); } }); return this; } removeChoice(value: string): this { const choice = this._store.choices.find((c) => c.value === value); if (!choice) { return this; } this._clearNotice(); this._store.dispatch(removeChoice(choice)); // @todo integrate with Store this._searcher.reset(); if (choice.selected) { this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice)); } return this; } clearChoices(clearOptions: boolean = true, clearItems: boolean = false): this { if (clearOptions) { if (clearItems) { this.passedElement.element.replaceChildren(''); } else { this.passedElement.element.querySelectorAll(':not([selected])').forEach((el): void => { el.remove(); }); } } this.itemList.element.replaceChildren(''); this.choiceList.element.replaceChildren(''); this._clearNotice(); this._store.withTxn(() => { const items = clearItems ? [] : this._store.items; this._store.reset(); items.forEach((item: ChoiceFull): void => { this._store.dispatch(addChoice(item)); this._store.dispatch(addItem(item)); }); }); // @todo integrate with Store this._searcher.reset(); return this; } clearStore(clearOptions: boolean = true): this { this.clearChoices(clearOptions, true); this._stopSearch(); this._lastAddedChoiceId = 0; this._lastAddedGroupId = 0; return this; } clearInput(): this { const shouldSetInputWidth = !this._isSelectOneElement; this.input.clear(shouldSetInputWidth); this._stopSearch(); return this; } _validateConfig(): void { const { config } = this; const invalidConfigOptions = diff(config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn('Unknown config option(s) passed', invalidConfigOptions.join(', ')); } if (config.allowHTML && config.allowHtmlUserInput) { if (config.addItems) { console.warn( 'Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks', ); } if (config.addChoices) { console.warn( 'Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks', ); } } } _render(changes: StateChangeSet = { choices: true, groups: true, items: true }): void { if (this._store.inTxn()) { return; } if (this._isSelectElement) { if (changes.choices || changes.groups) { this._renderChoices(); } } if (changes.items) { this._renderItems(); } } _renderChoices(): void { if (!this._canAddItems()) { return; // block rendering choices if the input limit is reached. } const { config, _isSearching: isSearching } = this; const { activeGroups, activeChoices } = this._store; const renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit; if (this._isSelectElement) { const backingOptions = activeChoices.filter((choice) => !choice.element); if (backingOptions.length) { (this.passedElement as WrappedSelect).addOptions(backingOptions); } } const fragment = document.createDocumentFragment(); const renderableChoices = (choices: ChoiceFull[]): ChoiceFull[] => choices.filter( (choice) => !choice.placeholder && (isSearching ? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank : config.renderSelectedChoices || !choice.selected), ); const showLabel = config.appendGroupInSearch && isSearching; let selectableChoices = false; let highlightedEl: HTMLElement | null = null; const renderChoices = (choices: ChoiceFull[], withinGroup: boolean): void => { if (isSearching) { // sortByRank is used to ensure stable sorting, as scores are non-unique // this additionally ensures fuseOptions.sortFn is not ignored choices.sort(sortByRank); } else if (config.shouldSort) { choices.sort(config.sorter); } let choiceLimit = choices.length; choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit; choiceLimit--; choices.every((choice, index) => { // choiceEl being empty signals the contents has probably significantly changed const dropdownItem = choice.choiceEl || this._templates.choice( config, choice, config.itemSelectText, showLabel && choice.group ? choice.group.label : undefined, ); choice.choiceEl = dropdownItem; fragment.appendChild(dropdownItem); if (isSearching || !choice.selected) { selectableChoices = true; } else if (!highlightedEl) { highlightedEl = dropdownItem; } return index < choiceLimit; }); }; if (activeChoices.length) { if (config.resetScrollPosition) { requestAnimationFrame(() => this.choiceList.scrollToTop()); } if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) { // If we have a placeholder choice along with groups renderChoices( activeChoices.filter((choice) => choice.placeholder && !choice.group), false, ); } // If we have grouped options if (activeGroups.length && !isSearching) { if (config.shouldSort) { activeGroups.sort(config.sorter); } // render Choices without group first, regardless of sort, otherwise they won't be distinguishable // from the last group renderChoices( activeChoices.filter((choice) => !choice.placeholder && !choice.group), false, ); activeGroups.forEach((group) => { const groupChoices = renderableChoices(group.choices); if (groupChoices.length) { if (group.label) { const dropdownGroup = group.groupEl || this._templates.choiceGroup(this.config, group); group.groupEl = dropdownGroup; dropdownGroup.remove(); fragment.appendChild(dropdownGroup); } renderChoices(groupChoices, true); } }); } else { renderChoices(renderableChoices(activeChoices), false); } } if (!selectableChoices && (isSearching || !fragment.children.length || !config.renderSelectedChoices)) { if (!this._notice) { this._notice = { text: resolveStringFunction(isSearching ? config.noResultsText : config.noChoicesText), type: isSearching ? NoticeTypes.noResults : NoticeTypes.noChoices, }; } fragment.replaceChildren(''); } this._renderNotice(fragment); this.choiceList.element.replaceChildren(fragment); this._highlightChoice(highlightedEl); } _renderItems(): void { const items = this._store.items || []; const itemList = this.itemList.element; const { config } = this; const fragment: DocumentFragment = document.createDocumentFragment(); const itemFromList = (item: ChoiceFull): HTMLElement | null => itemList.querySelector(`[data-item][data-id="${item.id}"]`); const addItemToFragment = (item: ChoiceFull): void => { let el = item.itemEl; if (el && el.parentElement) { return; } el = itemFromList(item) || this._templates.item(config, item, config.removeItemButton); item.itemEl = el; fragment.appendChild(el); }; // new items items.forEach(addItemToFragment); let addedItems = !!fragment.childNodes.length; if (this._isSelectOneElement) { const existingItems = itemList.children.length; if (addedItems || existingItems > 1) { const placeholder = itemList.querySelector(getClassNamesSelector(config.classNames.placeholder)); if (placeholder) { placeholder.remove(); } } else if (!addedItems && !existingItems && this._placeholderValue) { addedItems = true; addItemToFragment( mapInputToChoice( { selected: true, value: '', label: this._placeholderValue, placeholder: true, }, false, ), ); } } if (addedItems) { itemList.append(fragment); if (config.shouldSortItems && !this._isSelectOneElement) { items.sort(config.sorter); // push sorting into the DOM items.forEach((item) => { const el = itemFromList(item); if (el) { el.remove(); fragment.append(el); } }); itemList.append(fragment); } } if (this._isTextElement) { // Update the value of the hidden input this.passedElement.value = items.map(({ value }) => value).join(config.delimiter); } } _displayNotice(text: string, type: NoticeType, openDropdown: boolean = true): void { const oldNotice = this._notice; if ( oldNotice && ((oldNotice.type === type && oldNotice.text === text) || (oldNotice.type === NoticeTypes.addChoice && (type === NoticeTypes.noResults || type === NoticeTypes.noChoices))) ) { if (openDropdown) { this.showDropdown(true); } return; } this._clearNotice(); this._notice = text ? { text, type, } : undefined; this._renderNotice(); if (openDropdown && text) { this.showDropdown(true); } } _clearNotice(): void { if (!this._notice) { return; } const noticeElement = this.choiceList.element.querySelector( getClassNamesSelector(this.config.classNames.notice), ); if (noticeElement) { noticeElement.remove(); } this._notice = undefined; } _renderNotice(fragment?: DocumentFragment): void { const noticeConf = this._notice; if (noticeConf) { const notice = this._templates.notice(this.config, noticeConf.text, noticeConf.type); if (fragment) { fragment.append(notice); } else { this.choiceList.prepend(notice); } } } /** * @deprecated Use utils.getChoiceForOutput */ // eslint-disable-next-line class-methods-use-this _getChoiceForOutput(choice: ChoiceFull, keyCode?: number): EventChoice { return getChoiceForOutput(choice, keyCode); } _triggerChange(value): void { if (value === undefined || value === null) { return; } this.passedElement.triggerEvent(EventType.change, { value, }); } _handleButtonAction(element: HTMLElement): void { const { items } = this._store; if (!items.length || !this.config.removeItems || !this.config.removeItemButton) { return; } const id = element && parseDataSetId(element.closest('[data-id]')); const itemToRemove = id && items.find((item) => item.id === id); if (!itemToRemove) { return; } this._store.withTxn(() => { // Remove item associated with button this._removeItem(itemToRemove); this._triggerChange(itemToRemove.value); if (this._isSelectOneElement && !this._hasNonChoicePlaceholder) { const placeholderChoice = (this.config.shouldSort ? this._store.choices.reverse() : this._store.choices).find( (choice) => choice.placeholder, ); if (placeholderChoice) { this._addItem(placeholderChoice); this.unhighlightAll(); if (placeholderChoice.value) { this._triggerChange(placeholderChoice.value); } } } }); } _handleItemAction(element: HTMLElement, hasShiftKey = false): void { const { items } = this._store; if (!items.length || !this.config.removeItems || this._isSelectOneElement) { return; } const id = parseDataSetId(element); if (!id) { return; } // We only want to select one item with a click // so we deselect any items that aren't the target // unless shift is being pressed items.forEach((item) => { if (item.id === id && !item.highlighted) { this.highlightItem(item); } else if (!hasShiftKey && item.highlighted) { this.unhighlightItem(item); } }); // Focus input as without focus, a user cannot do anything with a // highlighted item this.input.focus(); } _handleChoiceAction(element: HTMLElement): boolean { // If we are clicking on an option const id = parseDataSetId(element); const choice = id && this._store.getChoiceById(id); if (!choice || choice.disabled) { return false; } const hasActiveDropdown = this.dropdown.isActive; if (!choice.selected) { if (!this._canAddItems()) { return true; // causes _onEnterKey to early out } this._store.withTxn(() => { this._addItem(choice, true, true); this.clearInput(); this.unhighlightAll(); }); this._triggerChange(choice.value); } // We want to close the dropdown if we are dealing with a single select box if (hasActiveDropdown && this.config.closeDropdownOnSelect) { this.hideDropdown(true); this.containerOuter.element.focus(); } return true; } _handleBackspace(items: ChoiceFull[]): void { const { config } = this; if (!config.removeItems || !items.length) { return; } const lastItem = items[items.length - 1]; const hasHighlightedItems = items.some((item) => item.highlighted); // If editing the last item is allowed and there are not other selected items, // we can edit the item value. Otherwise if we can remove items, remove all selected items if (config.editItems && !hasHighlightedItems && lastItem) { this.input.value = lastItem.value; this.input.setWidth(); this._removeItem(lastItem); this._triggerChange(lastItem.value); } else { if (!hasHighlightedItems) { // Highlight last item if none already highlighted this.highlightItem(lastItem, false); } this.removeHighlightedItems(true); } } _loadChoices(): void { const { config } = this; if (this._isTextElement) { // Assign preset items from passed object first this._presetChoices = config.items.map((e: InputChoice | string) => mapInputToChoice(e, false)); // Add any values passed from attribute if (this.passedElement.value) { const elementItems: ChoiceFull[] = this.passedElement.value .split(config.delimiter) .map((e: string) => mapInputToChoice(e, false, this.config.allowHtmlUserInput)); this._presetChoices = this._presetChoices.concat(elementItems); } this._presetChoices.forEach((choice: ChoiceFull) => { choice.selected = true; }); } else if (this._isSelectElement) { // Assign preset choices from passed object this._presetChoices = config.choices.map((e: InputChoice) => mapInputToChoice(e, true)); // Create array of choices from option elements const choicesFromOptions = (this.passedElement as WrappedSelect).optionsAsChoices(); if (choicesFromOptions) { this._presetChoices.push(...choicesFromOptions); } } } _handleLoadingState(setLoading = true): void { const el = this.itemList.element; if (setLoading) { this.disable(); this.containerOuter.addLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(this._templates.placeholder(this.config, this.config.loadingText)); } else { this.input.placeholder = this.config.loadingText; } } else { this.enable(); this.containerOuter.removeLoadingState(); if (this._isSelectOneElement) { el.replaceChildren(''); this._render(); } else { this.input.placeholder = this._placeholderValue || ''; } } } _handleSearch(value?: string): void { if (!this.input.isFocussed) { return; } // Check that we have a value to search and the input was an alphanumeric character if (value !== null && typeof value !== 'undefined' && value.length >= this.config.searchFloor) { const resultCount = this.config.searchChoices ? this._searchChoices(value) : 0; if (resultCount !== null) { // Trigger search event this.passedElement.triggerEvent(EventType.search, { value, resultCount, }); } } else if (this._store.choices.some((option) => !option.active)) { this._stopSearch(); } } _canAddItems(): boolean { const { config } = this; const { maxItemCount, maxItemText } = config; if (!config.singleModeForMultiSelect && maxItemCount > 0 && maxItemCount <= this._store.items.length) { this.choiceList.element.replaceChildren(''); this._notice = undefined; this._displayNotice( typeof maxItemText === 'function' ? maxItemText(maxItemCount) : maxItemText, NoticeTypes.addChoice, ); return false; } if (this._notice && this._notice.type === NoticeTypes.addChoice) { this._clearNotice(); } return true; } _canCreateItem(value: string): boolean { const { config } = this; let canAddItem = true; let notice = ''; if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) { canAddItem = false; notice = resolveNoticeFunction(config.customAddItemText, value, undefined); } if (canAddItem) { const foundChoice = this._store.choices.find((choice) => config.valueComparer(choice.value, value)); if (foundChoice) { if (this._isSelectElement) { // for exact matches, do not prompt to add it as a custom choice this._displayNotice('', NoticeTypes.addChoice); return false; } if (!config.duplicateItemsAllowed) { canAddItem = false; notice = resolveNoticeFunction(config.uniqueItemText, value, undefined); } } } if (canAddItem) { notice = resolveNoticeFunction(config.addItemText, value, undefined); } if (notice) { this._displayNotice(notice, NoticeTypes.addChoice); } return canAddItem; } _searchChoices(value: string): number | null { const newValue = value.trim().replace(/\s{2,}/, ' '); // signal input didn't change search if (!newValue.length || newValue === this._currentValue) { return null; } const searcher = this._searcher; if (searcher.isEmptyIndex()) { searcher.index(this._store.searchableChoices); } // If new value matches the desired length and is not the same as the current value with a space const results = searcher.search(newValue); this._currentValue = newValue; this._highlightPosition = 0; this._isSearching = true; const notice = this._notice; const noticeType = notice && notice.type; if (noticeType !== NoticeTypes.addChoice) { if (!results.length) { this._displayNotice(resolveStringFunction(this.config.noResultsText), NoticeTypes.noResults); } else { this._clearNotice(); } } this._store.dispatch(filterChoices(results)); return results.length; } _stopSearch(): void { if (this._isSearching) { this._currentValue = ''; this._isSearching = false; this._clearNotice(); this._store.dispatch(activateChoices(true)); this.passedElement.triggerEvent(EventType.search, { value: '', resultCount: 0, }); } } _addEventListeners(): void { const documentElement = this._docRoot; const outerElement = this.containerOuter.element; const inputElement = this.input.element; const passedElement = this.passedElement.element; // capture events - can cancel event processing or propagation documentElement.addEventListener('touchend', this._onTouchEnd, true); outerElement.addEventListener('keydown', this._onKeyDown, true); outerElement.addEventListener('mousedown', this._onMouseDown, true); // passive events - doesn't call `preventDefault` or `stopPropagation` documentElement.addEventListener('click', this._onClick, { passive: true }); documentElement.addEventListener('touchmove', this._onTouchMove, { passive: true, }); this.dropdown.element.addEventListener('mouseover', this._onMouseOver, { passive: true, }); if (this._isSelectOneElement) { outerElement.addEventListener('focus', this._onFocus, { passive: true, }); outerElement.addEventListener('blur', this._onBlur, { passive: true, }); } inputElement.addEventListener('keyup', this._onKeyUp, { passive: true, }); inputElement.addEventListener('input', this._onInput, { passive: true, }); inputElement.addEventListener('focus', this._onFocus, { passive: true, }); inputElement.addEventListener('blur', this._onBlur, { passive: true, }); if (inputElement.form) { inputElement.form.addEventListener('reset', this._onFormReset, { passive: true, }); } if (passedElement.hasAttribute('required')) { passedElement.addEventListener('change', this._onChange, { passive: true, }); passedElement.addEventListener('invalid', this._onInvalid, { passive: true, }); } this.input.addEventListeners(); } _removeEventListeners(): void { const documentElement = this._docRoot; const outerElement = this.containerOuter.element; const inputElement = this.input.element; const passedElement = this.passedElement.element; documentElement.removeEventListener('touchend', this._onTouchEnd, true); outerElement.removeEventListener('keydown', this._onKeyDown, true); outerElement.removeEventListener('mousedown', this._onMouseDown, true); documentElement.removeEventListener('click', this._onClick); documentElement.removeEventListener('touchmove', this._onTouchMove); this.dropdown.element.removeEventListener('mouseover', this._onMouseOver); if (this._isSelectOneElement) { outerElement.removeEventListener('focus', this._onFocus); outerElement.removeEventListener('blur', this._onBlur); } inputElement.removeEventListener('keyup', this._onKeyUp); inputElement.removeEventListener('input', this._onInput); inputElement.removeEventListener('focus', this._onFocus); inputElement.removeEventListener('blur', this._onBlur); if (inputElement.form) { inputElement.form.removeEventListener('reset', this._onFormReset); } if (passedElement.hasAttribute('required')) { passedElement.removeEventListener('change', this._onChange); passedElement.removeEventListener('invalid', this._onInvalid); } this.input.removeEventListeners(); } _onKeyDown(event: KeyboardEvent): void { const { keyCode } = event; const hasActiveDropdown = this.dropdown.isActive; /* See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF - UTF-16 surrogate pairs https://stackoverflow.com/a/70866532 - "Unidentified" for mobile http://www.unicode.org/versions/Unicode5.2.0/ch16.pdf#G19635 - U+FFFF is reserved (Section 16.7) Logic: when a key event is sent, `event.key` represents its printable value _or_ one of a large list of special values indicating meta keys/functionality. In addition, key events for compose functionality contain a value of `Dead` when mid-composition. I can't quite verify it, but non-English IMEs may also be able to generate key codes for code points in the surrogate-pair range, which could potentially be seen as having key.length > 1. Since `Fn` is one of the special keys, we can't distinguish by that alone. Here, key.length === 1 means we know for sure the input was printable and not a special `key` value. When the length is greater than 1, it could be either a printable surrogate pair or a special `key` value. We can tell the difference by checking if the _character code_ value (not code point!) is in the "surrogate pair" range or not. We don't use .codePointAt because an invalid code point would return 65535, which wouldn't pass the >= 0x10000 check we would otherwise use. > ...The Unicode Standard sets aside 66 noncharacter code points. The last two code points > of each plane are noncharacters: U+FFFE and U+FFFF on the BMP... */ const wasPrintableChar = event.key.length === 1 || (event.key.length === 2 && event.key.charCodeAt(0) >= 0xd800) || event.key === 'Unidentified'; /* We do not show the dropdown if focusing out with esc or navigating through input fields. An activated search can still be opened with any other key. */ if ( !this._isTextElement && !hasActiveDropdown && keyCode !== KeyCodeMap.ESC_KEY && keyCode !== KeyCodeMap.TAB_KEY && keyCode !== KeyCodeMap.SHIFT_KEY ) { this.showDropdown(); if (!this.input.isFocussed && wasPrintableChar) { /* We update the input value with the pressed key as the input was not focussed at the time of key press therefore does not have the value of the key. */ this.input.value += event.key; // browsers interpret a space as pagedown if (event.key === ' ') { event.preventDefault(); } } } switch (keyCode) { case KeyCodeMap.A_KEY: return this._onSelectKey(event, this.itemList.element.hasChildNodes()); case KeyCodeMap.ENTER_KEY: return this._onEnterKey(event, hasActiveDropdown); case KeyCodeMap.ESC_KEY: return this._onEscapeKey(event, hasActiveDropdown); case KeyCodeMap.UP_KEY: case KeyCodeMap.PAGE_UP_KEY: case KeyCodeMap.DOWN_KEY: case KeyCodeMap.PAGE_DOWN_KEY: return this._onDirectionKey(event, hasActiveDropdown); case KeyCodeMap.DELETE_KEY: case KeyCodeMap.BACK_KEY: return this._onDeleteKey(event, this._store.items, this.input.isFocussed); default: } } _onKeyUp(/* event: KeyboardEvent */): void { this._canSearch = this.config.searchEnabled; } _onInput(/* event: InputEvent */): void { const { value } = this.input; if (!value) { if (this._isTextElement) { this.hideDropdown(true); } else { this._stopSearch(); } return; } if (!this._canAddItems()) { return; } if (this._canSearch) { // do the search even if the entered text can not be added this._handleSearch(value); } if (!this._canAddUserChoices) { return; } // determine if a notice needs to be displayed for why a search result can't be added this._canCreateItem(value); if (this._isSelectElement) { this._highlightPosition = 0; // reset to select the notice and/or exact match this._highlightChoice(); } } _onSelectKey(event: KeyboardEvent, hasItems: boolean): void { // If CTRL + A or CMD + A have been pressed and there are items to select if ((event.ctrlKey || event.metaKey) && hasItems) { this._canSearch = false; const shouldHightlightAll = this.config.removeItems && !this.input.value && this.input.element === document.activeElement; if (shouldHightlightAll) { this.highlightAll(); } } } _onEnterKey(event: KeyboardEvent, hasActiveDropdown: boolean): void { const { value } = this.input; const target = event.target as HTMLElement | null; event.preventDefault(); if (target && target.hasAttribute('data-button')) { this._handleButtonAction(target); return; } if (!hasActiveDropdown) { if (this._isSelectElement || this._notice) { this.showDropdown(); } return; } const highlightedChoice = this.dropdown.element.querySelector( getClassNamesSelector(this.config.classNames.highlightedState), ); if (highlightedChoice && this._handleChoiceAction(highlightedChoice)) { return; } if (!target || !value) { this.hideDropdown(true); return; } if (!this._canAddItems()) { return; } let addedItem = false; this._store.withTxn(() => { addedItem = this._findAndSelectChoiceByValue(value, true); if (!addedItem) { if (!this._canAddUserChoices) { return; } if (!this._canCreateItem(value)) { return; } this._addChoice(mapInputToChoice(value, false, this.config.allowHtmlUserInput), true, true); addedItem = true; } this.clearInput(); this.unhighlightAll(); }); if (!addedItem) { return; } this._triggerChange(value); if (this.config.closeDropdownOnSelect) { this.hideDropdown(true); } } _onEscapeKey(event: KeyboardEvent, hasActiveDropdown: boolean): void { if (hasActiveDropdown) { event.stopPropagation(); this.hideDropdown(true); this._stopSearch(); this.containerOuter.element.focus(); } } _onDirectionKey(event: KeyboardEvent, hasActiveDropdown: boolean): void { const { keyCode } = event; // If up or down key is pressed, traverse through options if (hasActiveDropdown || this._isSelectOneElement) { this.showDropdown(); this._canSearch = false; const directionInt = keyCode === KeyCodeMap.DOWN_KEY || keyCode === KeyCodeMap.PAGE_DOWN_KEY ? 1 : -1; const skipKey = event.metaKey || keyCode === KeyCodeMap.PAGE_DOWN_KEY || keyCode === KeyCodeMap.PAGE_UP_KEY; let nextEl: HTMLElement | null; if (skipKey) { if (directionInt > 0) { nextEl = this.dropdown.element.querySelector(`${selectableChoiceIdentifier}:last-of-type`); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } else { const currentEl = this.dropdown.element.querySelector( getClassNamesSelector(this.config.classNames.highlightedState), ); if (currentEl) { nextEl = getAdjacentEl(currentEl, selectableChoiceIdentifier, directionInt); } else { nextEl = this.dropdown.element.querySelector(selectableChoiceIdentifier); } } if (nextEl) { // We prevent default to stop the cursor moving // when pressing the arrow if (!isScrolledIntoView(nextEl, this.choiceList.element, directionInt)) { this.choiceList.scrollToChildElement(nextEl, directionInt); } this._highlightChoice(nextEl); } // Prevent default to maintain cursor position whilst // traversing dropdown options event.preventDefault(); } } _onDeleteKey(event: KeyboardEvent, items: ChoiceFull[], hasFocusedInput: boolean): void { // If backspace or delete key is pressed and the input has no value if (!this._isSelectOneElement && !(event.target as HTMLInputElement).value && hasFocusedInput) { this._handleBackspace(items); event.preventDefault(); } } _onTouchMove(): void { if (this._wasTap) { this._wasTap = false; } } _onTouchEnd(event: TouchEvent): void { const { target } = event || (event as TouchEvent).touches[0]; const touchWasWithinContainer = this._wasTap && this.containerOuter.element.contains(target as Node); if (touchWasWithinContainer) { const containerWasExactTarget = target === this.containerOuter.element || target === this.containerInner.element; if (containerWasExactTarget) { if (this._isTextElement) { this.input.focus(); } else if (this._isSelectMultipleElement) { this.showDropdown(); } } // Prevents focus event firing event.stopPropagation(); } this._wasTap = true; } /** * Handles mousedown event in capture mode for containetOuter.element */ _onMouseDown(event: MouseEvent): void { const { target } = event; if (!(target instanceof Element)) { return; } // If we have our mouse down on the scrollbar and are on IE11... if (IS_IE11 && this.choiceList.element.contains(target)) { // check if click was on a scrollbar area const firstChoice = this.choiceList.element.firstElementChild as HTMLElement; this._isScrollingOnIe = this._direction === 'ltr' ? event.offsetX >= firstChoice.offsetWidth : event.offsetX < firstChoice.offsetLeft; } if (target === this.input.element) { return; } const item = target.closest('[data-button],[data-item],[data-choice]'); if (item instanceof HTMLElement) { if ('button' in item.dataset) { this._handleButtonAction(item); } else if ('item' in item.dataset) { this._handleItemAction(item, event.shiftKey); } else if ('choice' in item.dataset) { this._handleChoiceAction(item); } } event.preventDefault(); } /** * Handles mouseover event over this.dropdown * @param {MouseEvent} event */ _onMouseOver({ target }: Pick): void { if (target instanceof HTMLElement && 'choice' in target.dataset) { this._highlightChoice(target); } } _onClick({ target }: Pick): void { const { containerOuter } = this; const clickWasWithinContainer = containerOuter.element.contains(target as Node); if (clickWasWithinContainer) { if (!this.dropdown.isActive && !containerOuter.isDisabled) { if (this._isTextElement) { if (document.activeElement !== this.input.element) { this.input.focus(); } } else { this.showDropdown(); containerOuter.element.focus(); } } else if ( this._isSelectOneElement && target !== this.input.element && !this.dropdown.element.contains(target as Node) ) { this.hideDropdown(); } } else { containerOuter.removeFocusState(); this.hideDropdown(true); this.unhighlightAll(); } } _onFocus({ target }: Pick): void { const { containerOuter } = this; const focusWasWithinContainer = target && containerOuter.element.contains(target as Node); if (!focusWasWithinContainer) { return; } const targetIsInput = target === this.input.element; if (this._isTextElement) { if (targetIsInput) { containerOuter.addFocusState(); } } else if (this._isSelectMultipleElement) { if (targetIsInput) { this.showDropdown(true); // If element is a select box, the focused element is the container and the dropdown // isn't already open, focus and show dropdown containerOuter.addFocusState(); } } else { containerOuter.addFocusState(); if (targetIsInput) { this.showDropdown(true); } } } _onBlur({ target }: Pick): void { const { containerOuter } = this; const blurWasWithinContainer = target && containerOuter.element.contains(target as Node); if (blurWasWithinContainer && !this._isScrollingOnIe) { if (target === this.input.element) { containerOuter.removeFocusState(); this.hideDropdown(true); if (this._isTextElement || this._isSelectMultipleElement) { this.unhighlightAll(); } } else if (target === this.containerOuter.element) { // Remove the focus state when the past outerContainer was the target containerOuter.removeFocusState(); // Also close the dropdown if search is disabled if (!this.config.searchEnabled) { this.hideDropdown(true); } } } else { // On IE11, clicking the scollbar blurs our input and thus // closes the dropdown. To stop this, we refocus our input // if we know we are on IE *and* are scrolling. this._isScrollingOnIe = false; this.input.element.focus(); } } _onFormReset(): void { this._store.withTxn(() => { this.clearInput(); this.hideDropdown(); this.refresh(false, false, true); if (this._initialItems.length) { this.setChoiceByValue(this._initialItems); } }); } _onChange(event: Event & { target: HTMLInputElement | HTMLSelectElement }): void { if (!event.target.checkValidity()) { return; } this.containerOuter.removeInvalidState(); } _onInvalid(): void { this.containerOuter.addInvalidState(); } /** * Removes any highlighted choice options */ _removeHighlightedChoices(): void { const { highlightedState } = this.config.classNames; const highlightedChoices = Array.from( this.dropdown.element.querySelectorAll(getClassNamesSelector(highlightedState)), ); // Remove any highlighted choices highlightedChoices.forEach((choice) => { removeClassesFromElement(choice, highlightedState); choice.setAttribute('aria-selected', 'false'); }); } _highlightChoice(el: HTMLElement | null = null): void { const choices = Array.from(this.dropdown.element.querySelectorAll(selectableChoiceIdentifier)); if (!choices.length) { return; } let passedEl = el; const { highlightedState } = this.config.classNames; this._removeHighlightedChoices(); if (passedEl) { this._highlightPosition = choices.indexOf(passedEl); } else { // Highlight choice based on last known highlight location if (choices.length > this._highlightPosition) { // If we have an option to highlight passedEl = choices[this._highlightPosition]; } else { // Otherwise highlight the option before passedEl = choices[choices.length - 1]; } if (!passedEl) { passedEl = choices[0]; } } addClassesToElement(passedEl, highlightedState); passedEl.setAttribute('aria-selected', 'true'); this.passedElement.triggerEvent(EventType.highlightChoice, { el: passedEl, }); if (this.dropdown.isActive) { // IE11 ignores aria-label and blocks virtual keyboard // if aria-activedescendant is set without a dropdown this.input.setActiveDescendant(passedEl.id); this.containerOuter.setActiveDescendant(passedEl.id); } } _addItem(item: ChoiceFull, withEvents: boolean = true, userTriggered = false): void { if (!item.id) { throw new TypeError('item.id must be set before _addItem is called for a choice/item'); } if (this.config.singleModeForMultiSelect || this._isSelectOneElement) { this.removeActiveItems(item.id); } this._store.dispatch(addItem(item)); if (withEvents) { const eventChoice = getChoiceForOutput(item); this.passedElement.triggerEvent(EventType.addItem, eventChoice); if (userTriggered) { this.passedElement.triggerEvent(EventType.choice, eventChoice); } } } _removeItem(item: ChoiceFull): void { if (!item.id) { return; } this._store.dispatch(removeItem(item)); const notice = this._notice; if (notice && notice.type === NoticeTypes.noChoices) { this._clearNotice(); } this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item)); } _addChoice(choice: ChoiceFull, withEvents: boolean = true, userTriggered = false): void { if (choice.id) { throw new TypeError('Can not re-add a choice which has already been added'); } const { config } = this; if (!config.duplicateItemsAllowed && this._store.choices.find((c) => config.valueComparer(c.value, choice.value))) { return; } // Generate unique id, in-place update is required so chaining _addItem works as expected this._lastAddedChoiceId++; choice.id = this._lastAddedChoiceId; choice.elementId = `${this._baseId}-${this._idNames.itemChoice}-${choice.id}`; const { prependValue, appendValue } = config; if (prependValue) { choice.value = prependValue + choice.value; } if (appendValue) { choice.value += appendValue.toString(); } if ((prependValue || appendValue) && choice.element) { (choice.element as HTMLOptionElement).value = choice.value; } this._clearNotice(); this._store.dispatch(addChoice(choice)); if (choice.selected) { this._addItem(choice, withEvents, userTriggered); } } _addGroup(group: GroupFull, withEvents: boolean = true): void { if (group.id) { throw new TypeError('Can not re-add a group which has already been added'); } this._store.dispatch(addGroup(group)); if (!group.choices) { return; } // add unique id for the group(s), and do not store the full list of choices in this group this._lastAddedGroupId++; group.id = this._lastAddedGroupId; group.choices.forEach((item: ChoiceFull) => { item.group = group; if (group.disabled) { item.disabled = true; } this._addChoice(item, withEvents); }); } _createTemplates(): void { const { callbackOnCreateTemplates } = this.config; let userTemplates: Partial = {}; if (typeof callbackOnCreateTemplates === 'function') { userTemplates = callbackOnCreateTemplates.call(this, strToEl, escapeForTemplate, getClassNames); } const templating: Partial = {}; Object.keys(this._templates).forEach((name) => { if (name in userTemplates) { templating[name] = userTemplates[name].bind(this); } else { templating[name] = this._templates[name].bind(this); } }); this._templates = templating as Templates; } _createElements(): void { const templating = this._templates; const { config, _isSelectOneElement: isSelectOneElement } = this; const { position, classNames } = config; const elementType = this._elementType; this.containerOuter = new Container({ element: templating.containerOuter( config, this._direction, this._isSelectElement, isSelectOneElement, config.searchEnabled, elementType, config.labelId, ), classNames, type: elementType, position, }); this.containerInner = new Container({ element: templating.containerInner(config), classNames, type: elementType, position, }); this.input = new Input({ element: templating.input(config, this._placeholderValue), classNames, type: elementType, preventPaste: !config.paste, }); this.choiceList = new List({ element: templating.choiceList(config, isSelectOneElement), }); this.itemList = new List({ element: templating.itemList(config, isSelectOneElement), }); this.dropdown = new Dropdown({ element: templating.dropdown(config), classNames, type: elementType, }); } _createStructure(): void { const { containerInner, containerOuter, passedElement } = this; const dropdownElement = this.dropdown.element; // Hide original element passedElement.conceal(); // Wrap input in container preserving DOM ordering containerInner.wrap(passedElement.element); // Wrapper inner container with outer container containerOuter.wrap(containerInner.element); containerOuter.element.appendChild(containerInner.element); containerOuter.element.appendChild(dropdownElement); containerInner.element.appendChild(this.itemList.element); dropdownElement.appendChild(this.choiceList.element); if (this._isSelectOneElement) { this.input.placeholder = this.config.searchPlaceholderValue || ''; if (this.config.searchEnabled) { dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild); } } else { if (!this._isSelectMultipleElement || this.config.searchEnabled) { containerInner.element.appendChild(this.input.element); } if (this._placeholderValue) { this.input.placeholder = this._placeholderValue; } this.input.setWidth(); } this._highlightPosition = 0; this._isSearching = false; } _initStore(): void { this._store.subscribe(this._render).withTxn(() => { this._addPredefinedChoices( this._presetChoices, this._isSelectOneElement && !this._hasNonChoicePlaceholder, false, ); }); if (!this._store.choices.length || (this._isSelectOneElement && this._hasNonChoicePlaceholder)) { this._render(); } } _addPredefinedChoices( choices: (ChoiceFull | GroupFull)[], selectFirstOption: boolean = false, withEvents: boolean = true, ): void { if (selectFirstOption) { /** * If there is a selected choice already or the choice is not the first in * the array, add each choice normally. * * Otherwise we pre-select the first enabled choice in the array ("select-one" only) */ const noSelectedChoices = choices.findIndex((choice: ChoiceFull) => choice.selected) === -1; if (noSelectedChoices) { choices.some((choice) => { if (choice.disabled || 'choices' in choice) { return false; } choice.selected = true; return true; }); } } choices.forEach((item) => { if ('choices' in item) { if (this._isSelectElement) { this._addGroup(item, withEvents); } } else { this._addChoice(item, withEvents); } }); } _findAndSelectChoiceByValue(value: string, userTriggered: boolean = false): boolean { // Check 'value' property exists and the choice isn't already selected const foundChoice = this._store.choices.find((choice) => this.config.valueComparer(choice.value, value)); if (foundChoice && !foundChoice.disabled && !foundChoice.selected) { this._addItem(foundChoice, true, userTriggered); return true; } return false; } _generatePlaceholderValue(): string | null { const { config } = this; if (!config.placeholder) { return null; } if (this._hasNonChoicePlaceholder) { return config.placeholderValue; } if (this._isSelectElement) { const { placeholderOption } = this.passedElement as WrappedSelect; return placeholderOption ? placeholderOption.text : null; } return null; } _warnChoicesInitFailed(caller: string): void { if (this.config.silent) { return; } if (!this.initialised) { throw new TypeError(`${caller} called on a non-initialised instance of Choices`); } else if (!this.initialisedOK) { throw new TypeError(`${caller} called for an element which has multiple instances of Choices initialised on it`); } } } export default Choices; ================================================ FILE: src/scripts/components/container.ts ================================================ import { addClassesToElement, removeClassesFromElement } from '../lib/utils'; import { ClassNames } from '../interfaces/class-names'; import { PositionOptionsType } from '../interfaces/position-options-type'; import { PassedElementType, PassedElementTypes } from '../interfaces/passed-element-type'; export default class Container { element: HTMLElement; type: PassedElementType; classNames: ClassNames; position: PositionOptionsType; isOpen: boolean; isFlipped: boolean; isDisabled: boolean; isLoading: boolean; constructor({ element, type, classNames, position, }: { element: HTMLElement; type: PassedElementType; classNames: ClassNames; position: PositionOptionsType; }) { this.element = element; this.classNames = classNames; this.type = type; this.position = position; this.isOpen = false; this.isFlipped = false; this.isDisabled = false; this.isLoading = false; } /** * Determine whether container should be flipped based on passed * dropdown position */ shouldFlip(dropdownPos: number, dropdownHeight: number): boolean { // If flip is enabled and the dropdown bottom position is // greater than the window height flip the dropdown. let shouldFlip = false; if (this.position === 'auto') { shouldFlip = this.element.getBoundingClientRect().top - dropdownHeight >= 0 && !window.matchMedia(`(min-height: ${dropdownPos + 1}px)`).matches; } else if (this.position === 'top') { shouldFlip = true; } return shouldFlip; } setActiveDescendant(activeDescendantID: string): void { this.element.setAttribute('aria-activedescendant', activeDescendantID); } removeActiveDescendant(): void { this.element.removeAttribute('aria-activedescendant'); } open(dropdownPos: number, dropdownHeight: number): void { addClassesToElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'true'); this.isOpen = true; if (this.shouldFlip(dropdownPos, dropdownHeight)) { addClassesToElement(this.element, this.classNames.flippedState); this.isFlipped = true; } } close(): void { removeClassesFromElement(this.element, this.classNames.openState); this.element.setAttribute('aria-expanded', 'false'); this.removeActiveDescendant(); this.isOpen = false; // A dropdown flips if it does not have space within the page if (this.isFlipped) { removeClassesFromElement(this.element, this.classNames.flippedState); this.isFlipped = false; } } addFocusState(): void { addClassesToElement(this.element, this.classNames.focusState); } removeFocusState(): void { removeClassesFromElement(this.element, this.classNames.focusState); } addInvalidState(): void { addClassesToElement(this.element, this.classNames.invalidState); } removeInvalidState(): void { removeClassesFromElement(this.element, this.classNames.invalidState); } enable(): void { removeClassesFromElement(this.element, this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; } disable(): void { addClassesToElement(this.element, this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); if (this.type === PassedElementTypes.SelectOne) { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; } wrap(element: HTMLElement): void { const el = this.element; const { parentNode } = element; if (parentNode) { if (element.nextSibling) { parentNode.insertBefore(el, element.nextSibling); } else { parentNode.appendChild(el); } } el.appendChild(element); } unwrap(element: HTMLElement): void { const el = this.element; const { parentNode } = el; if (parentNode) { // Move passed element outside this element parentNode.insertBefore(element, el); // Remove this element parentNode.removeChild(el); } } addLoadingState(): void { addClassesToElement(this.element, this.classNames.loadingState); this.element.setAttribute('aria-busy', 'true'); this.isLoading = true; } removeLoadingState(): void { removeClassesFromElement(this.element, this.classNames.loadingState); this.element.removeAttribute('aria-busy'); this.isLoading = false; } } ================================================ FILE: src/scripts/components/dropdown.ts ================================================ import { ClassNames } from '../interfaces/class-names'; import { PassedElementType } from '../interfaces/passed-element-type'; import { addClassesToElement, removeClassesFromElement } from '../lib/utils'; export default class Dropdown { element: HTMLElement; type: PassedElementType; classNames: ClassNames; isActive: boolean; constructor({ element, type, classNames, }: { element: HTMLElement; type: PassedElementType; classNames: ClassNames; }) { this.element = element; this.classNames = classNames; this.type = type; this.isActive = false; } /** * Show dropdown to user by adding active state class */ show(): this { addClassesToElement(this.element, this.classNames.activeState); this.element.setAttribute('aria-expanded', 'true'); this.isActive = true; return this; } /** * Hide dropdown from user */ hide(): this { removeClassesFromElement(this.element, this.classNames.activeState); this.element.setAttribute('aria-expanded', 'false'); this.isActive = false; return this; } } ================================================ FILE: src/scripts/components/index.ts ================================================ import Dropdown from './dropdown'; import Container from './container'; import Input from './input'; import List from './list'; import WrappedInput from './wrapped-input'; import WrappedSelect from './wrapped-select'; export { Dropdown, Container, Input, List, WrappedInput, WrappedSelect }; ================================================ FILE: src/scripts/components/input.ts ================================================ import { ClassNames } from '../interfaces/class-names'; import { PassedElementType, PassedElementTypes } from '../interfaces/passed-element-type'; export default class Input { element: HTMLInputElement; type: PassedElementType; classNames: ClassNames; preventPaste: boolean; isFocussed: boolean; isDisabled: boolean; constructor({ element, type, classNames, preventPaste, }: { element: HTMLInputElement; type: PassedElementType; classNames: ClassNames; preventPaste: boolean; }) { this.element = element; this.type = type; this.classNames = classNames; this.preventPaste = preventPaste; this.isFocussed = this.element.isEqualNode(document.activeElement); this.isDisabled = element.disabled; this._onPaste = this._onPaste.bind(this); this._onInput = this._onInput.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } set placeholder(placeholder: string) { this.element.placeholder = placeholder; } get value(): string { return this.element.value; } set value(value: string) { this.element.value = value; } addEventListeners(): void { const el = this.element; el.addEventListener('paste', this._onPaste); el.addEventListener('input', this._onInput, { passive: true, }); el.addEventListener('focus', this._onFocus, { passive: true, }); el.addEventListener('blur', this._onBlur, { passive: true, }); } removeEventListeners(): void { const el = this.element; el.removeEventListener('input', this._onInput); el.removeEventListener('paste', this._onPaste); el.removeEventListener('focus', this._onFocus); el.removeEventListener('blur', this._onBlur); } enable(): void { const el = this.element; el.removeAttribute('disabled'); this.isDisabled = false; } disable(): void { const el = this.element; el.setAttribute('disabled', ''); this.isDisabled = true; } focus(): void { if (!this.isFocussed) { this.element.focus(); } } blur(): void { if (this.isFocussed) { this.element.blur(); } } clear(setWidth = true): this { this.element.value = ''; if (setWidth) { this.setWidth(); } return this; } /** * Set the correct input width based on placeholder * value or input value */ setWidth(): void { // Resize input to contents or placeholder const { element } = this; element.style.minWidth = `${element.placeholder.length + 1}ch`; element.style.width = `${element.value.length + 1}ch`; } setActiveDescendant(activeDescendantID: string): void { this.element.setAttribute('aria-activedescendant', activeDescendantID); } removeActiveDescendant(): void { this.element.removeAttribute('aria-activedescendant'); } _onInput(): void { if (this.type !== PassedElementTypes.SelectOne) { this.setWidth(); } } _onPaste(event: ClipboardEvent): void { if (this.preventPaste) { event.preventDefault(); } } _onFocus(): void { this.isFocussed = true; } _onBlur(): void { this.isFocussed = false; } } ================================================ FILE: src/scripts/components/list.ts ================================================ import { SCROLLING_SPEED } from '../constants'; export default class List { element: HTMLElement; scrollPos: number; height: number; constructor({ element }: { element: HTMLElement }) { this.element = element; this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; } prepend(node: Element | DocumentFragment): void { const child = this.element.firstElementChild; if (child) { this.element.insertBefore(node, child); } else { this.element.append(node); } } scrollToTop(): void { this.element.scrollTop = 0; } scrollToChildElement(element: HTMLElement, direction: 1 | -1): void { if (!element) { return; } const listHeight = this.element.offsetHeight; // Scroll position of dropdown const listScrollPosition = this.element.scrollTop + listHeight; const elementHeight = element.offsetHeight; // Distance from bottom of element to top of parent const elementPos = element.offsetTop + elementHeight; // Difference between the element and scroll position const destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop; requestAnimationFrame(() => { this._animateScroll(destination, direction); }); } _scrollDown(scrollPos: number, strength: number, destination: number): void { const easing = (destination - scrollPos) / strength; const distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos + distance; } _scrollUp(scrollPos: number, strength: number, destination: number): void { const easing = (scrollPos - destination) / strength; const distance = easing > 1 ? easing : 1; this.element.scrollTop = scrollPos - distance; } _animateScroll(destination: number, direction: number): void { const strength = SCROLLING_SPEED; const choiceListScrollTop = this.element.scrollTop; let continueAnimation = false; if (direction > 0) { this._scrollDown(choiceListScrollTop, strength, destination); if (choiceListScrollTop < destination) { continueAnimation = true; } } else { this._scrollUp(choiceListScrollTop, strength, destination); if (choiceListScrollTop > destination) { continueAnimation = true; } } if (continueAnimation) { requestAnimationFrame(() => { this._animateScroll(destination, direction); }); } } } ================================================ FILE: src/scripts/components/wrapped-element.ts ================================================ import { ClassNames } from '../interfaces/class-names'; import { EventTypes } from '../interfaces/event-type'; import { addClassesToElement, dispatchEvent, removeClassesFromElement } from '../lib/utils'; import { EventMap } from '../interfaces'; export default class WrappedElement { element: T; classNames: ClassNames; isDisabled: boolean; constructor({ element, classNames }) { this.element = element; this.classNames = classNames; this.isDisabled = false; } get isActive(): boolean { return this.element.dataset.choice === 'active'; } get dir(): string { return this.element.dir; } get value(): string { return this.element.value; } set value(value: string) { this.element.setAttribute('value', value); this.element.value = value; } conceal(): void { const el = this.element; // Hide passed input addClassesToElement(el, this.classNames.input); el.hidden = true; // Remove element from tab index el.tabIndex = -1; // Backup original styles if any const origStyle = el.getAttribute('style'); if (origStyle) { el.setAttribute('data-choice-orig-style', origStyle); } el.setAttribute('data-choice', 'active'); } reveal(): void { const el = this.element; // Reinstate passed element removeClassesFromElement(el, this.classNames.input); el.hidden = false; el.removeAttribute('tabindex'); // Recover original styles if any const origStyle = el.getAttribute('data-choice-orig-style'); if (origStyle) { el.removeAttribute('data-choice-orig-style'); el.setAttribute('style', origStyle); } else { el.removeAttribute('style'); } el.removeAttribute('data-choice'); } enable(): void { this.element.removeAttribute('disabled'); this.element.disabled = false; this.isDisabled = false; } disable(): void { this.element.setAttribute('disabled', ''); this.element.disabled = true; this.isDisabled = true; } triggerEvent(eventType: EventTypes, data?: EventMap[K]['detail']): void { dispatchEvent(this.element, eventType, data || {}); } } ================================================ FILE: src/scripts/components/wrapped-input.ts ================================================ import WrappedElement from './wrapped-element'; export default class WrappedInput extends WrappedElement {} ================================================ FILE: src/scripts/components/wrapped-select.ts ================================================ import { parseCustomProperties } from '../lib/utils'; import { ClassNames } from '../interfaces/class-names'; import WrappedElement from './wrapped-element'; import { GroupFull } from '../interfaces/group-full'; import { ChoiceFull } from '../interfaces/choice-full'; import { stringToHtmlClass } from '../lib/choice-input'; import { isHtmlOptgroup, isHtmlOption } from '../lib/html-guard-statements'; export default class WrappedSelect extends WrappedElement { classNames: ClassNames; template: (data: object) => HTMLOptionElement; extractPlaceholder: boolean; constructor({ element, classNames, template, extractPlaceholder, }: { element: HTMLSelectElement; classNames: ClassNames; template: (data: object) => HTMLOptionElement; extractPlaceholder: boolean; }) { super({ element, classNames }); this.template = template; this.extractPlaceholder = extractPlaceholder; } get placeholderOption(): HTMLOptionElement | null { return ( this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]') ); } addOptions(choices: ChoiceFull[]): void { const fragment = document.createDocumentFragment(); choices.forEach((obj) => { const choice = obj; if (choice.element) { return; } const option = this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); } optionsAsChoices(): (ChoiceFull | GroupFull)[] { const choices: (ChoiceFull | GroupFull)[] = []; this.element.querySelectorAll(':scope > option, :scope > optgroup').forEach((e) => { if (isHtmlOption(e)) { choices.push(this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; } // eslint-disable-next-line class-methods-use-this _optionToChoice(option: HTMLOptionElement): ChoiceFull { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? { trusted: option.dataset.labelDescription } : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; } _optgroupToChoice(optgroup: HTMLOptGroupElement): GroupFull { const options = optgroup.querySelectorAll('option'); const choices = Array.from(options).map((option) => this._optionToChoice(option)); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices, }; } } ================================================ FILE: src/scripts/constants.ts ================================================ export const SCROLLING_SPEED: number = 4 as const; ================================================ FILE: src/scripts/defaults.ts ================================================ import { ClassNames } from './interfaces/class-names'; import { Options } from './interfaces/options'; import { sanitise, sortByAlpha } from './lib/utils'; import { EventChoice } from './interfaces'; export const DEFAULT_CLASSNAMES: ClassNames = { containerOuter: ['choices'], containerInner: ['choices__inner'], input: ['choices__input'], inputCloned: ['choices__input--cloned'], list: ['choices__list'], listItems: ['choices__list--multiple'], listSingle: ['choices__list--single'], listDropdown: ['choices__list--dropdown'], item: ['choices__item'], itemSelectable: ['choices__item--selectable'], itemDisabled: ['choices__item--disabled'], itemChoice: ['choices__item--choice'], description: ['choices__description'], placeholder: ['choices__placeholder'], group: ['choices__group'], groupHeading: ['choices__heading'], button: ['choices__button'], activeState: ['is-active'], focusState: ['is-focused'], openState: ['is-open'], disabledState: ['is-disabled'], highlightedState: ['is-highlighted'], selectedState: ['is-selected'], flippedState: ['is-flipped'], loadingState: ['is-loading'], invalidState: ['is-invalid'], notice: ['choices__notice'], addChoice: ['choices__item--selectable', 'add-choice'], noResults: ['has-no-results'], noChoices: ['has-no-choices'], } as const; export const DEFAULT_CONFIG: Options = { items: [], choices: [], silent: false, renderChoiceLimit: -1, maxItemCount: -1, closeDropdownOnSelect: 'auto', singleModeForMultiSelect: false, addChoices: false, addItems: true, addItemFilter: (value: string): boolean => !!value && value !== '', removeItems: true, removeItemButton: false, removeItemButtonAlignLeft: false, editItems: false, allowHTML: false, allowHtmlUserInput: false, duplicateItemsAllowed: true, delimiter: ',', paste: true, searchEnabled: true, searchChoices: true, searchDisabledChoices: false, searchFloor: 1, searchResultLimit: 4, searchFields: ['label', 'value'], position: 'auto', resetScrollPosition: true, shouldSort: true, shouldSortItems: false, sorter: sortByAlpha, shadowRoot: null, placeholder: true, placeholderValue: null, searchPlaceholderValue: null, prependValue: null, appendValue: null, renderSelectedChoices: 'auto', searchRenderSelectedChoices: true, loadingText: 'Loading...', noResultsText: 'No results found', noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', customAddItemText: 'Only values matching specific conditions can be added', addItemText: (value: string) => `Press Enter to add "${value}"`, removeItemIconText: (): string => `Remove item`, removeItemLabelText: (value: string, _valueRaw: string, i?: EventChoice): string => `Remove item: ${i ? sanitise(i.label) : value}`, maxItemText: (maxItemCount: number): string => `Only ${maxItemCount} values can be added`, valueComparer: (value1: string, value2: string): boolean => value1 === value2, fuseOptions: { includeScore: true, }, labelId: '', callbackOnInit: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, appendGroupInSearch: false, } as const; ================================================ FILE: src/scripts/interfaces/action-type.ts ================================================ import { Types } from './types'; export const ActionType = { ADD_CHOICE: 'ADD_CHOICE', REMOVE_CHOICE: 'REMOVE_CHOICE', FILTER_CHOICES: 'FILTER_CHOICES', ACTIVATE_CHOICES: 'ACTIVATE_CHOICES', CLEAR_CHOICES: 'CLEAR_CHOICES', ADD_GROUP: 'ADD_GROUP', ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', } as const; export type ActionTypes = Types.ValueOf; ================================================ FILE: src/scripts/interfaces/build-flags.ts ================================================ export const canUseDom: boolean = process.env.CHOICES_CAN_USE_DOM !== undefined ? process.env.CHOICES_CAN_USE_DOM === '1' : !!(typeof document !== 'undefined' && document.createElement); export const searchFuse: string | undefined = process.env.CHOICES_SEARCH_FUSE; export const searchKMP: boolean = process.env.CHOICES_SEARCH_KMP === '1'; /** * These are not directly used, as an exported object (even as const) will prevent tree-shake away code paths */ export const BuildFlags = { searchFuse, searchKMP, canUseDom, } as const; ================================================ FILE: src/scripts/interfaces/choice-full.ts ================================================ import { StringUntrusted } from './string-untrusted'; import { StringPreEscaped } from './string-pre-escaped'; import { Types } from './types'; // eslint-disable-next-line import/no-cycle import { GroupFull } from './group-full'; /* A disabled choice appears in the choice dropdown but cannot be selected A selected choice has been added to the passed input's value (added as an item) An active choice appears within the choice dropdown (ie search sets active to false if it doesn't match) */ export interface ChoiceFull { id: number; highlighted: boolean; element?: HTMLOptionElement | HTMLOptGroupElement; itemEl?: HTMLElement; choiceEl?: HTMLElement; labelClass?: Array; labelDescription?: StringPreEscaped | StringUntrusted | string; customProperties?: Types.CustomProperties; disabled: boolean; active: boolean; elementId?: string; group: GroupFull | null; label: StringUntrusted | string; placeholder: boolean; selected: boolean; value: string; score: number; rank: number; } ================================================ FILE: src/scripts/interfaces/class-names.ts ================================================ /** Classes added to HTML generated by By default classnames follow the BEM notation. */ export interface ClassNames { /** @default ['choices'] */ containerOuter: string | Array; /** @default ['choices__inner'] */ containerInner: string | Array; /** @default ['choices__input'] */ input: string | Array; /** @default ['choices__input--cloned'] */ inputCloned: string | Array; /** @default ['choices__list'] */ list: string | Array; /** @default ['choices__list--multiple'] */ listItems: string | Array; /** @default ['choices__list--single'] */ listSingle: string | Array; /** @default ['choices__list--dropdown'] */ listDropdown: string | Array; /** @default ['choices__item'] */ item: string | Array; /** @default ['choices__item--selectable'] */ itemSelectable: string | Array; /** @default ['choices__item--disabled'] */ itemDisabled: string | Array; /** @default ['choices__item--choice'] */ itemChoice: string | Array; /** @default ['choices__description'] */ description: string | Array; /** @default ['choices__placeholder'] */ placeholder: string | Array; /** @default ['choices__group'] */ group: string | Array; /** @default ['choices__heading'] */ groupHeading: string | Array; /** @default ['choices__button'] */ button: string | Array; /** @default ['is-active'] */ activeState: string | Array; /** @default ['is-focused'] */ focusState: string | Array; /** @default ['is-open'] */ openState: string | Array; /** @default ['is-disabled'] */ disabledState: string | Array; /** @default ['is-highlighted'] */ highlightedState: string | Array; /** @default ['is-selected'] */ selectedState: string | Array; /** @default ['is-flipped'] */ flippedState: string | Array; /** @default ['is-loading'] */ loadingState: string | Array; /** @default ['is-invalid'] */ invalidState: string | Array; /** @default ['choices__notice'] */ notice: string | Array; /** @default ['choices__item--selectable', 'add-choice'] */ addChoice: string | Array; /** @default ['has-no-results'] */ noResults: string | Array; /** @default ['has-no-choices'] */ noChoices: string | Array; } ================================================ FILE: src/scripts/interfaces/event-choice.ts ================================================ // eslint-disable-next-line import/no-cycle import { InputChoice } from './input-choice'; export type EventChoiceValueType = B extends true ? string : EventChoice; export interface EventChoice extends InputChoice { element?: HTMLOptionElement | HTMLOptGroupElement; groupValue?: string; keyCode?: number; } ================================================ FILE: src/scripts/interfaces/event-type.ts ================================================ import { Types } from './types'; export const EventType = { showDropdown: 'showDropdown', hideDropdown: 'hideDropdown', change: 'change', choice: 'choice', search: 'search', addItem: 'addItem', removeItem: 'removeItem', highlightItem: 'highlightItem', highlightChoice: 'highlightChoice', unhighlightItem: 'unhighlightItem', } as const; export type EventTypes = Types.ValueOf; ================================================ FILE: src/scripts/interfaces/group-full.ts ================================================ // eslint-disable-next-line import/no-cycle import { ChoiceFull } from './choice-full'; export interface GroupFull { id: number; active: boolean; disabled: boolean; label?: string; element?: HTMLOptGroupElement; groupEl?: HTMLElement; choices: ChoiceFull[]; } ================================================ FILE: src/scripts/interfaces/index.ts ================================================ export * from './action-type'; export * from './input-choice'; export * from './input-group'; export * from './event-choice'; export * from './class-names'; export * from './event-type'; export * from './item'; export * from './keycode-map'; export * from './options'; export * from './passed-element'; export * from './passed-element-type'; export * from './position-options-type'; export * from './state'; export * from './types'; ================================================ FILE: src/scripts/interfaces/input-choice.ts ================================================ import { StringUntrusted } from './string-untrusted'; import { StringPreEscaped } from './string-pre-escaped'; // eslint-disable-next-line import { Types } from './types'; export interface InputChoice { id?: number; highlighted?: boolean; labelClass?: string | Array; labelDescription?: StringPreEscaped | StringUntrusted | string; customProperties?: Types.CustomProperties; disabled?: boolean; active?: boolean; label: StringUntrusted | string; placeholder?: boolean; selected?: boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any; // string; } ================================================ FILE: src/scripts/interfaces/input-group.ts ================================================ import { InputChoice } from './input-choice'; import { StringUntrusted } from './string-untrusted'; export interface InputGroup { id?: number; active?: boolean; disabled?: boolean; label?: StringUntrusted | string; value: string; choices: InputChoice[]; } ================================================ FILE: src/scripts/interfaces/item.ts ================================================ import { InputChoice } from './input-choice'; import { InputGroup } from './input-group'; /** * @deprecated Use InputChoice instead */ export interface Item extends InputChoice {} /** * @deprecated Use InputChoice instead */ export interface Choice extends InputChoice {} /** * @deprecated Use InputGroup instead */ export interface Group extends InputGroup {} ================================================ FILE: src/scripts/interfaces/keycode-map.ts ================================================ export const KeyCodeMap = { TAB_KEY: 9, SHIFT_KEY: 16, BACK_KEY: 46, DELETE_KEY: 8, ENTER_KEY: 13, A_KEY: 65, ESC_KEY: 27, UP_KEY: 38, DOWN_KEY: 40, PAGE_UP_KEY: 33, PAGE_DOWN_KEY: 34, } as const; ================================================ FILE: src/scripts/interfaces/options.ts ================================================ import { IFuseOptions } from 'fuse.js'; import { InputChoice } from './input-choice'; import { ClassNames } from './class-names'; import { PositionOptionsType } from './position-options-type'; import { Types } from './types'; // eslint-disable-next-line import/no-cycle import { CallbackOnCreateTemplatesFn } from './templates'; export const ObjectsInConfig: string[] = ['fuseOptions', 'classNames']; /** * Choices options interface * * **Terminology** * * - **Choice:** A choice is a value a user can select. A choice would be equivalent to the `` element within a select input. * - **Group:** A group is a collection of choices. A group should be seen as equivalent to a `` element within a select input. * - **Item:** An item is an inputted value **_(text input)_** or a selected choice **_(select element)_**. In the context of a select element, an item is equivelent to a selected option element: `` whereas in the context of a text input an item is equivelant to `` */ export interface Options { /** * Optionally suppress console errors and warnings. * * **Input types affected:** text, select-single, select-multiple * * @default false */ silent: boolean; /** * Add pre-selected items (see terminology) to text input. * * **Input types affected:** text * * @example * ``` * ['value 1', 'value 2', 'value 3'] * ``` * * @example * ``` * [{ * value: 'Value 1', * label: 'Label 1', * id: 1 * }, * { * value: 'Value 2', * label: 'Label 2', * id: 2, * customProperties: { * random: 'I am a custom property' * } * }] * ``` * * @default [] */ items: string[] | InputChoice[]; /** * Add choices (see terminology) to select input. * * **Input types affected:** select-one, select-multiple * * @example * ``` * [{ * value: 'Option 1', * label: 'Option 1', * selected: true, * disabled: false, * }, * { * value: 'Option 2', * label: 'Option 2', * selected: false, * disabled: true, * customProperties: { * description: 'Custom description about Option 2', * random: 'Another random custom property' * }, * }, * { * label: 'Group 1', * choices: [{ * value: 'Option 3', * label: 'Option 4', * selected: true, * disabled: false, * }, * { * value: 'Option 2', * label: 'Option 2', * selected: false, * disabled: true, * customProperties: { * description: 'Custom description about Option 2', * random: 'Another random custom property' * } * }] * }] * ``` * * @default [] */ choices: InputChoice[]; /** * The amount of choices to be rendered within the dropdown list `("-1" indicates no limit)`. This is useful if you have a lot of choices where it is easier for a user to use the search area to find a choice. * * **Input types affected:** select-one, select-multiple * * @default -1 */ renderChoiceLimit: number; /** * The amount of items a user can input/select `("-1" indicates no limit)`. * * **Input types affected:** text, select-multiple * * @default -1 */ maxItemCount: number; /** * Control how the dropdown closes after making a selection for select-one or select-multiple * * 'auto' defaults based on backing-element type: * select-one: true * select-multiple: false * * **Input types affected:** select-one, select-multiple * * @default 'auto' */ closeDropdownOnSelect: boolean | 'auto'; /** * Make select-multiple with a max item count of 1 work similar to select-one does. * Selecting an item will auto-close the dropdown and swap any existing item for the just selected choice. * If applied to a select-one, it functions as above and not the standard select-one. * * **Input types affected:** select-one, select-multiple * * @default false */ singleModeForMultiSelect: boolean; /** * Whether a user can add choices dynamically. * * **Input types affected:** select-one, select-multiple * * @default false */ addChoices: boolean; /** * Whether a user can add items. * * **Input types affected:** text * * @default true */ addItems: boolean; /** * A filter that will need to pass for a user to successfully add an item. * * **Input types affected:** text, select-one, select-multiple * * @default (value) => !!value && value !== '' */ addItemFilter: string | RegExp | Types.FilterFunction | null; /** * The text that is shown when a user has inputted a new item but has not pressed the enter key. To access the current input value, pass a function with a `value` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string. * The raw non-sanitised value is passed as a 2nd argument. * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * **Input types affected:** text, one-select, select-one, select-multiple * * @default * ``` * (value, valueRaw) => `Press Enter to add "${value}"`; * ``` */ addItemText: string | Types.NoticeStringFunction; /** * The text/icon for the remove button. To access the item's value, pass a function with a `value` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string. * The raw non-sanitised value is passed as a 2nd argument. * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * **Input types affected:** text, select-one, select-multiple * * @default * ``` * (value, valueRaw, item) => `Remove item`; * ``` */ removeItemIconText: string | Types.NoticeStringFunction; /** * The text for the remove button's aria label. To access the item's value, pass a function with a `value` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string. * The raw non-sanitised value is passed as a 2nd argument. * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * **Input types affected:** text, select-one, select-multiple * * @default * ``` * (value, valueRaw, item) => `Remove item: ${value}`; * ``` */ removeItemLabelText: string | Types.NoticeStringFunction; /** * Whether a user can remove items. * * **Input types affected:** text, select-multiple * * @default true */ removeItems: boolean; /** * Whether each item should have a remove button. * * **Input types affected:** text, select-one, select-multiple * * @default false */ removeItemButton: boolean; /** * Align item remove button left vs right. * * **Input types affected:** text, select-one, select-multiple * * @default false */ removeItemButtonAlignLeft: boolean; /** * Whether a user can edit items. An item's value can be edited by pressing the backspace. * * **Input types affected:** text * * @default false */ editItems: boolean; /** * Whether HTML should be rendered in all Choices elements. * If `false`, all elements (placeholder, items, etc.) will be treated as plain text. * If `true`, this can be used to perform XSS scripting attacks if you load choices from a remote source. * * **Input types affected:** text, select-one, select-multiple * * @default false */ allowHTML: boolean; /** * Whether HTML should be escaped on input when `addItems` or `addChoices` is true. * If `false`, user input will be treated as plain text. * If `true`, this can be used to perform XSS scripting attacks if you load previously submitted choices from a remote source. * * **Input types affected:** text, select-one, select-multiple * * @default false */ allowHtmlUserInput: boolean; /** * Whether each inputted/chosen item should be unique. * * **Input types affected:** text, `select-multiple`, `select-one` * * @default true */ duplicateItemsAllowed: boolean; /** * What divides each value. The default delimiter separates each value with a comma: `"Value 1, Value 2, Value 3"`. * * **Input types affected:** text * * @default ',' */ delimiter: string; /** * Whether a user can paste into the input. * * **Input types affected:** text, select-multiple * * @default true */ paste: boolean; /** * Whether a search area should be shown. * * **Input types affected:** select-one, select-multiple * * @default true */ searchEnabled: boolean; /** * Whether choices should be filtered by input or not. If `false`, the search event will still emit, but choices will not be filtered. * * **Input types affected:** select-one * * @default true */ searchChoices: boolean; /** * Whether disabled choices should be included in search results. If `true`, disabled choices will appear in search results but still cannot be selected. * * **Input types affected:** select-one, select-multiple * * @default false */ searchDisabledChoices: boolean; /** * The minimum length a search value should be before choices are searched. * * **Input types affected:** select-one, select-multiple * * @default 1 */ searchFloor: number; /** * The maximum amount of search results to show. `("-1" indicates no limit)` * * **Input types affected:** select-one, select-multiple * * @default 4 */ searchResultLimit: number; /** * Specify which fields should be used when a user is searching. If you have added custom properties to your choices, you can add these values thus: `['label', 'value', 'customProperties.example']`. * * Input types affected:select-one, select-multiple * * @default ['label', 'value'] */ searchFields: string[]; /** * Whether the dropdown should appear above `(top)` or below `(bottom)` the input. By default, if there is not enough space within the window the dropdown will appear above the input, otherwise below it. * * **Input types affected:** select-one, select-multiple * * @default 'auto' */ position: PositionOptionsType; /** * Whether the scroll position should reset after adding an item. * * **Input types affected:** select-multiple * * @default true */ resetScrollPosition: boolean; /** * The shadow root for use within ShadowDom */ shadowRoot: ShadowRoot | null; /** * Whether choices and groups should be sorted. If false, choices/groups will appear in the order they were given. * * **Input types affected:** select-one, select-multiple * * @default true */ shouldSort: boolean; /** * Whether items should be sorted. If false, items will appear in the order they were selected. * * **Input types affected:** text, select-multiple * * @default false */ shouldSortItems: boolean; /** * The function that will sort choices and items before they are displayed (unless a user is searching). By default choices and items are sorted by alphabetical order. * * **Input types affected:** select-one, select-multiple * * @example * ``` * // Sorting via length of label from largest to smallest * const example = new Choices(element, { * sorter: function(a, b) { * return b.label.length - a.label.length; * }, * }; * ``` * * @default sortByAlpha */ sorter: (current: Types.RecordToCompare, next: Types.RecordToCompare) => number; /** * Whether the input should show a placeholder. Used in conjunction with `placeholderValue`. If `placeholder` is set to true and no value is passed to `placeholderValue`, the passed input's placeholder attribute will be used as the placeholder value. * * **Input types affected:** text, select-multiple * * @note For single select boxes, the recommended way of adding a placeholder is as follows: * ``` * * ... * ... * ... * * ``` * * @default true */ placeholder: boolean; /** * The value of the inputs placeholder. * * **Input types affected:** text, select-multiple * * @default null */ placeholderValue: string | null; /** * The value of the search inputs placeholder. * * **Input types affected:** select-one * * @default null */ searchPlaceholderValue: string | null; /** * Prepend a value to each item added/selected. * * **Input types affected:** text, select-one, select-multiple * * @default null */ prependValue: string | null; /** * Append a value to each item added/selected. * * **Input types affected:** text, select-one, select-multiple * * @default null */ appendValue: string | null; /** * Whether selected choices should be removed from the list. By default choices are removed when they are selected in multiple select box. To always render choices pass `always`. * * **Input types affected:** select-one, select-multiple * * @default 'auto'; */ renderSelectedChoices: 'auto' | 'always' | boolean; /** * Whether selected choices should be removed from the list during search. * * **Input types affected:** select-multiple * * @default false; */ searchRenderSelectedChoices: boolean; /** * The text that is shown whilst choices are being populated via AJAX. * * **Input types affected:** select-one, select-multiple * * @default 'Loading...' */ loadingText: string; /** * The text that is shown when a user's search has returned no results. Optionally pass a function returning a string. * * **Input types affected:** select-one, select-multiple * * @default 'No results found' */ noResultsText: string | Types.StringFunction; /** * The text that is shown when a user has selected all possible choices, or no choices exist. Optionally pass a function returning a string. * * **Input types affected:** select-multiple, select-one * * @default 'No choices to choose from' */ noChoicesText: string | Types.StringFunction; /** * The text that is shown when a user hovers over a selectable choice. Set to empty to not reserve space for this text. * * **Input types affected:** select-multiple, select-one * * @default 'Press to select' */ itemSelectText: string; /** * The text that is shown when a user has focus on the input but has already reached the **max item count** [https://github.com/jshjohnson/Choices#maxitemcount]. To access the max item count, pass a function with a `maxItemCount` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string. * * **Input types affected:** text * * @default * ``` * (maxItemCount) => `Only ${maxItemCount} values can be added.`; * ``` */ maxItemText: string | Types.NoticeLimitFunction; /** * If no duplicates are allowed, and the value already exists in the array. * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * @default 'Only unique values can be added' */ uniqueItemText: string | Types.NoticeStringFunction; /** * The text that is shown when addItemFilter is passed and it returns false * * Return type must be safe to insert into HTML (ie use the 1st argument which is sanitised) * * **Input types affected:** text * * @default 'Only values matching specific conditions can be added' */ customAddItemText: string | Types.NoticeStringFunction; /** * Compare choice and value in appropriate way (e.g. deep equality for objects). To compare choice and value, pass a function with a `valueComparer` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example). * * **Input types affected:** select-one, select-multiple * * @default * ``` * (choice, item) => choice === item; * ``` */ valueComparer: Types.ValueCompareFunction; /** * Classes added to HTML generated by By default classnames follow the BEM notation. * * **Input types affected:** text, select-one, select-multiple */ classNames: ClassNames; /** * Choices uses the great Fuse library for searching. You can find more options here: https://fusejs.io/api/options.html */ fuseOptions: IFuseOptions; // IFuseOptions; /** * ID of the connected label to improve a11y. If set, aria-labelledby will be added. */ labelId: string; /** * Function to run once Choices initialises. * * **Input types affected:** text, select-one, select-multiple * * @note For each callback, this refers to the current instance of This can be useful if you need access to methods `(this.disable())` or the config object `(this.config)`. * * @default null */ callbackOnInit: (() => void) | null; /** * Function to run on template creation. Through this callback it is possible to provide custom templates for the various components of Choices (see terminology). For Choices to work with custom templates, it is important you maintain the various data attributes defined here [https://github.com/jshjohnson/Choices/blob/67f29c286aa21d88847adfcd6304dc7d068dc01f/assets/scripts/src/choices.js#L1993-L2067]. * * **Input types affected:** text, select-one, select-multiple * * @note For each callback, `this` refers to the current instance of Choices. This can be useful if you need access to methods `(this.disable())`. * * @example * ``` * const example = new Choices(element, { * callbackOnCreateTemplates: function (template, originalTemplates, getClassNames) { * var classNames = this.config.classNames; * return { * item: (data) => { * return template(` * * ★ ${data.label} * * `); * }, * choice: (data) => { * return template(` * * ★ ${data.label} * * `); * }, * }; * } * }); * ``` * * @default null */ callbackOnCreateTemplates: CallbackOnCreateTemplatesFn | null; appendGroupInSearch: boolean; } ================================================ FILE: src/scripts/interfaces/passed-element-type.ts ================================================ import { Types } from './types'; export const PassedElementTypes = { Text: 'text', SelectOne: 'select-one', SelectMultiple: 'select-multiple', } as const; export type PassedElementType = Types.ValueOf; ================================================ FILE: src/scripts/interfaces/passed-element.ts ================================================ import { InputChoice } from './input-choice'; import { EventChoice } from './event-choice'; /** * Events fired by Choices behave the same as standard events. Each event is triggered on the element passed to Choices (accessible via `this.passedElement`. Arguments are accessible within the `event.detail` object. */ export interface EventMap { /** * Triggered each time an item is added (programmatically or by the user). * * **Input types affected:** text, select-one, select-multiple * * Arguments: id, value, label, groupValue */ addItem: CustomEvent; /** * Triggered each time an item is removed (programmatically or by the user). * * **Input types affected:** text, select-one, select-multiple * * Arguments: id, value, label, groupValue */ removeItem: CustomEvent; /** * Triggered each time an item is highlighted. * * **Input types affected:** text, select-multiple * * Arguments: id, value, label, groupValue */ highlightItem: CustomEvent
Choices.js is a lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.
For all config options, visit the GitHub repo.
Interested in writing your own ES6 JavaScript plugins? Check out ES6.io for great tutorials! 💪🏼
Sponsored by:
If the following example do not load, the Discogs rate limit has probably been reached. Try again later!
If the following two examples do not load, the Discogs rate limit has probably been reached. Try again later!
Try searching for 'fantastic', "Label 3" should display
data-custom-properties
Below is an example of how you could have two select inputs depend on eachother. 'Tube stations' will only be enabled if the value of 'Cities' is 'London'
Change the values and press reset to restore to initial state.
Try submitting the form first.
Then try setting the values as they are required!