mirror of
https://github.com/Choices-js/Choices.git
synced 2024-06-09 01:12:15 +02:00
Merge remote-tracking branch 'origin/master' into fix-573
This commit is contained in:
commit
a3f20c05ea
24
.babelrc
24
.babelrc
|
@ -1,11 +1,17 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-modules-commonjs",
|
||||
["@babel/plugin-transform-spread", {
|
||||
"loose": true
|
||||
}]
|
||||
]
|
||||
"presets": [["@babel/preset-env", { "loose": true }]],
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": true
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
> 5%
|
||||
IE 11
|
||||
> 1%
|
||||
|
|
4
.codecov.yml
Normal file
4
.codecov.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
coverage:
|
||||
parsers:
|
||||
javascript:
|
||||
enable_partials: yes
|
|
@ -1 +1,3 @@
|
|||
node_modules/
|
||||
types/
|
||||
public/
|
54
.eslintrc
54
.eslintrc
|
@ -1,54 +0,0 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": [
|
||||
"prettier",
|
||||
"cypress"
|
||||
],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"mocha": true,
|
||||
"cypress/globals": true
|
||||
},
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"it": true,
|
||||
"before": true,
|
||||
"after": true,
|
||||
"beforeEach": true,
|
||||
"afterEach": true
|
||||
},
|
||||
"rules": {
|
||||
"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",
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
95
.eslintrc.json
Normal file
95
.eslintrc.json
Normal file
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:compat/recommended"
|
||||
],
|
||||
"plugins": ["prettier", "sort-class-members"],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"browser": true
|
||||
},
|
||||
"rules": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.test.js"],
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
"no-restricted-syntax": "off",
|
||||
"compat/compat": "off",
|
||||
"no-new": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["cypress/**"],
|
||||
"plugins": ["cypress"],
|
||||
"rules": {
|
||||
"no-unused-vars": "warn"
|
||||
},
|
||||
"env": {
|
||||
"cypress/globals": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"polyfills": [
|
||||
"Array.from",
|
||||
"Array.prototype.find",
|
||||
"Array.prototype.includes",
|
||||
"Symbol",
|
||||
"Symbol.iterator",
|
||||
"Object.assign",
|
||||
"CustomEvent",
|
||||
"Element.prototype.classList",
|
||||
"Element.prototype.closest"
|
||||
]
|
||||
}
|
||||
}
|
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,28 +1,31 @@
|
|||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
<!--- Describe your changes in detail -->
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
## How Has This Been Tested?
|
||||
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, tests ran to see how -->
|
||||
<!--- your change affects other areas of the code, etc. -->
|
||||
|
||||
## Screenshots (if appropriate):
|
||||
## Screenshots (if appropriate)
|
||||
|
||||
## Types of changes
|
||||
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] Chore (tooling change or documentation change)
|
||||
- [ ] 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:
|
||||
## Checklist
|
||||
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
|
|
14
.github/actions-scripts/polyfills-sync.js
vendored
Normal file
14
.github/actions-scripts/polyfills-sync.js
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
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);
|
26
.github/release-drafter.yml
vendored
Normal file
26
.github/release-drafter.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
name-template: 'Draft (next release)'
|
||||
tag-template: 'v$NEXT_PATCH_VERSION'
|
||||
sort-direction: descending
|
||||
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
|
20
.github/stale.yml
vendored
20
.github/stale.yml
vendored
|
@ -1,20 +0,0 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 100
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 10
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- feature request
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
Thanks for contributing to this issue. As it has been 60 days since the last
|
||||
activity, this issue is being automatically closed. This is often because the
|
||||
request was already solved in some way and it just wasn't updated or it's no
|
||||
longer applicable. If that's not the case, please do feel free to either
|
||||
reopen this issue or open a new one. We'll gladly take a look again!
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
46
.github/workflows/build-and-test.yml
vendored
Normal file
46
.github/workflows/build-and-test.yml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
name: Build and test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
# run all tests
|
||||
- run: |
|
||||
npm ci
|
||||
npm run build
|
||||
npx bundlesize
|
||||
npm run test:unit:coverage
|
||||
npm run test:e2e
|
||||
env:
|
||||
CI: true
|
||||
CI_REPO_NAME: ${{ github.event.repository.name }}
|
||||
CI_REPO_OWNER: ${{ github.event.organization.login }}
|
||||
CI_COMMIT_SHA: ${{ github.sha }}
|
||||
GIT_COMMIT: ${{ github.sha }}
|
||||
CI_BRANCH: ${{ github.head_ref }}
|
||||
BUNDLESIZE_GITHUB_TOKEN: ${{secrets.BUNDLESIZE_GITHUB_TOKEN}}
|
||||
FORCE_COLOR: 2
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
- 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}}
|
42
.github/workflows/bundlesize.yml
vendored
Normal file
42
.github/workflows/bundlesize.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: Bundle size checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/scripts/**'
|
||||
- 'src/styles/**'
|
||||
- 'package-lock.json'
|
||||
- '.browserslistrc'
|
||||
|
||||
jobs:
|
||||
measure:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
|
||||
- name: Install dependencies and build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
# we don't need to build here, as even minized assets expected to be commited
|
||||
|
||||
- run: npx bundlesize
|
||||
env:
|
||||
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
|
87
.github/workflows/deployment.yml
vendored
Normal file
87
.github/workflows/deployment.yml
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
name: Publish and deploy
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
# run all tests
|
||||
- run: |
|
||||
npm ci
|
||||
npm run build
|
||||
npx bundlesize
|
||||
npm run test:unit:coverage
|
||||
npm run test:e2e
|
||||
env:
|
||||
CI: true
|
||||
CI_REPO_NAME: ${{ github.event.repository.name }}
|
||||
CI_REPO_OWNER: ${{ github.event.organization.login }}
|
||||
CI_COMMIT_SHA: ${{ github.sha }}
|
||||
GIT_COMMIT: ${{ github.sha }}
|
||||
CI_BRANCH: ${{ github.head_ref }}
|
||||
BUNDLESIZE_GITHUB_TOKEN: ${{secrets.BUNDLESIZE_GITHUB_TOKEN}}
|
||||
FORCE_COLOR: 2
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
- 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}}
|
||||
|
||||
publish-npm:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm ci
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
- run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
|
||||
deploy-gh-pages:
|
||||
needs: publish-npm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
rm -rf public/test
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v2.5.0
|
||||
env:
|
||||
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
|
||||
PUBLISH_BRANCH: gh-pages
|
||||
PUBLISH_DIR: ./public
|
43
.github/workflows/e2e-tests.yml
vendored
Normal file
43
.github/workflows/e2e-tests.yml
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
name: End-to-end tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'package-lock.json'
|
||||
- '.browserslistrc'
|
||||
- '.babelrc'
|
||||
- 'webpack.config.*'
|
||||
- 'public/test/**'
|
||||
- 'cypress/**'
|
||||
- '.github/workflows/cypress.yml'
|
||||
|
||||
jobs:
|
||||
test-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
- name: run Cypress CI
|
||||
run: npx run-p --race start cypress:ci
|
||||
env:
|
||||
CI: true
|
||||
TERM: xterm-256color
|
||||
NODE_ENV: production # prevent watching
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
DEBUG: commit-info,cypress:server:record
|
||||
# https://docs.cypress.io/guides/guides/continuous-integration.html#Environment-variables
|
||||
COMMIT_INFO_BRANCH: ${{ github.head_ref }}
|
||||
COMMIT_INFO_AUTHOR: ${{ github.event.sender.login }}
|
||||
COMMIT_INFO_SHA: ${{ github.event.after }}
|
38
.github/workflows/lint.yml
vendored
Normal file
38
.github/workflows/lint.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
name: Code linting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/scripts/**'
|
||||
- package-lock.json
|
||||
- '.browserslistrc'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --no-optional --no-audit --ignore-scripts
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
- name: run eslint
|
||||
run: |
|
||||
CHANGED_JS=$(git --no-pager diff --name-only ..origin/master | grep '^src\/scripts\/.*\.js$' | xargs ls -d 2>/dev/null | paste -sd " " -)
|
||||
if [[ -z $(sed -e 's/[[:space:]]*$//' <<<${CHANGED_JS}) ]]; then CHANGED_JS="src/scripts"; fi
|
||||
echo $CHANGED_JS
|
||||
node node_modules/eslint/bin/eslint.js $CHANGED_JS
|
||||
|
||||
- name: Lint JS bundle
|
||||
run: |
|
||||
npm run js:build
|
||||
npx eslint --no-ignore ./public/assets/scripts/*.js
|
23
.github/workflows/polyfills-sync.yml
vendored
Normal file
23
.github/workflows/polyfills-sync.yml
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
name: Polyfills documentation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'README.md'
|
||||
- '.browserslistrc'
|
||||
- '.eslintrc.json'
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Check Polyfills documentation and settings sync
|
||||
run: node .github/actions-scripts/polyfills-sync.js
|
14
.github/workflows/release-management.yml
vendored
Normal file
14
.github/workflows/release-management.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Release management
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
update-draft-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: toolmantim/release-drafter@v5.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
27
.github/workflows/types.yml
vendored
Normal file
27
.github/workflows/types.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: TypeScript Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'types/index.d.ts'
|
||||
|
||||
jobs:
|
||||
tsc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Install TypeScript
|
||||
run: npm install -g typescript
|
||||
|
||||
- name: Install required dependencies
|
||||
run: npm i --only=production --no-optional --no-audit --ignore-scripts
|
||||
|
||||
- name: Check typings file
|
||||
run: tsc types/index.d.ts
|
42
.github/workflows/unit-tests.yml
vendored
Normal file
42
.github/workflows/unit-tests.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: Unit tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/scripts/**'
|
||||
- package-lock.json
|
||||
- '.browserslistrc'
|
||||
|
||||
jobs:
|
||||
test-unit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --no-optional --no-audit --ignore-scripts
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
- 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}}
|
6
.huskyrc
Normal file
6
.huskyrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"skipCI": true,
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
}
|
7
.mocharc.yml
Normal file
7
.mocharc.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
require:
|
||||
- '@babel/register'
|
||||
- './config/jsdom.js'
|
||||
|
||||
exit: true
|
||||
|
||||
spec: src/**/*.test.js
|
13
.prettierrc.json
Normal file
13
.prettierrc.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.svg"],
|
||||
"options": {
|
||||
"parser": "html",
|
||||
"htmlWhitespaceSensitivity": "ignore"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
19
.travis.yml
19
.travis.yml
|
@ -1,19 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 10
|
||||
cache:
|
||||
directories:
|
||||
- ~/.npm
|
||||
- ~/.cache
|
||||
install:
|
||||
- npm ci
|
||||
jobs:
|
||||
include:
|
||||
- stage: Judging bundle size
|
||||
script: npm run bundlesize
|
||||
|
||||
- stage: Linting code
|
||||
script: npm run lint
|
||||
|
||||
- stage: Running tests
|
||||
script: npm run test
|
17
.vscode/extensions.json
vendored
Normal file
17
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
// 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"
|
||||
// Mocha recommended - https://mochajs.org/#mocha-sidebar-vs-code,
|
||||
// but it's buggy
|
||||
// "maty.vscode-mocha-sidebar"
|
||||
]
|
||||
}
|
70
.vscode/launch.json
vendored
Normal file
70
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome",
|
||||
"preLaunchTask": "buildAndWatch",
|
||||
"url": "http://localhost:3001",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack://Choices/*": "${workspaceFolder}/*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Mocha Current File",
|
||||
"program": "${workspaceFolder}/node_modules/mocha/bin/mocha",
|
||||
"args": ["-u", "bdd", "--timeout", "999999", "--colors", "${file}"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"env": {
|
||||
"NODE_ENV": "test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Mocha All",
|
||||
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
|
||||
"args": ["-u", "bdd", "--timeout", "999999", "--colors"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"env": {
|
||||
"NODE_ENV": "test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Cypress Current File",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/cypress",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\cypress.cmd"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"--headed",
|
||||
"--no-exit",
|
||||
"--browser=electron",
|
||||
"--port",
|
||||
"9898",
|
||||
"--spec"
|
||||
],
|
||||
"protocol": "legacy",
|
||||
"port": 9898,
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"preLaunchTask": "buildAndWatch",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"timeout": 999999999999999,
|
||||
"autoAttachChildProcesses": false,
|
||||
"env": {
|
||||
"NODE_ENV": "test"
|
||||
// "DEBUG": "cypress:*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
64
.vscode/settings.json
vendored
64
.vscode/settings.json
vendored
|
@ -1,3 +1,63 @@
|
|||
{
|
||||
"eslint.enable": true
|
||||
}
|
||||
"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
|
||||
},
|
||||
// Mocha Sidebar settings
|
||||
"mocha.env": {
|
||||
"NODE_ENV": "test"
|
||||
},
|
||||
"mocha.files.glob": "src/scripts/**/*.test.js",
|
||||
"mocha.requires": ["@babel/register", "./config/jsdom.js"],
|
||||
// for Windows collaborators
|
||||
"files.eol": "\n",
|
||||
"files.encoding": "utf8",
|
||||
// associations for some files this project is using
|
||||
"files.associations": {
|
||||
".browserslistrc": "gitignore",
|
||||
".huskyrc": "jsonc",
|
||||
".npmrc": "ini"
|
||||
},
|
||||
// We use NPM as package manager
|
||||
"npm.packageManager": "npm",
|
||||
"npm.autoDetect": "on",
|
||||
"npm.fetchOnlinePackageInfo": true,
|
||||
"eslint.packageManager": "npm",
|
||||
"json.schemas": [
|
||||
// Cypress related settings - https://docs.cypress.io/guides/tooling/intelligent-code-completion.html#Features-1
|
||||
{
|
||||
"fileMatch": ["cypress.json"],
|
||||
"url": "https://on.cypress.io/cypress.schema.json"
|
||||
},
|
||||
// Husky config file
|
||||
{
|
||||
"fileMatch": [".huskyrc"],
|
||||
"url": "http://json.schemastore.org/huskyrc"
|
||||
},
|
||||
// Prettier config
|
||||
{
|
||||
"fileMatch": [".prettierrc.json"],
|
||||
"url": "http://json.schemastore.org/prettierrc"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
87
.vscode/tasks.json
vendored
Normal file
87
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
// 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"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "cypress:open",
|
||||
"isBackground": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
# Contributions
|
||||
# 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:
|
||||
|
||||
### NPM tasks
|
||||
| Task | Usage |
|
||||
| -------------------- | ------------------------------------------------------------ |
|
||||
| `npm run start` | Fire up local server for development |
|
||||
| `npm run test` | Run sequence of tests once |
|
||||
| `npm run test:unit` | Run sequence of unit tests once |
|
||||
| `npm run test:e2e` | Run sequence of integration tests once |
|
||||
| `npm run test:watch` | Fire up test server and re-test on file change |
|
||||
| `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 |
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const config = {
|
||||
files: ['public/index.html'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert node arguments into an object
|
||||
* @return {Object} Arguments
|
||||
*/
|
||||
const argvToObject = () => {
|
||||
const args = {};
|
||||
let arg = null;
|
||||
process.argv.forEach((val, index) => {
|
||||
if (/^--/.test(val)) {
|
||||
arg = {
|
||||
index,
|
||||
name: val.replace(/^--/, ''),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg && arg.index + 1 === index) {
|
||||
args[arg.name] = val;
|
||||
}
|
||||
});
|
||||
|
||||
return args;
|
||||
};
|
||||
|
||||
/**
|
||||
* Loop through files updating the current version
|
||||
* @param {Object} config
|
||||
*/
|
||||
const updateVersion = ({ files }) => {
|
||||
const args = argvToObject();
|
||||
const version = args.current;
|
||||
|
||||
console.log(`Updating version to ${version}`);
|
||||
|
||||
files.forEach(file => {
|
||||
const filePath = path.join(__dirname, file);
|
||||
const regex = new RegExp(/\?version=(.*?)\"/, 'g');
|
||||
|
||||
let contents = fs.readFileSync(filePath, 'utf-8');
|
||||
contents = contents.replace(regex, `?version=${version}"`);
|
||||
fs.writeFileSync(filePath, contents);
|
||||
});
|
||||
|
||||
console.log(`Updated version to ${version}`);
|
||||
};
|
||||
|
||||
updateVersion(config);
|
|
@ -1,6 +1,13 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
|
||||
const { JSDOM } = require('jsdom');
|
||||
|
||||
const jsdom = new JSDOM('<!doctype html><html><body></body></html>');
|
||||
const jsdom = new JSDOM(
|
||||
'<!doctype html><html><head><meta charset="utf-8"></head><body></body></html>',
|
||||
{
|
||||
pretendToBeVisual: true,
|
||||
},
|
||||
);
|
||||
const { window } = jsdom;
|
||||
|
||||
function copyProps(src, target) {
|
||||
|
@ -20,26 +27,6 @@ function ignoreExtensions(extensions = [], returnValue = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
function mockRAF(global) {
|
||||
let callbacksQueue = [];
|
||||
|
||||
global.setInterval(() => {
|
||||
for (let i = 0; i < callbacksQueue.length; i++) {
|
||||
if (callbacksQueue[i] !== false) {
|
||||
callbacksQueue[i].call(null);
|
||||
}
|
||||
}
|
||||
|
||||
callbacksQueue = [];
|
||||
}, 1000 / 60);
|
||||
|
||||
global.requestAnimationFrame = callback => callbacksQueue.push(callback) - 1;
|
||||
|
||||
global.cancelAnimationFrame = id => {
|
||||
callbacksQueue[id] = false;
|
||||
};
|
||||
}
|
||||
|
||||
global.window = window;
|
||||
global.document = window.document;
|
||||
global.navigator = {
|
||||
|
@ -48,12 +35,16 @@ global.navigator = {
|
|||
global.CustomEvent = window.CustomEvent;
|
||||
global.Element = window.Element;
|
||||
global.HTMLElement = window.HTMLElement;
|
||||
global.Option = window.Option;
|
||||
global.HTMLOptionElement = window.HTMLOptionElement;
|
||||
global.HTMLOptGroupElement = window.HTMLOptGroupElement;
|
||||
global.HTMLSelectElement = window.HTMLSelectElement;
|
||||
global.HTMLInputElement = window.HTMLInputElement;
|
||||
global.DocumentFragment = window.DocumentFragment;
|
||||
global.requestAnimationFrame = window.requestAnimationFrame;
|
||||
window.matchMedia = () => true;
|
||||
|
||||
copyProps(window, global);
|
||||
mockRAF(global);
|
||||
|
||||
ignoreExtensions(['.scss', '.css']);
|
||||
ignoreExtensions(['.jpg', '.png', '.svg'], '');
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:3001/test",
|
||||
"video": false
|
||||
}
|
||||
"video": false,
|
||||
"projectId": "n7g5qp"
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ describe('Choices - select multiple', () => {
|
|||
|
||||
it('updates the value of the original input', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__input.is-hidden')
|
||||
.find('.choices__input[hidden]')
|
||||
.should($select => {
|
||||
expect($select.val()).to.contain(selectedChoiceText);
|
||||
});
|
||||
|
@ -150,7 +150,7 @@ describe('Choices - select multiple', () => {
|
|||
|
||||
it('updates the value of the original input', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__input.is-hidden')
|
||||
.find('.choices__input[hidden]')
|
||||
.should($select => {
|
||||
const val = $select.val() || [];
|
||||
expect(val).to.not.contain(removedChoiceText);
|
||||
|
@ -334,7 +334,7 @@ describe('Choices - select multiple', () => {
|
|||
});
|
||||
|
||||
describe('on click', () => {
|
||||
it('does not opens choice dropdown', () => {
|
||||
it('does not open choice dropdown', () => {
|
||||
cy.get('[data-test-hook=disabled-via-attr]')
|
||||
.find('.choices')
|
||||
.click()
|
||||
|
@ -515,7 +515,7 @@ describe('Choices - select multiple', () => {
|
|||
});
|
||||
|
||||
describe('on click', () => {
|
||||
it('does not opens choice dropdown', () => {
|
||||
it('does not open choice dropdown', () => {
|
||||
cy.get('[data-test-hook=remote-data]')
|
||||
.find('.choices')
|
||||
.click()
|
||||
|
@ -567,6 +567,8 @@ describe('Choices - select multiple', () => {
|
|||
|
||||
it('scrolls to next choice on down arrow', () => {
|
||||
for (let index = 0; index < choicesCount; index++) {
|
||||
cy.wait(100);
|
||||
|
||||
cy.get('[data-test-hook=scrolling-dropdown]')
|
||||
.find('.choices__list--dropdown .choices__list .is-highlighted')
|
||||
.should($choice => {
|
||||
|
@ -753,7 +755,7 @@ describe('Choices - select multiple', () => {
|
|||
describe('within form', () => {
|
||||
describe('selecting choice', () => {
|
||||
describe('on enter key', () => {
|
||||
it('does not submit form', () => {
|
||||
it('selects choice', () => {
|
||||
cy.get('[data-test-hook=within-form] form').then($form => {
|
||||
$form.submit(() => {
|
||||
// this will fail the test if the form submits
|
||||
|
@ -763,7 +765,7 @@ describe('Choices - select multiple', () => {
|
|||
|
||||
cy.get('[data-test-hook=within-form]')
|
||||
.find('.choices__input--cloned')
|
||||
.focus()
|
||||
.click()
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=within-form]')
|
||||
|
@ -804,7 +806,7 @@ describe('Choices - select multiple', () => {
|
|||
|
||||
it('updates the value of the original input', () => {
|
||||
cy.get('[data-test-hook=set-choice-by-value]')
|
||||
.find('.choices__input.is-hidden')
|
||||
.find('.choices__input[hidden]')
|
||||
.should($select => {
|
||||
const val = $select.val() || [];
|
||||
expect(val).to.contain(dynamicallySelectedChoiceValue);
|
||||
|
|
|
@ -208,7 +208,7 @@ describe('Choices - select one', () => {
|
|||
|
||||
it('updates the value of the original input', () => {
|
||||
cy.get('[data-test-hook=remove-button]')
|
||||
.find('.choices__input.is-hidden')
|
||||
.find('.choices__input[hidden]')
|
||||
.should($select => {
|
||||
const val = $select.val() || [];
|
||||
|
||||
|
@ -466,7 +466,7 @@ describe('Choices - select one', () => {
|
|||
});
|
||||
|
||||
describe('on click', () => {
|
||||
it('does not opens choice dropdown', () => {
|
||||
it('does not open choice dropdown', () => {
|
||||
cy.get('[data-test-hook=remote-data]')
|
||||
.find('.choices')
|
||||
.click()
|
||||
|
@ -818,7 +818,7 @@ describe('Choices - select one', () => {
|
|||
|
||||
it('updates the value of the original input', () => {
|
||||
cy.get('[data-test-hook=set-choice-by-value]')
|
||||
.find('.choices__input.is-hidden')
|
||||
.find('.choices__input[hidden]')
|
||||
.should($select => {
|
||||
const val = $select.val() || [];
|
||||
expect(val).to.contain(dynamicallySelectedChoiceValue);
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('Choices - text element', () => {
|
|||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__input.is-hidden')
|
||||
.find('.choices__input[hidden]')
|
||||
.should('have.value', textInput);
|
||||
});
|
||||
|
||||
|
@ -151,7 +151,7 @@ describe('Choices - text element', () => {
|
|||
.click();
|
||||
|
||||
cy.get('[data-test-hook=remove-button]')
|
||||
.find('.choices__input.is-hidden')
|
||||
.find('.choices__input[hidden]')
|
||||
.then($input => {
|
||||
expect($input.val()).to.not.contain(textInput);
|
||||
});
|
||||
|
|
|
@ -14,4 +14,4 @@
|
|||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
import './commands';
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
|
16
jsconfig.json
Normal file
16
jsconfig.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": true,
|
||||
"target": "es2020",
|
||||
"lib": ["esnext", "dom"],
|
||||
"types": ["cypress"],
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
/* Additional Checks */
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
14
lint-staged.config.js
Normal file
14
lint-staged.config.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
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',
|
||||
};
|
15472
package-lock.json
generated
15472
package-lock.json
generated
File diff suppressed because it is too large
Load diff
109
package.json
109
package.json
|
@ -1,32 +1,32 @@
|
|||
{
|
||||
"name": "choices.js",
|
||||
"version": "7.0.0",
|
||||
"version": "8.0.0",
|
||||
"description": "A vanilla JS customisable text input/select box plugin",
|
||||
"main": "./public/assets/scripts/choices.min.js",
|
||||
"main": "./public/assets/scripts/choices.js",
|
||||
"types": "./types/index.d.ts",
|
||||
"scripts": {
|
||||
"start": "run-p js:watch css:watch",
|
||||
"build": "npm run js:build && npm run css:build",
|
||||
"lint": "eslint $(find src -name '*.js')",
|
||||
"coverage": "nyc npm run test:unit",
|
||||
"build": "run-p js:build css:build",
|
||||
"lint": "eslint src/scripts",
|
||||
"bundlesize": "bundlesize",
|
||||
"cypress:run": "$(npm bin)/cypress run",
|
||||
"cypress:open": "$(npm bin)/cypress open",
|
||||
"test": "npm run test:unit && npm run test:e2e",
|
||||
"test:unit": "mocha --require ./config/jsdom.js --require @babel/register $(find src -name '*.test.js') --exit",
|
||||
"test:unit:watch": "npm run test:unit -- --watch --inspect=5556",
|
||||
"cypress:ci": "cypress run --record --group $GITHUB_REF --ci-build-id $GITHUB_SHA",
|
||||
"test": "run-s test:unit test:e2e",
|
||||
"test:unit": "NODE_ENV=test mocha",
|
||||
"test:unit:watch": "NODE_ENV=test mocha --watch --inspect=5556",
|
||||
"test:unit:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text --reporter=text-summary mocha",
|
||||
"test:e2e": "run-p --race start cypress:run",
|
||||
"js:watch": "NODE_ENV=development node server.js",
|
||||
"js:build": "webpack --config webpack.config.prod.js",
|
||||
"css:watch": "nodemon -e scss -x \"npm run css:build\"",
|
||||
"css:build": "run-p css:sass css:prefix css:min",
|
||||
"css:sass": "node-sass --output-style expanded --include-path scss src/styles/base.scss public/assets/styles/base.css && node-sass --output-style expanded --include-path scss src/styles/choices.scss public/assets/styles/choices.css",
|
||||
"css:prefix": "postcss --use autoprefixer -b 'last 2 versions' public/assets/styles/*.css -d public/assets/styles",
|
||||
"css:min": "csso public/assets/styles/base.css public/assets/styles/base.min.css && csso public/assets/styles/choices.css public/assets/styles/choices.min.css",
|
||||
"bump-cache": "node bump-cache.js --current $npm_package_version",
|
||||
"css:build": "run-s css:sass css:prefix css:min",
|
||||
"css:sass": "node-sass --output-style expanded --include-path scss src/styles/base.scss public/assets/styles/base.css && node-sass --output-style expanded --include-path 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",
|
||||
"deploy": "git subtree push --prefix public origin gh-pages",
|
||||
"postversion": "npm run js:build && npm run bump-cache",
|
||||
"prepush": "run-p lint test:unit && npm run bundlesize"
|
||||
"postversion": "git push --no-verify --atomic --follow-tags",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -38,6 +38,7 @@
|
|||
"public/assets/scripts",
|
||||
"public/assets/styles",
|
||||
"src",
|
||||
"!src/**/*.test.js",
|
||||
"types"
|
||||
],
|
||||
"bugs": {
|
||||
|
@ -53,53 +54,45 @@
|
|||
"js"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.2.2",
|
||||
"@babel/plugin-proposal-class-properties": "^7.3.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.2.0",
|
||||
"@babel/plugin-transform-spread": "^7.2.2",
|
||||
"@babel/preset-env": "^7.3.1",
|
||||
"@babel/register": "^7.0.0",
|
||||
"autoprefixer": "^6.3.3",
|
||||
"babel-eslint": "^9.0.0",
|
||||
"babel-loader": "^8.0.5",
|
||||
"bundlesize": "^0.17.1",
|
||||
"@babel/core": "^7.6.4",
|
||||
"@babel/preset-env": "^7.6.3",
|
||||
"@babel/register": "^7.6.2",
|
||||
"autoprefixer": "^9.6.5",
|
||||
"babel-loader": "^8.0.6",
|
||||
"bundlesize": "^0.18.0",
|
||||
"chai": "^4.2.0",
|
||||
"csso": "^1.8.2",
|
||||
"cypress": "^3.1.5",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-config-airbnb": "^15.1.0",
|
||||
"eslint-config-prettier": "^2.10.0",
|
||||
"eslint-loader": "^2.1.2",
|
||||
"eslint-plugin-cypress": "^2.2.0",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-jsx-a11y": "^5.1.1",
|
||||
"eslint-plugin-prettier": "^2.7.0",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"csso-cli": "^3.0.0",
|
||||
"cypress": "3.5.0",
|
||||
"eslint": "^6.6.0",
|
||||
"eslint-config-airbnb-base": "^14.0.0",
|
||||
"eslint-config-prettier": "^6.5.0",
|
||||
"eslint-loader": "^3.0.2",
|
||||
"eslint-plugin-compat": "3.3.0",
|
||||
"eslint-plugin-cypress": "^2.7.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-prettier": "^3.1.1",
|
||||
"eslint-plugin-sort-class-members": "^1.6.0",
|
||||
"express": "^4.16.4",
|
||||
"husky": "^0.14.3",
|
||||
"jsdom": "^11.12.0",
|
||||
"mocha": "^5.2.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"husky": "^3.0.9",
|
||||
"jsdom": "^15.2.0",
|
||||
"lint-staged": "^9.4.2",
|
||||
"mocha": "^6.2.2",
|
||||
"node-sass": "^4.12.0",
|
||||
"nodemon": "^1.18.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^11.9.0",
|
||||
"opn": "^5.4.0",
|
||||
"postcss-cli": "^2.5.1",
|
||||
"prettier": "^1.16.4",
|
||||
"sinon": "^2.4.0",
|
||||
"unminified-webpack-plugin": "^2.0.0",
|
||||
"webpack": "^4.29.3",
|
||||
"webpack-cli": "^3.2.3",
|
||||
"webpack-dev-middleware": "^3.5.2",
|
||||
"webpack-hot-middleware": "^2.24.3",
|
||||
"whatwg-fetch": "^1.0.0",
|
||||
"wrapper-webpack-plugin": "^2.1.0"
|
||||
"nyc": "^14.1.1",
|
||||
"postcss-cli": "^6.1.3",
|
||||
"prettier": "^1.18.2",
|
||||
"sinon": "^7.5.0",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-cli": "^3.3.9",
|
||||
"webpack-dev-middleware": "^3.7.2",
|
||||
"webpack-hot-middleware": "^2.25.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.6",
|
||||
"deepmerge": "^2.2.1",
|
||||
"fuse.js": "3.4.2",
|
||||
"redux": "^3.3.1"
|
||||
"deepmerge": "^4.2.0",
|
||||
"fuse.js": "^3.4.5",
|
||||
"redux": "^4.0.4"
|
||||
},
|
||||
"npmName": "choices.js",
|
||||
"npmFileMap": [
|
||||
|
@ -122,11 +115,11 @@
|
|||
"bundlesize": [
|
||||
{
|
||||
"path": "public/assets/scripts/choices.min.js",
|
||||
"maxSize": "25 kB"
|
||||
"maxSize": "20 kB"
|
||||
},
|
||||
{
|
||||
"path": "public/assets/styles/choices.min.css",
|
||||
"maxSize": "2 kB"
|
||||
"maxSize": "1.8 kB"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
24
public/assets/scripts/.eslintrc.js
Normal file
24
public/assets/scripts/.eslintrc.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
|
||||
// get polyfill settings from top level config
|
||||
// @ts-ignore
|
||||
const { settings } = require('../../../.eslintrc.json');
|
||||
|
||||
// Adding non-polyfilable Symbol-related functions as they are most probably
|
||||
// behind the flag
|
||||
|
||||
settings.polyfills.push('Symbol.toStringTag', 'Symbol.for', 'Object.getOwnPropertySymbols', 'Object.getOwnPropertyDescriptors')
|
||||
|
||||
module.exports = /** @type {import('eslint').Linter.Config} */({
|
||||
root: true,
|
||||
extends: [
|
||||
"plugin:compat/recommended"
|
||||
],
|
||||
parserOptions: {
|
||||
// ensure that it's compatible with ES5 browsers, so, no `const`, etc
|
||||
ecmaVersion: 5
|
||||
},
|
||||
env: {
|
||||
browser: true
|
||||
},
|
||||
settings
|
||||
})
|
File diff suppressed because one or more lines are too long
55
public/assets/scripts/choices.min.js
vendored
55
public/assets/scripts/choices.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -21,10 +21,10 @@ body {
|
|||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
background-color: #333;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ h6 {
|
|||
a,
|
||||
a:visited,
|
||||
a:focus {
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ label + p {
|
|||
}
|
||||
|
||||
.section {
|
||||
background-color: #FFFFFF;
|
||||
background-color: #ffffff;
|
||||
padding: 24px;
|
||||
color: #333;
|
||||
}
|
||||
|
@ -180,10 +180,6 @@ label + p {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-test-hook] {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
|
2
public/assets/styles/base.min.css
vendored
2
public/assets/styles/base.min.css
vendored
|
@ -1 +1 @@
|
|||
*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{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:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label,p{margin-bottom:8px}label{font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:30px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;-moz-appearance:none;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:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.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}.is-hidden{display:none}[data-test-hook]{margin-bottom:24px}
|
||||
*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{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:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label,p{margin-bottom:8px}label{font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:30px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;-moz-appearance:none;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:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.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}
|
|
@ -17,33 +17,39 @@
|
|||
|
||||
.choices.is-disabled .choices__inner,
|
||||
.choices.is-disabled .choices__input {
|
||||
background-color: #EAEAEA;
|
||||
background-color: #eaeaea;
|
||||
cursor: not-allowed;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.choices.is-disabled .choices__item {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] {
|
||||
.choices [hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.choices[data-type*='select-one'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__inner {
|
||||
.choices[data-type*='select-one'] .choices__inner {
|
||||
padding-bottom: 7.5px;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__input {
|
||||
.choices[data-type*='select-one'] .choices__input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #DDDDDD;
|
||||
background-color: #FFFFFF;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
background-color: #ffffff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__button {
|
||||
.choices[data-type*='select-one'] .choices__button {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==);
|
||||
padding: 0;
|
||||
background-size: 8px;
|
||||
|
@ -55,19 +61,19 @@
|
|||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 10em;
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__button:hover, .choices[data-type*="select-one"] .choices__button:focus {
|
||||
.choices[data-type*='select-one'] .choices__button:hover, .choices[data-type*='select-one'] .choices__button:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__button:focus {
|
||||
box-shadow: 0px 0px 0px 2px #00BCD4;
|
||||
.choices[data-type*='select-one'] .choices__button:focus {
|
||||
box-shadow: 0px 0px 0px 2px #00bcd4;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"]:after {
|
||||
content: "";
|
||||
.choices[data-type*='select-one']:after {
|
||||
content: '';
|
||||
height: 0;
|
||||
width: 0;
|
||||
border-style: solid;
|
||||
|
@ -80,30 +86,30 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"].is-open:after {
|
||||
.choices[data-type*='select-one'].is-open:after {
|
||||
border-color: transparent transparent #333333 transparent;
|
||||
margin-top: -7.5px;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"][dir="rtl"]:after {
|
||||
.choices[data-type*='select-one'][dir='rtl']:after {
|
||||
left: 11.5px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"][dir="rtl"] .choices__button {
|
||||
.choices[data-type*='select-one'][dir='rtl'] .choices__button {
|
||||
right: auto;
|
||||
left: 0;
|
||||
margin-left: 25px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-multiple"] .choices__inner,
|
||||
.choices[data-type*="text"] .choices__inner {
|
||||
.choices[data-type*='select-multiple'] .choices__inner,
|
||||
.choices[data-type*='text'] .choices__inner {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-multiple"] .choices__button,
|
||||
.choices[data-type*="text"] .choices__button {
|
||||
.choices[data-type*='select-multiple'] .choices__button,
|
||||
.choices[data-type*='text'] .choices__button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
|
@ -116,13 +122,13 @@
|
|||
background-size: 8px;
|
||||
width: 8px;
|
||||
line-height: 1;
|
||||
opacity: .75;
|
||||
opacity: 0.75;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-multiple"] .choices__button:hover, .choices[data-type*="select-multiple"] .choices__button:focus,
|
||||
.choices[data-type*="text"] .choices__button:hover,
|
||||
.choices[data-type*="text"] .choices__button:focus {
|
||||
.choices[data-type*='select-multiple'] .choices__button:hover, .choices[data-type*='select-multiple'] .choices__button:focus,
|
||||
.choices[data-type*='text'] .choices__button:hover,
|
||||
.choices[data-type*='text'] .choices__button:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
@ -132,7 +138,7 @@
|
|||
width: 100%;
|
||||
background-color: #f9f9f9;
|
||||
padding: 7.5px 7.5px 3.75px;
|
||||
border: 1px solid #DDDDDD;
|
||||
border: 1px solid #dddddd;
|
||||
border-radius: 2.5px;
|
||||
font-size: 14px;
|
||||
min-height: 44px;
|
||||
|
@ -164,7 +170,7 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
[dir="rtl"] .choices__list--single {
|
||||
[dir='rtl'] .choices__list--single {
|
||||
padding-right: 4px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
@ -186,9 +192,9 @@
|
|||
font-weight: 500;
|
||||
margin-right: 3.75px;
|
||||
margin-bottom: 3.75px;
|
||||
background-color: #00BCD4;
|
||||
background-color: #00bcd4;
|
||||
border: 1px solid #00a5bb;
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
|
@ -196,7 +202,7 @@
|
|||
padding-right: 5px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .choices__list--multiple .choices__item {
|
||||
[dir='rtl'] .choices__list--multiple .choices__item {
|
||||
margin-right: 0;
|
||||
margin-left: 3.75px;
|
||||
}
|
||||
|
@ -216,8 +222,8 @@
|
|||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #DDDDDD;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #dddddd;
|
||||
top: 100%;
|
||||
margin-top: -1px;
|
||||
border-bottom-left-radius: 2.5px;
|
||||
|
@ -239,7 +245,7 @@
|
|||
bottom: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: -1px;
|
||||
border-radius: .25rem .25rem 0 0;
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__list {
|
||||
|
@ -256,7 +262,7 @@
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .choices__list--dropdown .choices__item {
|
||||
[dir='rtl'] .choices__list--dropdown .choices__item {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
@ -273,12 +279,12 @@
|
|||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
[dir="rtl"] .choices__list--dropdown .choices__item--selectable {
|
||||
[dir='rtl'] .choices__list--dropdown .choices__item--selectable {
|
||||
text-align: right;
|
||||
padding-left: 100px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
[dir="rtl"] .choices__list--dropdown .choices__item--selectable:after {
|
||||
[dir='rtl'] .choices__list--dropdown .choices__item--selectable:after {
|
||||
right: auto;
|
||||
left: 10px;
|
||||
}
|
||||
|
@ -289,7 +295,7 @@
|
|||
}
|
||||
|
||||
.choices__list--dropdown .choices__item--selectable.is-highlighted:after {
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.choices__item {
|
||||
|
@ -302,8 +308,10 @@
|
|||
|
||||
.choices__item--disabled {
|
||||
cursor: not-allowed;
|
||||
user-select: none;
|
||||
opacity: .5;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.choices__heading {
|
||||
|
@ -317,7 +325,8 @@
|
|||
.choices__button {
|
||||
text-indent: -9999px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
|
@ -345,19 +354,13 @@
|
|||
outline: 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] .choices__input {
|
||||
[dir='rtl'] .choices__input {
|
||||
padding-right: 2px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.choices__placeholder {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.choices__input.is-hidden,
|
||||
.choices[data-type*="select-one"] .choices__input.is-hidden,
|
||||
.choices[data-type*="select-multiple"] .choices__input.is-hidden {
|
||||
display: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/*===== End of Choices ======*/
|
||||
|
|
2
public/assets/styles/choices.min.css
vendored
2
public/assets/styles/choices.min.css
vendored
File diff suppressed because one or more lines are too long
1429
public/index.html
1429
public/index.html
File diff suppressed because it is too large
Load diff
|
@ -1,28 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,user-scalable=no"
|
||||
/>
|
||||
<title>Choices</title>
|
||||
<meta
|
||||
name="description"
|
||||
itemprop="description"
|
||||
content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency."
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../assets/images/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="../assets/images/favicon-32x32.png"
|
||||
sizes="32x32"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="../assets/images/favicon-16x16.png"
|
||||
sizes="16x16"
|
||||
/>
|
||||
<link rel="manifest" href="../assets/images/manifest.json" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="../assets/images/safari-pinned-tab.svg"
|
||||
color="#00bcd4"
|
||||
/>
|
||||
<link rel="shortcut icon" href="../assets/images/favicon.ico" />
|
||||
<meta
|
||||
name="msapplication-config"
|
||||
content="../assets/images/browserconfig.xml"
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<title>Choices</title>
|
||||
<meta name=description itemprop=description content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="../assets/images/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" href="../assets/images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="../assets/images/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="manifest" href="../assets/images/manifest.json">
|
||||
<link rel="mask-icon" href="../assets/images/safari-pinned-tab.svg" color="#00bcd4">
|
||||
<link rel="shortcut icon" href="../assets/images/favicon.ico">
|
||||
<meta name="msapplication-config" content="../assets/images/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<!-- Ignore these -->
|
||||
<link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3" />
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Ignore these -->
|
||||
<link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3">
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Choices includes -->
|
||||
<link rel="stylesheet" href="../assets/styles/choices.min.css?version=6.0.3">
|
||||
<script src="../assets/scripts/choices.min.js?version=6.0.3"></script>
|
||||
<!-- End Choices includes -->
|
||||
<!-- Choices includes -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../assets/styles/choices.min.css?version=6.0.3"
|
||||
/>
|
||||
<script src="../assets/scripts/choices.min.js?version=6.0.3"></script>
|
||||
<!-- End Choices includes -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -33,7 +63,12 @@
|
|||
<label for="choices-basic">Basic</label>
|
||||
<button class="disable push-bottom">Disable</button>
|
||||
<button class="enable push-bottom">Enable</button>
|
||||
<select class="form-control" name="choices-basic" id="choices-basic" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-basic"
|
||||
id="choices-basic"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Find me">Choice 3</option>
|
||||
|
@ -43,7 +78,12 @@
|
|||
|
||||
<div data-test-hook="remove-button">
|
||||
<label for="choices-remove-button">Remove button</label>
|
||||
<select class="form-control" name="choices-remove-button" id="choices-remove-button" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-remove-button"
|
||||
id="choices-remove-button"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -53,7 +93,12 @@
|
|||
|
||||
<div data-test-hook="disabled-choice">
|
||||
<label for="choices-disabled-choice">Disabled choice</label>
|
||||
<select class="form-control" name="choices-disabled-choice" id="choices-disabled-choice" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-disabled-choice"
|
||||
id="choices-disabled-choice"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -63,7 +108,12 @@
|
|||
|
||||
<div data-test-hook="add-items-disabled">
|
||||
<label for="choices-add-items-disabled">Add items disabled</label>
|
||||
<select class="form-control" name="choices-add-items-disabled" id="choices-add-items-disabled" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-add-items-disabled"
|
||||
id="choices-add-items-disabled"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -72,7 +122,13 @@
|
|||
|
||||
<div data-test-hook="disabled-via-attr">
|
||||
<label for="choices-disabled-via-attr">Disabled via attribute</label>
|
||||
<select class="form-control" name="choices-disabled-via-attr" id="choices-disabled-via-attr" multiple disabled>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-disabled-via-attr"
|
||||
id="choices-disabled-via-attr"
|
||||
multiple
|
||||
disabled
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -81,7 +137,12 @@
|
|||
|
||||
<div data-test-hook="selection-limit">
|
||||
<label for="choices-selection-limit">Input limit</label>
|
||||
<select class="form-control" name="choices-selection-limit" id="choices-selection-limit" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-selection-limit"
|
||||
id="choices-selection-limit"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -93,7 +154,12 @@
|
|||
|
||||
<div data-test-hook="prepend-append">
|
||||
<label for="choices-prepend-append">Prepend/append</label>
|
||||
<select class="form-control" name="choices-prepend-append" id="choices-prepend-append" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-prepend-append"
|
||||
id="choices-prepend-append"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -102,7 +168,12 @@
|
|||
|
||||
<div data-test-hook="render-choice-limit">
|
||||
<label for="choices-render-choice-limit">Render choice limit</label>
|
||||
<select class="form-control" name="choices-render-choice-limit" id="choices-render-choice-limit" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-render-choice-limit"
|
||||
id="choices-render-choice-limit"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -111,7 +182,12 @@
|
|||
|
||||
<div data-test-hook="search-floor">
|
||||
<label for="choices-search-floor">Search floor</label>
|
||||
<select class="form-control" name="choices-search-floor" id="choices-search-floor" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-search-floor"
|
||||
id="choices-search-floor"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -120,7 +196,12 @@
|
|||
|
||||
<div data-test-hook="placeholder">
|
||||
<label for="choices-placeholder">Placeholder</label>
|
||||
<select class="form-control" name="choices-placeholder" id="choices-placeholder" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-placeholder"
|
||||
id="choices-placeholder"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -129,12 +210,22 @@
|
|||
|
||||
<div data-test-hook="remote-data">
|
||||
<label for="choices-remote-data">Remote data</label>
|
||||
<select class="form-control" name="choices-remote-data" id="choices-remote-data" multiple></select>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-remote-data"
|
||||
id="choices-remote-data"
|
||||
multiple
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="scrolling-dropdown">
|
||||
<label for="choices-scrolling-dropdown">Scrolling dropdown</label>
|
||||
<select class="form-control" name="choices-scrolling-dropdown" id="choices-scrolling-dropdown" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-scrolling-dropdown"
|
||||
id="choices-scrolling-dropdown"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -155,7 +246,12 @@
|
|||
|
||||
<div data-test-hook="groups">
|
||||
<label for="choices-groups">Choice groups</label>
|
||||
<select class="form-control" name="choices-groups" id="choices-groups" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-groups"
|
||||
id="choices-groups"
|
||||
multiple
|
||||
>
|
||||
<optgroup label="UK">
|
||||
<option value="London">London</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
|
@ -171,26 +267,47 @@
|
|||
|
||||
<div data-test-hook="custom-properties">
|
||||
<label for="choices-custom-properties">Custom properties</label>
|
||||
<select class="form-control" name="choices-custom-properties" id="choices-custom-properties" multiple></select>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-custom-properties"
|
||||
id="choices-custom-properties"
|
||||
multiple
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="non-string-values">
|
||||
<label for="choices-non-string-values">Non-string values</label>
|
||||
<select class="form-control" name="choices-non-string-values" id="choices-non-string-values"></select>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-non-string-values"
|
||||
id="choices-non-string-values"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="within-form">
|
||||
<form>
|
||||
<label for="choices-within-form">Within form</label>
|
||||
<select class="form-control" name="choices-within-form" id="choices-within-form" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-within-form"
|
||||
id="choices-within-form"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="set-choice-by-value">
|
||||
<label for="choices-set-choice-by-value">Dynamically set choice by value</label>
|
||||
<select class="form-control" name="choices-set-choice-by-value" id="choices-set-choice-by-value" multiple>
|
||||
<label for="choices-set-choice-by-value"
|
||||
>Dynamically set choice by value</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-set-choice-by-value"
|
||||
id="choices-set-choice-by-value"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -199,7 +316,12 @@
|
|||
|
||||
<div data-test-hook="search-by-label">
|
||||
<label for="choices-search-by-label">Search by label</label>
|
||||
<select class="form-control" name="choices-search-by-label" id="choices-search-by-label" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-search-by-label"
|
||||
id="choices-search-by-label"
|
||||
multiple
|
||||
>
|
||||
<option value="value1">label1</option>
|
||||
<option value="value2">label2</option>
|
||||
</select>
|
||||
|
@ -210,13 +332,17 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const choicesBasic = new Choices('#choices-basic');
|
||||
|
||||
document.querySelector('button.disable').addEventListener('click', () => {
|
||||
choicesBasic.disable();
|
||||
});
|
||||
document
|
||||
.querySelector('button.disable')
|
||||
.addEventListener('click', () => {
|
||||
choicesBasic.disable();
|
||||
});
|
||||
|
||||
document.querySelector('button.enable').addEventListener('click', () => {
|
||||
choicesBasic.enable();
|
||||
});
|
||||
document
|
||||
.querySelector('button.enable')
|
||||
.addEventListener('click', () => {
|
||||
choicesBasic.enable();
|
||||
});
|
||||
|
||||
new Choices('#choices-remove-button', {
|
||||
removeItemButton: true,
|
||||
|
@ -254,16 +380,9 @@
|
|||
|
||||
new Choices('#choices-remote-data', {
|
||||
shouldSort: false,
|
||||
}).ajax((callback) => {
|
||||
fetch('/data')
|
||||
.then((response) => {
|
||||
response.json().then((data) => {
|
||||
callback(data, 'value', 'label');
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}).setChoices(async () => {
|
||||
const data = await fetch('/data');
|
||||
return data.json();
|
||||
});
|
||||
|
||||
new Choices('#choices-scrolling-dropdown', {
|
||||
|
@ -331,10 +450,12 @@
|
|||
|
||||
new Choices('#choices-within-form');
|
||||
|
||||
new Choices('#choices-set-choice-by-value').setChoiceByValue('Choice 2');
|
||||
new Choices('#choices-set-choice-by-value').setChoiceByValue(
|
||||
'Choice 2',
|
||||
);
|
||||
|
||||
new Choices('#choices-search-by-label', { searchFields: ['label'] });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,28 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,user-scalable=no"
|
||||
/>
|
||||
<title>Choices</title>
|
||||
<meta
|
||||
name="description"
|
||||
itemprop="description"
|
||||
content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency."
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../assets/images/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="../assets/images/favicon-32x32.png"
|
||||
sizes="32x32"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="../assets/images/favicon-16x16.png"
|
||||
sizes="16x16"
|
||||
/>
|
||||
<link rel="manifest" href="../assets/images/manifest.json" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="../assets/images/safari-pinned-tab.svg"
|
||||
color="#00bcd4"
|
||||
/>
|
||||
<link rel="shortcut icon" href="../assets/images/favicon.ico" />
|
||||
<meta
|
||||
name="msapplication-config"
|
||||
content="../assets/images/browserconfig.xml"
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<title>Choices</title>
|
||||
<meta name=description itemprop=description content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="../assets/images/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" href="../assets/images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="../assets/images/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="manifest" href="../assets/images/manifest.json">
|
||||
<link rel="mask-icon" href="../assets/images/safari-pinned-tab.svg" color="#00bcd4">
|
||||
<link rel="shortcut icon" href="../assets/images/favicon.ico">
|
||||
<meta name="msapplication-config" content="../assets/images/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<!-- Ignore these -->
|
||||
<link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3" />
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Ignore these -->
|
||||
<link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3">
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Choices includes -->
|
||||
<link rel="stylesheet" href="../assets/styles/choices.min.css?version=6.0.3">
|
||||
<script src="../assets/scripts/choices.min.js?version=6.0.3"></script>
|
||||
<!-- End Choices includes -->
|
||||
<!-- Choices includes -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../assets/styles/choices.min.css?version=6.0.3"
|
||||
/>
|
||||
<script src="../assets/scripts/choices.min.js?version=6.0.3"></script>
|
||||
<!-- End Choices includes -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -43,7 +73,11 @@
|
|||
|
||||
<div data-test-hook="remove-button">
|
||||
<label for="choices-remove-button">Remove button</label>
|
||||
<select class="form-control" name="choices-remove-button" id="choices-remove-button">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-remove-button"
|
||||
id="choices-remove-button"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -53,7 +87,11 @@
|
|||
|
||||
<div data-test-hook="disabled-choice">
|
||||
<label for="choices-disabled-choice">Disabled choice</label>
|
||||
<select class="form-control" name="choices-disabled-choice" id="choices-disabled-choice">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-disabled-choice"
|
||||
id="choices-disabled-choice"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -63,7 +101,11 @@
|
|||
|
||||
<div data-test-hook="add-items-disabled">
|
||||
<label for="choices-add-items-disabled">Add items disabled</label>
|
||||
<select class="form-control" name="choices-add-items-disabled" id="choices-add-items-disabled">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-add-items-disabled"
|
||||
id="choices-add-items-disabled"
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -72,7 +114,12 @@
|
|||
|
||||
<div data-test-hook="disabled-via-attr">
|
||||
<label for="choices-disabled-via-attr">Disabled via attribute</label>
|
||||
<select class="form-control" name="choices-disabled-via-attr" id="choices-disabled-via-attr" disabled>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-disabled-via-attr"
|
||||
id="choices-disabled-via-attr"
|
||||
disabled
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -81,7 +128,11 @@
|
|||
|
||||
<div data-test-hook="prepend-append">
|
||||
<label for="choices-prepend-append">Prepend/append</label>
|
||||
<select class="form-control" name="choices-prepend-append" id="choices-prepend-append">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-prepend-append"
|
||||
id="choices-prepend-append"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -90,7 +141,11 @@
|
|||
|
||||
<div data-test-hook="render-choice-limit">
|
||||
<label for="choices-render-choice-limit">Render choice limit</label>
|
||||
<select class="form-control" name="choices-render-choice-limit" id="choices-render-choice-limit">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-render-choice-limit"
|
||||
id="choices-render-choice-limit"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -99,7 +154,11 @@
|
|||
|
||||
<div data-test-hook="search-disabled">
|
||||
<label for="choices-search-disabled">Search disabled</label>
|
||||
<select class="form-control" name="choices-search-disabled" id="choices-search-disabled">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-search-disabled"
|
||||
id="choices-search-disabled"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -108,7 +167,11 @@
|
|||
|
||||
<div data-test-hook="search-floor">
|
||||
<label for="choices-search-floor">Search floor</label>
|
||||
<select class="form-control" name="choices-search-floor" id="choices-search-floor">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-search-floor"
|
||||
id="choices-search-floor"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -117,12 +180,20 @@
|
|||
|
||||
<div data-test-hook="remote-data">
|
||||
<label for="choices-remote-data">Remote data</label>
|
||||
<select class="form-control" name="choices-remote-data" id="choices-remote-data"></select>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-remote-data"
|
||||
id="choices-remote-data"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="scrolling-dropdown">
|
||||
<label for="choices-scrolling-dropdown">Scrolling dropdown</label>
|
||||
<select class="form-control" name="choices-scrolling-dropdown" id="choices-scrolling-dropdown">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-scrolling-dropdown"
|
||||
id="choices-scrolling-dropdown"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -143,7 +214,12 @@
|
|||
|
||||
<div data-test-hook="groups">
|
||||
<label for="choices-groups">Choice groups</label>
|
||||
<select class="form-control" name="choices-groups" id="choices-groups" multiple>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-groups"
|
||||
id="choices-groups"
|
||||
multiple
|
||||
>
|
||||
<optgroup label="UK">
|
||||
<option value="London">London</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
|
@ -159,7 +235,11 @@
|
|||
|
||||
<div data-test-hook="parent-child">
|
||||
<label for="choices-parent">Parent</label>
|
||||
<select class="form-control" name="choices-parent" id="choices-parent">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-parent"
|
||||
id="choices-parent"
|
||||
>
|
||||
<option value="Parent choice 1">Parent choice 1</option>
|
||||
<option value="Parent choice 2">Parent choice 2</option>
|
||||
<option value="Parent choice 3">Parent choice 3</option>
|
||||
|
@ -175,26 +255,44 @@
|
|||
|
||||
<div data-test-hook="custom-properties">
|
||||
<label for="choices-custom-properties">Custom properties</label>
|
||||
<select class="form-control" name="choices-custom-properties" id="choices-custom-properties"></select>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-custom-properties"
|
||||
id="choices-custom-properties"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="non-string-values">
|
||||
<label for="choices-non-string-values">Non-string values</label>
|
||||
<select class="form-control" name="choices-non-string-values" id="choices-non-string-values"></select>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-non-string-values"
|
||||
id="choices-non-string-values"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="within-form">
|
||||
<form>
|
||||
<label for="choices-within-form">Within form</label>
|
||||
<select class="form-control" name="choices-within-form" id="choices-within-form">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-within-form"
|
||||
id="choices-within-form"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="set-choice-by-value">
|
||||
<label for="choices-set-choice-by-value">Dynamically set choice by value</label>
|
||||
<select class="form-control" name="choices-set-choice-by-value" id="choices-set-choice-by-value">
|
||||
<label for="choices-set-choice-by-value"
|
||||
>Dynamically set choice by value</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-set-choice-by-value"
|
||||
id="choices-set-choice-by-value"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
|
@ -203,7 +301,11 @@
|
|||
|
||||
<div data-test-hook="search-by-label">
|
||||
<label for="choices-search-by-label">Search by label</label>
|
||||
<select class="form-control" name="choices-search-by-label" id="choices-search-by-label">
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-search-by-label"
|
||||
id="choices-search-by-label"
|
||||
>
|
||||
<option value="value1">label1</option>
|
||||
<option value="value2">label2</option>
|
||||
</select>
|
||||
|
@ -225,13 +327,17 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const choicesBasic = new Choices('#choices-basic');
|
||||
|
||||
document.querySelector('button.disable').addEventListener('click', () => {
|
||||
choicesBasic.disable();
|
||||
});
|
||||
document
|
||||
.querySelector('button.disable')
|
||||
.addEventListener('click', () => {
|
||||
choicesBasic.disable();
|
||||
});
|
||||
|
||||
document.querySelector('button.enable').addEventListener('click', () => {
|
||||
choicesBasic.enable();
|
||||
});
|
||||
document
|
||||
.querySelector('button.enable')
|
||||
.addEventListener('click', () => {
|
||||
choicesBasic.enable();
|
||||
});
|
||||
|
||||
new Choices('#choices-remove-button', {
|
||||
removeItemButton: true,
|
||||
|
@ -253,12 +359,12 @@
|
|||
});
|
||||
|
||||
new Choices('#choices-render-choice-limit', {
|
||||
renderChoiceLimit: 1
|
||||
renderChoiceLimit: 1,
|
||||
});
|
||||
|
||||
new Choices('#choices-search-disabled', {
|
||||
searchEnabled: false
|
||||
})
|
||||
searchEnabled: false,
|
||||
});
|
||||
|
||||
new Choices('#choices-search-floor', {
|
||||
searchFloor: 5,
|
||||
|
@ -266,16 +372,9 @@
|
|||
|
||||
new Choices('#choices-remote-data', {
|
||||
shouldSort: false,
|
||||
}).ajax((callback) => {
|
||||
fetch('/data')
|
||||
.then((response) => {
|
||||
response.json().then((data) => {
|
||||
callback(data, 'value', 'label');
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}).setChoices(async () => {
|
||||
const res = await fetch('/data');
|
||||
return res.json();
|
||||
});
|
||||
|
||||
new Choices('#choices-scrolling-dropdown', {
|
||||
|
@ -287,7 +386,7 @@
|
|||
const parent = new Choices('#choices-parent');
|
||||
const child = new Choices('#choices-child').disable();
|
||||
|
||||
parent.passedElement.element.addEventListener('change', (event) => {
|
||||
parent.passedElement.element.addEventListener('change', event => {
|
||||
if (event.detail.value === 'Parent choice 2') {
|
||||
child.enable();
|
||||
} else {
|
||||
|
@ -321,8 +420,8 @@
|
|||
customProperties: {
|
||||
country: 'Portugal',
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
new Choices('#choices-non-string-values', {
|
||||
|
@ -354,7 +453,9 @@
|
|||
|
||||
new Choices('#choices-within-form');
|
||||
|
||||
new Choices('#choices-set-choice-by-value').setChoiceByValue('Choice 2');
|
||||
new Choices('#choices-set-choice-by-value').setChoiceByValue(
|
||||
'Choice 2',
|
||||
);
|
||||
|
||||
new Choices('#choices-search-by-label', { searchFields: ['label'] });
|
||||
|
||||
|
@ -368,4 +469,4 @@
|
|||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,28 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,user-scalable=no"
|
||||
/>
|
||||
<title>Choices</title>
|
||||
<meta
|
||||
name="description"
|
||||
itemprop="description"
|
||||
content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency."
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../assets/images/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="../assets/images/favicon-32x32.png"
|
||||
sizes="32x32"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="../assets/images/favicon-16x16.png"
|
||||
sizes="16x16"
|
||||
/>
|
||||
<link rel="manifest" href="../assets/images/manifest.json" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="../assets/images/safari-pinned-tab.svg"
|
||||
color="#00bcd4"
|
||||
/>
|
||||
<link rel="shortcut icon" href="../assets/images/favicon.ico" />
|
||||
<meta
|
||||
name="msapplication-config"
|
||||
content="../assets/images/browserconfig.xml"
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<title>Choices</title>
|
||||
<meta name=description itemprop=description content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="../assets/images/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" href="../assets/images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="../assets/images/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="manifest" href="../assets/images/manifest.json">
|
||||
<link rel="mask-icon" href="../assets/images/safari-pinned-tab.svg" color="#00bcd4">
|
||||
<link rel="shortcut icon" href="../assets/images/favicon.ico">
|
||||
<meta name="msapplication-config" content="../assets/images/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<!-- Ignore these -->
|
||||
<link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3" />
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Ignore these -->
|
||||
<link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3">
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Choices includes -->
|
||||
<link rel="stylesheet" href="../assets/styles/choices.min.css?version=6.0.3">
|
||||
<script src="../assets/scripts/choices.min.js?version=6.0.3"></script>
|
||||
<!-- End Choices includes -->
|
||||
<!-- Choices includes -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../assets/styles/choices.min.css?version=6.0.3"
|
||||
/>
|
||||
<script src="../assets/scripts/choices.min.js?version=6.0.3"></script>
|
||||
<!-- End Choices includes -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -31,63 +61,76 @@
|
|||
<h2>Text inputs</h2>
|
||||
<div data-test-hook="basic">
|
||||
<label for="choices-basic">Basic</label>
|
||||
<input class="form-control" id="choices-basic" type="text">
|
||||
<input class="form-control" id="choices-basic" type="text" />
|
||||
</div>
|
||||
|
||||
<div data-test-hook="edit-items">
|
||||
<label for="choices-edit-items">Edit items</label>
|
||||
<input class="form-control" id="choices-edit-items" type="text">
|
||||
<input class="form-control" id="choices-edit-items" type="text" />
|
||||
</div>
|
||||
|
||||
<div data-test-hook="remove-button">
|
||||
<label for="choices-remove-button">Remove button</label>
|
||||
<input class="form-control" id="choices-remove-button" type="text">
|
||||
<input class="form-control" id="choices-remove-button" type="text" />
|
||||
</div>
|
||||
|
||||
<div data-test-hook="unique-values">
|
||||
<label for="choices-unique-values">Unique values</label>
|
||||
<input class="form-control" id="choices-unique-values" type="text">
|
||||
<input class="form-control" id="choices-unique-values" type="text" />
|
||||
</div>
|
||||
|
||||
<div data-test-hook="input-limit">
|
||||
<label for="choices-input-limit">Input limit</label>
|
||||
<input class="form-control" id="choices-input-limit" type="text">
|
||||
<input class="form-control" id="choices-input-limit" type="text" />
|
||||
</div>
|
||||
|
||||
<div data-test-hook="add-item-filter">
|
||||
<label for="choices-add-item-filter">Add item filter</label>
|
||||
<input class="form-control" id="choices-add-item-filter" type="text">
|
||||
<input
|
||||
class="form-control"
|
||||
id="choices-add-item-filter"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="adding-items-disabled">
|
||||
<label for="choices-adding-items-disabled">Add items disabled</label>
|
||||
<input class="form-control" id="choices-adding-items-disabled" type="text">
|
||||
<input
|
||||
class="form-control"
|
||||
id="choices-adding-items-disabled"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="disabled-via-attr">
|
||||
<label for="choices-disabled-via-attr">Disabled via attribute</label>
|
||||
<input class="form-control" id="choices-disabled-via-attr" type="text" disabled>
|
||||
<input
|
||||
class="form-control"
|
||||
id="choices-disabled-via-attr"
|
||||
type="text"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="prepend-append">
|
||||
<label for="choices-prepend-append">Prepend/append</label>
|
||||
<input class="form-control" id="choices-prepend-append" type="text">
|
||||
<input class="form-control" id="choices-prepend-append" type="text" />
|
||||
</div>
|
||||
|
||||
<div data-test-hook="prepopulated">
|
||||
<label for="choices-prepopulated">Pre-populated choices</label>
|
||||
<input class="form-control" id="choices-prepopulated" type="text">
|
||||
<input class="form-control" id="choices-prepopulated" type="text" />
|
||||
</div>
|
||||
|
||||
<div data-test-hook="placeholder">
|
||||
<label for="choices-placeholder">Placeholder</label>
|
||||
<input class="form-control" id="choices-placeholder" type="text">
|
||||
<input class="form-control" id="choices-placeholder" type="text" />
|
||||
</div>
|
||||
|
||||
<div data-test-hook="within-form">
|
||||
<form>
|
||||
<label for="choices-within-form">Within form</label>
|
||||
<input class="form-control" id="choices-within-form" type="text">
|
||||
<input class="form-control" id="choices-within-form" type="text" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -114,8 +157,8 @@
|
|||
|
||||
new Choices('#choices-add-item-filter', {
|
||||
addItems: true,
|
||||
addItemFilterFn: (value) => {
|
||||
const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
addItemFilter: value => {
|
||||
const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
const expression = new RegExp(regex.source, 'i');
|
||||
return expression.test(value);
|
||||
},
|
||||
|
@ -133,13 +176,16 @@
|
|||
});
|
||||
|
||||
new Choices('#choices-prepopulated', {
|
||||
items: ['Josh Johnson', {
|
||||
value: 'joe@bloggs.co.uk',
|
||||
label: 'Joe Bloggs',
|
||||
customProperties: {
|
||||
description: 'Joe Blogg is such a generic name',
|
||||
}
|
||||
}],
|
||||
items: [
|
||||
'Josh Johnson',
|
||||
{
|
||||
value: 'joe@bloggs.co.uk',
|
||||
label: 'Joe Bloggs',
|
||||
customProperties: {
|
||||
description: 'Joe Blogg is such a generic name',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
new Choices('#choices-placeholder', {
|
||||
|
@ -151,4 +197,4 @@
|
|||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ACTION_TYPES } from './../constants';
|
||||
import { ACTION_TYPES } from '../constants';
|
||||
|
||||
export const addChoice = ({
|
||||
value,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable import/prefer-default-export */
|
||||
|
||||
export const setIsLoading = isLoading => ({
|
||||
type: 'SET_IS_LOADING',
|
||||
isLoading,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { ACTION_TYPES } from './../constants';
|
||||
import { ACTION_TYPES } from '../constants';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export const addGroup = (value, id, active, disabled) => ({
|
||||
type: ACTION_TYPES.ADD_GROUP,
|
||||
value,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ACTION_TYPES } from './../constants';
|
||||
import { ACTION_TYPES } from '../constants';
|
||||
|
||||
export const addItem = ({
|
||||
value,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,40 +2,222 @@ import { expect } from 'chai';
|
|||
import { spy, stub } from 'sinon';
|
||||
|
||||
import Choices from './choices';
|
||||
import { EVENTS, ACTION_TYPES } from './constants';
|
||||
import { EVENTS, ACTION_TYPES, DEFAULT_CONFIG } from './constants';
|
||||
import { WrappedSelect, WrappedInput } from './components/index';
|
||||
|
||||
describe('choices', () => {
|
||||
let instance;
|
||||
let output;
|
||||
let passedElement;
|
||||
|
||||
beforeEach(() => {
|
||||
passedElement = document.createElement('input');
|
||||
passedElement.type = 'text';
|
||||
passedElement.className = 'js-choices';
|
||||
document.body.appendChild(passedElement);
|
||||
|
||||
instance = new Choices(passedElement);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
output = null;
|
||||
instance = null;
|
||||
});
|
||||
|
||||
const returnsInstance = () => {
|
||||
it('returns this', () => {
|
||||
expect(output).to.eql(instance);
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
describe('config', () => {
|
||||
describe('not passing config options', () => {
|
||||
it('uses the default config', () => {
|
||||
document.body.innerHTML = `
|
||||
<input data-choice type="text" id="input-1" />
|
||||
`;
|
||||
|
||||
instance = new Choices();
|
||||
|
||||
expect(instance.config).to.eql(DEFAULT_CONFIG);
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing config options', () => {
|
||||
it('merges the passed config with the default config', () => {
|
||||
document.body.innerHTML = `
|
||||
<input data-choice type="text" id="input-1" />
|
||||
`;
|
||||
|
||||
const config = {
|
||||
renderChoiceLimit: 5,
|
||||
};
|
||||
instance = new Choices('[data-choice]', config);
|
||||
|
||||
expect(instance.config).to.eql({
|
||||
...DEFAULT_CONFIG,
|
||||
...config,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('not passing an element', () => {
|
||||
it('returns a Choices instance for the first element with a "data-choice" attribute', () => {
|
||||
document.body.innerHTML = `
|
||||
<input data-choice type="text" id="input-1" />
|
||||
<input data-choice type="text" id="input-2" />
|
||||
<input data-choice type="text" id="input-3" />
|
||||
`;
|
||||
|
||||
const inputs = document.querySelectorAll('[data-choice]');
|
||||
expect(inputs.length).to.equal(3);
|
||||
|
||||
instance = new Choices();
|
||||
|
||||
expect(instance.passedElement.element.id).to.equal(inputs[0].id);
|
||||
});
|
||||
|
||||
describe('when an element cannot be found in the DOM', () => {
|
||||
it('throws an error', () => {
|
||||
document.body.innerHTML = ``;
|
||||
expect(() => new Choices()).to.throw(
|
||||
TypeError,
|
||||
'Expected one of the following types text|select-one|select-multiple',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing an element', () => {
|
||||
describe('passing an element that has not been initialised with Choices', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<input type="text" id="input-1" />
|
||||
`;
|
||||
});
|
||||
|
||||
it('sets the initialised flag to true', () => {
|
||||
instance = new Choices('#input-1');
|
||||
expect(instance.initialised).to.equal(true);
|
||||
});
|
||||
|
||||
it('intialises', () => {
|
||||
const initSpy = spy();
|
||||
// initialise with the same element
|
||||
instance = new Choices('#input-1', {
|
||||
silent: true,
|
||||
callbackOnInit: initSpy,
|
||||
});
|
||||
|
||||
expect(initSpy.called).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing an element that has already be initialised with Choices', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<input type="text" id="input-1" />
|
||||
`;
|
||||
|
||||
// initialise once
|
||||
new Choices('#input-1', { silent: true });
|
||||
});
|
||||
|
||||
it('sets the initialised flag to true', () => {
|
||||
// initialise with the same element
|
||||
instance = new Choices('#input-1', { silent: true });
|
||||
|
||||
expect(instance.initialised).to.equal(true);
|
||||
});
|
||||
|
||||
it('does not reinitialise', () => {
|
||||
const initSpy = spy();
|
||||
// initialise with the same element
|
||||
instance = new Choices('#input-1', {
|
||||
silent: true,
|
||||
callbackOnInit: initSpy,
|
||||
});
|
||||
|
||||
expect(initSpy.called).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`passing an element as a DOMString`, () => {
|
||||
describe('passing a input element type', () => {
|
||||
it('sets the "passedElement" instance property as an instance of WrappedInput', () => {
|
||||
document.body.innerHTML = `
|
||||
<input data-choice type="text" id="input-1" />
|
||||
`;
|
||||
|
||||
instance = new Choices('[data-choice]');
|
||||
|
||||
expect(instance.passedElement).to.be.an.instanceOf(WrappedInput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing a select element type', () => {
|
||||
it('sets the "passedElement" instance property as an instance of WrappedSelect', () => {
|
||||
document.body.innerHTML = `
|
||||
<select data-choice id="select-1"></select>
|
||||
`;
|
||||
|
||||
instance = new Choices('[data-choice]');
|
||||
|
||||
expect(instance.passedElement).to.be.an.instanceOf(WrappedSelect);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`passing an element as a HTMLElement`, () => {
|
||||
describe('passing a input element type', () => {
|
||||
it('sets the "passedElement" instance property as an instance of WrappedInput', () => {
|
||||
document.body.innerHTML = `
|
||||
<input data-choice type="text" id="input-1" />
|
||||
`;
|
||||
|
||||
instance = new Choices(document.querySelector('[data-choice]'));
|
||||
|
||||
expect(instance.passedElement).to.be.an.instanceOf(WrappedInput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing a select element type', () => {
|
||||
it('sets the "passedElement" instance property as an instance of WrappedSelect', () => {
|
||||
document.body.innerHTML = `
|
||||
<select data-choice id="select-1"></select>
|
||||
`;
|
||||
|
||||
instance = new Choices(document.querySelector('[data-choice]'));
|
||||
|
||||
expect(instance.passedElement).to.be.an.instanceOf(WrappedSelect);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing an invalid element type', () => {
|
||||
it('throws an TypeError', () => {
|
||||
document.body.innerHTML = `
|
||||
<div data-choice id="div-1"></div>
|
||||
`;
|
||||
expect(() => new Choices('[data-choice]')).to.throw(
|
||||
TypeError,
|
||||
'Expected one of the following types text|select-one|select-multiple',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('public methods', () => {
|
||||
beforeEach(() => {
|
||||
passedElement = document.createElement('input');
|
||||
passedElement.type = 'text';
|
||||
passedElement.className = 'js-choices';
|
||||
document.body.appendChild(passedElement);
|
||||
|
||||
instance = new Choices(passedElement);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
output = null;
|
||||
instance = null;
|
||||
});
|
||||
|
||||
describe('init', () => {
|
||||
const callbackOnInitSpy = spy();
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new Choices(passedElement, {
|
||||
callbackOnInit: callbackOnInitSpy,
|
||||
silent: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -171,7 +353,7 @@ describe('choices', () => {
|
|||
});
|
||||
|
||||
it('nullifys templates config', () => {
|
||||
expect(instance.config.templates).to.equal(null);
|
||||
expect(instance._templates).to.equal(null);
|
||||
});
|
||||
|
||||
it('resets initialise flag', () => {
|
||||
|
@ -797,6 +979,29 @@ describe('choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('clearChoices', () => {
|
||||
let storeDispatchStub;
|
||||
|
||||
beforeEach(() => {
|
||||
storeDispatchStub = stub();
|
||||
instance._store.dispatch = storeDispatchStub;
|
||||
|
||||
output = instance.clearChoices();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance._store.dispatch.reset();
|
||||
});
|
||||
|
||||
returnsInstance(output);
|
||||
|
||||
it('dispatches clearChoices action', () => {
|
||||
expect(storeDispatchStub.lastCall.args[0]).to.eql({
|
||||
type: ACTION_TYPES.CLEAR_CHOICES,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearStore', () => {
|
||||
let storeDispatchStub;
|
||||
|
||||
|
@ -880,84 +1085,75 @@ describe('choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('ajax', () => {
|
||||
const callbackoutput = 'worked';
|
||||
|
||||
let handleLoadingStateStub;
|
||||
let ajaxCallbackStub;
|
||||
|
||||
const returnsEarly = () => {
|
||||
it('returns early', () => {
|
||||
expect(handleLoadingStateStub.called).to.equal(false);
|
||||
expect(ajaxCallbackStub.called).to.equal(false);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
handleLoadingStateStub = stub();
|
||||
ajaxCallbackStub = stub().returns(callbackoutput);
|
||||
|
||||
instance._ajaxCallback = ajaxCallbackStub;
|
||||
instance._handleLoadingState = handleLoadingStateStub;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance._ajaxCallback.reset();
|
||||
instance._handleLoadingState.reset();
|
||||
});
|
||||
|
||||
describe('setChoices with callback/Promise', () => {
|
||||
describe('not initialised', () => {
|
||||
beforeEach(() => {
|
||||
instance.initialised = false;
|
||||
output = instance.ajax(() => {});
|
||||
});
|
||||
|
||||
returnsInstance(output);
|
||||
returnsEarly();
|
||||
it('should throw', () => {
|
||||
expect(() => instance.setChoices(null)).Throw(ReferenceError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('text element', () => {
|
||||
beforeEach(() => {
|
||||
instance._isSelectElement = false;
|
||||
output = instance.ajax(() => {});
|
||||
});
|
||||
|
||||
returnsInstance(output);
|
||||
returnsEarly();
|
||||
it('should throw', () => {
|
||||
expect(() => instance.setChoices(null)).Throw(TypeError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing invalid function', () => {
|
||||
beforeEach(() => {
|
||||
output = instance.ajax(null);
|
||||
instance._isSelectElement = true;
|
||||
});
|
||||
|
||||
returnsInstance(output);
|
||||
returnsEarly();
|
||||
it('should throw on non function', () => {
|
||||
expect(() => instance.setChoices(null)).Throw(TypeError, /Promise/i);
|
||||
});
|
||||
|
||||
it(`should throw on function that doesn't return promise`, () => {
|
||||
expect(() => instance.setChoices(() => 'boo')).to.throw(
|
||||
TypeError,
|
||||
/promise/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('select element', () => {
|
||||
let callback;
|
||||
it('fetches and sets choices', async () => {
|
||||
document.body.innerHTML = '<select id="test" />';
|
||||
const choice = new Choices('#test');
|
||||
const handleLoadingStateSpy = spy(choice, '_handleLoadingState');
|
||||
|
||||
beforeEach(() => {
|
||||
instance.initialised = true;
|
||||
instance._isSelectElement = true;
|
||||
ajaxCallbackStub = stub();
|
||||
callback = stub();
|
||||
output = instance.ajax(callback);
|
||||
});
|
||||
let fetcherCalled = false;
|
||||
const fetcher = async inst => {
|
||||
expect(inst).to.eq(choice);
|
||||
fetcherCalled = true;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
returnsInstance(output);
|
||||
|
||||
it('sets loading state', done => {
|
||||
requestAnimationFrame(() => {
|
||||
expect(handleLoadingStateStub.called).to.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls passed function with ajax callback', () => {
|
||||
expect(callback.called).to.equal(true);
|
||||
expect(callback.lastCall.args[0]).to.eql(callbackoutput);
|
||||
return [
|
||||
{ label: 'l1', value: 'v1', customProperties: 'prop1' },
|
||||
{ label: 'l2', value: 'v2', customProperties: 'prop2' },
|
||||
];
|
||||
};
|
||||
expect(choice._store.choices.length).to.equal(0);
|
||||
const promise = choice.setChoices(fetcher);
|
||||
await new Promise(resolve =>
|
||||
requestAnimationFrame(() => {
|
||||
expect(handleLoadingStateSpy.callCount).to.equal(1);
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
expect(fetcherCalled).to.be.true;
|
||||
const res = await promise;
|
||||
expect(res).to.equal(choice);
|
||||
expect(choice._store.choices[1].value).to.equal('v2');
|
||||
expect(choice._store.choices[1].label).to.equal('l2');
|
||||
expect(choice._store.choices[1].customProperties).to.equal('prop2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1330,7 +1526,7 @@ describe('choices', () => {
|
|||
...choices[0],
|
||||
choices,
|
||||
},
|
||||
...choices[1],
|
||||
choices[1],
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -1352,31 +1548,29 @@ describe('choices', () => {
|
|||
instance.containerOuter.removeLoadingState.reset();
|
||||
});
|
||||
|
||||
const returnsEarly = () => {
|
||||
it('returns early', () => {
|
||||
expect(addGroupStub.called).to.equal(false);
|
||||
expect(addChoiceStub.called).to.equal(false);
|
||||
expect(clearChoicesStub.called).to.equal(false);
|
||||
});
|
||||
};
|
||||
|
||||
describe('when element is not select element', () => {
|
||||
beforeEach(() => {
|
||||
instance._isSelectElement = false;
|
||||
instance.setChoices(choices, value, label, false);
|
||||
});
|
||||
|
||||
returnsEarly();
|
||||
it('throws', () => {
|
||||
expect(() =>
|
||||
instance.setChoices(choices, value, label, false),
|
||||
).to.throw(TypeError, /input/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing invalid arguments', () => {
|
||||
describe('passing no value', () => {
|
||||
beforeEach(() => {
|
||||
instance._isSelectElement = true;
|
||||
instance.setChoices(choices, undefined, 'label', false);
|
||||
});
|
||||
|
||||
returnsEarly();
|
||||
it('throws', () => {
|
||||
expect(() =>
|
||||
instance.setChoices(choices, null, 'label', false),
|
||||
).to.throw(TypeError, /value/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1451,7 +1645,9 @@ describe('choices', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('private methods', () => {
|
||||
describe('_createGroupsFragment', () => {
|
||||
let _createChoicesFragmentStub;
|
||||
const choices = [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getWindowHeight, wrap } from '../lib/utils';
|
||||
import { wrap } from '../lib/utils';
|
||||
|
||||
export default class Container {
|
||||
constructor({ element, type, classNames, position }) {
|
||||
|
@ -38,8 +38,8 @@ export default class Container {
|
|||
* @param {Number} dropdownPos
|
||||
* @returns
|
||||
*/
|
||||
shouldFlip(dropdownPos, windowHeight = getWindowHeight()) {
|
||||
if (dropdownPos === undefined) {
|
||||
shouldFlip(dropdownPos) {
|
||||
if (typeof dropdownPos !== 'number') {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,8 @@ export default class Container {
|
|||
// greater than the window height flip the dropdown.
|
||||
let shouldFlip = false;
|
||||
if (this.position === 'auto') {
|
||||
shouldFlip = dropdownPos >= windowHeight;
|
||||
shouldFlip = !window.matchMedia(`(min-height: ${dropdownPos + 1}px)`)
|
||||
.matches;
|
||||
} else if (this.position === 'top') {
|
||||
shouldFlip = true;
|
||||
}
|
||||
|
|
|
@ -102,18 +102,6 @@ describe('components/container', () => {
|
|||
beforeEach(() => {
|
||||
instance.position = 'auto';
|
||||
});
|
||||
|
||||
describe('dropdownPos is greater than window height', () => {
|
||||
it('returns false', () => {
|
||||
expect(instance.shouldFlip(100, 1000)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dropdownPos is less than window height', () => {
|
||||
it('returns true', () => {
|
||||
expect(instance.shouldFlip(100, 50)).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('position config option set to "top"', () => {
|
||||
|
|
|
@ -6,16 +6,11 @@ export default class Dropdown {
|
|||
}
|
||||
|
||||
/**
|
||||
* Determine how far the top of our element is from
|
||||
* the top of the window
|
||||
* @return {Number} Vertical position
|
||||
* Bottom position of dropdown in viewport coordinates
|
||||
* @type {number} Vertical position
|
||||
*/
|
||||
distanceFromTopWindow() {
|
||||
this.dimensions = this.element.getBoundingClientRect();
|
||||
this.position = Math.ceil(
|
||||
this.dimensions.top + window.pageYOffset + this.element.offsetHeight,
|
||||
);
|
||||
return this.position;
|
||||
get distanceFromTopWindow() {
|
||||
return this.element.getBoundingClientRect().bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,6 +30,7 @@ export default class Dropdown {
|
|||
this.element.classList.add(this.classNames.activeState);
|
||||
this.element.setAttribute('aria-expanded', 'true');
|
||||
this.isActive = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -47,6 +43,7 @@ export default class Dropdown {
|
|||
this.element.classList.remove(this.classNames.activeState);
|
||||
this.element.setAttribute('aria-expanded', 'false');
|
||||
this.isActive = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,15 +34,13 @@ describe('components/dropdown', () => {
|
|||
|
||||
describe('distanceFromTopWindow', () => {
|
||||
let top;
|
||||
let offset;
|
||||
let dimensions;
|
||||
let getBoundingClientRectStub;
|
||||
|
||||
beforeEach(() => {
|
||||
top = 100;
|
||||
offset = 50;
|
||||
dimensions = {
|
||||
bottom: 0,
|
||||
bottom: 121,
|
||||
height: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
|
@ -53,31 +51,17 @@ describe('components/dropdown', () => {
|
|||
getBoundingClientRectStub = sinon
|
||||
.stub(instance.element, 'getBoundingClientRect')
|
||||
.returns(dimensions);
|
||||
|
||||
window.pageYOffset = 50;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
getBoundingClientRectStub.restore();
|
||||
});
|
||||
|
||||
it('determines how far the top of our element is from the top of the window', () => {
|
||||
const expectedResponse = top + offset;
|
||||
const actualResponse = instance.distanceFromTopWindow();
|
||||
it('determines how far the top of our element is from the top of the viewport', () => {
|
||||
const expectedResponse = dimensions.bottom;
|
||||
const actualResponse = instance.distanceFromTopWindow;
|
||||
expect(actualResponse).to.equal(expectedResponse);
|
||||
});
|
||||
|
||||
it('assigns dimensions to instance', () => {
|
||||
instance.distanceFromTopWindow();
|
||||
const expectedResponse = dimensions;
|
||||
expect(instance.dimensions).to.equal(expectedResponse);
|
||||
});
|
||||
|
||||
it('assigns posisiton to instance', () => {
|
||||
instance.distanceFromTopWindow();
|
||||
const expectedResponse = top + offset;
|
||||
expect(instance.position).to.equal(expectedResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChild', () => {
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import { calcWidthOfInput, sanitise } from '../lib/utils';
|
||||
import { sanitise } from '../lib/utils';
|
||||
|
||||
export default class Input {
|
||||
constructor({ element, type, classNames, placeholderValue }) {
|
||||
Object.assign(this, { element, type, classNames, placeholderValue });
|
||||
/**
|
||||
*
|
||||
* @typedef {import('../../../types/index').Choices.passedElement} passedElement
|
||||
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames
|
||||
* @param {{element: HTMLInputElement, type: passedElement['type'], classNames: ClassNames, preventPaste: boolean }} p
|
||||
*/
|
||||
constructor({ element, type, classNames, preventPaste }) {
|
||||
this.element = element;
|
||||
this.type = type;
|
||||
this.classNames = classNames;
|
||||
this.preventPaste = preventPaste;
|
||||
|
||||
this.isFocussed = this.element === document.activeElement;
|
||||
this.isDisabled = false;
|
||||
this.isDisabled = element.disabled;
|
||||
this._onPaste = this._onPaste.bind(this);
|
||||
this._onInput = this._onInput.bind(this);
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
|
@ -17,34 +25,38 @@ export default class Input {
|
|||
this.element.placeholder = placeholder;
|
||||
}
|
||||
|
||||
set value(value) {
|
||||
this.element.value = value;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return sanitise(this.element.value);
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
this.element.addEventListener('input', this._onInput);
|
||||
this.element.addEventListener('paste', this._onPaste);
|
||||
this.element.addEventListener('focus', this._onFocus);
|
||||
this.element.addEventListener('blur', this._onBlur);
|
||||
set value(value) {
|
||||
this.element.value = value;
|
||||
}
|
||||
|
||||
if (this.element.form) {
|
||||
this.element.form.addEventListener('reset', this._onFormReset);
|
||||
}
|
||||
addEventListeners() {
|
||||
this.element.addEventListener('paste', this._onPaste);
|
||||
this.element.addEventListener('input', this._onInput, {
|
||||
passive: true,
|
||||
});
|
||||
this.element.addEventListener('focus', this._onFocus, {
|
||||
passive: true,
|
||||
});
|
||||
this.element.addEventListener('blur', this._onBlur, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
removeEventListeners() {
|
||||
this.element.removeEventListener('input', this._onInput);
|
||||
this.element.removeEventListener('input', this._onInput, {
|
||||
passive: true,
|
||||
});
|
||||
this.element.removeEventListener('paste', this._onPaste);
|
||||
this.element.removeEventListener('focus', this._onFocus);
|
||||
this.element.removeEventListener('blur', this._onBlur);
|
||||
|
||||
if (this.element.form) {
|
||||
this.element.form.removeEventListener('reset', this._onFormReset);
|
||||
}
|
||||
this.element.removeEventListener('focus', this._onFocus, {
|
||||
passive: true,
|
||||
});
|
||||
this.element.removeEventListener('blur', this._onBlur, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
enable() {
|
||||
|
@ -89,30 +101,12 @@ export default class Input {
|
|||
/**
|
||||
* Set the correct input width based on placeholder
|
||||
* value or input value
|
||||
* @return
|
||||
*/
|
||||
setWidth(enforceWidth) {
|
||||
const callback = width => {
|
||||
this.element.style.width = width;
|
||||
};
|
||||
|
||||
if (this._placeholderValue) {
|
||||
// If there is a placeholder, we only want to set the width of the input when it is a greater
|
||||
// length than 75% of the placeholder. This stops the input jumping around.
|
||||
const valueHasDesiredLength =
|
||||
this.element.value.length >= this._placeholderValue.length / 1.25;
|
||||
|
||||
if ((this.element.value && valueHasDesiredLength) || enforceWidth) {
|
||||
this.calcWidth(callback);
|
||||
}
|
||||
} else {
|
||||
// If there is no placeholder, resize input to contents
|
||||
this.calcWidth(callback);
|
||||
}
|
||||
}
|
||||
|
||||
calcWidth(callback) {
|
||||
return calcWidthOfInput(this.element, callback);
|
||||
setWidth() {
|
||||
// Resize input to contents or placeholder
|
||||
const { style, value, placeholder } = this.element;
|
||||
style.minWidth = `${placeholder.length + 1}ch`;
|
||||
style.width = `${value.length + 1}ch`;
|
||||
}
|
||||
|
||||
setActiveDescendant(activeDescendantID) {
|
||||
|
@ -130,8 +124,7 @@ export default class Input {
|
|||
}
|
||||
|
||||
_onPaste(event) {
|
||||
const { target } = event;
|
||||
if (target === this.element && this.preventPaste) {
|
||||
if (this.preventPaste) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,11 +46,12 @@ describe('components/input', () => {
|
|||
|
||||
it('adds event listeners', () => {
|
||||
instance.addEventListeners();
|
||||
expect(addEventListenerStub.callCount).to.equal(4);
|
||||
expect(addEventListenerStub.getCall(0).args[0]).to.equal('input');
|
||||
expect(addEventListenerStub.getCall(1).args[0]).to.equal('paste');
|
||||
expect(addEventListenerStub.getCall(2).args[0]).to.equal('focus');
|
||||
expect(addEventListenerStub.getCall(3).args[0]).to.equal('blur');
|
||||
expect(['input', 'paste', 'focus', 'blur']).to.have.members(
|
||||
Array.from(
|
||||
{ length: addEventListenerStub.callCount },
|
||||
(v, i) => addEventListenerStub.getCall(i).args[0],
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -264,61 +265,21 @@ describe('components/input', () => {
|
|||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Blocked by lack of ch support in JSDOM
|
||||
* @see {@link https://github.com/jsdom/cssstyle/pull/107}
|
||||
*
|
||||
describe('setWidth', () => {
|
||||
let calcWidthStub;
|
||||
const inputWidth = '200px';
|
||||
|
||||
beforeEach(() => {
|
||||
calcWidthStub = stub(instance, 'calcWidth').callsArgWith(0, inputWidth);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
calcWidthStub.restore();
|
||||
});
|
||||
|
||||
describe('with a placeholder', () => {
|
||||
describe('when value length is greater or equal to 75% of the placeholder length', () => {
|
||||
it('sets the width of the element based on input value', () => {
|
||||
instance._placeholderValue = 'This is a test';
|
||||
instance.element.value = 'This is a test';
|
||||
expect(instance.element.style.width).to.not.equal(inputWidth);
|
||||
instance.setWidth();
|
||||
expect(calcWidthStub.callCount).to.equal(1);
|
||||
expect(instance.element.style.width).to.equal(inputWidth);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when width is enforced', () => {
|
||||
it('sets the width of the element based on input value', () => {
|
||||
instance._placeholderValue = 'This is a test';
|
||||
instance.element.value = '';
|
||||
expect(instance.element.style.width).to.not.equal(inputWidth);
|
||||
instance.setWidth(true);
|
||||
expect(calcWidthStub.callCount).to.equal(1);
|
||||
expect(instance.element.style.width).to.equal(inputWidth);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when value length is less than 75% of the placeholder length', () => {
|
||||
it('does not set the width of the element', () => {
|
||||
instance._placeholderValue = 'This is a test';
|
||||
instance.element.value = 'Test';
|
||||
instance.setWidth();
|
||||
expect(calcWidthStub.callCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without a placeholder', () => {
|
||||
it('sets the width of the element based on input value', () => {
|
||||
instance.placeholder = null;
|
||||
expect(instance.element.style.width).to.not.equal(inputWidth);
|
||||
instance.setWidth();
|
||||
expect(calcWidthStub.callCount).to.equal(1);
|
||||
expect(instance.element.style.width).to.equal(inputWidth);
|
||||
});
|
||||
it('sets the width of the element based on input value and placeholder', () => {
|
||||
instance.placeholder = 'This is a placeholder';
|
||||
instance.element.value = 'This is a value';
|
||||
expect(instance.element.style.width).to.not.equal('16ch');
|
||||
instance.setWidth();
|
||||
expect(instance.element.style.width).to.equal('16ch');
|
||||
expect(instance.element.style.minWidth).to.equal('22ch');
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
describe('placeholder setter', () => {
|
||||
it('sets value of element to passed placeholder', () => {
|
||||
|
|
|
@ -6,7 +6,6 @@ export default class List {
|
|||
|
||||
this.scrollPos = this.element.scrollTop;
|
||||
this.height = this.element.offsetHeight;
|
||||
this.hasChildren = !!this.element.children;
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
@ -21,6 +20,10 @@ export default class List {
|
|||
return this.element.querySelector(selector);
|
||||
}
|
||||
|
||||
hasChildren() {
|
||||
return this.element.hasChildNodes();
|
||||
}
|
||||
|
||||
scrollToTop() {
|
||||
this.element.scrollTop = 0;
|
||||
}
|
||||
|
@ -37,52 +40,52 @@ export default class List {
|
|||
// Scroll position of dropdown
|
||||
const containerScrollPos = this.element.scrollTop + dropdownHeight;
|
||||
// Difference between the choice and scroll position
|
||||
const endpoint =
|
||||
const destination =
|
||||
direction > 0
|
||||
? this.element.scrollTop + choicePos - containerScrollPos
|
||||
: choice.offsetTop;
|
||||
|
||||
requestAnimationFrame(time => {
|
||||
this._animateScroll(time, endpoint, direction);
|
||||
this._animateScroll(time, destination, direction);
|
||||
});
|
||||
}
|
||||
|
||||
_scrollDown(scrollPos, strength, endpoint) {
|
||||
const easing = (endpoint - scrollPos) / strength;
|
||||
_scrollDown(scrollPos, strength, destination) {
|
||||
const easing = (destination - scrollPos) / strength;
|
||||
const distance = easing > 1 ? easing : 1;
|
||||
|
||||
this.element.scrollTop = scrollPos + distance;
|
||||
}
|
||||
|
||||
_scrollUp(scrollPos, strength, endpoint) {
|
||||
const easing = (scrollPos - endpoint) / strength;
|
||||
_scrollUp(scrollPos, strength, destination) {
|
||||
const easing = (scrollPos - destination) / strength;
|
||||
const distance = easing > 1 ? easing : 1;
|
||||
|
||||
this.element.scrollTop = scrollPos - distance;
|
||||
}
|
||||
|
||||
_animateScroll(time, endpoint, direction) {
|
||||
_animateScroll(time, destination, direction) {
|
||||
const strength = SCROLLING_SPEED;
|
||||
const choiceListScrollTop = this.element.scrollTop;
|
||||
let continueAnimation = false;
|
||||
|
||||
if (direction > 0) {
|
||||
this._scrollDown(choiceListScrollTop, strength, endpoint);
|
||||
this._scrollDown(choiceListScrollTop, strength, destination);
|
||||
|
||||
if (choiceListScrollTop < endpoint) {
|
||||
if (choiceListScrollTop < destination) {
|
||||
continueAnimation = true;
|
||||
}
|
||||
} else {
|
||||
this._scrollUp(choiceListScrollTop, strength, endpoint);
|
||||
this._scrollUp(choiceListScrollTop, strength, destination);
|
||||
|
||||
if (choiceListScrollTop > endpoint) {
|
||||
if (choiceListScrollTop > destination) {
|
||||
continueAnimation = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (continueAnimation) {
|
||||
requestAnimationFrame(() => {
|
||||
this._animateScroll(time, endpoint, direction);
|
||||
this._animateScroll(time, destination, direction);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,10 @@ describe('components/list', () => {
|
|||
it('assigns choices element to class', () => {
|
||||
expect(instance.element).to.eql(choicesElement);
|
||||
});
|
||||
|
||||
it('sets the height of the element', () => {
|
||||
expect(instance.height).to.eql(choicesElement.scrollTop);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
|
@ -62,4 +66,31 @@ describe('components/list', () => {
|
|||
expect(expectedResponse).to.eql(actualResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasChildren', () => {
|
||||
describe('when list has children', () => {
|
||||
it('returns true', () => {
|
||||
const childElement = document.createElement('span');
|
||||
instance.element.appendChild(childElement);
|
||||
const response = instance.hasChildren();
|
||||
expect(response).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when list does not have children', () => {
|
||||
it('returns false', () => {
|
||||
instance.element.innerHTML = '';
|
||||
const response = instance.hasChildren();
|
||||
expect(response).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('scrollToTop', () => {
|
||||
it("sets the position's scroll position to 0", () => {
|
||||
instance.element.scrollTop = 10;
|
||||
instance.scrollToTop();
|
||||
expect(instance.element.scrollTop).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { dispatchEvent, isElement } from '../lib/utils';
|
||||
import { dispatchEvent } from '../lib/utils';
|
||||
|
||||
export default class WrappedElement {
|
||||
constructor({ element, classNames }) {
|
||||
Object.assign(this, { element, classNames });
|
||||
|
||||
if (!isElement(element)) {
|
||||
if (!(element instanceof Element)) {
|
||||
throw new TypeError('Invalid element passed');
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,15 @@ export default class WrappedElement {
|
|||
return this.element.value;
|
||||
}
|
||||
|
||||
set value(value) {
|
||||
// you must define setter here otherwise it will be readonly property
|
||||
this.element.value = value;
|
||||
}
|
||||
|
||||
conceal() {
|
||||
// Hide passed input
|
||||
this.element.classList.add(this.classNames.input);
|
||||
this.element.classList.add(this.classNames.hiddenState);
|
||||
this.element.hidden = true;
|
||||
|
||||
// Remove element from tab index
|
||||
this.element.tabIndex = '-1';
|
||||
|
@ -30,14 +35,13 @@ export default class WrappedElement {
|
|||
this.element.setAttribute('data-choice-orig-style', origStyle);
|
||||
}
|
||||
|
||||
this.element.setAttribute('aria-hidden', 'true');
|
||||
this.element.setAttribute('data-choice', 'active');
|
||||
}
|
||||
|
||||
reveal() {
|
||||
// Reinstate passed element
|
||||
this.element.classList.remove(this.classNames.input);
|
||||
this.element.classList.remove(this.classNames.hiddenState);
|
||||
this.element.hidden = false;
|
||||
this.element.removeAttribute('tabindex');
|
||||
|
||||
// Recover original styles if any
|
||||
|
@ -49,11 +53,11 @@ export default class WrappedElement {
|
|||
} else {
|
||||
this.element.removeAttribute('style');
|
||||
}
|
||||
this.element.removeAttribute('aria-hidden');
|
||||
this.element.removeAttribute('data-choice');
|
||||
|
||||
// Re-assign values - this is weird, I know
|
||||
this.element.value = this.element.value;
|
||||
// @todo Figure out why we need to do this
|
||||
this.element.value = this.element.value; // eslint-disable-line no-self-assign
|
||||
}
|
||||
|
||||
enable() {
|
||||
|
|
|
@ -53,10 +53,7 @@ describe('components/wrappedElement', () => {
|
|||
expect(
|
||||
instance.element.classList.contains(instance.classNames.input),
|
||||
).to.equal(true);
|
||||
expect(
|
||||
instance.element.classList.contains(instance.classNames.hiddenState),
|
||||
).to.equal(true);
|
||||
expect(instance.element.getAttribute('aria-hidden')).to.equal('true');
|
||||
expect(instance.element.hidden).to.be.true;
|
||||
expect(instance.element.getAttribute('data-choice')).to.equal('active');
|
||||
expect(instance.element.getAttribute('data-choice-orig-style')).to.equal(
|
||||
originalStyling,
|
||||
|
@ -78,9 +75,7 @@ describe('components/wrappedElement', () => {
|
|||
expect(
|
||||
instance.element.classList.contains(instance.classNames.input),
|
||||
).to.equal(false);
|
||||
expect(
|
||||
instance.element.classList.contains(instance.classNames.hiddenState),
|
||||
).to.equal(false);
|
||||
expect(instance.element.hidden).to.be.false;
|
||||
expect(instance.element.getAttribute('style')).to.equal(originalStyling);
|
||||
expect(instance.element.getAttribute('aria-hidden')).to.equal(null);
|
||||
expect(instance.element.getAttribute('data-choice')).to.equal(null);
|
||||
|
|
|
@ -6,6 +6,10 @@ export default class WrappedInput extends WrappedElement {
|
|||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.element.value;
|
||||
}
|
||||
|
||||
set value(items) {
|
||||
const itemValues = items.map(({ value }) => value);
|
||||
const joinedValues = itemValues.join(this.delimiter);
|
||||
|
@ -13,9 +17,4 @@ export default class WrappedInput extends WrappedElement {
|
|||
this.element.setAttribute('value', joinedValues);
|
||||
this.element.value = joinedValues;
|
||||
}
|
||||
|
||||
// @todo figure out why we need this? Perhaps a babel issue
|
||||
get value() {
|
||||
return super.value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import WrappedElement from './wrapped-element';
|
||||
import templates from './../templates';
|
||||
|
||||
export default class WrappedSelect extends WrappedElement {
|
||||
constructor({ element, classNames }) {
|
||||
constructor({ element, classNames, template }) {
|
||||
super({ element, classNames });
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
get placeholderOption() {
|
||||
return this.element.querySelector('option[placeholder]');
|
||||
return (
|
||||
this.element.querySelector('option[value=""]') ||
|
||||
// Backward compatibility layer for the non-standard placeholder attribute supported in older versions.
|
||||
this.element.querySelector('option[placeholder]')
|
||||
);
|
||||
}
|
||||
|
||||
get optionGroups() {
|
||||
|
@ -22,9 +26,9 @@ export default class WrappedSelect extends WrappedElement {
|
|||
const fragment = document.createDocumentFragment();
|
||||
const addOptionToFragment = data => {
|
||||
// Create a standard select option
|
||||
const template = templates.option(data);
|
||||
const option = this.template(data);
|
||||
// Append it to fragment
|
||||
fragment.appendChild(template);
|
||||
fragment.appendChild(option);
|
||||
};
|
||||
|
||||
// Add each list item to list
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { expect } from 'chai';
|
||||
import { stub } from 'sinon';
|
||||
import { stub, spy } from 'sinon';
|
||||
import WrappedElement from './wrapped-element';
|
||||
import WrappedSelect from './wrapped-select';
|
||||
import { DEFAULT_CLASSNAMES } from '../constants';
|
||||
import Templates from '../templates';
|
||||
|
||||
describe('components/wrappedSelect', () => {
|
||||
let instance;
|
||||
|
@ -11,11 +12,16 @@ describe('components/wrappedSelect', () => {
|
|||
beforeEach(() => {
|
||||
element = document.createElement('select');
|
||||
element.id = 'target';
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
const option = document.createElement('option');
|
||||
|
||||
option.value = `Value ${i}`;
|
||||
option.innerHTML = `Label ${i}`;
|
||||
if (i === 0) {
|
||||
option.value = '';
|
||||
option.innerHTML = 'Placeholder label';
|
||||
} else {
|
||||
option.value = `Value ${i}`;
|
||||
option.innerHTML = `Label ${i}`;
|
||||
}
|
||||
|
||||
if (i === 1) {
|
||||
option.setAttribute('placeholder', '');
|
||||
|
@ -28,6 +34,7 @@ describe('components/wrappedSelect', () => {
|
|||
instance = new WrappedSelect({
|
||||
element: document.getElementById('target'),
|
||||
classNames: DEFAULT_CLASSNAMES,
|
||||
template: spy(Templates.option),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -67,8 +74,16 @@ describe('components/wrappedSelect', () => {
|
|||
});
|
||||
|
||||
describe('placeholderOption getter', () => {
|
||||
it('returns option element with placeholder attribute', () => {
|
||||
it('returns option element with empty value attribute', () => {
|
||||
expect(instance.placeholderOption).to.be.instanceOf(HTMLOptionElement);
|
||||
expect(instance.placeholderOption.value).to.equal('');
|
||||
});
|
||||
|
||||
it('returns option element with placeholder attribute as fallback', () => {
|
||||
instance.element.removeChild(instance.element.firstChild);
|
||||
|
||||
expect(instance.placeholderOption).to.be.instanceOf(HTMLOptionElement);
|
||||
expect(instance.placeholderOption.value).to.equal('Value 1');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -133,6 +148,7 @@ describe('components/wrappedSelect', () => {
|
|||
selectElement.appendChild(fragment);
|
||||
|
||||
expect(fragment).to.be.instanceOf(DocumentFragment);
|
||||
expect(instance.template.callCount).to.equal(2);
|
||||
expect(selectElement.options.length).to.equal(2);
|
||||
expect(selectElement.options[0].value).to.equal(options[0].value);
|
||||
expect(selectElement.options[1].value).to.equal(options[1].value);
|
||||
|
@ -142,7 +158,7 @@ describe('components/wrappedSelect', () => {
|
|||
describe('appendDocFragment', () => {
|
||||
it('empties contents of element', () => {
|
||||
expect(instance.element.getElementsByTagName('option').length).to.equal(
|
||||
4,
|
||||
5,
|
||||
);
|
||||
instance.appendDocFragment(document.createDocumentFragment());
|
||||
expect(instance.element.getElementsByTagName('option').length).to.equal(
|
||||
|
|
|
@ -22,7 +22,6 @@ export const DEFAULT_CLASSNAMES = {
|
|||
openState: 'is-open',
|
||||
disabledState: 'is-disabled',
|
||||
highlightedState: 'is-highlighted',
|
||||
hiddenState: 'is-hidden',
|
||||
flippedState: 'is-flipped',
|
||||
loadingState: 'is-loading',
|
||||
noResults: 'has-no-results',
|
||||
|
@ -36,7 +35,7 @@ export const DEFAULT_CONFIG = {
|
|||
renderChoiceLimit: -1,
|
||||
maxItemCount: -1,
|
||||
addItems: true,
|
||||
addItemFilterFn: null,
|
||||
addItemFilter: null,
|
||||
removeItems: true,
|
||||
removeItemButton: false,
|
||||
editItems: false,
|
||||
|
|
|
@ -35,7 +35,6 @@ describe('constants', () => {
|
|||
'openState',
|
||||
'disabledState',
|
||||
'highlightedState',
|
||||
'hiddenState',
|
||||
'flippedState',
|
||||
'loadingState',
|
||||
'noResults',
|
||||
|
@ -56,7 +55,7 @@ describe('constants', () => {
|
|||
expect(DEFAULT_CONFIG.renderChoiceLimit).to.be.a('number');
|
||||
expect(DEFAULT_CONFIG.maxItemCount).to.be.a('number');
|
||||
expect(DEFAULT_CONFIG.addItems).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.addItemFilterFn).to.equal(null);
|
||||
expect(DEFAULT_CONFIG.addItemFilter).to.equal(null);
|
||||
expect(DEFAULT_CONFIG.removeItems).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.removeItemButton).to.be.a('boolean');
|
||||
expect(DEFAULT_CONFIG.editItems).to.be.a('boolean');
|
||||
|
|
45
src/scripts/lib/delegate-events.js
Normal file
45
src/scripts/lib/delegate-events.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
window.delegateEvent = (function delegateEvent() {
|
||||
let events;
|
||||
let addedListenerTypes;
|
||||
if (typeof events === 'undefined') {
|
||||
events = new Map();
|
||||
}
|
||||
if (typeof addedListenerTypes === 'undefined') {
|
||||
addedListenerTypes = [];
|
||||
}
|
||||
|
||||
function _callback(event) {
|
||||
const type = events.get(event.type);
|
||||
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
|
||||
type.forEach(fn => fn(event));
|
||||
}
|
||||
|
||||
return {
|
||||
add: function add(type, fn) {
|
||||
// Cache list of events.
|
||||
if (events.has(type)) {
|
||||
events.get(type).push(fn);
|
||||
} else {
|
||||
events.set(type, [fn]);
|
||||
}
|
||||
// Setup events.
|
||||
if (addedListenerTypes.indexOf(type) === -1) {
|
||||
document.documentElement.addEventListener(type, _callback, true);
|
||||
addedListenerTypes.push(type);
|
||||
}
|
||||
},
|
||||
remove: function remove(type, fn) {
|
||||
if (!events.get(type)) {
|
||||
return;
|
||||
}
|
||||
events.set(type, events.get(type).filter(item => item !== fn));
|
||||
if (!events.get(type).length) {
|
||||
addedListenerTypes.splice(addedListenerTypes.indexOf(type), 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -1,16 +1,8 @@
|
|||
export const getRandomNumber = (min, max) =>
|
||||
Math.floor(Math.random() * (max - min) + min);
|
||||
|
||||
export const generateChars = length => {
|
||||
let chars = '';
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomChar = getRandomNumber(0, 36);
|
||||
chars += randomChar.toString(36);
|
||||
}
|
||||
|
||||
return chars;
|
||||
};
|
||||
export const generateChars = length =>
|
||||
Array.from({ length }, () => getRandomNumber(0, 36).toString(36)).join('');
|
||||
|
||||
export const generateId = (element, prefix) => {
|
||||
let id =
|
||||
|
@ -28,44 +20,46 @@ export const getType = obj => Object.prototype.toString.call(obj).slice(8, -1);
|
|||
export const isType = (type, obj) =>
|
||||
obj !== undefined && obj !== null && getType(obj) === type;
|
||||
|
||||
export const isElement = element => element instanceof Element;
|
||||
|
||||
export const wrap = (element, wrapper = document.createElement('div')) => {
|
||||
if (element.nextSibling) {
|
||||
element.parentNode.insertBefore(wrapper, element.nextSibling);
|
||||
} else {
|
||||
element.parentNode.appendChild(wrapper);
|
||||
}
|
||||
|
||||
return wrapper.appendChild(element);
|
||||
};
|
||||
|
||||
export const findAncestorByAttrName = (el, attr) => {
|
||||
let target = el;
|
||||
/**
|
||||
* @param {HTMLElement} el
|
||||
* @param {string} attr
|
||||
*/
|
||||
export const findAncestorByAttrName = (el, attr) => el.closest(`[${attr}]`);
|
||||
|
||||
while (target) {
|
||||
if (target.hasAttribute(attr)) {
|
||||
return target;
|
||||
export const getAdjacentEl =
|
||||
/**
|
||||
* @param {Element} startEl
|
||||
* @param {string} selector
|
||||
* @param {1 | -1} direction
|
||||
* @returns {Element | undefined}
|
||||
*/
|
||||
(startEl, selector, direction = 1) => {
|
||||
if (!(startEl instanceof Element) || typeof selector !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
target = target.parentElement;
|
||||
}
|
||||
const prop = `${direction > 0 ? 'next' : 'previous'}ElementSibling`;
|
||||
|
||||
return null;
|
||||
};
|
||||
let sibling = startEl[prop];
|
||||
while (sibling) {
|
||||
if (sibling.matches(selector)) {
|
||||
return sibling;
|
||||
}
|
||||
sibling = sibling[prop];
|
||||
}
|
||||
|
||||
export const getAdjacentEl = (startEl, className, direction = 1) => {
|
||||
if (!startEl || !className) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = startEl.parentNode.parentNode;
|
||||
const children = Array.from(parent.querySelectorAll(className));
|
||||
|
||||
const startPos = children.indexOf(startEl);
|
||||
const operatorDirection = direction > 0 ? 1 : -1;
|
||||
|
||||
return children[startPos + operatorDirection];
|
||||
};
|
||||
return sibling;
|
||||
};
|
||||
|
||||
export const isScrolledIntoView = (el, parent, direction = 1) => {
|
||||
if (!el) {
|
||||
|
@ -87,7 +81,7 @@ export const isScrolledIntoView = (el, parent, direction = 1) => {
|
|||
};
|
||||
|
||||
export const sanitise = value => {
|
||||
if (!isType('String', value)) {
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -100,6 +94,7 @@ export const sanitise = value => {
|
|||
|
||||
export const strToEl = (() => {
|
||||
const tmpEl = document.createElement('div');
|
||||
|
||||
return str => {
|
||||
const cleanedInput = str.trim();
|
||||
tmpEl.innerHTML = cleanedInput;
|
||||
|
@ -113,67 +108,18 @@ export const strToEl = (() => {
|
|||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Determines the width of a passed input based on its value and passes
|
||||
* it to the supplied callback function.
|
||||
*/
|
||||
export const calcWidthOfInput = (input, callback) => {
|
||||
const value = input.value || input.placeholder;
|
||||
let width = input.offsetWidth;
|
||||
|
||||
if (value) {
|
||||
const testEl = strToEl(`<span>${sanitise(value)}</span>`);
|
||||
testEl.style.position = 'absolute';
|
||||
testEl.style.padding = '0';
|
||||
testEl.style.top = '-9999px';
|
||||
testEl.style.left = '-9999px';
|
||||
testEl.style.width = 'auto';
|
||||
testEl.style.whiteSpace = 'pre';
|
||||
|
||||
if (document.body.contains(input) && window.getComputedStyle) {
|
||||
const inputStyle = window.getComputedStyle(input);
|
||||
|
||||
if (inputStyle) {
|
||||
testEl.style.fontSize = inputStyle.fontSize;
|
||||
testEl.style.fontFamily = inputStyle.fontFamily;
|
||||
testEl.style.fontWeight = inputStyle.fontWeight;
|
||||
testEl.style.fontStyle = inputStyle.fontStyle;
|
||||
testEl.style.letterSpacing = inputStyle.letterSpacing;
|
||||
testEl.style.textTransform = inputStyle.textTransform;
|
||||
testEl.style.padding = inputStyle.padding;
|
||||
}
|
||||
}
|
||||
|
||||
document.body.appendChild(testEl);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (value && testEl.offsetWidth !== input.offsetWidth) {
|
||||
width = testEl.offsetWidth + 4;
|
||||
}
|
||||
|
||||
document.body.removeChild(testEl);
|
||||
|
||||
callback.call(this, `${width}px`);
|
||||
export const sortByAlpha =
|
||||
/**
|
||||
* @param {{ label?: string, value: string }} a
|
||||
* @param {{ label?: string, value: string }} b
|
||||
* @returns {number}
|
||||
*/
|
||||
({ value, label = value }, { value: value2, label: label2 = value2 }) =>
|
||||
label.localeCompare(label2, [], {
|
||||
sensitivity: 'base',
|
||||
ignorePunctuation: true,
|
||||
numeric: true,
|
||||
});
|
||||
} else {
|
||||
callback.call(this, `${width}px`);
|
||||
}
|
||||
};
|
||||
|
||||
export const sortByAlpha = (a, b) => {
|
||||
const labelA = `${a.label || a.value}`.toLowerCase();
|
||||
const labelB = `${b.label || b.value}`.toLowerCase();
|
||||
|
||||
if (labelA < labelB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (labelA > labelB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const sortByScore = (a, b) => a.score - b.score;
|
||||
|
||||
|
@ -187,40 +133,12 @@ export const dispatchEvent = (element, type, customArgs = null) => {
|
|||
return element.dispatchEvent(event);
|
||||
};
|
||||
|
||||
export const getWindowHeight = () => {
|
||||
const body = document.body;
|
||||
const html = document.documentElement;
|
||||
return Math.max(
|
||||
body.scrollHeight,
|
||||
body.offsetHeight,
|
||||
html.clientHeight,
|
||||
html.scrollHeight,
|
||||
html.offsetHeight,
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchFromObject = (object, path) => {
|
||||
const index = path.indexOf('.');
|
||||
|
||||
if (index > -1) {
|
||||
return fetchFromObject(
|
||||
object[path.substring(0, index)],
|
||||
path.substr(index + 1),
|
||||
);
|
||||
}
|
||||
|
||||
return object[path];
|
||||
};
|
||||
|
||||
export const isIE11 = () =>
|
||||
!!(
|
||||
navigator.userAgent.match(/Trident/) &&
|
||||
navigator.userAgent.match(/rv[ :]11/)
|
||||
);
|
||||
export const isIE11 = userAgent =>
|
||||
!!(userAgent.match(/Trident/) && userAgent.match(/rv[ :]11/));
|
||||
|
||||
export const existsInArray = (array, value, key = 'value') =>
|
||||
array.some(item => {
|
||||
if (isType('String', value)) {
|
||||
if (typeof value === 'string') {
|
||||
return item[key] === value.trim();
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,10 @@ import {
|
|||
generateId,
|
||||
getType,
|
||||
isType,
|
||||
isElement,
|
||||
sanitise,
|
||||
sortByAlpha,
|
||||
sortByScore,
|
||||
fetchFromObject,
|
||||
isIE11,
|
||||
existsInArray,
|
||||
cloneObject,
|
||||
dispatchEvent,
|
||||
|
@ -97,14 +96,6 @@ describe('utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('isElement', () => {
|
||||
it('checks with given object is an element', () => {
|
||||
const element = document.createElement('div');
|
||||
expect(isElement(element)).to.equal(true);
|
||||
expect(isElement({})).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitise', () => {
|
||||
it('strips HTML from value', () => {
|
||||
const value = '<script>somethingMalicious();</script>';
|
||||
|
@ -198,16 +189,15 @@ describe('utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('fetchFromObject', () => {
|
||||
it('fetches value from object using given path', () => {
|
||||
const object = {
|
||||
band: {
|
||||
name: 'The Strokes',
|
||||
},
|
||||
};
|
||||
describe('isIE11', () => {
|
||||
it('returns whether the given user agent string matches an IE11 user agent string', () => {
|
||||
const IE11UserAgent =
|
||||
'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
|
||||
const firefoxUserAgent =
|
||||
'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0';
|
||||
|
||||
const output = fetchFromObject(object, 'band.name');
|
||||
expect(output).to.equal(object.band.name);
|
||||
expect(isIE11(IE11UserAgent)).to.equal(true);
|
||||
expect(isIE11(firefoxUserAgent)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ export default function choices(state = defaultState, action) {
|
|||
return state.map(obj => {
|
||||
const choice = obj;
|
||||
choice.active = action.active;
|
||||
|
||||
return choice;
|
||||
});
|
||||
}
|
||||
|
@ -45,6 +46,7 @@ export default function choices(state = defaultState, action) {
|
|||
if (choice.id === parseInt(action.choiceId, 10)) {
|
||||
choice.selected = true;
|
||||
}
|
||||
|
||||
return choice;
|
||||
});
|
||||
}
|
||||
|
@ -61,6 +63,7 @@ export default function choices(state = defaultState, action) {
|
|||
if (choice.id === parseInt(action.choiceId, 10)) {
|
||||
choice.selected = false;
|
||||
}
|
||||
|
||||
return choice;
|
||||
});
|
||||
}
|
||||
|
@ -76,8 +79,10 @@ export default function choices(state = defaultState, action) {
|
|||
choice.active = action.results.some(({ item, score }) => {
|
||||
if (item.id === choice.id) {
|
||||
choice.score = score;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
@ -89,6 +94,7 @@ export default function choices(state = defaultState, action) {
|
|||
return state.map(obj => {
|
||||
const choice = obj;
|
||||
choice.active = action.active;
|
||||
|
||||
return choice;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export default function items(state = defaultState, action) {
|
|||
return newState.map(obj => {
|
||||
const item = obj;
|
||||
item.highlighted = false;
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
@ -34,6 +35,7 @@ export default function items(state = defaultState, action) {
|
|||
if (item.id === action.id) {
|
||||
item.active = false;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
@ -44,6 +46,7 @@ export default function items(state = defaultState, action) {
|
|||
if (item.id === action.id) {
|
||||
item.highlighted = action.highlighted;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createStore } from 'redux';
|
||||
import rootReducer from './../reducers/index';
|
||||
import rootReducer from '../reducers/index';
|
||||
|
||||
export default class Store {
|
||||
constructor() {
|
||||
|
@ -73,7 +73,7 @@ export default class Store {
|
|||
* @return {Array} Option objects
|
||||
*/
|
||||
get activeChoices() {
|
||||
const choices = this.choices;
|
||||
const { choices } = this;
|
||||
const values = choices.filter(choice => choice.active === true);
|
||||
|
||||
return values;
|
||||
|
@ -118,14 +118,14 @@ export default class Store {
|
|||
* @return {Array} Group objects
|
||||
*/
|
||||
get activeGroups() {
|
||||
const groups = this.groups;
|
||||
const choices = this.choices;
|
||||
const { groups, choices } = this;
|
||||
|
||||
return groups.filter(group => {
|
||||
const isActive = group.active === true && group.disabled === false;
|
||||
const hasActiveOptions = choices.some(
|
||||
choice => choice.active === true && choice.disabled === false,
|
||||
);
|
||||
|
||||
return isActive && hasActiveOptions;
|
||||
}, []);
|
||||
}
|
||||
|
@ -140,16 +140,16 @@ export default class Store {
|
|||
|
||||
/**
|
||||
* Get single choice by it's ID
|
||||
* @return {Object} Found choice
|
||||
* @param {id} string
|
||||
* @return {import('../../../types/index').Choices.Choice | false} Found choice
|
||||
*/
|
||||
getChoiceById(id) {
|
||||
if (id) {
|
||||
const choices = this.activeChoices;
|
||||
const foundChoice = choices.find(
|
||||
choice => choice.id === parseInt(id, 10),
|
||||
);
|
||||
return foundChoice;
|
||||
const n = parseInt(id, 10);
|
||||
|
||||
return this.activeChoices.find(choice => choice.id === n);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,238 +1,248 @@
|
|||
import classNames from 'classnames';
|
||||
import { strToEl } from './lib/utils';
|
||||
/**
|
||||
* Helpers to create HTML elements used by Choices
|
||||
* Can be overridden by providing `callbackOnCreateTemplates` option
|
||||
* @typedef {import('../../types/index').Choices.Templates} Templates
|
||||
*/
|
||||
|
||||
export const TEMPLATES = {
|
||||
export const TEMPLATES = /** @type {Templates} */ ({
|
||||
containerOuter(
|
||||
globalClasses,
|
||||
direction,
|
||||
{ containerOuter },
|
||||
dir,
|
||||
isSelectElement,
|
||||
isSelectOneElement,
|
||||
searchEnabled,
|
||||
passedElementType,
|
||||
) {
|
||||
const tabIndex = isSelectOneElement ? 'tabindex="0"' : '';
|
||||
let role = isSelectElement ? 'role="listbox"' : '';
|
||||
let ariaAutoComplete = '';
|
||||
const div = Object.assign(document.createElement('div'), {
|
||||
className: containerOuter,
|
||||
});
|
||||
|
||||
if (isSelectElement && searchEnabled) {
|
||||
role = 'role="combobox"';
|
||||
ariaAutoComplete = 'aria-autocomplete="list"';
|
||||
div.dataset.type = passedElementType;
|
||||
|
||||
if (dir) {
|
||||
div.dir = dir;
|
||||
}
|
||||
|
||||
return strToEl(`
|
||||
<div
|
||||
class="${globalClasses.containerOuter}"
|
||||
data-type="${passedElementType}"
|
||||
${role}
|
||||
${tabIndex}
|
||||
${ariaAutoComplete}
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
dir="${direction}"
|
||||
>
|
||||
</div>
|
||||
`);
|
||||
if (isSelectOneElement) {
|
||||
div.tabIndex = 0;
|
||||
}
|
||||
|
||||
if (isSelectElement) {
|
||||
div.setAttribute('role', searchEnabled ? 'combobox' : 'listbox');
|
||||
if (searchEnabled) {
|
||||
div.setAttribute('aria-autocomplete', 'list');
|
||||
}
|
||||
}
|
||||
|
||||
div.setAttribute('aria-haspopup', 'true');
|
||||
div.setAttribute('aria-expanded', 'false');
|
||||
|
||||
return div;
|
||||
},
|
||||
containerInner(globalClasses) {
|
||||
return strToEl(`
|
||||
<div class="${globalClasses.containerInner}"></div>
|
||||
`);
|
||||
containerInner({ containerInner }) {
|
||||
return Object.assign(document.createElement('div'), {
|
||||
className: containerInner,
|
||||
});
|
||||
},
|
||||
itemList(globalClasses, isSelectOneElement) {
|
||||
const localClasses = classNames(globalClasses.list, {
|
||||
[globalClasses.listSingle]: isSelectOneElement,
|
||||
[globalClasses.listItems]: !isSelectOneElement,
|
||||
itemList({ list, listSingle, listItems }, isSelectOneElement) {
|
||||
return Object.assign(document.createElement('div'), {
|
||||
className: `${list} ${isSelectOneElement ? listSingle : listItems}`,
|
||||
});
|
||||
},
|
||||
placeholder({ placeholder }, value) {
|
||||
return Object.assign(document.createElement('div'), {
|
||||
className: placeholder,
|
||||
innerHTML: value,
|
||||
});
|
||||
},
|
||||
|
||||
item(
|
||||
{ item, button, highlightedState, itemSelectable, placeholder },
|
||||
{
|
||||
id,
|
||||
value,
|
||||
label,
|
||||
customProperties,
|
||||
active,
|
||||
disabled,
|
||||
highlighted,
|
||||
placeholder: isPlaceholder,
|
||||
},
|
||||
removeItemButton,
|
||||
) {
|
||||
const div = Object.assign(document.createElement('div'), {
|
||||
className: item,
|
||||
innerHTML: label,
|
||||
});
|
||||
|
||||
return strToEl(`
|
||||
<div class="${localClasses}"></div>
|
||||
`);
|
||||
},
|
||||
placeholder(globalClasses, value) {
|
||||
return strToEl(`
|
||||
<div class="${globalClasses.placeholder}">
|
||||
${value}
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
item(globalClasses, data, removeItemButton) {
|
||||
const ariaSelected = data.active ? 'aria-selected="true"' : '';
|
||||
const ariaDisabled = data.disabled ? 'aria-disabled="true"' : '';
|
||||
|
||||
let localClasses = classNames(globalClasses.item, {
|
||||
[globalClasses.highlightedState]: data.highlighted,
|
||||
[globalClasses.itemSelectable]: !data.highlighted,
|
||||
[globalClasses.placeholder]: data.placeholder,
|
||||
Object.assign(div.dataset, {
|
||||
item: '',
|
||||
id,
|
||||
value,
|
||||
customProperties,
|
||||
});
|
||||
|
||||
if (active) {
|
||||
div.setAttribute('aria-selected', 'true');
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
div.setAttribute('aria-disabled', 'true');
|
||||
}
|
||||
|
||||
if (isPlaceholder) {
|
||||
div.classList.add(placeholder);
|
||||
}
|
||||
div.classList.add(highlighted ? highlightedState : itemSelectable);
|
||||
|
||||
if (removeItemButton) {
|
||||
localClasses = classNames(globalClasses.item, {
|
||||
[globalClasses.highlightedState]: data.highlighted,
|
||||
[globalClasses.itemSelectable]: !data.disabled,
|
||||
[globalClasses.placeholder]: data.placeholder,
|
||||
if (disabled) {
|
||||
div.classList.remove(itemSelectable);
|
||||
}
|
||||
div.dataset.deletable = '';
|
||||
/** @todo This MUST be localizable, not hardcoded! */
|
||||
const REMOVE_ITEM_TEXT = 'Remove item';
|
||||
const removeButton = Object.assign(document.createElement('button'), {
|
||||
type: 'button',
|
||||
className: button,
|
||||
innerHTML: REMOVE_ITEM_TEXT,
|
||||
});
|
||||
|
||||
return strToEl(`
|
||||
<div
|
||||
class="${localClasses}"
|
||||
data-item
|
||||
data-id="${data.id}"
|
||||
data-value="${data.value}"
|
||||
data-custom-properties='${data.customProperties}'
|
||||
data-deletable
|
||||
${ariaSelected}
|
||||
${ariaDisabled}
|
||||
>
|
||||
${data.label}<!--
|
||||
--><button
|
||||
type="button"
|
||||
class="${globalClasses.button}"
|
||||
data-button
|
||||
aria-label="Remove item: '${data.value}'"
|
||||
>
|
||||
Remove item
|
||||
</button>
|
||||
</div>
|
||||
`);
|
||||
removeButton.setAttribute(
|
||||
'aria-label',
|
||||
`${REMOVE_ITEM_TEXT}: '${value}'`,
|
||||
);
|
||||
removeButton.dataset.button = '';
|
||||
div.appendChild(removeButton);
|
||||
}
|
||||
|
||||
return strToEl(`
|
||||
<div
|
||||
class="${localClasses}"
|
||||
data-item
|
||||
data-id="${data.id}"
|
||||
data-value="${data.value}"
|
||||
${ariaSelected}
|
||||
${ariaDisabled}
|
||||
>
|
||||
${data.label}
|
||||
</div>
|
||||
`);
|
||||
return div;
|
||||
},
|
||||
choiceList(globalClasses, isSelectOneElement) {
|
||||
const ariaMultiSelectable = !isSelectOneElement
|
||||
? 'aria-multiselectable="true"'
|
||||
: '';
|
||||
|
||||
return strToEl(`
|
||||
<div
|
||||
class="${globalClasses.list}"
|
||||
dir="ltr"
|
||||
role="listbox"
|
||||
${ariaMultiSelectable}
|
||||
>
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
choiceGroup(globalClasses, data) {
|
||||
const ariaDisabled = data.disabled ? 'aria-disabled="true"' : '';
|
||||
const localClasses = classNames(globalClasses.group, {
|
||||
[globalClasses.itemDisabled]: data.disabled,
|
||||
choiceList({ list }, isSelectOneElement) {
|
||||
const div = Object.assign(document.createElement('div'), {
|
||||
className: list,
|
||||
});
|
||||
|
||||
return strToEl(`
|
||||
<div
|
||||
class="${localClasses}"
|
||||
data-group
|
||||
data-id="${data.id}"
|
||||
data-value="${data.value}"
|
||||
role="group"
|
||||
${ariaDisabled}
|
||||
>
|
||||
<div class="${globalClasses.groupHeading}">${data.value}</div>
|
||||
</div>
|
||||
`);
|
||||
if (!isSelectOneElement) {
|
||||
div.setAttribute('aria-multiselectable', 'true');
|
||||
}
|
||||
div.setAttribute('role', 'listbox');
|
||||
|
||||
return div;
|
||||
},
|
||||
choice(globalClasses, data, itemSelectText) {
|
||||
const role = data.groupId > 0 ? 'role="treeitem"' : 'role="option"';
|
||||
const localClasses = classNames(
|
||||
globalClasses.item,
|
||||
globalClasses.itemChoice,
|
||||
{
|
||||
[globalClasses.itemDisabled]: data.disabled,
|
||||
[globalClasses.itemSelectable]: !data.disabled,
|
||||
[globalClasses.placeholder]: data.placeholder,
|
||||
},
|
||||
|
||||
choiceGroup({ group, groupHeading, itemDisabled }, { id, value, disabled }) {
|
||||
const div = Object.assign(document.createElement('div'), {
|
||||
className: `${group} ${disabled ? itemDisabled : ''}`,
|
||||
});
|
||||
|
||||
div.setAttribute('role', 'group');
|
||||
|
||||
Object.assign(div.dataset, {
|
||||
group: '',
|
||||
id,
|
||||
value,
|
||||
});
|
||||
|
||||
if (disabled) {
|
||||
div.setAttribute('aria-disabled', 'true');
|
||||
}
|
||||
|
||||
div.appendChild(
|
||||
Object.assign(document.createElement('div'), {
|
||||
className: groupHeading,
|
||||
innerHTML: value,
|
||||
}),
|
||||
);
|
||||
|
||||
return strToEl(`
|
||||
<div
|
||||
class="${localClasses}"
|
||||
data-select-text="${itemSelectText}"
|
||||
data-choice
|
||||
data-id="${data.id}"
|
||||
data-value="${data.value}"
|
||||
${
|
||||
data.disabled
|
||||
? 'data-choice-disabled aria-disabled="true"'
|
||||
: 'data-choice-selectable'
|
||||
}
|
||||
id="${data.elementId}"
|
||||
${role}
|
||||
>
|
||||
${data.label}
|
||||
</div>
|
||||
`);
|
||||
return div;
|
||||
},
|
||||
input(globalClasses) {
|
||||
const localClasses = classNames(
|
||||
globalClasses.input,
|
||||
globalClasses.inputCloned,
|
||||
);
|
||||
|
||||
return strToEl(`
|
||||
<input
|
||||
type="text"
|
||||
class="${localClasses}"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
role="textbox"
|
||||
aria-autocomplete="list"
|
||||
>
|
||||
`);
|
||||
},
|
||||
dropdown(globalClasses) {
|
||||
const localClasses = classNames(
|
||||
globalClasses.list,
|
||||
globalClasses.listDropdown,
|
||||
);
|
||||
choice(
|
||||
{ item, itemChoice, itemSelectable, itemDisabled, placeholder },
|
||||
{
|
||||
id,
|
||||
value,
|
||||
label,
|
||||
groupId,
|
||||
elementId,
|
||||
disabled,
|
||||
placeholder: isPlaceholder,
|
||||
},
|
||||
selectText,
|
||||
) {
|
||||
const div = Object.assign(document.createElement('div'), {
|
||||
id: elementId,
|
||||
innerHTML: label,
|
||||
className: `${item} ${itemChoice} ${
|
||||
disabled ? itemDisabled : itemSelectable
|
||||
} ${isPlaceholder ? placeholder : ''}`,
|
||||
});
|
||||
|
||||
return strToEl(`
|
||||
<div
|
||||
class="${localClasses}"
|
||||
aria-expanded="false"
|
||||
>
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
notice(globalClasses, label, type = '') {
|
||||
const localClasses = classNames(
|
||||
globalClasses.item,
|
||||
globalClasses.itemChoice,
|
||||
{
|
||||
[globalClasses.noResults]: type === 'no-results',
|
||||
[globalClasses.noChoices]: type === 'no-choices',
|
||||
},
|
||||
);
|
||||
div.setAttribute('role', groupId > 0 ? 'treeitem' : 'option');
|
||||
|
||||
return strToEl(`
|
||||
<div class="${localClasses}">
|
||||
${label}
|
||||
</div>
|
||||
`);
|
||||
Object.assign(div.dataset, {
|
||||
choice: '',
|
||||
id,
|
||||
value,
|
||||
selectText,
|
||||
});
|
||||
|
||||
if (disabled) {
|
||||
div.dataset.choiceDisabled = '';
|
||||
div.setAttribute('aria-disabled', 'true');
|
||||
} else {
|
||||
div.dataset.choiceSelectable = '';
|
||||
}
|
||||
|
||||
return div;
|
||||
},
|
||||
option(data) {
|
||||
return strToEl(`
|
||||
<option value="${data.value}" ${data.active ? 'selected' : ''} ${
|
||||
data.disabled ? 'disabled' : ''
|
||||
} ${
|
||||
data.customProperties
|
||||
? `data-custom-properties=${data.customProperties}`
|
||||
: ''
|
||||
}>${data.label}</option>
|
||||
`);
|
||||
input({ input, inputCloned }, placeholderValue) {
|
||||
const inp = Object.assign(document.createElement('input'), {
|
||||
type: 'text',
|
||||
className: `${input} ${inputCloned}`,
|
||||
autocomplete: 'off',
|
||||
autocapitalize: 'off',
|
||||
spellcheck: false,
|
||||
});
|
||||
|
||||
inp.setAttribute('role', 'textbox');
|
||||
inp.setAttribute('aria-autocomplete', 'list');
|
||||
inp.setAttribute('aria-label', placeholderValue);
|
||||
|
||||
return inp;
|
||||
},
|
||||
};
|
||||
dropdown({ list, listDropdown }) {
|
||||
const div = document.createElement('div');
|
||||
|
||||
div.classList.add(list, listDropdown);
|
||||
div.setAttribute('aria-expanded', 'false');
|
||||
|
||||
return div;
|
||||
},
|
||||
notice({ item, itemChoice, noResults, noChoices }, innerHTML, type = '') {
|
||||
const classes = [item, itemChoice];
|
||||
|
||||
if (type === 'no-choices') {
|
||||
classes.push(noChoices);
|
||||
} else if (type === 'no-results') {
|
||||
classes.push(noResults);
|
||||
}
|
||||
|
||||
return Object.assign(document.createElement('div'), {
|
||||
innerHTML,
|
||||
className: classes.join(' '),
|
||||
});
|
||||
},
|
||||
option({ label, value, customProperties, active, disabled }) {
|
||||
const opt = new Option(label, value, false, active);
|
||||
|
||||
if (customProperties) {
|
||||
opt.dataset.customProperties = customProperties;
|
||||
}
|
||||
opt.disabled = disabled;
|
||||
|
||||
return opt;
|
||||
},
|
||||
});
|
||||
|
||||
export default TEMPLATES;
|
||||
|
|
|
@ -1,9 +1,25 @@
|
|||
import { expect } from 'chai';
|
||||
import templates from './templates';
|
||||
import { getType, strToEl } from './lib/utils';
|
||||
import { strToEl } from './lib/utils';
|
||||
|
||||
const stripElement = element =>
|
||||
element.outerHTML.replace(/(^|>)\s+|\s+(?=<|$)/g, '$1');
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} element1
|
||||
* @param {HTMLElement} element2
|
||||
*/
|
||||
function expectEqualElements(element1, element2) {
|
||||
expect(element1.tagName).to.equal(element2.tagName);
|
||||
expect(element1.attributes.length).to.equal(element2.attributes.length);
|
||||
expect(Object.keys(element1.dataset)).to.have.members(
|
||||
Object.keys(element2.dataset),
|
||||
);
|
||||
// compare attributes values
|
||||
for (const attribute of Object.values(element1.attributes)) {
|
||||
expect(element1.getAttribute(attribute)).to.equal(
|
||||
element2.getAttribute(attribute),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
describe('templates', () => {
|
||||
describe('containerOuter', () => {
|
||||
|
@ -40,11 +56,7 @@ describe('templates', () => {
|
|||
searchEnabled,
|
||||
passedElementType,
|
||||
);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -75,10 +87,7 @@ describe('templates', () => {
|
|||
passedElementType,
|
||||
);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -110,10 +119,7 @@ describe('templates', () => {
|
|||
passedElementType,
|
||||
);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -144,10 +150,7 @@ describe('templates', () => {
|
|||
passedElementType,
|
||||
);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -162,8 +165,7 @@ describe('templates', () => {
|
|||
);
|
||||
const actualOutput = templates.containerInner(classes);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput));
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -181,10 +183,7 @@ describe('templates', () => {
|
|||
);
|
||||
const actualOutput = templates.itemList(classes, true);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -195,10 +194,7 @@ describe('templates', () => {
|
|||
);
|
||||
const actualOutput = templates.itemList(classes, false);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -213,15 +209,10 @@ describe('templates', () => {
|
|||
<div class="${classes.placeholder}">${value}</div>`);
|
||||
const actualOutput = templates.placeholder(classes, value);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput));
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
// describe('item', () => {
|
||||
|
||||
// });
|
||||
|
||||
describe('choiceList', () => {
|
||||
const classes = {
|
||||
list: 'test',
|
||||
|
@ -232,17 +223,13 @@ describe('templates', () => {
|
|||
const expectedOutput = strToEl(`
|
||||
<div
|
||||
class="${classes.list}"
|
||||
dir="ltr"
|
||||
role="listbox"
|
||||
>
|
||||
</div>
|
||||
`);
|
||||
const actualOutput = templates.choiceList(classes, true);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -251,7 +238,6 @@ describe('templates', () => {
|
|||
const expectedOutput = strToEl(`
|
||||
<div
|
||||
class="${classes.list}"
|
||||
dir="ltr"
|
||||
role="listbox"
|
||||
aria-multiselectable="true"
|
||||
>
|
||||
|
@ -259,10 +245,7 @@ describe('templates', () => {
|
|||
`);
|
||||
const actualOutput = templates.choiceList(classes, false);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -299,10 +282,7 @@ describe('templates', () => {
|
|||
`);
|
||||
const actualOutput = templates.choiceGroup(classes, data);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -329,10 +309,7 @@ describe('templates', () => {
|
|||
`);
|
||||
const actualOutput = templates.choiceGroup(classes, data);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -365,9 +342,7 @@ describe('templates', () => {
|
|||
it('returns expected html', () => {
|
||||
const expectedOutput = strToEl(`
|
||||
<div
|
||||
class="${classes.item} ${classes.itemChoice} ${
|
||||
classes.itemSelectable
|
||||
}"
|
||||
class="${classes.item} ${classes.itemChoice} ${classes.itemSelectable}"
|
||||
data-select-text="${itemSelectText}"
|
||||
data-choice
|
||||
data-id="${data.id}"
|
||||
|
@ -381,10 +356,7 @@ describe('templates', () => {
|
|||
`);
|
||||
const actualOutput = templates.choice(classes, data, itemSelectText);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -399,9 +371,7 @@ describe('templates', () => {
|
|||
it('returns expected html', () => {
|
||||
const expectedOutput = strToEl(`
|
||||
<div
|
||||
class="${classes.item} ${classes.itemChoice} ${
|
||||
classes.itemDisabled
|
||||
}"
|
||||
class="${classes.item} ${classes.itemChoice} ${classes.itemDisabled}"
|
||||
data-select-text="${itemSelectText}"
|
||||
data-choice
|
||||
data-id="${data.id}"
|
||||
|
@ -416,10 +386,7 @@ describe('templates', () => {
|
|||
`);
|
||||
const actualOutput = templates.choice(classes, data, itemSelectText);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -434,9 +401,7 @@ describe('templates', () => {
|
|||
it('returns expected html', () => {
|
||||
const expectedOutput = strToEl(`
|
||||
<div
|
||||
class="${classes.item} ${classes.itemChoice} ${
|
||||
classes.itemSelectable
|
||||
} ${classes.placeholder}"
|
||||
class="${classes.item} ${classes.itemChoice} ${classes.itemSelectable} ${classes.placeholder}"
|
||||
data-select-text="${itemSelectText}"
|
||||
data-choice
|
||||
data-id="${data.id}"
|
||||
|
@ -450,10 +415,7 @@ describe('templates', () => {
|
|||
`);
|
||||
const actualOutput = templates.choice(classes, data, itemSelectText);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -468,9 +430,7 @@ describe('templates', () => {
|
|||
it('returns expected html', () => {
|
||||
const expectedOutput = strToEl(`
|
||||
<div
|
||||
class="${classes.item} ${classes.itemChoice} ${
|
||||
classes.itemSelectable
|
||||
}"
|
||||
class="${classes.item} ${classes.itemChoice} ${classes.itemSelectable}"
|
||||
data-select-text="${itemSelectText}"
|
||||
data-choice
|
||||
data-id="${data.id}"
|
||||
|
@ -484,10 +444,7 @@ describe('templates', () => {
|
|||
`);
|
||||
const actualOutput = templates.choice(classes, data, itemSelectText);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -499,49 +456,49 @@ describe('templates', () => {
|
|||
};
|
||||
|
||||
it('returns expected html', () => {
|
||||
/*
|
||||
Following attributes are not supported by JSDOM, so, can't compare
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
*/
|
||||
const expectedOutput = strToEl(`
|
||||
<input
|
||||
type="text"
|
||||
class="${classes.input} ${classes.inputCloned}"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
role="textbox"
|
||||
aria-autocomplete="list"
|
||||
aria-label="test placeholder"
|
||||
>
|
||||
`);
|
||||
const actualOutput = templates.input(classes);
|
||||
const actualOutput = templates.input(classes, 'test placeholder');
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLInputElement');
|
||||
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput));
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dropdown', () => {
|
||||
const classes = {
|
||||
list: 'test 1',
|
||||
listDropdown: 'test 2',
|
||||
list: 'test-1',
|
||||
listDropdown: 'test-2',
|
||||
};
|
||||
it('returns expected html', () => {
|
||||
const value = 'test';
|
||||
const expectedOutput = strToEl(
|
||||
`<div class="${classes.list} ${
|
||||
classes.listDropdown
|
||||
}" aria-expanded="false"></div>`,
|
||||
`<div class="${classes.list} ${classes.listDropdown}" aria-expanded="false"></div>`,
|
||||
);
|
||||
const actualOutput = templates.dropdown(classes, value);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput));
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('notice', () => {
|
||||
const classes = {
|
||||
item: 'test 1',
|
||||
itemChoice: 'test 2',
|
||||
noResults: 'test 3',
|
||||
noChoices: 'test 4',
|
||||
item: 'test-1',
|
||||
itemChoice: 'test-2',
|
||||
noResults: 'test-3',
|
||||
noChoices: 'test-4',
|
||||
};
|
||||
|
||||
const label = 'test';
|
||||
|
@ -554,42 +511,33 @@ describe('templates', () => {
|
|||
`);
|
||||
const actualOutput = templates.notice(classes, label);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLDivElement');
|
||||
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput));
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
|
||||
describe('passing a notice type', () => {
|
||||
describe('no results', () => {
|
||||
it('adds no results classname', () => {
|
||||
const expectedOutput = strToEl(`
|
||||
<div class="${classes.item} ${classes.itemChoice} ${
|
||||
classes.noResults
|
||||
}">
|
||||
<div class="${classes.item} ${classes.itemChoice} ${classes.noResults}">
|
||||
${label}
|
||||
</div>
|
||||
`);
|
||||
const actualOutput = templates.notice(classes, label, 'no-results');
|
||||
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('no choices', () => {
|
||||
it('adds no choices classname', () => {
|
||||
const expectedOutput = strToEl(`
|
||||
<div class="${classes.item} ${classes.itemChoice} ${
|
||||
classes.noChoices
|
||||
}">
|
||||
<div class="${classes.item} ${classes.itemChoice} ${classes.noChoices}">
|
||||
${label}
|
||||
</div>
|
||||
`);
|
||||
const actualOutput = templates.notice(classes, label, 'no-choices');
|
||||
|
||||
expect(stripElement(actualOutput)).to.equal(
|
||||
stripElement(expectedOutput),
|
||||
);
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -615,8 +563,7 @@ describe('templates', () => {
|
|||
);
|
||||
const actualOutput = templates.option(data);
|
||||
|
||||
expect(getType(actualOutput)).to.equal('HTMLOptionElement');
|
||||
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput));
|
||||
expectEqualElements(actualOutput, expectedOutput);
|
||||
});
|
||||
|
||||
describe('when selected', () => {
|
||||
|
|
|
@ -2,23 +2,23 @@
|
|||
= Generic styling =
|
||||
=============================================*/
|
||||
|
||||
$global-guttering : 24px;
|
||||
$global-font-size-h1 : 32px;
|
||||
$global-font-size-h2 : 24px;
|
||||
$global-font-size-h3 : 20px;
|
||||
$global-font-size-h4 : 18px;
|
||||
$global-font-size-h5 : 16px;
|
||||
$global-font-size-h6 : 14px;
|
||||
$global-guttering: 24px;
|
||||
$global-font-size-h1: 32px;
|
||||
$global-font-size-h2: 24px;
|
||||
$global-font-size-h3: 20px;
|
||||
$global-font-size-h4: 18px;
|
||||
$global-font-size-h5: 16px;
|
||||
$global-font-size-h6: 14px;
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
|
@ -30,10 +30,10 @@ body {
|
|||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
background-color: #333;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ p {
|
|||
|
||||
hr {
|
||||
display: block;
|
||||
margin: $global-guttering*1.25 0;
|
||||
margin: $global-guttering * 1.25 0;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
height: 1px;
|
||||
|
@ -73,7 +73,7 @@ h6 {
|
|||
a,
|
||||
a:visited,
|
||||
a:focus {
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -133,14 +133,14 @@ label + p {
|
|||
display: block;
|
||||
margin: auto;
|
||||
max-width: 40em;
|
||||
padding: $global-guttering*2;
|
||||
padding: $global-guttering * 2;
|
||||
@media (max-width: 620px) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #FFFFFF;
|
||||
background-color: #ffffff;
|
||||
padding: $global-guttering;
|
||||
color: #333;
|
||||
a,
|
||||
|
@ -184,12 +184,8 @@ label + p {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-test-hook] {
|
||||
margin-bottom: $global-guttering;
|
||||
}
|
||||
|
||||
/*===== End of Section comment block ======*/
|
||||
/*===== End of Section comment block ======*/
|
||||
|
|
|
@ -10,11 +10,11 @@ $choices-guttering: 24px !default;
|
|||
$choices-border-radius: 2.5px !default;
|
||||
$choices-border-radius-item: 20px !default;
|
||||
$choices-bg-color: #f9f9f9 !default;
|
||||
$choices-bg-color-disabled: #EAEAEA !default;
|
||||
$choices-bg-color-dropdown: #FFFFFF !default;
|
||||
$choices-bg-color-disabled: #eaeaea !default;
|
||||
$choices-bg-color-dropdown: #ffffff !default;
|
||||
$choices-text-color: #333333 !default;
|
||||
$choices-keyline-color: #DDDDDD !default;
|
||||
$choices-primary-color: #00BCD4 !default;
|
||||
$choices-keyline-color: #dddddd !default;
|
||||
$choices-primary-color: #00bcd4 !default;
|
||||
$choices-disabled-color: #eaeaea !default;
|
||||
$choices-highlight-color: $choices-primary-color !default;
|
||||
$choices-button-dimension: 8px !default;
|
||||
|
@ -43,9 +43,13 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$choices-selector}[data-type*="select-one"] {
|
||||
.#{$choices-selector}[data-type*='select-one'] {
|
||||
cursor: pointer;
|
||||
.#{$choices-selector}__inner {
|
||||
padding-bottom: 7.5px;
|
||||
|
@ -55,7 +59,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
width: 100%;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid $choices-keyline-color;
|
||||
background-color: #FFFFFF;
|
||||
background-color: #ffffff;
|
||||
margin: 0;
|
||||
}
|
||||
.#{$choices-selector}__button {
|
||||
|
@ -70,7 +74,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 10em;
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
|
@ -80,7 +84,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
}
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
content: '';
|
||||
height: 0;
|
||||
width: 0;
|
||||
border-style: solid;
|
||||
|
@ -96,7 +100,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
border-color: transparent transparent $choices-text-color transparent;
|
||||
margin-top: -7.5px;
|
||||
}
|
||||
&[dir="rtl"] {
|
||||
&[dir='rtl'] {
|
||||
&:after {
|
||||
left: 11.5px;
|
||||
right: auto;
|
||||
|
@ -110,8 +114,8 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
}
|
||||
}
|
||||
|
||||
.#{$choices-selector}[data-type*="select-multiple"],
|
||||
.#{$choices-selector}[data-type*="text"] {
|
||||
.#{$choices-selector}[data-type*='select-multiple'],
|
||||
.#{$choices-selector}[data-type*='text'] {
|
||||
.#{$choices-selector}__inner {
|
||||
cursor: text;
|
||||
}
|
||||
|
@ -122,13 +126,13 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
margin-right: -$choices-button-offset/2;
|
||||
margin-bottom: 0;
|
||||
margin-left: $choices-button-offset;
|
||||
padding-left: $choices-button-offset*2;
|
||||
padding-left: $choices-button-offset * 2;
|
||||
border-left: 1px solid darken($choices-primary-color, 10%);
|
||||
background-image: $choices-icon-cross;
|
||||
background-size: $choices-button-dimension;
|
||||
width: $choices-button-dimension;
|
||||
line-height: 1;
|
||||
opacity: .75;
|
||||
opacity: 0.75;
|
||||
border-radius: 0;
|
||||
&:hover,
|
||||
&:focus {
|
||||
|
@ -170,7 +174,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
display: inline-block;
|
||||
padding: 4px 16px 4px 4px;
|
||||
width: 100%;
|
||||
[dir="rtl"] & {
|
||||
[dir='rtl'] & {
|
||||
padding-right: 4px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
@ -192,12 +196,12 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
margin-bottom: 3.75px;
|
||||
background-color: $choices-primary-color;
|
||||
border: 1px solid darken($choices-primary-color, 5%);
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
word-break: break-all;
|
||||
&[data-deletable] {
|
||||
padding-right: 5px;
|
||||
}
|
||||
[dir="rtl"] & {
|
||||
[dir='rtl'] & {
|
||||
margin-right: 0;
|
||||
margin-left: 3.75px;
|
||||
}
|
||||
|
@ -213,7 +217,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
}
|
||||
|
||||
.#{$choices-selector}__list--dropdown {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
@ -225,8 +229,9 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
border-bottom-right-radius: $choices-border-radius;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
will-change: visibility;
|
||||
&.is-active {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
}
|
||||
.is-open & {
|
||||
border-color: darken($choices-keyline-color, 15%);
|
||||
|
@ -236,7 +241,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
bottom: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: -1px;
|
||||
border-radius: .25rem .25rem 0 0;
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
}
|
||||
.#{$choices-selector}__list {
|
||||
position: relative;
|
||||
|
@ -249,7 +254,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
position: relative;
|
||||
padding: 10px;
|
||||
font-size: $choices-font-size-md;
|
||||
[dir="rtl"] & {
|
||||
[dir='rtl'] & {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
@ -265,7 +270,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
[dir="rtl"] & {
|
||||
[dir='rtl'] & {
|
||||
text-align: right;
|
||||
padding-left: 100px;
|
||||
padding-right: 10px;
|
||||
|
@ -276,9 +281,9 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
}
|
||||
}
|
||||
&.is-highlighted {
|
||||
background-color: mix(#000000, #FFFFFF, 5%);
|
||||
background-color: mix(#000000, #ffffff, 5%);
|
||||
&:after {
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +300,7 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
.#{$choices-selector}__item--disabled {
|
||||
cursor: not-allowed;
|
||||
user-select: none;
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.#{$choices-selector}__heading {
|
||||
|
@ -333,20 +338,14 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
|
|||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
[dir="rtl"] & {
|
||||
[dir='rtl'] & {
|
||||
padding-right: 2px;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$choices-selector}__placeholder {
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.#{$choices-selector}__input.is-hidden,
|
||||
.#{$choices-selector}[data-type*="select-one"] .#{$choices-selector}__input.is-hidden,
|
||||
.#{$choices-selector}[data-type*="select-multiple"] .#{$choices-selector}__input.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*===== End of Choices ======*/
|
||||
/*===== End of Choices ======*/
|
||||
|
|
520
types/index.d.ts
vendored
520
types/index.d.ts
vendored
|
@ -1,19 +1,37 @@
|
|||
// Type definitions for Choices.js 3.0.2
|
||||
// Type definitions for Choices.js 7.1.x
|
||||
// Project: https://github.com/jshjohnson/Choices
|
||||
// Definitions by: Arthur vasconcelos <https://github.com/arthurvasconcelos>, Josh Johnson <https://github.com/jshjohnson>, Zack Schuster <https://github.com/zackschuster>
|
||||
// Definitions by:
|
||||
// Arthur vasconcelos <https://github.com/arthurvasconcelos>,
|
||||
// Josh Johnson <https://github.com/jshjohnson>,
|
||||
// Zack Schuster <https://github.com/zackschuster>
|
||||
// Konstantin Vyatkin <https://github.com/tinovyatkin>
|
||||
// Definitions: https://github.com/jshjohnson/Choices
|
||||
// TypeScript Version: 2.6.2
|
||||
|
||||
import { FuseOptions } from 'fuse.js';
|
||||
|
||||
// Choices Namespace
|
||||
declare namespace Choices {
|
||||
namespace Types {
|
||||
type renderSelected = 'auto' | 'always';
|
||||
type dropdownPosition = 'auto' | 'top';
|
||||
type strToEl = (str: string) => HTMLElement | HTMLInputElement | HTMLOptionElement;
|
||||
type strToEl = (
|
||||
str: string,
|
||||
) => HTMLElement | HTMLInputElement | HTMLOptionElement;
|
||||
type stringFunction = () => string;
|
||||
type noticeStringFunction = (value: string) => string;
|
||||
type noticeLimitFunction = (maxItemCount: number) => string;
|
||||
type callbackOnCreateTemplates = (template: strToEl) => Choices.Templates;
|
||||
type filterFunction = (value: string) => boolean;
|
||||
}
|
||||
|
||||
interface Choice {
|
||||
customProperties?: Record<string, any>;
|
||||
disabled?: boolean;
|
||||
elementId?: string;
|
||||
groupId?: string;
|
||||
id?: string;
|
||||
keyCode?: number;
|
||||
label: string;
|
||||
placeholder?: boolean;
|
||||
selected?: boolean;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,16 +45,13 @@ declare namespace Choices {
|
|||
*
|
||||
* Arguments: id, value, label, groupValue, keyCode
|
||||
*/
|
||||
"addItem": CustomEvent;
|
||||
|
||||
/**
|
||||
* A filter that will need to pass for a user to successfully add an item.
|
||||
*
|
||||
* **Input types affected:** text
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
addItemFilterFn?: () => any;
|
||||
addItem: CustomEvent<{
|
||||
id: string;
|
||||
value: string;
|
||||
label: string;
|
||||
groupValue: string;
|
||||
keyCode: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Triggered each time an item is removed (programmatically or by the user).
|
||||
|
@ -45,7 +60,12 @@ declare namespace Choices {
|
|||
*
|
||||
* Arguments: id, value, label, groupValue
|
||||
*/
|
||||
"removeItem": CustomEvent;
|
||||
removeItem: CustomEvent<{
|
||||
id: string;
|
||||
value: string;
|
||||
label: string;
|
||||
groupValue: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Triggered each time an item is highlighted.
|
||||
|
@ -54,7 +74,12 @@ declare namespace Choices {
|
|||
*
|
||||
* Arguments: id, value, label, groupValue
|
||||
*/
|
||||
"highlightItem": CustomEvent;
|
||||
highlightItem: CustomEvent<{
|
||||
id: string;
|
||||
value: string;
|
||||
label: string;
|
||||
groupValue: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Triggered each time an item is unhighlighted.
|
||||
|
@ -63,16 +88,21 @@ declare namespace Choices {
|
|||
*
|
||||
* Arguments: id, value, label, groupValue
|
||||
*/
|
||||
"unhighlightItem": CustomEvent;
|
||||
unhighlightItem: CustomEvent<{
|
||||
id: string;
|
||||
value: string;
|
||||
label: string;
|
||||
groupValue: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 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: value, keyCode
|
||||
* Arguments: choice: Choice
|
||||
*/
|
||||
"choice": CustomEvent;
|
||||
choice: CustomEvent<{ choice: Choices.Choice }>;
|
||||
|
||||
/**
|
||||
* Triggered each time an item is added/removed **by a user**.
|
||||
|
@ -81,7 +111,7 @@ declare namespace Choices {
|
|||
*
|
||||
* Arguments: value
|
||||
*/
|
||||
"change": CustomEvent;
|
||||
change: CustomEvent<{ value: string }>;
|
||||
|
||||
/**
|
||||
* Triggered when a user types into an input to search choices.
|
||||
|
@ -90,7 +120,7 @@ declare namespace Choices {
|
|||
*
|
||||
* Arguments: value, resultCount
|
||||
*/
|
||||
"search": CustomEvent;
|
||||
search: CustomEvent<{ value: string; resultCount: number }>;
|
||||
|
||||
/**
|
||||
* Triggered when the dropdown is shown.
|
||||
|
@ -99,7 +129,7 @@ declare namespace Choices {
|
|||
*
|
||||
* Arguments: -
|
||||
*/
|
||||
"showDropdown": CustomEvent;
|
||||
showDropdown: CustomEvent<undefined>;
|
||||
|
||||
/**
|
||||
* Triggered when the dropdown is hidden.
|
||||
|
@ -108,84 +138,156 @@ declare namespace Choices {
|
|||
*
|
||||
* Arguments: -
|
||||
*/
|
||||
"hideDropdown": CustomEvent;
|
||||
hideDropdown: CustomEvent<undefined>;
|
||||
|
||||
/**
|
||||
* 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: Choices.passedElement }>;
|
||||
}
|
||||
|
||||
interface Group {
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface Item extends Choice {
|
||||
choiceId?: string;
|
||||
keyCode?: number;
|
||||
}
|
||||
|
||||
interface Templates {
|
||||
containerOuter?: (direction: string) => HTMLElement;
|
||||
containerInner?: () => HTMLElement;
|
||||
itemList?: () => HTMLElement;
|
||||
placeholder?: (value: string) => HTMLElement;
|
||||
item?: (data: any) => HTMLElement;
|
||||
choiceList?: () => HTMLElement;
|
||||
choiceGroup?: (data: any) => HTMLElement;
|
||||
choice?: (data: any) => HTMLElement;
|
||||
input?: () => HTMLInputElement;
|
||||
dropdown?: () => HTMLElement;
|
||||
notice?: (label: string) => HTMLElement;
|
||||
option?: (data: any) => HTMLOptionElement;
|
||||
containerOuter: (
|
||||
this: Choices,
|
||||
classNames: ClassNames,
|
||||
direction: HTMLElement['dir'],
|
||||
isSelectElement: boolean,
|
||||
isSelectOneElement: boolean,
|
||||
searchEnabled: boolean,
|
||||
passedElementType: passedElement['type'],
|
||||
) => HTMLElement;
|
||||
containerInner: (this: Choices, classNames: ClassNames) => HTMLElement;
|
||||
itemList: (
|
||||
this: Choices,
|
||||
classNames: ClassNames,
|
||||
isSelectOneElement: boolean,
|
||||
) => HTMLElement;
|
||||
placeholder: (
|
||||
this: Choices,
|
||||
classNames: ClassNames,
|
||||
value: string,
|
||||
) => HTMLElement;
|
||||
item: (
|
||||
this: Choices,
|
||||
classNames: ClassNames,
|
||||
data: Choice,
|
||||
removeItemButton: boolean,
|
||||
) => HTMLElement;
|
||||
choiceList: (
|
||||
this: Choices,
|
||||
classNames: ClassNames,
|
||||
isSelectOneElement: boolean,
|
||||
) => HTMLElement;
|
||||
choiceGroup: (
|
||||
this: Choices,
|
||||
classNames: ClassNames,
|
||||
data: Choice,
|
||||
) => HTMLElement;
|
||||
choice: (
|
||||
this: Choices,
|
||||
classNames: ClassNames,
|
||||
data: Choice,
|
||||
selectText: string,
|
||||
) => HTMLElement;
|
||||
input: (
|
||||
this: Choices,
|
||||
classNames: ClassNames,
|
||||
placeholderValue: string,
|
||||
) => HTMLInputElement;
|
||||
dropdown: (this: Choices, classNames: ClassNames) => HTMLElement;
|
||||
notice: (
|
||||
this: Choices,
|
||||
classNames: ClassNames,
|
||||
label: string,
|
||||
type: '' | 'no-results' | 'no-choices',
|
||||
) => HTMLElement;
|
||||
option: (data: Choice) => HTMLOptionElement;
|
||||
}
|
||||
|
||||
/** Classes added to HTML generated by Choices. By default classnames follow the BEM notation. */
|
||||
interface ClassNames {
|
||||
/** @default 'choices' */
|
||||
containerOuter?: string;
|
||||
containerOuter: string;
|
||||
/** @default 'choices__inner' */
|
||||
containerInner?: string;
|
||||
containerInner: string;
|
||||
/** @default 'choices__input' */
|
||||
input?: string;
|
||||
input: string;
|
||||
/** @default 'choices__input--cloned' */
|
||||
inputCloned?: string;
|
||||
inputCloned: string;
|
||||
/** @default 'choices__list' */
|
||||
list?: string;
|
||||
list: string;
|
||||
/** @default 'choices__list--multiple' */
|
||||
listItems?: string;
|
||||
listItems: string;
|
||||
/** @default 'choices__list--single' */
|
||||
listSingle?: string;
|
||||
listSingle: string;
|
||||
/** @default 'choices__list--dropdown' */
|
||||
listDropdown?: string;
|
||||
listDropdown: string;
|
||||
/** @default 'choices__item' */
|
||||
item?: string;
|
||||
item: string;
|
||||
/** @default 'choices__item--selectable' */
|
||||
itemSelectable?: string;
|
||||
itemSelectable: string;
|
||||
/** @default 'choices__item--disabled' */
|
||||
itemDisabled?: string;
|
||||
itemDisabled: string;
|
||||
/** @default 'choices__item--choice' */
|
||||
itemChoice?: string;
|
||||
itemChoice: string;
|
||||
/** @default 'choices__placeholder' */
|
||||
placeholder?: string;
|
||||
placeholder: string;
|
||||
/** @default 'choices__group' */
|
||||
group?: string;
|
||||
group: string;
|
||||
/** @default 'choices__heading' */
|
||||
groupHeading?: string;
|
||||
groupHeading: string;
|
||||
/** @default 'choices__button' */
|
||||
button?: string;
|
||||
button: string;
|
||||
/** @default 'is-active' */
|
||||
activeState?: string;
|
||||
activeState: string;
|
||||
/** @default 'is-focused' */
|
||||
focusState?: string;
|
||||
focusState: string;
|
||||
/** @default 'is-open' */
|
||||
openState?: string;
|
||||
openState: string;
|
||||
/** @default 'is-disabled' */
|
||||
disabledState?: string;
|
||||
disabledState: string;
|
||||
/** @default 'is-highlighted' */
|
||||
highlightedState?: string;
|
||||
/** @default 'is-hidden' */
|
||||
hiddenState?: string;
|
||||
highlightedState: string;
|
||||
/** @default 'is-flipped' */
|
||||
flippedState?: string;
|
||||
flippedState: string;
|
||||
/** @default 'is-loading' */
|
||||
loadingState?: string;
|
||||
loadingState: string;
|
||||
/** @default 'has-no-results' */
|
||||
noResults?: string;
|
||||
noResults: string;
|
||||
/** @default 'has-no-choices' */
|
||||
noChoices?: string;
|
||||
noChoices: string;
|
||||
}
|
||||
|
||||
interface passedElement {
|
||||
classNames: Choices.ClassNames,
|
||||
element: HTMLElement,
|
||||
isDisabled: boolean,
|
||||
classNames: Choices.ClassNames;
|
||||
element: (HTMLInputElement | HTMLSelectElement) & {
|
||||
// Extends HTMLElement addEventListener with Choices events
|
||||
addEventListener<K extends keyof Choices.EventMap>(
|
||||
type: K,
|
||||
listener: (
|
||||
this: HTMLInputElement | HTMLSelectElement,
|
||||
ev: Choices.EventMap[K],
|
||||
) => void,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
};
|
||||
type: 'text' | 'select-one' | 'select-multiple';
|
||||
isDisabled: boolean;
|
||||
parentInstance: Choices;
|
||||
}
|
||||
|
||||
|
@ -194,7 +296,7 @@ declare namespace Choices {
|
|||
*
|
||||
* **Terminology**
|
||||
*
|
||||
* - **Choice:** A choice is a value a user can select. A choice would be equivelant to the `<option></option>` element within a select input.
|
||||
* - **Choice:** A choice is a value a user can select. A choice would be equivalent to the `<option></option>` element within a select input.
|
||||
* - **Group:** A group is a collection of choices. A group should be seen as equivalent to a `<optgroup></optgroup>` 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: `<option value="Hello" selected></option>` whereas in the context of a text input an item is equivelant to `<input type="text" value="Hello">`
|
||||
*/
|
||||
|
@ -206,7 +308,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default false
|
||||
*/
|
||||
silent?: boolean;
|
||||
silent: boolean;
|
||||
|
||||
/**
|
||||
* Add pre-selected items (see terminology) to text input.
|
||||
|
@ -237,7 +339,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default []
|
||||
*/
|
||||
items?: any[];
|
||||
items: string[] | Choice[];
|
||||
|
||||
/**
|
||||
* Add choices (see terminology) to select input.
|
||||
|
@ -266,7 +368,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default []
|
||||
*/
|
||||
choices?: any[];
|
||||
choices: Choice[];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -275,7 +377,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default -1
|
||||
*/
|
||||
renderChoiceLimit?: number;
|
||||
renderChoiceLimit: number;
|
||||
|
||||
/**
|
||||
* The amount of items a user can input/select `("-1" indicates no limit)`.
|
||||
|
@ -284,7 +386,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default -1
|
||||
*/
|
||||
maxItemCount?: number;
|
||||
maxItemCount: number;
|
||||
|
||||
/**
|
||||
* Whether a user can add items.
|
||||
|
@ -293,7 +395,28 @@ declare namespace Choices {
|
|||
*
|
||||
* @default true
|
||||
*/
|
||||
addItems?: boolean;
|
||||
addItems: boolean;
|
||||
|
||||
/**
|
||||
* A filter that will need to pass for a user to successfully add an item.
|
||||
*
|
||||
* **Input types affected:** text
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
addItemFilter: string | RegExp | Choices.Types.filterFunction;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* **Input types affected:** text
|
||||
*
|
||||
* @default
|
||||
* ```
|
||||
* (value) => `Press Enter to add <b>"${value}"</b>`;
|
||||
* ```
|
||||
*/
|
||||
addItemText: string | Choices.Types.noticeStringFunction;
|
||||
|
||||
/**
|
||||
* Whether a user can remove items.
|
||||
|
@ -302,7 +425,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default true
|
||||
*/
|
||||
removeItems?: boolean;
|
||||
removeItems: boolean;
|
||||
|
||||
/**
|
||||
* Whether each item should have a remove button.
|
||||
|
@ -311,7 +434,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default false
|
||||
*/
|
||||
removeItemButton?: boolean;
|
||||
removeItemButton: boolean;
|
||||
|
||||
/**
|
||||
* Whether a user can edit items. An item's value can be edited by pressing the backspace.
|
||||
|
@ -320,7 +443,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default false
|
||||
*/
|
||||
editItems?: boolean;
|
||||
editItems: boolean;
|
||||
|
||||
/**
|
||||
* Whether each inputted/chosen item should be unique.
|
||||
|
@ -329,16 +452,16 @@ declare namespace Choices {
|
|||
*
|
||||
* @default true
|
||||
*/
|
||||
duplicateItemsAllowed?: boolean;
|
||||
duplicateItemsAllowed: boolean;
|
||||
|
||||
/**
|
||||
* What divides each value. The default delimiter seperates each value with a comma: `"Value 1, Value 2, Value 3"`.
|
||||
* 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;
|
||||
delimiter: string;
|
||||
|
||||
/**
|
||||
* Whether a user can paste into the input.
|
||||
|
@ -347,7 +470,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default true
|
||||
*/
|
||||
paste?: boolean;
|
||||
paste: boolean;
|
||||
|
||||
/**
|
||||
* Whether a search area should be shown.
|
||||
|
@ -358,7 +481,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default true
|
||||
*/
|
||||
searchEnabled?: boolean;
|
||||
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.
|
||||
|
@ -367,7 +490,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default true
|
||||
*/
|
||||
searchChoices?: boolean;
|
||||
searchChoices: boolean;
|
||||
|
||||
/**
|
||||
* The minimum length a search value should be before choices are searched.
|
||||
|
@ -376,7 +499,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default 1
|
||||
*/
|
||||
searchFloor?: number;
|
||||
searchFloor: number;
|
||||
|
||||
/**
|
||||
* The maximum amount of search results to show.
|
||||
|
@ -385,7 +508,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default 4
|
||||
*/
|
||||
searchResultLimit?: number;
|
||||
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']`.
|
||||
|
@ -394,7 +517,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default ['label', 'value']
|
||||
*/
|
||||
searchFields?: string[];
|
||||
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.
|
||||
|
@ -403,7 +526,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default 'auto'
|
||||
*/
|
||||
position?: Choices.Types.dropdownPosition;
|
||||
position: 'auto' | 'top';
|
||||
|
||||
/**
|
||||
* Whether the scroll position should reset after adding an item.
|
||||
|
@ -412,7 +535,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default true
|
||||
*/
|
||||
resetScrollPosition?: boolean;
|
||||
resetScrollPosition: boolean;
|
||||
|
||||
/**
|
||||
* Whether choices and groups should be sorted. If false, choices/groups will appear in the order they were given.
|
||||
|
@ -421,7 +544,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default true
|
||||
*/
|
||||
shouldSort?: boolean;
|
||||
shouldSort: boolean;
|
||||
|
||||
/**
|
||||
* Whether items should be sorted. If false, items will appear in the order they were selected.
|
||||
|
@ -430,7 +553,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default false
|
||||
*/
|
||||
shouldSortItems?: boolean;
|
||||
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.
|
||||
|
@ -449,7 +572,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default sortByAlpha
|
||||
*/
|
||||
sortFilter?: (current: any, next: any) => number;
|
||||
sortFilter: (current: Choice, next: Choice) => 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.
|
||||
|
@ -468,7 +591,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default true
|
||||
*/
|
||||
placeholder?: boolean;
|
||||
placeholder: boolean;
|
||||
|
||||
/**
|
||||
* The value of the inputs placeholder.
|
||||
|
@ -477,7 +600,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default null
|
||||
*/
|
||||
placeholderValue?: string;
|
||||
placeholderValue: string;
|
||||
|
||||
/**
|
||||
* The value of the search inputs placeholder.
|
||||
|
@ -486,7 +609,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default null
|
||||
*/
|
||||
searchPlaceholderValue?: string;
|
||||
searchPlaceholderValue: string;
|
||||
|
||||
/**
|
||||
* Prepend a value to each item added/selected.
|
||||
|
@ -495,7 +618,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default null
|
||||
*/
|
||||
prependValue?: string;
|
||||
prependValue: string;
|
||||
|
||||
/**
|
||||
* Append a value to each item added/selected.
|
||||
|
@ -504,7 +627,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default null
|
||||
*/
|
||||
appendValue?: string;
|
||||
appendValue: string;
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
|
@ -513,7 +636,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default 'auto';
|
||||
*/
|
||||
renderSelectedChoices?: Choices.Types.renderSelected;
|
||||
renderSelectedChoices: 'auto' | 'always';
|
||||
|
||||
/**
|
||||
* The text that is shown whilst choices are being populated via AJAX.
|
||||
|
@ -522,7 +645,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default 'Loading...'
|
||||
*/
|
||||
loadingText?: string;
|
||||
loadingText: string;
|
||||
|
||||
/**
|
||||
* The text that is shown when a user's search has returned no results. Optionally pass a function returning a string.
|
||||
|
@ -531,7 +654,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default 'No results found'
|
||||
*/
|
||||
noResultsText?: string | Choices.Types.stringFunction;
|
||||
noResultsText: string | Choices.Types.stringFunction;
|
||||
|
||||
/**
|
||||
* The text that is shown when a user has selected all possible choices. Optionally pass a function returning a string.
|
||||
|
@ -540,7 +663,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default 'No choices to choose from'
|
||||
*/
|
||||
noChoicesText?: string | Choices.Types.stringFunction;
|
||||
noChoicesText: string | Choices.Types.stringFunction;
|
||||
|
||||
/**
|
||||
* The text that is shown when a user hovers over a selectable choice.
|
||||
|
@ -549,19 +672,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default 'Press to select'
|
||||
*/
|
||||
itemSelectText?: string;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* **Input types affected:** text
|
||||
*
|
||||
* @default
|
||||
* ```
|
||||
* (value) => `Press Enter to add <b>"${value}"</b>`;
|
||||
* ```
|
||||
*/
|
||||
addItemText?: string | Choices.Types.noticeStringFunction;
|
||||
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.
|
||||
|
@ -573,32 +684,26 @@ declare namespace Choices {
|
|||
* (maxItemCount) => `Only ${maxItemCount} values can be added.`;
|
||||
* ```
|
||||
*/
|
||||
maxItemText?: string | Choices.Types.noticeLimitFunction;
|
||||
maxItemText: string | Choices.Types.noticeLimitFunction;
|
||||
|
||||
/**
|
||||
* If no duplicates are allowed, and the value already exists in the array.
|
||||
*
|
||||
* @default 'Only unique values can be added.'
|
||||
*/
|
||||
uniqueItemText?: string | Choices.Types.noticeStringFunction;
|
||||
uniqueItemText: string | Choices.Types.noticeStringFunction;
|
||||
|
||||
/**
|
||||
* Classes added to HTML generated by Choices. By default classnames follow the BEM notation.
|
||||
*
|
||||
* **Input types affected:** text, select-one, select-multiple
|
||||
*/
|
||||
classNames?: Choices.ClassNames;
|
||||
classNames: Partial<Choices.ClassNames>;
|
||||
|
||||
/**
|
||||
* Choices uses the great Fuse library for searching. You can find more options here: https://github.com/krisk/Fuse#options
|
||||
*/
|
||||
fuseOptions?: {
|
||||
[index: string]: any;
|
||||
/**
|
||||
* @default 'score'
|
||||
*/
|
||||
include?: string;
|
||||
};
|
||||
fuseOptions: FuseOptions<Choice>;
|
||||
|
||||
/**
|
||||
* Function to run once Choices initialises.
|
||||
|
@ -609,7 +714,7 @@ declare namespace Choices {
|
|||
*
|
||||
* @default null
|
||||
*/
|
||||
callbackOnInit?: () => any;
|
||||
callbackOnInit: (this: Choices) => void;
|
||||
|
||||
/**
|
||||
* 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].
|
||||
|
@ -645,52 +750,32 @@ declare namespace Choices {
|
|||
*
|
||||
* @default null
|
||||
*/
|
||||
callbackOnCreateTemplates?: Choices.Types.callbackOnCreateTemplates;
|
||||
callbackOnCreateTemplates: (
|
||||
template: Choices.Types.strToEl,
|
||||
) => Partial<Choices.Templates>;
|
||||
}
|
||||
}
|
||||
|
||||
// Overload HTMLElement addEventListener with Choices events
|
||||
interface HTMLElement {
|
||||
addEventListener<K extends keyof Choices.EventMap>(type: K, listener: (this: HTMLElement, ev: Choices.EventMap[K]) => any, useCapture?: boolean): void;
|
||||
}
|
||||
|
||||
// Exporting default class
|
||||
export default class Choices {
|
||||
idNames: any;
|
||||
config: Choices.Options;
|
||||
static readonly defaults: {
|
||||
readonly options: Partial<Choices.Options>;
|
||||
readonly templates: Choices.Templates;
|
||||
};
|
||||
readonly config: Choices.Options;
|
||||
|
||||
// State Tracking
|
||||
store: any;
|
||||
initialised: boolean;
|
||||
currentState: any;
|
||||
prevState: any;
|
||||
currentValue: string;
|
||||
|
||||
// Element
|
||||
passedElement: Choices.passedElement;
|
||||
readonly passedElement: Choices.passedElement;
|
||||
|
||||
// Checks
|
||||
isTextElement: boolean;
|
||||
isSelectOneElement: boolean;
|
||||
isSelectMultipleElement: boolean;
|
||||
isSelectElement: boolean;
|
||||
isValidElementType: boolean;
|
||||
isIe11: boolean;
|
||||
isScrollingOnIe: boolean;
|
||||
|
||||
highlightPosition: number;
|
||||
canSearch: boolean;
|
||||
placeholder: boolean;
|
||||
|
||||
presetChoices: any[];
|
||||
presetItems: any[];
|
||||
|
||||
readonly baseId: string;
|
||||
|
||||
wasTap: boolean;
|
||||
|
||||
constructor(element: string | HTMLElement | HTMLCollectionOf<Element> | NodeList, userConfig?: Choices.Options);
|
||||
new(element?: string | HTMLElement | HTMLCollectionOf<Element> | NodeList, userConfig?: Choices.Options): this;
|
||||
constructor(
|
||||
selectorOrElement: string | HTMLInputElement | HTMLSelectElement,
|
||||
userConfig?: Partial<Choices.Options>,
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a new instance of Choices, adds event listeners, creates templates and renders a Choices element to the DOM.
|
||||
|
@ -749,7 +834,6 @@ export default class Choices {
|
|||
*/
|
||||
removeHighlightedItems(runEvent?: boolean): this;
|
||||
|
||||
|
||||
/**
|
||||
* Show option list dropdown (only affects select inputs).
|
||||
*
|
||||
|
@ -778,16 +862,47 @@ export default class Choices {
|
|||
*/
|
||||
getValue(valueOnly?: boolean): string | string[];
|
||||
|
||||
/** Direct populate choices
|
||||
*
|
||||
* @param {string[] | Choices.Item[]} items
|
||||
*/
|
||||
setValue(items: string[] | Choices.Item[]): this;
|
||||
|
||||
/**
|
||||
* Set choices of select input via an array of objects, a value name and a label name.
|
||||
* Set value of input based on existing Choice. `value` can be either a single string or an array of strings
|
||||
*
|
||||
* **Input types affected:** select-one, select-multiple
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* 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.
|
||||
* ```
|
||||
*/
|
||||
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 Example 1:
|
||||
* ```
|
||||
* @param {string} [value = 'value'] - name of `value` field
|
||||
* @param {string} [label = 'label'] - name of 'label' field
|
||||
* @param {boolean} [replaceChoices = false] - whether to replace of add choices
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const example = new Choices(element);
|
||||
*
|
||||
* example.setChoices([
|
||||
|
@ -797,8 +912,22 @@ export default class Choices {
|
|||
* ], 'value', 'label', false);
|
||||
* ```
|
||||
*
|
||||
* @example Example 2:
|
||||
* @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([{
|
||||
|
@ -826,30 +955,21 @@ export default class Choices {
|
|||
* }], 'value', 'label', false);
|
||||
* ```
|
||||
*/
|
||||
setValue(args: string[]): this;
|
||||
setChoices<
|
||||
T extends object[] | ((instance: Choices) => object[] | Promise<object[]>)
|
||||
>(
|
||||
choices: T,
|
||||
value?: string,
|
||||
label?: string,
|
||||
replaceChoices?: boolean,
|
||||
): T extends object[] ? this : Promise<this>;
|
||||
|
||||
/**
|
||||
* Set value of input based on existing Choice. `value` can be either a single string or an array of strings
|
||||
* Clear all choices from select.
|
||||
*
|
||||
* **Input types affected:** select-one, select-multiple
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* 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.
|
||||
* ```
|
||||
*/
|
||||
setChoiceByValue(value: string | string[]): this;
|
||||
|
||||
/** Direct populate choices */
|
||||
setChoices(choices: any[], value: string, label: string, replaceChoices?: boolean): this;
|
||||
clearChoices(): this;
|
||||
|
||||
/**
|
||||
* Removes all items, choices and groups. Use with caution.
|
||||
|
@ -878,40 +998,4 @@ export default class Choices {
|
|||
* **Input types affected:** text, select-one, select-multiple
|
||||
*/
|
||||
disable(): this;
|
||||
|
||||
/**
|
||||
* Populate choices/groups via a callback.
|
||||
*
|
||||
* **Input types affected:** select-one, select-multiple
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* var example = new Choices(element);
|
||||
*
|
||||
* example.ajax(function(callback) {
|
||||
* fetch(url)
|
||||
* .then(function(response) {
|
||||
* response.json().then(function(data) {
|
||||
* callback(data, 'value', 'label');
|
||||
* });
|
||||
* })
|
||||
* .catch(function(error) {
|
||||
* console.log(error);
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
ajax(fn: (values: any) => any): this;
|
||||
|
||||
/** Render group choices into a DOM fragment and append to choice list */
|
||||
private createGroupsFragment(groups: any[], choices: any[], fragment: DocumentFragment): DocumentFragment;
|
||||
|
||||
/** Render choices into a DOM fragment and append to choice list */
|
||||
private createChoicesFragment(choices: any[], fragment: DocumentFragment, withinGroup?: boolean): DocumentFragment;
|
||||
|
||||
/** Render items into a DOM fragment and append to items list */
|
||||
private _createItemsFragment(items: any[], fragment?: DocumentFragment): void;
|
||||
|
||||
/** Render DOM with values */
|
||||
private render(): void;
|
||||
}
|
||||
|
|
40
webpack.config.base.js
Normal file
40
webpack.config.base.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
const path = require('path');
|
||||
|
||||
const include = path.resolve(__dirname, './src/scripts');
|
||||
const exclude = /node_modules/;
|
||||
|
||||
/**
|
||||
* @type {import('webpack').Configuration}
|
||||
*/
|
||||
module.exports = {
|
||||
entry: ['./src/scripts/choices'],
|
||||
output: {
|
||||
library: 'Choices',
|
||||
libraryTarget: 'window',
|
||||
libraryExport: 'default',
|
||||
globalObject: 'window',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
enforce: 'pre',
|
||||
loader: 'eslint-loader',
|
||||
test: /\.js?$/,
|
||||
include,
|
||||
exclude,
|
||||
options: {
|
||||
quiet: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
test: /\.js?$/,
|
||||
include,
|
||||
exclude,
|
||||
options: {
|
||||
babelrc: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -1,42 +1,24 @@
|
|||
const path = require('path');
|
||||
const { HotModuleReplacementPlugin } = require('webpack');
|
||||
const deepMerge = require('deepmerge');
|
||||
const baseConfig = require('./webpack.config.base');
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: [
|
||||
'webpack/hot/dev-server',
|
||||
'webpack-hot-middleware/client',
|
||||
'./src/scripts/choices',
|
||||
],
|
||||
output: {
|
||||
path: path.resolve('public'),
|
||||
filename: 'choices.min.js',
|
||||
publicPath: 'http://localhost:3001/assets/scripts/',
|
||||
library: 'Choices',
|
||||
libraryTarget: 'umd',
|
||||
module.exports = deepMerge(
|
||||
baseConfig,
|
||||
/** @type {import('webpack').Configuration} */ ({
|
||||
mode: 'development',
|
||||
output: {
|
||||
path: path.resolve(__dirname, './public'),
|
||||
filename: 'choices.min.js',
|
||||
publicPath: 'http://localhost:3001/assets/scripts/',
|
||||
},
|
||||
devtool: 'source-map',
|
||||
entry: ['webpack/hot/dev-server', 'webpack-hot-middleware/client'],
|
||||
plugins: [new HotModuleReplacementPlugin()],
|
||||
}),
|
||||
{
|
||||
arrayMerge(target, source) {
|
||||
return [...source, ...target];
|
||||
},
|
||||
},
|
||||
plugins: [new HotModuleReplacementPlugin()],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
enforce: 'pre',
|
||||
test: /\.js?$/,
|
||||
include: path.join(__dirname, 'src/scripts'),
|
||||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader',
|
||||
query: {
|
||||
configFile: '.eslintrc',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.js?$/,
|
||||
include: path.join(__dirname, 'src/scripts'),
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
babelrc: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
);
|
||||
|
|
|
@ -1,56 +1,47 @@
|
|||
const path = require('path');
|
||||
const WrapperPlugin = require('wrapper-webpack-plugin');
|
||||
const UnminifiedWebpackPlugin = require('unminified-webpack-plugin');
|
||||
const deepMerge = require('deepmerge');
|
||||
const { BannerPlugin } = require('webpack');
|
||||
|
||||
const pkg = require('./package.json');
|
||||
const baseConfig = require('./webpack.config.base');
|
||||
const { name, version, author, homepage } = require('./package.json');
|
||||
|
||||
const banner = `/*! ${pkg.name} v${
|
||||
pkg.version
|
||||
} | (c) ${new Date().getFullYear()} ${pkg.author} | ${pkg.homepage} */ \n`;
|
||||
const arrayMerge = (target, source) => [...source, ...target];
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: ['./src/scripts/choices'],
|
||||
output: {
|
||||
path: path.join(__dirname, '/public/assets/scripts'),
|
||||
filename: 'choices.min.js',
|
||||
publicPath: '/public/assets/scripts/',
|
||||
library: 'Choices',
|
||||
libraryTarget: 'umd',
|
||||
auxiliaryComment: {
|
||||
root: 'Window',
|
||||
commonjs: 'CommonJS',
|
||||
commonjs2: 'CommonJS2',
|
||||
amd: 'AMD',
|
||||
const prodConfig = deepMerge(
|
||||
baseConfig,
|
||||
{
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: path.join(__dirname, '/public/assets/scripts'),
|
||||
publicPath: '/public/assets/scripts/',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new WrapperPlugin({
|
||||
header: banner,
|
||||
}),
|
||||
new UnminifiedWebpackPlugin(),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
enforce: 'pre',
|
||||
test: /\.js?$/,
|
||||
include: path.join(__dirname, 'src/scripts'),
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'eslint-loader',
|
||||
query: {
|
||||
configFile: '.eslintrc',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.js?$/,
|
||||
include: path.join(__dirname, 'src/scripts'),
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
babelrc: true,
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new BannerPlugin(
|
||||
`${name} v${version} | © ${new Date().getFullYear()} ${author} | ${homepage}`,
|
||||
),
|
||||
],
|
||||
},
|
||||
};
|
||||
{
|
||||
arrayMerge,
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = [
|
||||
deepMerge(
|
||||
prodConfig,
|
||||
{
|
||||
output: { filename: 'choices.js', libraryTarget: 'umd' },
|
||||
optimization: { minimize: false },
|
||||
},
|
||||
{
|
||||
arrayMerge,
|
||||
},
|
||||
),
|
||||
deepMerge(
|
||||
prodConfig,
|
||||
{ output: { filename: 'choices.min.js' } },
|
||||
{
|
||||
arrayMerge,
|
||||
},
|
||||
),
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue